From aa943d098e0299ea87485a607353d152f5ea5012 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 30 Nov 2018 07:39:02 +0100 Subject: http: make parser choice a runtime flag Add a `--http-parser=llhttp` vs `--http-parser=traditional` command line switch, to make testing and comparing the new llhttp-based implementation easier. PR-URL: https://github.com/nodejs/node/pull/24739 Refs: https://github.com/nodejs/node/issues/24730 Reviewed-By: Joyee Cheung Reviewed-By: Colin Ihrig Reviewed-By: Fedor Indutny Reviewed-By: Gus Caplan Reviewed-By: Matheus Marchini Reviewed-By: Matteo Collina Reviewed-By: Ali Ijaz Sheikh --- src/inspector_socket.cc | 16 +- src/node_binding.cc | 1 + src/node_http_parser.cc | 967 ----------------------------------- src/node_http_parser_impl.h | 973 ++++++++++++++++++++++++++++++++++++ src/node_http_parser_llhttp.cc | 17 + src/node_http_parser_traditional.cc | 18 + src/node_internals.h | 3 + src/node_metadata.cc | 16 +- src/node_metadata.h | 11 +- src/node_options.cc | 14 + src/node_options.h | 6 + 11 files changed, 1047 insertions(+), 995 deletions(-) delete mode 100644 src/node_http_parser.cc create mode 100644 src/node_http_parser_impl.h create mode 100644 src/node_http_parser_llhttp.cc create mode 100644 src/node_http_parser_traditional.cc (limited to 'src') diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc index 8c2d0a5a22..dcc42f25b4 100644 --- a/src/inspector_socket.cc +++ b/src/inspector_socket.cc @@ -1,6 +1,10 @@ #include "inspector_socket.h" +#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT +#define NODE_EXPERIMENTAL_HTTP +#endif #include "http_parser_adaptor.h" + #include "util-inl.h" #define NODE_WANT_INTERNALS 1 @@ -433,13 +437,13 @@ class HttpHandler : public ProtocolHandler { explicit HttpHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp) : ProtocolHandler(inspector, std::move(tcp)), parsing_value_(false) { -#ifdef NODE_EXPERIMENTAL_HTTP +#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT llhttp_init(&parser_, HTTP_REQUEST, &parser_settings); llhttp_settings_init(&parser_settings); -#else /* !NODE_EXPERIMENTAL_HTTP */ +#else /* !NODE_EXPERIMENTAL_HTTP_DEFAULT */ http_parser_init(&parser_, HTTP_REQUEST); http_parser_settings_init(&parser_settings); -#endif /* NODE_EXPERIMENTAL_HTTP */ +#endif /* NODE_EXPERIMENTAL_HTTP_DEFAULT */ parser_settings.on_header_field = OnHeaderField; parser_settings.on_header_value = OnHeaderValue; parser_settings.on_message_complete = OnMessageComplete; @@ -484,17 +488,17 @@ class HttpHandler : public ProtocolHandler { void OnData(std::vector* data) override { parser_errno_t err; -#ifdef NODE_EXPERIMENTAL_HTTP +#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT err = llhttp_execute(&parser_, data->data(), data->size()); if (err == HPE_PAUSED_UPGRADE) { err = HPE_OK; llhttp_resume_after_upgrade(&parser_); } -#else /* !NODE_EXPERIMENTAL_HTTP */ +#else /* !NODE_EXPERIMENTAL_HTTP_DEFAULT */ http_parser_execute(&parser_, &parser_settings, data->data(), data->size()); err = HTTP_PARSER_ERRNO(&parser_); -#endif /* NODE_EXPERIMENTAL_HTTP */ +#endif /* NODE_EXPERIMENTAL_HTTP_DEFAULT */ data->clear(); if (err != HPE_OK) { CancelHandshake(); diff --git a/src/node_binding.cc b/src/node_binding.cc index a2e5134bfa..6d70d0a501 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -36,6 +36,7 @@ V(heap_utils) \ V(http2) \ V(http_parser) \ + V(http_parser_llhttp) \ V(inspector) \ V(js_stream) \ V(messaging) \ diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc deleted file mode 100644 index 4d683b4db2..0000000000 --- a/src/node_http_parser.cc +++ /dev/null @@ -1,967 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "node.h" -#include "node_buffer.h" -#include "node_internals.h" - -#include "async_wrap-inl.h" -#include "env-inl.h" -#include "stream_base-inl.h" -#include "util-inl.h" -#include "v8.h" - -#include // free() -#include // strdup(), strchr() - -#include "http_parser_adaptor.h" - -// This is a binding to http_parser (https://github.com/nodejs/http-parser) -// The goal is to decouple sockets from parsing for more javascript-level -// agility. A Buffer is read from a socket and passed to parser.execute(). -// The parser then issues callbacks with slices of the data -// parser.onMessageBegin -// parser.onPath -// parser.onBody -// ... -// No copying is performed when slicing the buffer, only small reference -// allocations. - - -namespace node { -namespace { - -using v8::Array; -using v8::Boolean; -using v8::Context; -using v8::EscapableHandleScope; -using v8::Exception; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::HandleScope; -using v8::Int32; -using v8::Integer; -using v8::Local; -using v8::MaybeLocal; -using v8::Object; -using v8::String; -using v8::Uint32; -using v8::Undefined; -using v8::Value; - -const uint32_t kOnHeaders = 0; -const uint32_t kOnHeadersComplete = 1; -const uint32_t kOnBody = 2; -const uint32_t kOnMessageComplete = 3; -const uint32_t kOnExecute = 4; -// Any more fields than this will be flushed into JS -const size_t kMaxHeaderFieldsCount = 32; - -// helper class for the Parser -struct StringPtr { - StringPtr() { - on_heap_ = false; - Reset(); - } - - - ~StringPtr() { - Reset(); - } - - - // If str_ does not point to a heap string yet, this function makes it do - // so. This is called at the end of each http_parser_execute() so as not - // to leak references. See issue #2438 and test-http-parser-bad-ref.js. - void Save() { - if (!on_heap_ && size_ > 0) { - char* s = new char[size_]; - memcpy(s, str_, size_); - str_ = s; - on_heap_ = true; - } - } - - - void Reset() { - if (on_heap_) { - delete[] str_; - on_heap_ = false; - } - - str_ = nullptr; - size_ = 0; - } - - - void Update(const char* str, size_t size) { - if (str_ == nullptr) { - str_ = str; - } else if (on_heap_ || str_ + size_ != str) { - // Non-consecutive input, make a copy on the heap. - // TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad. - char* s = new char[size_ + size]; - memcpy(s, str_, size_); - memcpy(s + size_, str, size); - - if (on_heap_) - delete[] str_; - else - on_heap_ = true; - - str_ = s; - } - size_ += size; - } - - - Local ToString(Environment* env) const { - if (str_) - return OneByteString(env->isolate(), str_, size_); - else - return String::Empty(env->isolate()); - } - - - const char* str_; - bool on_heap_; - size_t size_; -}; - - -class Parser : public AsyncWrap, public StreamListener { - public: - Parser(Environment* env, Local wrap, parser_type_t type) - : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER), - current_buffer_len_(0), - current_buffer_data_(nullptr) { - Init(type); - } - - - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackField("current_buffer", current_buffer_); - } - - SET_MEMORY_INFO_NAME(Parser) - SET_SELF_SIZE(Parser) - - int on_message_begin() { - num_fields_ = num_values_ = 0; - url_.Reset(); - status_message_.Reset(); - return 0; - } - - - int on_url(const char* at, size_t length) { - int rv = TrackHeader(length); - if (rv != 0) { - return rv; - } - - url_.Update(at, length); - return 0; - } - - - int on_status(const char* at, size_t length) { - int rv = TrackHeader(length); - if (rv != 0) { - return rv; - } - - status_message_.Update(at, length); - return 0; - } - - - int on_header_field(const char* at, size_t length) { - int rv = TrackHeader(length); - if (rv != 0) { - return rv; - } - - if (num_fields_ == num_values_) { - // start of new field name - num_fields_++; - if (num_fields_ == kMaxHeaderFieldsCount) { - // ran out of space - flush to javascript land - Flush(); - num_fields_ = 1; - num_values_ = 0; - } - fields_[num_fields_ - 1].Reset(); - } - - CHECK_LT(num_fields_, kMaxHeaderFieldsCount); - CHECK_EQ(num_fields_, num_values_ + 1); - - fields_[num_fields_ - 1].Update(at, length); - - return 0; - } - - - int on_header_value(const char* at, size_t length) { - int rv = TrackHeader(length); - if (rv != 0) { - return rv; - } - - if (num_values_ != num_fields_) { - // start of new header value - num_values_++; - values_[num_values_ - 1].Reset(); - } - - CHECK_LT(num_values_, arraysize(values_)); - CHECK_EQ(num_values_, num_fields_); - - values_[num_values_ - 1].Update(at, length); - - return 0; - } - - - int on_headers_complete() { -#ifdef NODE_EXPERIMENTAL_HTTP - header_nread_ = 0; -#endif /* NODE_EXPERIMENTAL_HTTP */ - - // Arguments for the on-headers-complete javascript callback. This - // list needs to be kept in sync with the actual argument list for - // `parserOnHeadersComplete` in lib/_http_common.js. - enum on_headers_complete_arg_index { - A_VERSION_MAJOR = 0, - A_VERSION_MINOR, - A_HEADERS, - A_METHOD, - A_URL, - A_STATUS_CODE, - A_STATUS_MESSAGE, - A_UPGRADE, - A_SHOULD_KEEP_ALIVE, - A_MAX - }; - - Local argv[A_MAX]; - Local obj = object(); - Local cb = obj->Get(env()->context(), - kOnHeadersComplete).ToLocalChecked(); - - if (!cb->IsFunction()) - return 0; - - Local undefined = Undefined(env()->isolate()); - for (size_t i = 0; i < arraysize(argv); i++) - argv[i] = undefined; - - if (have_flushed_) { - // Slow case, flush remaining headers. - Flush(); - } else { - // Fast case, pass headers and URL to JS land. - argv[A_HEADERS] = CreateHeaders(); - if (parser_.type == HTTP_REQUEST) - argv[A_URL] = url_.ToString(env()); - } - - num_fields_ = 0; - num_values_ = 0; - - // METHOD - if (parser_.type == HTTP_REQUEST) { - argv[A_METHOD] = - Uint32::NewFromUnsigned(env()->isolate(), parser_.method); - } - - // STATUS - if (parser_.type == HTTP_RESPONSE) { - argv[A_STATUS_CODE] = - Integer::New(env()->isolate(), parser_.status_code); - argv[A_STATUS_MESSAGE] = status_message_.ToString(env()); - } - - // VERSION - argv[A_VERSION_MAJOR] = Integer::New(env()->isolate(), parser_.http_major); - argv[A_VERSION_MINOR] = Integer::New(env()->isolate(), parser_.http_minor); - - bool should_keep_alive; -#ifdef NODE_EXPERIMENTAL_HTTP - should_keep_alive = llhttp_should_keep_alive(&parser_); -#else /* !NODE_EXPERIMENTAL_HTTP */ - should_keep_alive = http_should_keep_alive(&parser_); -#endif /* NODE_EXPERIMENTAL_HTTP */ - - argv[A_SHOULD_KEEP_ALIVE] = - Boolean::New(env()->isolate(), should_keep_alive); - - argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade); - - Environment::AsyncCallbackScope callback_scope(env()); - - MaybeLocal head_response = - MakeCallback(cb.As(), arraysize(argv), argv); - - int64_t val; - - if (head_response.IsEmpty() || !head_response.ToLocalChecked() - ->IntegerValue(env()->context()) - .To(&val)) { - got_exception_ = true; - return -1; - } - - return val; - } - - - int on_body(const char* at, size_t length) { - EscapableHandleScope scope(env()->isolate()); - - Local obj = object(); - Local cb = obj->Get(env()->context(), kOnBody).ToLocalChecked(); - - if (!cb->IsFunction()) - return 0; - - // We came from consumed stream - if (current_buffer_.IsEmpty()) { - // Make sure Buffer will be in parent HandleScope - current_buffer_ = scope.Escape(Buffer::Copy( - env()->isolate(), - current_buffer_data_, - current_buffer_len_).ToLocalChecked()); - } - - Local argv[3] = { - current_buffer_, - Integer::NewFromUnsigned(env()->isolate(), at - current_buffer_data_), - Integer::NewFromUnsigned(env()->isolate(), length) - }; - - MaybeLocal r = MakeCallback(cb.As(), - arraysize(argv), - argv); - - if (r.IsEmpty()) { - got_exception_ = true; -#ifdef NODE_EXPERIMENTAL_HTTP - llhttp_set_error_reason(&parser_, "HPE_JS_EXCEPTION:JS Exception"); -#endif /* NODE_EXPERIMENTAL_HTTP */ - return HPE_USER; - } - - return 0; - } - - - int on_message_complete() { - HandleScope scope(env()->isolate()); - - if (num_fields_) - Flush(); // Flush trailing HTTP headers. - - Local obj = object(); - Local cb = obj->Get(env()->context(), - kOnMessageComplete).ToLocalChecked(); - - if (!cb->IsFunction()) - return 0; - - Environment::AsyncCallbackScope callback_scope(env()); - - MaybeLocal r = MakeCallback(cb.As(), 0, nullptr); - - if (r.IsEmpty()) { - got_exception_ = true; - return -1; - } - - return 0; - } - -#ifdef NODE_EXPERIMENTAL_HTTP - // Reset nread for the next chunk - int on_chunk_header() { - header_nread_ = 0; - return 0; - } - - - // Reset nread for the next chunk - int on_chunk_complete() { - header_nread_ = 0; - return 0; - } -#endif /* NODE_EXPERIMENTAL_HTTP */ - - - static void New(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(args[0]->IsInt32()); - parser_type_t type = - static_cast(args[0].As()->Value()); - CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); - new Parser(env, args.This(), type); - } - - - static void Close(const FunctionCallbackInfo& args) { - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - - delete parser; - } - - - static void Free(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - - // Since the Parser destructor isn't going to run the destroy() callbacks - // it needs to be triggered manually. - parser->EmitTraceEventDestroy(); - parser->EmitDestroy(env, parser->get_async_id()); - } - - - void Save() { - url_.Save(); - status_message_.Save(); - - for (size_t i = 0; i < num_fields_; i++) { - fields_[i].Save(); - } - - for (size_t i = 0; i < num_values_; i++) { - values_[i].Save(); - } - } - - - // var bytesParsed = parser->execute(buffer); - static void Execute(const FunctionCallbackInfo& args) { - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - CHECK(parser->current_buffer_.IsEmpty()); - CHECK_EQ(parser->current_buffer_len_, 0); - CHECK_NULL(parser->current_buffer_data_); - CHECK_EQ(Buffer::HasInstance(args[0]), true); - - Local buffer_obj = args[0].As(); - char* buffer_data = Buffer::Data(buffer_obj); - size_t buffer_len = Buffer::Length(buffer_obj); - - // This is a hack to get the current_buffer to the callbacks with the least - // amount of overhead. Nothing else will run while http_parser_execute() - // runs, therefore this pointer can be set and used for the execution. - parser->current_buffer_ = buffer_obj; - - Local ret = parser->Execute(buffer_data, buffer_len); - - if (!ret.IsEmpty()) - args.GetReturnValue().Set(ret); - } - - - static void Finish(const FunctionCallbackInfo& args) { - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - - CHECK(parser->current_buffer_.IsEmpty()); - Local ret = parser->Execute(nullptr, 0); - - if (!ret.IsEmpty()) - args.GetReturnValue().Set(ret); - } - - - static void Reinitialize(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK(args[0]->IsInt32()); - CHECK(args[1]->IsBoolean()); - bool isReused = args[1]->IsTrue(); - parser_type_t type = - static_cast(args[0].As()->Value()); - - CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - // Should always be called from the same context. - CHECK_EQ(env, parser->env()); - // This parser has either just been created or it is being reused. - // We must only call AsyncReset for the latter case, because AsyncReset has - // already been called via the constructor for the former case. - if (isReused) { - parser->AsyncReset(); - } - parser->Init(type); - } - - - template - static void Pause(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - // Should always be called from the same context. - CHECK_EQ(env, parser->env()); - -#ifdef NODE_EXPERIMENTAL_HTTP - if (parser->execute_depth_) { - parser->pending_pause_ = should_pause; - return; - } - - if (should_pause) { - llhttp_pause(&parser->parser_); - } else { - llhttp_resume(&parser->parser_); - } -#else /* !NODE_EXPERIMENTAL_HTTP */ - http_parser_pause(&parser->parser_, should_pause); -#endif /* NODE_EXPERIMENTAL_HTTP */ - } - - - static void Consume(const FunctionCallbackInfo& args) { - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - CHECK(args[0]->IsExternal()); - Local stream_obj = args[0].As(); - StreamBase* stream = static_cast(stream_obj->Value()); - CHECK_NOT_NULL(stream); - stream->PushStreamListener(parser); - } - - - static void Unconsume(const FunctionCallbackInfo& args) { - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - - // Already unconsumed - if (parser->stream_ == nullptr) - return; - - parser->stream_->RemoveStreamListener(parser); - } - - - static void GetCurrentBuffer(const FunctionCallbackInfo& args) { - Parser* parser; - ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); - - Local ret = Buffer::Copy( - parser->env(), - parser->current_buffer_data_, - parser->current_buffer_len_).ToLocalChecked(); - - args.GetReturnValue().Set(ret); - } - - protected: - static const size_t kAllocBufferSize = 64 * 1024; - - uv_buf_t OnStreamAlloc(size_t suggested_size) override { - // For most types of streams, OnStreamRead will be immediately after - // OnStreamAlloc, and will consume all data, so using a static buffer for - // reading is more efficient. For other streams, just use the default - // allocator, which uses Malloc(). - if (env()->http_parser_buffer_in_use()) - return StreamListener::OnStreamAlloc(suggested_size); - env()->set_http_parser_buffer_in_use(true); - - if (env()->http_parser_buffer() == nullptr) - env()->set_http_parser_buffer(new char[kAllocBufferSize]); - - return uv_buf_init(env()->http_parser_buffer(), kAllocBufferSize); - } - - - void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override { - HandleScope scope(env()->isolate()); - // Once we’re done here, either indicate that the HTTP parser buffer - // is free for re-use, or free() the data if it didn’t come from there - // in the first place. - OnScopeLeave on_scope_leave([&]() { - if (buf.base == env()->http_parser_buffer()) - env()->set_http_parser_buffer_in_use(false); - else - free(buf.base); - }); - - if (nread < 0) { - PassReadErrorToPreviousListener(nread); - return; - } - - // Ignore, empty reads have special meaning in http parser - if (nread == 0) - return; - - current_buffer_.Clear(); - Local ret = Execute(buf.base, nread); - - // Exception - if (ret.IsEmpty()) - return; - - Local cb = - object()->Get(env()->context(), kOnExecute).ToLocalChecked(); - - if (!cb->IsFunction()) - return; - - // Hooks for GetCurrentBuffer - current_buffer_len_ = nread; - current_buffer_data_ = buf.base; - - MakeCallback(cb.As(), 1, &ret); - - current_buffer_len_ = 0; - current_buffer_data_ = nullptr; - } - - - Local Execute(char* data, size_t len) { - EscapableHandleScope scope(env()->isolate()); - - current_buffer_len_ = len; - current_buffer_data_ = data; - got_exception_ = false; - - parser_errno_t err; - -#ifdef NODE_EXPERIMENTAL_HTTP - // Do not allow re-entering `http_parser_execute()` - CHECK_EQ(execute_depth_, 0); - - execute_depth_++; - if (data == nullptr) { - err = llhttp_finish(&parser_); - } else { - err = llhttp_execute(&parser_, data, len); - Save(); - } - execute_depth_--; - - // Calculate bytes read and resume after Upgrade/CONNECT pause - size_t nread = len; - if (err != HPE_OK) { - nread = llhttp_get_error_pos(&parser_) - data; - - // This isn't a real pause, just a way to stop parsing early. - if (err == HPE_PAUSED_UPGRADE) { - err = HPE_OK; - llhttp_resume_after_upgrade(&parser_); - } - } - - // Apply pending pause - if (pending_pause_) { - pending_pause_ = false; - llhttp_pause(&parser_); - } -#else /* !NODE_EXPERIMENTAL_HTTP */ - size_t nread = http_parser_execute(&parser_, &settings, data, len); - err = HTTP_PARSER_ERRNO(&parser_); - - // Finish() - if (data == nullptr) { - // `http_parser_execute()` returns either `0` or `1` when `len` is 0 - // (part of the finishing sequence). - CHECK_EQ(len, 0); - switch (nread) { - case 0: - err = HPE_OK; - break; - case 1: - nread = 0; - break; - default: - UNREACHABLE(); - } - - // Regular Execute() - } else { - Save(); - } -#endif /* NODE_EXPERIMENTAL_HTTP */ - - // Unassign the 'buffer_' variable - current_buffer_.Clear(); - current_buffer_len_ = 0; - current_buffer_data_ = nullptr; - - // If there was an exception in one of the callbacks - if (got_exception_) - return scope.Escape(Local()); - - Local nread_obj = Integer::New(env()->isolate(), nread); - - // If there was a parse error in one of the callbacks - // TODO(bnoordhuis) What if there is an error on EOF? - if (!parser_.upgrade && err != HPE_OK) { - Local e = Exception::Error(env()->parse_error_string()); - Local obj = e->ToObject(env()->isolate()->GetCurrentContext()) - .ToLocalChecked(); - obj->Set(env()->context(), - env()->bytes_parsed_string(), - nread_obj).FromJust(); -#ifdef NODE_EXPERIMENTAL_HTTP - const char* errno_reason = llhttp_get_error_reason(&parser_); - - Local code; - Local reason; - if (err == HPE_USER) { - const char* colon = strchr(errno_reason, ':'); - CHECK_NE(colon, nullptr); - code = OneByteString(env()->isolate(), errno_reason, - colon - errno_reason); - reason = OneByteString(env()->isolate(), colon + 1); - } else { - code = OneByteString(env()->isolate(), llhttp_errno_name(err)); - reason = OneByteString(env()->isolate(), errno_reason); - } - - obj->Set(env()->context(), env()->code_string(), code).FromJust(); - obj->Set(env()->context(), env()->reason_string(), reason).FromJust(); -#else /* !NODE_EXPERIMENTAL_HTTP */ - obj->Set(env()->context(), - env()->code_string(), - OneByteString(env()->isolate(), - http_errno_name(err))).FromJust(); -#endif /* NODE_EXPERIMENTAL_HTTP */ - return scope.Escape(e); - } - - // No return value is needed for `Finish()` - if (data == nullptr) { - return scope.Escape(Local()); - } - return scope.Escape(nread_obj); - } - - Local CreateHeaders() { - // There could be extra entries but the max size should be fixed - Local headers_v[kMaxHeaderFieldsCount * 2]; - - for (size_t i = 0; i < num_values_; ++i) { - headers_v[i * 2] = fields_[i].ToString(env()); - headers_v[i * 2 + 1] = values_[i].ToString(env()); - } - - return Array::New(env()->isolate(), headers_v, num_values_ * 2); - } - - - // spill headers and request path to JS land - void Flush() { - HandleScope scope(env()->isolate()); - - Local obj = object(); - Local cb = obj->Get(env()->context(), kOnHeaders).ToLocalChecked(); - - if (!cb->IsFunction()) - return; - - Local argv[2] = { - CreateHeaders(), - url_.ToString(env()) - }; - - MaybeLocal r = MakeCallback(cb.As(), - arraysize(argv), - argv); - - if (r.IsEmpty()) - got_exception_ = true; - - url_.Reset(); - have_flushed_ = true; - } - - - void Init(parser_type_t type) { -#ifdef NODE_EXPERIMENTAL_HTTP - llhttp_init(&parser_, type, &settings); - header_nread_ = 0; -#else /* !NODE_EXPERIMENTAL_HTTP */ - http_parser_init(&parser_, type); -#endif /* NODE_EXPERIMENTAL_HTTP */ - url_.Reset(); - status_message_.Reset(); - num_fields_ = 0; - num_values_ = 0; - have_flushed_ = false; - got_exception_ = false; - } - - - int TrackHeader(size_t len) { -#ifdef NODE_EXPERIMENTAL_HTTP - header_nread_ += len; - if (header_nread_ >= kMaxHeaderSize) { - llhttp_set_error_reason(&parser_, "HPE_HEADER_OVERFLOW:Header overflow"); - return HPE_USER; - } -#endif /* NODE_EXPERIMENTAL_HTTP */ - return 0; - } - - - int MaybePause() { -#ifdef NODE_EXPERIMENTAL_HTTP - CHECK_NE(execute_depth_, 0); - - if (!pending_pause_) { - return 0; - } - - pending_pause_ = false; - llhttp_set_error_reason(&parser_, "Paused in callback"); - return HPE_PAUSED; -#else /* !NODE_EXPERIMENTAL_HTTP */ - return 0; -#endif /* NODE_EXPERIMENTAL_HTTP */ - } - - parser_t parser_; - StringPtr fields_[kMaxHeaderFieldsCount]; // header fields - StringPtr values_[kMaxHeaderFieldsCount]; // header values - StringPtr url_; - StringPtr status_message_; - size_t num_fields_; - size_t num_values_; - bool have_flushed_; - bool got_exception_; - Local current_buffer_; - size_t current_buffer_len_; - char* current_buffer_data_; -#ifdef NODE_EXPERIMENTAL_HTTP - unsigned int execute_depth_ = 0; - bool pending_pause_ = false; - uint64_t header_nread_ = 0; -#endif /* NODE_EXPERIMENTAL_HTTP */ - - // These are helper functions for filling `http_parser_settings`, which turn - // a member function of Parser into a C-style HTTP parser callback. - template struct Proxy; - template - struct Proxy { - static int Raw(parser_t* p, Args ... args) { - Parser* parser = ContainerOf(&Parser::parser_, p); - int rv = (parser->*Member)(std::forward(args)...); - if (rv == 0) { - rv = parser->MaybePause(); - } - return rv; - } - }; - - typedef int (Parser::*Call)(); - typedef int (Parser::*DataCall)(const char* at, size_t length); - - static const parser_settings_t settings; -#ifdef NODE_EXPERIMENTAL_HTTP - static const uint64_t kMaxHeaderSize = 8 * 1024; -#endif /* NODE_EXPERIMENTAL_HTTP */ -}; - -const parser_settings_t Parser::settings = { - Proxy::Raw, - Proxy::Raw, - Proxy::Raw, - Proxy::Raw, - Proxy::Raw, - Proxy::Raw, - Proxy::Raw, - Proxy::Raw, -#ifdef NODE_EXPERIMENTAL_HTTP - Proxy::Raw, - Proxy::Raw, -#else /* !NODE_EXPERIMENTAL_HTTP */ - nullptr, - nullptr, -#endif /* NODE_EXPERIMENTAL_HTTP */ -}; - - -void Initialize(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - Local t = env->NewFunctionTemplate(Parser::New); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser")); - - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "REQUEST"), - Integer::New(env->isolate(), HTTP_REQUEST)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "RESPONSE"), - Integer::New(env->isolate(), HTTP_RESPONSE)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeaders"), - Integer::NewFromUnsigned(env->isolate(), kOnHeaders)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeadersComplete"), - Integer::NewFromUnsigned(env->isolate(), kOnHeadersComplete)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnBody"), - Integer::NewFromUnsigned(env->isolate(), kOnBody)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnMessageComplete"), - Integer::NewFromUnsigned(env->isolate(), kOnMessageComplete)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnExecute"), - Integer::NewFromUnsigned(env->isolate(), kOnExecute)); - - Local methods = Array::New(env->isolate()); -#define V(num, name, string) \ - methods->Set(env->context(), \ - num, FIXED_ONE_BYTE_STRING(env->isolate(), #string)).FromJust(); - HTTP_METHOD_MAP(V) -#undef V - target->Set(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "methods"), - methods).FromJust(); - - t->Inherit(AsyncWrap::GetConstructorTemplate(env)); - env->SetProtoMethod(t, "close", Parser::Close); - env->SetProtoMethod(t, "free", Parser::Free); - env->SetProtoMethod(t, "execute", Parser::Execute); - env->SetProtoMethod(t, "finish", Parser::Finish); - env->SetProtoMethod(t, "reinitialize", Parser::Reinitialize); - env->SetProtoMethod(t, "pause", Parser::Pause); - env->SetProtoMethod(t, "resume", Parser::Pause); - env->SetProtoMethod(t, "consume", Parser::Consume); - env->SetProtoMethod(t, "unconsume", Parser::Unconsume); - env->SetProtoMethod(t, "getCurrentBuffer", Parser::GetCurrentBuffer); - - target->Set(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"), - t->GetFunction(env->context()).ToLocalChecked()).FromJust(); -} - -} // anonymous namespace -} // namespace node - -NODE_MODULE_CONTEXT_AWARE_INTERNAL(http_parser, node::Initialize) diff --git a/src/node_http_parser_impl.h b/src/node_http_parser_impl.h new file mode 100644 index 0000000000..4233ccbfca --- /dev/null +++ b/src/node_http_parser_impl.h @@ -0,0 +1,973 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This file is included from 2 files, node_http_parser_traditional.cc +// and node_http_parser_llhttp.cc. + +#ifndef SRC_NODE_HTTP_PARSER_IMPL_H_ +#define SRC_NODE_HTTP_PARSER_IMPL_H_ + +#include "node.h" +#include "node_buffer.h" +#include "node_internals.h" + +#include "async_wrap-inl.h" +#include "env-inl.h" +#include "stream_base-inl.h" +#include "util-inl.h" +#include "v8.h" + +#include // free() +#include // strdup(), strchr() + +#include "http_parser_adaptor.h" + +// This is a binding to http_parser (https://github.com/nodejs/http-parser) +// The goal is to decouple sockets from parsing for more javascript-level +// agility. A Buffer is read from a socket and passed to parser.execute(). +// The parser then issues callbacks with slices of the data +// parser.onMessageBegin +// parser.onPath +// parser.onBody +// ... +// No copying is performed when slicing the buffer, only small reference +// allocations. + + +namespace node { +namespace { // NOLINT(build/namespaces) + +using v8::Array; +using v8::Boolean; +using v8::Context; +using v8::EscapableHandleScope; +using v8::Exception; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::HandleScope; +using v8::Int32; +using v8::Integer; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::String; +using v8::Uint32; +using v8::Undefined; +using v8::Value; + +const uint32_t kOnHeaders = 0; +const uint32_t kOnHeadersComplete = 1; +const uint32_t kOnBody = 2; +const uint32_t kOnMessageComplete = 3; +const uint32_t kOnExecute = 4; +// Any more fields than this will be flushed into JS +const size_t kMaxHeaderFieldsCount = 32; + +// helper class for the Parser +struct StringPtr { + StringPtr() { + on_heap_ = false; + Reset(); + } + + + ~StringPtr() { + Reset(); + } + + + // If str_ does not point to a heap string yet, this function makes it do + // so. This is called at the end of each http_parser_execute() so as not + // to leak references. See issue #2438 and test-http-parser-bad-ref.js. + void Save() { + if (!on_heap_ && size_ > 0) { + char* s = new char[size_]; + memcpy(s, str_, size_); + str_ = s; + on_heap_ = true; + } + } + + + void Reset() { + if (on_heap_) { + delete[] str_; + on_heap_ = false; + } + + str_ = nullptr; + size_ = 0; + } + + + void Update(const char* str, size_t size) { + if (str_ == nullptr) { + str_ = str; + } else if (on_heap_ || str_ + size_ != str) { + // Non-consecutive input, make a copy on the heap. + // TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad. + char* s = new char[size_ + size]; + memcpy(s, str_, size_); + memcpy(s + size_, str, size); + + if (on_heap_) + delete[] str_; + else + on_heap_ = true; + + str_ = s; + } + size_ += size; + } + + + Local ToString(Environment* env) const { + if (str_) + return OneByteString(env->isolate(), str_, size_); + else + return String::Empty(env->isolate()); + } + + + const char* str_; + bool on_heap_; + size_t size_; +}; + + +class Parser : public AsyncWrap, public StreamListener { + public: + Parser(Environment* env, Local wrap, parser_type_t type) + : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER), + current_buffer_len_(0), + current_buffer_data_(nullptr) { + Init(type); + } + + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackField("current_buffer", current_buffer_); + } + + SET_MEMORY_INFO_NAME(Parser) + SET_SELF_SIZE(Parser) + + int on_message_begin() { + num_fields_ = num_values_ = 0; + url_.Reset(); + status_message_.Reset(); + return 0; + } + + + int on_url(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + + url_.Update(at, length); + return 0; + } + + + int on_status(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + + status_message_.Update(at, length); + return 0; + } + + + int on_header_field(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + + if (num_fields_ == num_values_) { + // start of new field name + num_fields_++; + if (num_fields_ == kMaxHeaderFieldsCount) { + // ran out of space - flush to javascript land + Flush(); + num_fields_ = 1; + num_values_ = 0; + } + fields_[num_fields_ - 1].Reset(); + } + + CHECK_LT(num_fields_, kMaxHeaderFieldsCount); + CHECK_EQ(num_fields_, num_values_ + 1); + + fields_[num_fields_ - 1].Update(at, length); + + return 0; + } + + + int on_header_value(const char* at, size_t length) { + int rv = TrackHeader(length); + if (rv != 0) { + return rv; + } + + if (num_values_ != num_fields_) { + // start of new header value + num_values_++; + values_[num_values_ - 1].Reset(); + } + + CHECK_LT(num_values_, arraysize(values_)); + CHECK_EQ(num_values_, num_fields_); + + values_[num_values_ - 1].Update(at, length); + + return 0; + } + + + int on_headers_complete() { +#ifdef NODE_EXPERIMENTAL_HTTP + header_nread_ = 0; +#endif /* NODE_EXPERIMENTAL_HTTP */ + + // Arguments for the on-headers-complete javascript callback. This + // list needs to be kept in sync with the actual argument list for + // `parserOnHeadersComplete` in lib/_http_common.js. + enum on_headers_complete_arg_index { + A_VERSION_MAJOR = 0, + A_VERSION_MINOR, + A_HEADERS, + A_METHOD, + A_URL, + A_STATUS_CODE, + A_STATUS_MESSAGE, + A_UPGRADE, + A_SHOULD_KEEP_ALIVE, + A_MAX + }; + + Local argv[A_MAX]; + Local obj = object(); + Local cb = obj->Get(env()->context(), + kOnHeadersComplete).ToLocalChecked(); + + if (!cb->IsFunction()) + return 0; + + Local undefined = Undefined(env()->isolate()); + for (size_t i = 0; i < arraysize(argv); i++) + argv[i] = undefined; + + if (have_flushed_) { + // Slow case, flush remaining headers. + Flush(); + } else { + // Fast case, pass headers and URL to JS land. + argv[A_HEADERS] = CreateHeaders(); + if (parser_.type == HTTP_REQUEST) + argv[A_URL] = url_.ToString(env()); + } + + num_fields_ = 0; + num_values_ = 0; + + // METHOD + if (parser_.type == HTTP_REQUEST) { + argv[A_METHOD] = + Uint32::NewFromUnsigned(env()->isolate(), parser_.method); + } + + // STATUS + if (parser_.type == HTTP_RESPONSE) { + argv[A_STATUS_CODE] = + Integer::New(env()->isolate(), parser_.status_code); + argv[A_STATUS_MESSAGE] = status_message_.ToString(env()); + } + + // VERSION + argv[A_VERSION_MAJOR] = Integer::New(env()->isolate(), parser_.http_major); + argv[A_VERSION_MINOR] = Integer::New(env()->isolate(), parser_.http_minor); + + bool should_keep_alive; +#ifdef NODE_EXPERIMENTAL_HTTP + should_keep_alive = llhttp_should_keep_alive(&parser_); +#else /* !NODE_EXPERIMENTAL_HTTP */ + should_keep_alive = http_should_keep_alive(&parser_); +#endif /* NODE_EXPERIMENTAL_HTTP */ + + argv[A_SHOULD_KEEP_ALIVE] = + Boolean::New(env()->isolate(), should_keep_alive); + + argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade); + + Environment::AsyncCallbackScope callback_scope(env()); + + MaybeLocal head_response = + MakeCallback(cb.As(), arraysize(argv), argv); + + int64_t val; + + if (head_response.IsEmpty() || !head_response.ToLocalChecked() + ->IntegerValue(env()->context()) + .To(&val)) { + got_exception_ = true; + return -1; + } + + return val; + } + + + int on_body(const char* at, size_t length) { + EscapableHandleScope scope(env()->isolate()); + + Local obj = object(); + Local cb = obj->Get(env()->context(), kOnBody).ToLocalChecked(); + + if (!cb->IsFunction()) + return 0; + + // We came from consumed stream + if (current_buffer_.IsEmpty()) { + // Make sure Buffer will be in parent HandleScope + current_buffer_ = scope.Escape(Buffer::Copy( + env()->isolate(), + current_buffer_data_, + current_buffer_len_).ToLocalChecked()); + } + + Local argv[3] = { + current_buffer_, + Integer::NewFromUnsigned(env()->isolate(), at - current_buffer_data_), + Integer::NewFromUnsigned(env()->isolate(), length) + }; + + MaybeLocal r = MakeCallback(cb.As(), + arraysize(argv), + argv); + + if (r.IsEmpty()) { + got_exception_ = true; +#ifdef NODE_EXPERIMENTAL_HTTP + llhttp_set_error_reason(&parser_, "HPE_JS_EXCEPTION:JS Exception"); +#endif /* NODE_EXPERIMENTAL_HTTP */ + return HPE_USER; + } + + return 0; + } + + + int on_message_complete() { + HandleScope scope(env()->isolate()); + + if (num_fields_) + Flush(); // Flush trailing HTTP headers. + + Local obj = object(); + Local cb = obj->Get(env()->context(), + kOnMessageComplete).ToLocalChecked(); + + if (!cb->IsFunction()) + return 0; + + Environment::AsyncCallbackScope callback_scope(env()); + + MaybeLocal r = MakeCallback(cb.As(), 0, nullptr); + + if (r.IsEmpty()) { + got_exception_ = true; + return -1; + } + + return 0; + } + +#ifdef NODE_EXPERIMENTAL_HTTP + // Reset nread for the next chunk + int on_chunk_header() { + header_nread_ = 0; + return 0; + } + + + // Reset nread for the next chunk + int on_chunk_complete() { + header_nread_ = 0; + return 0; + } +#endif /* NODE_EXPERIMENTAL_HTTP */ + + + static void New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsInt32()); + parser_type_t type = + static_cast(args[0].As()->Value()); + CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); + new Parser(env, args.This(), type); + } + + + static void Close(const FunctionCallbackInfo& args) { + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + + delete parser; + } + + + static void Free(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + + // Since the Parser destructor isn't going to run the destroy() callbacks + // it needs to be triggered manually. + parser->EmitTraceEventDestroy(); + parser->EmitDestroy(env, parser->get_async_id()); + } + + + void Save() { + url_.Save(); + status_message_.Save(); + + for (size_t i = 0; i < num_fields_; i++) { + fields_[i].Save(); + } + + for (size_t i = 0; i < num_values_; i++) { + values_[i].Save(); + } + } + + + // var bytesParsed = parser->execute(buffer); + static void Execute(const FunctionCallbackInfo& args) { + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + CHECK(parser->current_buffer_.IsEmpty()); + CHECK_EQ(parser->current_buffer_len_, 0); + CHECK_NULL(parser->current_buffer_data_); + CHECK_EQ(Buffer::HasInstance(args[0]), true); + + Local buffer_obj = args[0].As(); + char* buffer_data = Buffer::Data(buffer_obj); + size_t buffer_len = Buffer::Length(buffer_obj); + + // This is a hack to get the current_buffer to the callbacks with the least + // amount of overhead. Nothing else will run while http_parser_execute() + // runs, therefore this pointer can be set and used for the execution. + parser->current_buffer_ = buffer_obj; + + Local ret = parser->Execute(buffer_data, buffer_len); + + if (!ret.IsEmpty()) + args.GetReturnValue().Set(ret); + } + + + static void Finish(const FunctionCallbackInfo& args) { + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + + CHECK(parser->current_buffer_.IsEmpty()); + Local ret = parser->Execute(nullptr, 0); + + if (!ret.IsEmpty()) + args.GetReturnValue().Set(ret); + } + + + static void Reinitialize(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsInt32()); + CHECK(args[1]->IsBoolean()); + bool isReused = args[1]->IsTrue(); + parser_type_t type = + static_cast(args[0].As()->Value()); + + CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + // Should always be called from the same context. + CHECK_EQ(env, parser->env()); + // This parser has either just been created or it is being reused. + // We must only call AsyncReset for the latter case, because AsyncReset has + // already been called via the constructor for the former case. + if (isReused) { + parser->AsyncReset(); + } + parser->Init(type); + } + + + template + static void Pause(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + // Should always be called from the same context. + CHECK_EQ(env, parser->env()); + +#ifdef NODE_EXPERIMENTAL_HTTP + if (parser->execute_depth_) { + parser->pending_pause_ = should_pause; + return; + } + + if (should_pause) { + llhttp_pause(&parser->parser_); + } else { + llhttp_resume(&parser->parser_); + } +#else /* !NODE_EXPERIMENTAL_HTTP */ + http_parser_pause(&parser->parser_, should_pause); +#endif /* NODE_EXPERIMENTAL_HTTP */ + } + + + static void Consume(const FunctionCallbackInfo& args) { + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + CHECK(args[0]->IsExternal()); + Local stream_obj = args[0].As(); + StreamBase* stream = static_cast(stream_obj->Value()); + CHECK_NOT_NULL(stream); + stream->PushStreamListener(parser); + } + + + static void Unconsume(const FunctionCallbackInfo& args) { + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + + // Already unconsumed + if (parser->stream_ == nullptr) + return; + + parser->stream_->RemoveStreamListener(parser); + } + + + static void GetCurrentBuffer(const FunctionCallbackInfo& args) { + Parser* parser; + ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); + + Local ret = Buffer::Copy( + parser->env(), + parser->current_buffer_data_, + parser->current_buffer_len_).ToLocalChecked(); + + args.GetReturnValue().Set(ret); + } + + protected: + static const size_t kAllocBufferSize = 64 * 1024; + + uv_buf_t OnStreamAlloc(size_t suggested_size) override { + // For most types of streams, OnStreamRead will be immediately after + // OnStreamAlloc, and will consume all data, so using a static buffer for + // reading is more efficient. For other streams, just use the default + // allocator, which uses Malloc(). + if (env()->http_parser_buffer_in_use()) + return StreamListener::OnStreamAlloc(suggested_size); + env()->set_http_parser_buffer_in_use(true); + + if (env()->http_parser_buffer() == nullptr) + env()->set_http_parser_buffer(new char[kAllocBufferSize]); + + return uv_buf_init(env()->http_parser_buffer(), kAllocBufferSize); + } + + + void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override { + HandleScope scope(env()->isolate()); + // Once we’re done here, either indicate that the HTTP parser buffer + // is free for re-use, or free() the data if it didn’t come from there + // in the first place. + OnScopeLeave on_scope_leave([&]() { + if (buf.base == env()->http_parser_buffer()) + env()->set_http_parser_buffer_in_use(false); + else + free(buf.base); + }); + + if (nread < 0) { + PassReadErrorToPreviousListener(nread); + return; + } + + // Ignore, empty reads have special meaning in http parser + if (nread == 0) + return; + + current_buffer_.Clear(); + Local ret = Execute(buf.base, nread); + + // Exception + if (ret.IsEmpty()) + return; + + Local cb = + object()->Get(env()->context(), kOnExecute).ToLocalChecked(); + + if (!cb->IsFunction()) + return; + + // Hooks for GetCurrentBuffer + current_buffer_len_ = nread; + current_buffer_data_ = buf.base; + + MakeCallback(cb.As(), 1, &ret); + + current_buffer_len_ = 0; + current_buffer_data_ = nullptr; + } + + + Local Execute(char* data, size_t len) { + EscapableHandleScope scope(env()->isolate()); + + current_buffer_len_ = len; + current_buffer_data_ = data; + got_exception_ = false; + + parser_errno_t err; + +#ifdef NODE_EXPERIMENTAL_HTTP + // Do not allow re-entering `http_parser_execute()` + CHECK_EQ(execute_depth_, 0); + + execute_depth_++; + if (data == nullptr) { + err = llhttp_finish(&parser_); + } else { + err = llhttp_execute(&parser_, data, len); + Save(); + } + execute_depth_--; + + // Calculate bytes read and resume after Upgrade/CONNECT pause + size_t nread = len; + if (err != HPE_OK) { + nread = llhttp_get_error_pos(&parser_) - data; + + // This isn't a real pause, just a way to stop parsing early. + if (err == HPE_PAUSED_UPGRADE) { + err = HPE_OK; + llhttp_resume_after_upgrade(&parser_); + } + } + + // Apply pending pause + if (pending_pause_) { + pending_pause_ = false; + llhttp_pause(&parser_); + } +#else /* !NODE_EXPERIMENTAL_HTTP */ + size_t nread = http_parser_execute(&parser_, &settings, data, len); + err = HTTP_PARSER_ERRNO(&parser_); + + // Finish() + if (data == nullptr) { + // `http_parser_execute()` returns either `0` or `1` when `len` is 0 + // (part of the finishing sequence). + CHECK_EQ(len, 0); + switch (nread) { + case 0: + err = HPE_OK; + break; + case 1: + nread = 0; + break; + default: + UNREACHABLE(); + } + + // Regular Execute() + } else { + Save(); + } +#endif /* NODE_EXPERIMENTAL_HTTP */ + + // Unassign the 'buffer_' variable + current_buffer_.Clear(); + current_buffer_len_ = 0; + current_buffer_data_ = nullptr; + + // If there was an exception in one of the callbacks + if (got_exception_) + return scope.Escape(Local()); + + Local nread_obj = Integer::New(env()->isolate(), nread); + + // If there was a parse error in one of the callbacks + // TODO(bnoordhuis) What if there is an error on EOF? + if (!parser_.upgrade && err != HPE_OK) { + Local e = Exception::Error(env()->parse_error_string()); + Local obj = e->ToObject(env()->isolate()->GetCurrentContext()) + .ToLocalChecked(); + obj->Set(env()->context(), + env()->bytes_parsed_string(), + nread_obj).FromJust(); +#ifdef NODE_EXPERIMENTAL_HTTP + const char* errno_reason = llhttp_get_error_reason(&parser_); + + Local code; + Local reason; + if (err == HPE_USER) { + const char* colon = strchr(errno_reason, ':'); + CHECK_NE(colon, nullptr); + code = OneByteString(env()->isolate(), errno_reason, + colon - errno_reason); + reason = OneByteString(env()->isolate(), colon + 1); + } else { + code = OneByteString(env()->isolate(), llhttp_errno_name(err)); + reason = OneByteString(env()->isolate(), errno_reason); + } + + obj->Set(env()->context(), env()->code_string(), code).FromJust(); + obj->Set(env()->context(), env()->reason_string(), reason).FromJust(); +#else /* !NODE_EXPERIMENTAL_HTTP */ + obj->Set(env()->context(), + env()->code_string(), + OneByteString(env()->isolate(), + http_errno_name(err))).FromJust(); +#endif /* NODE_EXPERIMENTAL_HTTP */ + return scope.Escape(e); + } + + // No return value is needed for `Finish()` + if (data == nullptr) { + return scope.Escape(Local()); + } + return scope.Escape(nread_obj); + } + + Local CreateHeaders() { + // There could be extra entries but the max size should be fixed + Local headers_v[kMaxHeaderFieldsCount * 2]; + + for (size_t i = 0; i < num_values_; ++i) { + headers_v[i * 2] = fields_[i].ToString(env()); + headers_v[i * 2 + 1] = values_[i].ToString(env()); + } + + return Array::New(env()->isolate(), headers_v, num_values_ * 2); + } + + + // spill headers and request path to JS land + void Flush() { + HandleScope scope(env()->isolate()); + + Local obj = object(); + Local cb = obj->Get(env()->context(), kOnHeaders).ToLocalChecked(); + + if (!cb->IsFunction()) + return; + + Local argv[2] = { + CreateHeaders(), + url_.ToString(env()) + }; + + MaybeLocal r = MakeCallback(cb.As(), + arraysize(argv), + argv); + + if (r.IsEmpty()) + got_exception_ = true; + + url_.Reset(); + have_flushed_ = true; + } + + + void Init(parser_type_t type) { +#ifdef NODE_EXPERIMENTAL_HTTP + llhttp_init(&parser_, type, &settings); + header_nread_ = 0; +#else /* !NODE_EXPERIMENTAL_HTTP */ + http_parser_init(&parser_, type); +#endif /* NODE_EXPERIMENTAL_HTTP */ + url_.Reset(); + status_message_.Reset(); + num_fields_ = 0; + num_values_ = 0; + have_flushed_ = false; + got_exception_ = false; + } + + + int TrackHeader(size_t len) { +#ifdef NODE_EXPERIMENTAL_HTTP + header_nread_ += len; + if (header_nread_ >= kMaxHeaderSize) { + llhttp_set_error_reason(&parser_, "HPE_HEADER_OVERFLOW:Header overflow"); + return HPE_USER; + } +#endif /* NODE_EXPERIMENTAL_HTTP */ + return 0; + } + + + int MaybePause() { +#ifdef NODE_EXPERIMENTAL_HTTP + CHECK_NE(execute_depth_, 0); + + if (!pending_pause_) { + return 0; + } + + pending_pause_ = false; + llhttp_set_error_reason(&parser_, "Paused in callback"); + return HPE_PAUSED; +#else /* !NODE_EXPERIMENTAL_HTTP */ + return 0; +#endif /* NODE_EXPERIMENTAL_HTTP */ + } + + parser_t parser_; + StringPtr fields_[kMaxHeaderFieldsCount]; // header fields + StringPtr values_[kMaxHeaderFieldsCount]; // header values + StringPtr url_; + StringPtr status_message_; + size_t num_fields_; + size_t num_values_; + bool have_flushed_; + bool got_exception_; + Local current_buffer_; + size_t current_buffer_len_; + char* current_buffer_data_; +#ifdef NODE_EXPERIMENTAL_HTTP + unsigned int execute_depth_ = 0; + bool pending_pause_ = false; + uint64_t header_nread_ = 0; +#endif /* NODE_EXPERIMENTAL_HTTP */ + + // These are helper functions for filling `http_parser_settings`, which turn + // a member function of Parser into a C-style HTTP parser callback. + template struct Proxy; + template + struct Proxy { + static int Raw(parser_t* p, Args ... args) { + Parser* parser = ContainerOf(&Parser::parser_, p); + int rv = (parser->*Member)(std::forward(args)...); + if (rv == 0) { + rv = parser->MaybePause(); + } + return rv; + } + }; + + typedef int (Parser::*Call)(); + typedef int (Parser::*DataCall)(const char* at, size_t length); + + static const parser_settings_t settings; +#ifdef NODE_EXPERIMENTAL_HTTP + static const uint64_t kMaxHeaderSize = 8 * 1024; +#endif /* NODE_EXPERIMENTAL_HTTP */ +}; + +const parser_settings_t Parser::settings = { + Proxy::Raw, + Proxy::Raw, + Proxy::Raw, + Proxy::Raw, + Proxy::Raw, + Proxy::Raw, + Proxy::Raw, + Proxy::Raw, +#ifdef NODE_EXPERIMENTAL_HTTP + Proxy::Raw, + Proxy::Raw, +#else /* !NODE_EXPERIMENTAL_HTTP */ + nullptr, + nullptr, +#endif /* NODE_EXPERIMENTAL_HTTP */ +}; + + +void InitializeHttpParser(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + Local t = env->NewFunctionTemplate(Parser::New); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser")); + + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "REQUEST"), + Integer::New(env->isolate(), HTTP_REQUEST)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "RESPONSE"), + Integer::New(env->isolate(), HTTP_RESPONSE)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeaders"), + Integer::NewFromUnsigned(env->isolate(), kOnHeaders)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeadersComplete"), + Integer::NewFromUnsigned(env->isolate(), kOnHeadersComplete)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnBody"), + Integer::NewFromUnsigned(env->isolate(), kOnBody)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnMessageComplete"), + Integer::NewFromUnsigned(env->isolate(), kOnMessageComplete)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnExecute"), + Integer::NewFromUnsigned(env->isolate(), kOnExecute)); + + Local methods = Array::New(env->isolate()); +#define V(num, name, string) \ + methods->Set(env->context(), \ + num, FIXED_ONE_BYTE_STRING(env->isolate(), #string)).FromJust(); + HTTP_METHOD_MAP(V) +#undef V + target->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "methods"), + methods).FromJust(); + + t->Inherit(AsyncWrap::GetConstructorTemplate(env)); + env->SetProtoMethod(t, "close", Parser::Close); + env->SetProtoMethod(t, "free", Parser::Free); + env->SetProtoMethod(t, "execute", Parser::Execute); + env->SetProtoMethod(t, "finish", Parser::Finish); + env->SetProtoMethod(t, "reinitialize", Parser::Reinitialize); + env->SetProtoMethod(t, "pause", Parser::Pause); + env->SetProtoMethod(t, "resume", Parser::Pause); + env->SetProtoMethod(t, "consume", Parser::Consume); + env->SetProtoMethod(t, "unconsume", Parser::Unconsume); + env->SetProtoMethod(t, "getCurrentBuffer", Parser::GetCurrentBuffer); + + target->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"), + t->GetFunction(env->context()).ToLocalChecked()).FromJust(); +} + +} // anonymous namespace +} // namespace node + +#endif // SRC_NODE_HTTP_PARSER_IMPL_H_ diff --git a/src/node_http_parser_llhttp.cc b/src/node_http_parser_llhttp.cc new file mode 100644 index 0000000000..7b9999d13c --- /dev/null +++ b/src/node_http_parser_llhttp.cc @@ -0,0 +1,17 @@ +#define NODE_EXPERIMENTAL_HTTP 1 + +#include "node_http_parser_impl.h" + +namespace node { + +const char* llhttp_version = + NODE_STRINGIFY(LLHTTP_VERSION_MAJOR) + "." + NODE_STRINGIFY(LLHTTP_VERSION_MINOR) + "." + NODE_STRINGIFY(LLHTTP_VERSION_PATCH); + +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(http_parser_llhttp, + node::InitializeHttpParser) diff --git a/src/node_http_parser_traditional.cc b/src/node_http_parser_traditional.cc new file mode 100644 index 0000000000..2bff20c165 --- /dev/null +++ b/src/node_http_parser_traditional.cc @@ -0,0 +1,18 @@ +#ifdef NODE_EXPERIMENTAL_HTTP +#undef NODE_EXPERIMENTAL_HTTP +#endif + +#include "node_http_parser_impl.h" + +namespace node { + +const char* http_parser_version = + NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) + "." + NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) + "." + NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); + +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(http_parser, node::InitializeHttpParser) diff --git a/src/node_internals.h b/src/node_internals.h index 6cb40c9070..1d43d4b141 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -697,6 +697,9 @@ static inline const char* errno_string(int errorno) { extern double prog_start_time; +extern const char* llhttp_version; +extern const char* http_parser_version; + void Abort(const v8::FunctionCallbackInfo& args); void Chdir(const v8::FunctionCallbackInfo& args); void CPUUsage(const v8::FunctionCallbackInfo& args); diff --git a/src/node_metadata.cc b/src/node_metadata.cc index 1bf2fcfce9..fd37dbb97b 100644 --- a/src/node_metadata.cc +++ b/src/node_metadata.cc @@ -11,12 +11,6 @@ #include "node_crypto.h" #endif -#ifdef NODE_EXPERIMENTAL_HTTP -#include "llhttp.h" -#else /* !NODE_EXPERIMENTAL_HTTP */ -#include "http_parser.h" -#endif /* NODE_EXPERIMENTAL_HTTP */ - namespace node { namespace per_process { @@ -32,14 +26,8 @@ Metadata::Versions::Versions() { modules = NODE_STRINGIFY(NODE_MODULE_VERSION); nghttp2 = NGHTTP2_VERSION; napi = NODE_STRINGIFY(NAPI_VERSION); - -#ifdef NODE_EXPERIMENTAL_HTTP - llhttp = NODE_STRINGIFY(LLHTTP_VERSION_MAJOR) "." NODE_STRINGIFY( - LLHTTP_VERSION_MINOR) "." NODE_STRINGIFY(LLHTTP_VERSION_PATCH); -#else /* !NODE_EXPERIMENTAL_HTTP */ - http_parser = NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) "." NODE_STRINGIFY( - HTTP_PARSER_VERSION_MINOR) "." NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); -#endif /* NODE_EXPERIMENTAL_HTTP */ + llhttp = llhttp_version; + http_parser = http_parser_version; #if HAVE_OPENSSL openssl = crypto::GetOpenSSLVersion(); diff --git a/src/node_metadata.h b/src/node_metadata.h index 0f32fcf21d..9f383be5f8 100644 --- a/src/node_metadata.h +++ b/src/node_metadata.h @@ -15,13 +15,9 @@ namespace node { V(ares) \ V(modules) \ V(nghttp2) \ - V(napi) - -#ifdef NODE_EXPERIMENTAL_HTTP -#define NODE_VERSIONS_KEY_HTTP(V) V(llhttp) -#else /* !NODE_EXPERIMENTAL_HTTP */ -#define NODE_VERSIONS_KEY_HTTP(V) V(http_parser) -#endif /* NODE_EXPERIMENTAL_HTTP */ + V(napi) \ + V(llhttp) \ + V(http_parser) \ #if HAVE_OPENSSL #define NODE_VERSIONS_KEY_CRYPTO(V) V(openssl) @@ -31,7 +27,6 @@ namespace node { #define NODE_VERSIONS_KEYS(V) \ NODE_VERSIONS_KEYS_BASE(V) \ - NODE_VERSIONS_KEY_HTTP(V) \ NODE_VERSIONS_KEY_CRYPTO(V) class Metadata { diff --git a/src/node_options.cc b/src/node_options.cc index 2168b99075..885501839c 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -39,6 +39,11 @@ void EnvironmentOptions::CheckOptions(std::vector* errors) { if (syntax_check_only && has_eval_string) { errors->push_back("either --check or --eval can be used, not both"); } + + if (http_parser != "legacy" && http_parser != "llhttp") { + errors->push_back("invalid value for --http-parser"); + } + debug_options->CheckOptions(errors); } @@ -102,6 +107,15 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { &EnvironmentOptions::experimental_worker, kAllowedInEnvironment); AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals); + AddOption("--http-parser", + "Select which HTTP parser to use; either 'legacy' or 'llhttp' " +#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT + "(default: llhttp).", +#else + "(default: legacy).", +#endif + &EnvironmentOptions::http_parser, + kAllowedInEnvironment); AddOption("--loader", "(with --experimental-modules) use the specified file as a " "custom loader", diff --git a/src/node_options.h b/src/node_options.h index 8e9ea6304e..dfe7ff6c5e 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -72,6 +72,12 @@ class EnvironmentOptions : public Options { bool experimental_vm_modules = false; bool experimental_worker = false; bool expose_internals = false; + std::string http_parser = +#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT + "llhttp"; +#else + "legacy"; +#endif bool no_deprecation = false; bool no_force_async_hooks_checks = false; bool no_warnings = false; -- cgit v1.2.3