diff options
author | Fedor Indutny <fedor@indutny.com> | 2018-10-29 22:06:09 -0400 |
---|---|---|
committer | Fedor Indutny <fedor@indutny.com> | 2018-11-10 17:54:21 -0500 |
commit | d4654d89be0c20f8ca1e153d074a236348618b00 (patch) | |
tree | f6aa2013a63ad85987bdef23fa82f1eade6d08ee /src | |
parent | d3f02d0da3d574b91a15d3ace10e76014b7574fc (diff) | |
download | android-node-v8-d4654d89be0c20f8ca1e153d074a236348618b00.tar.gz android-node-v8-d4654d89be0c20f8ca1e153d074a236348618b00.tar.bz2 android-node-v8-d4654d89be0c20f8ca1e153d074a236348618b00.zip |
deps: introduce `llhttp`
llhttp is modern, written in human-readable TypeScript, verifiable, and
is very easy to maintain.
See: https://github.com/indutny/llhttp
PR-URL: https://github.com/nodejs/node/pull/24059
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Diffstat (limited to 'src')
-rw-r--r-- | src/env.h | 1 | ||||
-rw-r--r-- | src/http_parser_adaptor.h | 24 | ||||
-rw-r--r-- | src/inspector_socket.cc | 34 | ||||
-rw-r--r-- | src/node.cc | 46 | ||||
-rw-r--r-- | src/node_http_parser.cc | 229 |
5 files changed, 266 insertions, 68 deletions
@@ -265,6 +265,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(raw_string, "raw") \ V(read_host_object_string, "_readHostObject") \ V(readable_string, "readable") \ + V(reason_string, "reason") \ V(refresh_string, "refresh") \ V(regexp_string, "regexp") \ V(rename_string, "rename") \ diff --git a/src/http_parser_adaptor.h b/src/http_parser_adaptor.h new file mode 100644 index 0000000000..6d786bd095 --- /dev/null +++ b/src/http_parser_adaptor.h @@ -0,0 +1,24 @@ +#ifndef SRC_HTTP_PARSER_ADAPTOR_H_ +#define SRC_HTTP_PARSER_ADAPTOR_H_ + +#ifdef NODE_EXPERIMENTAL_HTTP +# include "llhttp.h" + +typedef llhttp_type_t parser_type_t; +typedef llhttp_errno_t parser_errno_t; +typedef llhttp_settings_t parser_settings_t; +typedef llhttp_t parser_t; + +#else /* !NODE_EXPERIMENTAL_HTTP */ +# include "http_parser.h" + +typedef enum http_parser_type parser_type_t; +typedef enum http_errno parser_errno_t; +typedef http_parser_settings parser_settings_t; +typedef http_parser parser_t; + +#define HPE_USER HPE_UNKNOWN + +#endif /* NODE_EXPERIMENTAL_HTTP */ + +#endif /* SRC_HTTP_PARSER_ADAPTOR_H_ */ diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc index dc36359b5c..8c2d0a5a22 100644 --- a/src/inspector_socket.cc +++ b/src/inspector_socket.cc @@ -1,6 +1,6 @@ #include "inspector_socket.h" -#include "http_parser.h" +#include "http_parser_adaptor.h" #include "util-inl.h" #define NODE_WANT_INTERNALS 1 @@ -433,8 +433,13 @@ class HttpHandler : public ProtocolHandler { explicit HttpHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp) : ProtocolHandler(inspector, std::move(tcp)), parsing_value_(false) { +#ifdef NODE_EXPERIMENTAL_HTTP + llhttp_init(&parser_, HTTP_REQUEST, &parser_settings); + llhttp_settings_init(&parser_settings); +#else /* !NODE_EXPERIMENTAL_HTTP */ http_parser_init(&parser_, HTTP_REQUEST); http_parser_settings_init(&parser_settings); +#endif /* NODE_EXPERIMENTAL_HTTP */ parser_settings.on_header_field = OnHeaderField; parser_settings.on_header_value = OnHeaderValue; parser_settings.on_message_complete = OnMessageComplete; @@ -478,9 +483,20 @@ class HttpHandler : public ProtocolHandler { } void OnData(std::vector<char>* data) override { + parser_errno_t err; +#ifdef NODE_EXPERIMENTAL_HTTP + 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 */ http_parser_execute(&parser_, &parser_settings, data->data(), data->size()); + err = HTTP_PARSER_ERRNO(&parser_); +#endif /* NODE_EXPERIMENTAL_HTTP */ data->clear(); - if (parser_.http_errno != HPE_OK) { + if (err != HPE_OK) { CancelHandshake(); } // Event handling may delete *this @@ -517,14 +533,14 @@ class HttpHandler : public ProtocolHandler { handler->inspector()->SwitchProtocol(nullptr); } - static int OnHeaderValue(http_parser* parser, const char* at, size_t length) { + static int OnHeaderValue(parser_t* parser, const char* at, size_t length) { HttpHandler* handler = From(parser); handler->parsing_value_ = true; handler->headers_[handler->current_header_].append(at, length); return 0; } - static int OnHeaderField(http_parser* parser, const char* at, size_t length) { + static int OnHeaderField(parser_t* parser, const char* at, size_t length) { HttpHandler* handler = From(parser); if (handler->parsing_value_) { handler->parsing_value_ = false; @@ -534,17 +550,17 @@ class HttpHandler : public ProtocolHandler { return 0; } - static int OnPath(http_parser* parser, const char* at, size_t length) { + static int OnPath(parser_t* parser, const char* at, size_t length) { HttpHandler* handler = From(parser); handler->path_.append(at, length); return 0; } - static HttpHandler* From(http_parser* parser) { + static HttpHandler* From(parser_t* parser) { return node::ContainerOf(&HttpHandler::parser_, parser); } - static int OnMessageComplete(http_parser* parser) { + static int OnMessageComplete(parser_t* parser) { // Event needs to be fired after the parser is done. HttpHandler* handler = From(parser); handler->events_.push_back( @@ -581,8 +597,8 @@ class HttpHandler : public ProtocolHandler { } bool parsing_value_; - http_parser parser_; - http_parser_settings parser_settings; + parser_t parser_; + parser_settings_t parser_settings; std::vector<HttpEvent> events_; std::string current_header_; std::map<std::string, std::string> headers_; diff --git a/src/node.cc b/src/node.cc index b612ef3c34..0ee788460e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -53,7 +53,11 @@ #include "async_wrap-inl.h" #include "env-inl.h" #include "handle_wrap.h" -#include "http_parser.h" +#ifdef NODE_EXPERIMENTAL_HTTP +# include "llhttp.h" +#else /* !NODE_EXPERIMENTAL_HTTP */ +# include "http_parser.h" +#endif /* NODE_EXPERIMENTAL_HTTP */ #include "nghttp2/nghttp2ver.h" #include "req_wrap-inl.h" #include "string_bytes.h" @@ -179,6 +183,22 @@ static node_module* modlist_internal; static node_module* modlist_linked; static node_module* modlist_addon; +#ifdef NODE_EXPERIMENTAL_HTTP +static const char llhttp_version[] = + NODE_STRINGIFY(LLHTTP_VERSION_MAJOR) + "." + NODE_STRINGIFY(LLHTTP_VERSION_MINOR) + "." + NODE_STRINGIFY(LLHTTP_VERSION_PATCH); +#else /* !NODE_EXPERIMENTAL_HTTP */ +static const char http_parser_version[] = + NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) + "." + NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) + "." + NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); +#endif /* NODE_EXPERIMENTAL_HTTP */ + // Bit flag used to track security reverts (see node_revert.h) unsigned int reverted = 0; @@ -217,17 +237,15 @@ class NodeTraceStateObserver : auto trace_process = tracing::TracedValue::Create(); trace_process->BeginDictionary("versions"); - const char http_parser_version[] = - NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) - "." - NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) - "." - NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); +#ifdef NODE_EXPERIMENTAL_HTTP + trace_process->SetString("llhttp", llhttp_version); +#else /* !NODE_EXPERIMENTAL_HTTP */ + trace_process->SetString("http_parser", http_parser_version); +#endif /* NODE_EXPERIMENTAL_HTTP */ const char node_napi_version[] = NODE_STRINGIFY(NAPI_VERSION); const char node_modules_version[] = NODE_STRINGIFY(NODE_MODULE_VERSION); - trace_process->SetString("http_parser", http_parser_version); trace_process->SetString("node", NODE_VERSION_STRING); trace_process->SetString("v8", V8::GetVersion()); trace_process->SetString("uv", uv_version_string()); @@ -1344,14 +1362,16 @@ void SetupProcessObject(Environment* env, Local<Object> versions = Object::New(env->isolate()); READONLY_PROPERTY(process, "versions", versions); - const char http_parser_version[] = NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) - "." - NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) - "." - NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); +#ifdef NODE_EXPERIMENTAL_HTTP + READONLY_PROPERTY(versions, + "llhttp", + FIXED_ONE_BYTE_STRING(env->isolate(), llhttp_version)); +#else /* !NODE_EXPERIMENTAL_HTTP */ READONLY_PROPERTY(versions, "http_parser", FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version)); +#endif /* NODE_EXPERIMENTAL_HTTP */ + // +1 to get rid of the leading 'v' READONLY_PROPERTY(versions, "node", diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index b82710480d..482c7263ca 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -25,7 +25,6 @@ #include "async_wrap-inl.h" #include "env-inl.h" -#include "http_parser.h" #include "stream_base-inl.h" #include "util-inl.h" #include "v8.h" @@ -33,6 +32,8 @@ #include <stdlib.h> // free() #include <string.h> // strdup() +#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(). @@ -148,7 +149,7 @@ struct StringPtr { class Parser : public AsyncWrap, public StreamListener { public: - Parser(Environment* env, Local<Object> wrap, enum http_parser_type type) + Parser(Environment* env, Local<Object> wrap, parser_type_t type) : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER), current_buffer_len_(0), current_buffer_data_(nullptr) { @@ -172,18 +173,33 @@ class Parser : public AsyncWrap, public StreamListener { 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_++; @@ -206,6 +222,11 @@ class Parser : public AsyncWrap, public StreamListener { 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_++; @@ -222,6 +243,10 @@ class Parser : public AsyncWrap, public StreamListener { 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. @@ -279,8 +304,15 @@ class Parser : public AsyncWrap, public StreamListener { 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(), http_should_keep_alive(&parser_)); + Boolean::New(env()->isolate(), should_keep_alive); argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade); @@ -332,7 +364,10 @@ class Parser : public AsyncWrap, public StreamListener { if (r.IsEmpty()) { got_exception_ = true; - return -1; +#ifdef NODE_EXPERIMENTAL_HTTP + llhttp_set_error_reason(&parser_, "JS Exception"); +#endif /* NODE_EXPERIMENTAL_HTTP */ + return HPE_USER; } return 0; @@ -357,18 +392,33 @@ class Parser : public AsyncWrap, public StreamListener { if (r.IsEmpty()) { got_exception_ = true; - return -1; + return HPE_USER; } 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<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsInt32()); - http_parser_type type = - static_cast<http_parser_type>(args[0].As<Int32>()->Value()); + parser_type_t type = + static_cast<parser_type_t>(args[0].As<Int32>()->Value()); CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); new Parser(env, args.This(), type); } @@ -434,30 +484,11 @@ class Parser : public AsyncWrap, public StreamListener { static void Finish(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - Parser* parser; ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); CHECK(parser->current_buffer_.IsEmpty()); - parser->got_exception_ = false; - - int rv = http_parser_execute(&(parser->parser_), &settings, nullptr, 0); - - if (parser->got_exception_) - return; - - if (rv != 0) { - enum http_errno err = HTTP_PARSER_ERRNO(&parser->parser_); - - Local<Value> e = Exception::Error(env->parse_error_string()); - Local<Object> obj = e.As<Object>(); - obj->Set(env->bytes_parsed_string(), Integer::New(env->isolate(), 0)); - obj->Set(env->code_string(), - OneByteString(env->isolate(), http_errno_name(err))); - - args.GetReturnValue().Set(e); - } + parser->Execute(nullptr, 0); } @@ -467,8 +498,8 @@ class Parser : public AsyncWrap, public StreamListener { CHECK(args[0]->IsInt32()); CHECK(args[1]->IsBoolean()); bool isReused = args[1]->IsTrue(); - http_parser_type type = - static_cast<http_parser_type>(args[0].As<Int32>()->Value()); + parser_type_t type = + static_cast<parser_type_t>(args[0].As<Int32>()->Value()); CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); Parser* parser; @@ -492,7 +523,21 @@ class Parser : public AsyncWrap, public StreamListener { 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 */ } @@ -602,10 +647,46 @@ class Parser : public AsyncWrap, public StreamListener { current_buffer_data_ = data; got_exception_ = false; - size_t nparsed = - http_parser_execute(&parser_, &settings, data, len); + 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); + if (data != nullptr) { + Save(); + } - Save(); + err = HTTP_PARSER_ERRNO(&parser_); +#endif /* NODE_EXPERIMENTAL_HTTP */ // Unassign the 'buffer_' variable current_buffer_.Clear(); @@ -616,22 +697,29 @@ class Parser : public AsyncWrap, public StreamListener { if (got_exception_) return scope.Escape(Local<Value>()); - Local<Integer> nparsed_obj = Integer::New(env()->isolate(), nparsed); + Local<Integer> 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 && nparsed != len) { - enum http_errno err = HTTP_PARSER_ERRNO(&parser_); - + if (!parser_.upgrade && err != HPE_OK) { Local<Value> e = Exception::Error(env()->parse_error_string()); Local<Object> obj = e->ToObject(env()->isolate()->GetCurrentContext()) .ToLocalChecked(); - obj->Set(env()->bytes_parsed_string(), nparsed_obj); + obj->Set(env()->bytes_parsed_string(), nread_obj); +#ifdef NODE_EXPERIMENTAL_HTTP + obj->Set(env()->code_string(), + OneByteString(env()->isolate(), llhttp_errno_name(err))); + obj->Set(env()->reason_string(), + OneByteString(env()->isolate(), parser_.reason)); +#else /* !NODE_EXPERIMENTAL_HTTP */ obj->Set(env()->code_string(), OneByteString(env()->isolate(), http_errno_name(err))); +#endif /* NODE_EXPERIMENTAL_HTTP */ return scope.Escape(e); } - return scope.Escape(nparsed_obj); + + return scope.Escape(nread_obj); } Local<Array> CreateHeaders() { @@ -684,8 +772,12 @@ class Parser : public AsyncWrap, public StreamListener { } - void Init(enum http_parser_type type) { + void Init(parser_type_t type) { +#ifdef NODE_EXPERIMENTAL_HTTP + llhttp_init(&parser_, type, &settings); +#else /* !NODE_EXPERIMENTAL_HTTP */ http_parser_init(&parser_, type); +#endif /* NODE_EXPERIMENTAL_HTTP */ url_.Reset(); status_message_.Reset(); num_fields_ = 0; @@ -695,7 +787,35 @@ class Parser : public AsyncWrap, public StreamListener { } - http_parser parser_; + int TrackHeader(size_t len) { +#ifdef NODE_EXPERIMENTAL_HTTP + header_nread_ += len; + if (header_nread_ >= kMaxHeaderSize) { + llhttp_set_error_reason(&parser_, "Headers 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_[32]; // header fields StringPtr values_[32]; // header values StringPtr url_; @@ -707,25 +827,37 @@ class Parser : public AsyncWrap, public StreamListener { Local<Object> 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 <typename Parser, Parser> struct Proxy; template <typename Parser, typename ...Args, int (Parser::*Member)(Args...)> struct Proxy<int (Parser::*)(Args...), Member> { - static int Raw(http_parser* p, Args ... args) { + static int Raw(parser_t* p, Args ... args) { Parser* parser = ContainerOf(&Parser::parser_, p); - return (parser->*Member)(std::forward<Args>(args)...); + int rv = (parser->*Member)(std::forward<Args>(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 struct http_parser_settings settings; + static const parser_settings_t settings; +#ifdef NODE_EXPERIMENTAL_HTTP + static const uint64_t kMaxHeaderSize = 80 * 1024; +#endif /* NODE_EXPERIMENTAL_HTTP */ }; -const struct http_parser_settings Parser::settings = { +const parser_settings_t Parser::settings = { Proxy<Call, &Parser::on_message_begin>::Raw, Proxy<DataCall, &Parser::on_url>::Raw, Proxy<DataCall, &Parser::on_status>::Raw, @@ -734,8 +866,13 @@ const struct http_parser_settings Parser::settings = { Proxy<Call, &Parser::on_headers_complete>::Raw, Proxy<DataCall, &Parser::on_body>::Raw, Proxy<Call, &Parser::on_message_complete>::Raw, - nullptr, // on_chunk_header - nullptr // on_chunk_complete +#ifdef NODE_EXPERIMENTAL_HTTP + Proxy<Call, &Parser::on_chunk_header>::Raw, + Proxy<Call, &Parser::on_chunk_complete>::Raw, +#else /* !NODE_EXPERIMENTAL_HTTP */ + nullptr, + nullptr, +#endif /* NODE_EXPERIMENTAL_HTTP */ }; |