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>
This commit is contained in:
Fedor Indutny 2018-10-29 22:06:09 -04:00
parent d3f02d0da3
commit d4654d89be
22 changed files with 7253 additions and 77 deletions

26
LICENSE
View File

@ -606,6 +606,32 @@ The externally maintained libraries used by Node.js are:
n° 289016). Three clause BSD license. n° 289016). Three clause BSD license.
""" """
- llhttp, located at deps/llhttp, is licensed as follows:
"""
This software is licensed under the MIT License.
Copyright Fedor Indutny, 2018.
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.
"""
- OpenSSL, located at deps/openssl, is licensed as follows: - OpenSSL, located at deps/openssl, is licensed as follows:
""" """
Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved. Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.

View File

@ -182,6 +182,11 @@ parser.add_option('--openssl-system-ca-path',
help='Use the specified path to system CA (PEM format) in addition to ' help='Use the specified path to system CA (PEM format) in addition to '
'the OpenSSL supplied CA store or compiled-in Mozilla CA copy.') 'the OpenSSL supplied CA store or compiled-in Mozilla CA copy.')
parser.add_option('--experimental-http-parser',
action='store_true',
dest='experimental_http_parser',
help='use llhttp instead of http_parser')
shared_optgroup.add_option('--shared-http-parser', shared_optgroup.add_option('--shared-http-parser',
action='store_true', action='store_true',
dest='shared_http_parser', dest='shared_http_parser',
@ -1106,6 +1111,9 @@ def configure_node(o):
else: else:
o['variables']['node_target_type'] = 'executable' o['variables']['node_target_type'] = 'executable'
o['variables']['node_experimental_http_parser'] = \
b(options.experimental_http_parser)
def configure_library(lib, output): def configure_library(lib, output):
shared_lib = 'shared_' + lib shared_lib = 'shared_' + lib
output['variables']['node_' + shared_lib] = b(getattr(options, shared_lib)) output['variables']['node_' + shared_lib] = b(getattr(options, shared_lib))

22
deps/llhttp/LICENSE-MIT vendored Normal file
View File

@ -0,0 +1,22 @@
This software is licensed under the MIT License.
Copyright Fedor Indutny, 2018.
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.

129
deps/llhttp/README.md vendored Normal file
View File

@ -0,0 +1,129 @@
# llhttp
[![Build Status](https://secure.travis-ci.org/indutny/llhttp.svg)](http://travis-ci.org/indutny/llhttp)
Port of [http_parser][0] to [llparse][1].
## Why?
Let's face it, [http_parser][0] is practically unmaintainable. Even
introduction of a single new method results in a significant code churn.
This project aims to:
* Make it maintainable
* Verifiable
* Improving benchmarks where possible
## How?
Over time, different approaches for improving [http_parser][0]'s code base
were tried. However, all of them failed due to resulting significant performance
degradation.
This project is a port of [http_parser][0] to TypeScript. [llparse][1] is used
to generate the output C and/or bitcode artifacts, which could be compiled and
linked with the embedder's program (like [Node.js][7]).
## Peformance
So far llhttp outperforms http_parser:
| | input size | bandwidth | reqs/sec | time |
|:----------------|-----------:|-------------:|-----------:|--------:|
| **llhttp** _(C)_ | 8192.00 mb | 1497.88 mb/s | 3020458.87 ops/sec | 5.47 s |
| **llhttp** _(bitcode)_ | 8192.00 mb | 1131.75 mb/s | 2282171.24 ops/sec | 7.24 s |
| **http_parser** | 8192.00 mb | 694.66 mb/s | 1406180.33 req/sec | 11.79 s |
llhttp is faster by approximately **116%**.
## Maintenance
llhttp project has about 1400 lines of TypeScript code describing the parser
itself and around 450 lines of C code and headers providing the helper methods.
The whole [http_parser][0] is implemented in approximately 2500 lines of C, and
436 lines of headers.
All optimizations and multi-character matching in llhttp are generated
automatically, and thus doesn't add any extra maintenance cost. On the contrary,
most of http_parser's code is hand-optimized and unrolled. Instead describing
"how" it should parse the HTTP requests/responses, a maintainer should
implement the new features in [http_parser][0] cautiously, considering
possible performance degradation and manually optimizing the new code.
## Verification
The state machine graph is encoded explicitly in llhttp. The [llparse][1]
automatically checks the graph for absence of loops and correct reporting of the
input ranges (spans) like header names and values. In the future, additional
checks could be performed to get even stricter verification of the llhttp.
## Usage
```C
#include "llhttp.h"
llhttp_t parser;
llhttp_settings_t settings;
/* Initialize user callbacks and settings */
llhttp_settings_init(&settings);
/* Set user callback */
settings.on_message_complete = handle_on_message_complete;
/* Initialize the parser in HTTP_BOTH mode, meaning that it will select between
* HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first
* input.
*/
llhttp_init(&parser, HTTP_BOTH, &settings);
/* Use `llhttp_set_type(&parser, HTTP_REQUEST);` to override the mode */
/* Parse request! */
const char* request = "GET / HTTP/1.1\r\n\r\n";
int request_len = strlen(request);
enum llhttp_errno err = llhttp_execute(&parser, request, request_len);
if (err == HPE_OK) {
/* Successfully parsed! */
} else {
fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err),
parser.reason);
}
```
---
#### LICENSE
This software is licensed under the MIT License.
Copyright Fedor Indutny, 2018.
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.
[0]: https://github.com/nodejs/http-parser
[1]: https://github.com/indutny/llparse
[2]: https://en.wikipedia.org/wiki/Register_allocation#Spilling
[3]: https://en.wikipedia.org/wiki/Tail_call
[4]: https://llvm.org/docs/LangRef.html
[5]: https://llvm.org/docs/LangRef.html#call-instruction
[6]: https://clang.llvm.org/
[7]: https://github.com/nodejs/node

46
deps/llhttp/common.gypi vendored Normal file
View File

@ -0,0 +1,46 @@
{
'target_defaults': {
'default_configuration': 'Debug',
'configurations': {
# TODO: hoist these out and put them somewhere common, because
# RuntimeLibrary MUST MATCH across the entire project
'Debug': {
'defines': [ 'DEBUG', '_DEBUG' ],
'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ],
'msvs_settings': {
'VCCLCompilerTool': {
'RuntimeLibrary': 1, # static debug
},
},
},
'Release': {
'defines': [ 'NDEBUG' ],
'cflags': [ '-Wall', '-Wextra', '-O3' ],
'msvs_settings': {
'VCCLCompilerTool': {
'RuntimeLibrary': 0, # static release
},
},
}
},
'msvs_settings': {
'VCCLCompilerTool': {
# Compile as C++. llhttp.c is actually C99, but C++ is
# close enough in this case.
'CompileAs': 2,
},
'VCLibrarianTool': {
},
'VCLinkerTool': {
'GenerateDebugInformation': 'true',
},
},
'conditions': [
['OS == "win"', {
'defines': [
'WIN32'
],
}]
],
},
}

353
deps/llhttp/include/llhttp.h vendored Normal file
View File

@ -0,0 +1,353 @@
#ifndef INCLUDE_LLHTTP_H_
#define INCLUDE_LLHTTP_H_
#define LLHTTP_VERSION_MAJOR 1
#define LLHTTP_VERSION_MINOR 0
#define LLHTTP_VERSION_PATCH 0
#ifndef INCLUDE_LLHTTP_ITSELF_H_
#define INCLUDE_LLHTTP_ITSELF_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
typedef struct llhttp__internal_s llhttp__internal_t;
struct llhttp__internal_s {
int32_t _index;
void* _span_pos0;
void* _span_cb0;
int32_t error;
const char* reason;
const char* error_pos;
void* data;
void* _current;
uint64_t content_length;
uint8_t type;
uint8_t method;
uint8_t http_major;
uint8_t http_minor;
uint8_t header_state;
uint8_t flags;
uint8_t upgrade;
uint16_t status_code;
uint8_t finish;
void* settings;
};
int llhttp__internal_init(llhttp__internal_t* s);
int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* INCLUDE_LLHTTP_ITSELF_H_ */
#ifndef LLLLHTTP_C_HEADERS_
#define LLLLHTTP_C_HEADERS_
#ifdef __cplusplus
extern "C" {
#endif
enum llhttp_errno {
HPE_OK = 0,
HPE_INTERNAL = 1,
HPE_STRICT = 2,
HPE_LF_EXPECTED = 3,
HPE_UNEXPECTED_CONTENT_LENGTH = 4,
HPE_CLOSED_CONNECTION = 5,
HPE_INVALID_METHOD = 6,
HPE_INVALID_URL = 7,
HPE_INVALID_CONSTANT = 8,
HPE_INVALID_VERSION = 9,
HPE_INVALID_HEADER_TOKEN = 10,
HPE_INVALID_CONTENT_LENGTH = 11,
HPE_INVALID_CHUNK_SIZE = 12,
HPE_INVALID_STATUS = 13,
HPE_INVALID_EOF_STATE = 14,
HPE_CB_MESSAGE_BEGIN = 15,
HPE_CB_HEADERS_COMPLETE = 16,
HPE_CB_MESSAGE_COMPLETE = 17,
HPE_CB_CHUNK_HEADER = 18,
HPE_CB_CHUNK_COMPLETE = 19,
HPE_PAUSED = 20,
HPE_PAUSED_UPGRADE = 21,
HPE_USER = 22
};
typedef enum llhttp_errno llhttp_errno_t;
enum llhttp_flags {
F_CONNECTION_KEEP_ALIVE = 0x1,
F_CONNECTION_CLOSE = 0x2,
F_CONNECTION_UPGRADE = 0x4,
F_CHUNKED = 0x8,
F_UPGRADE = 0x10,
F_CONTENT_LENGTH = 0x20,
F_SKIPBODY = 0x40,
F_TRAILING = 0x80
};
typedef enum llhttp_flags llhttp_flags_t;
enum llhttp_type {
HTTP_BOTH = 0,
HTTP_REQUEST = 1,
HTTP_RESPONSE = 2
};
typedef enum llhttp_type llhttp_type_t;
enum llhttp_finish {
HTTP_FINISH_SAFE = 0,
HTTP_FINISH_SAFE_WITH_CB = 1,
HTTP_FINISH_UNSAFE = 2
};
typedef enum llhttp_finish llhttp_finish_t;
enum llhttp_method {
HTTP_DELETE = 0,
HTTP_GET = 1,
HTTP_HEAD = 2,
HTTP_POST = 3,
HTTP_PUT = 4,
HTTP_CONNECT = 5,
HTTP_OPTIONS = 6,
HTTP_TRACE = 7,
HTTP_COPY = 8,
HTTP_LOCK = 9,
HTTP_MKCOL = 10,
HTTP_MOVE = 11,
HTTP_PROPFIND = 12,
HTTP_PROPPATCH = 13,
HTTP_SEARCH = 14,
HTTP_UNLOCK = 15,
HTTP_BIND = 16,
HTTP_REBIND = 17,
HTTP_UNBIND = 18,
HTTP_ACL = 19,
HTTP_REPORT = 20,
HTTP_MKACTIVITY = 21,
HTTP_CHECKOUT = 22,
HTTP_MERGE = 23,
HTTP_MSEARCH = 24,
HTTP_NOTIFY = 25,
HTTP_SUBSCRIBE = 26,
HTTP_UNSUBSCRIBE = 27,
HTTP_PATCH = 28,
HTTP_PURGE = 29,
HTTP_MKCALENDAR = 30,
HTTP_LINK = 31,
HTTP_UNLINK = 32,
HTTP_SOURCE = 33
};
typedef enum llhttp_method llhttp_method_t;
#define HTTP_ERRNO_MAP(XX) \
XX(0, OK, OK) \
XX(1, INTERNAL, INTERNAL) \
XX(2, STRICT, STRICT) \
XX(3, LF_EXPECTED, LF_EXPECTED) \
XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \
XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \
XX(6, INVALID_METHOD, INVALID_METHOD) \
XX(7, INVALID_URL, INVALID_URL) \
XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \
XX(9, INVALID_VERSION, INVALID_VERSION) \
XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \
XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \
XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \
XX(13, INVALID_STATUS, INVALID_STATUS) \
XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \
XX(15, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \
XX(16, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \
XX(17, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \
XX(18, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \
XX(19, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \
XX(20, PAUSED, PAUSED) \
XX(21, PAUSED_UPGRADE, PAUSED_UPGRADE) \
XX(22, USER, USER) \
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
XX(30, MKCALENDAR, MKCALENDAR) \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
XX(33, SOURCE, SOURCE) \
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* LLLLHTTP_C_HEADERS_ */
#ifndef INCLUDE_LLHTTP_API_H_
#define INCLUDE_LLHTTP_API_H_
#ifdef __cplusplus
extern "C" {
#endif
typedef llhttp__internal_t llhttp_t;
typedef struct llhttp_settings_s llhttp_settings_t;
typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length);
typedef int (*llhttp_cb)(llhttp_t*);
struct llhttp_settings_s {
/* Possible return values 0, -1, `HPE_PAUSED` */
llhttp_cb on_message_begin;
llhttp_data_cb on_url;
llhttp_data_cb on_status;
llhttp_data_cb on_header_field;
llhttp_data_cb on_header_value;
/* Possible return values:
* 0 - Proceed normally
* 1 - Assume that request/response has no body, and proceed to parsing the
* next message
* 2 - Assume absence of body (as above) and make `llhttp_execute()` return
* `HPE_PAUSED_UPGRADE`
* -1 - Error
* `HPE_PAUSED`
*/
llhttp_cb on_headers_complete;
llhttp_data_cb on_body;
/* Possible return values 0, -1, `HPE_PAUSED` */
llhttp_cb on_message_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
* Possible return values 0, -1, `HPE_PAUSED`
*/
llhttp_cb on_chunk_header;
llhttp_cb on_chunk_complete;
};
/* Initialize the parser with specific type and user settings */
void llhttp_init(llhttp_t* parser, llhttp_type_t type,
const llhttp_settings_t* settings);
/* Initialize the settings object */
void llhttp_settings_init(llhttp_settings_t* settings);
/* Parse full or partial request/response, invoking user callbacks along the
* way.
*
* If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing
* interrupts, and such errno is returned from `llhttp_execute()`. If
* `HPE_PAUSED` was used as a errno, the execution can be resumed with
* `llhttp_resume()` call.
*
* In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE`
* is returned after fully parsing the request/response. If the user wishes to
* continue parsing, they need to invoke `llhttp_resume_after_upgrade()`.
*/
llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len);
/* This method should be called when the other side has no further bytes to
* send (e.g. shutdown of readable side of the TCP connection.)
*
* Requests without `Content-Length` and other messages might require treating
* all incoming bytes as the part of the body, up to the last byte of the
* connection. This method will invoke `on_message_complete()` callback if the
* request was terminated safely. Otherwise a error code would be returned.
*/
llhttp_errno_t llhttp_finish(llhttp_t* parser);
/* Returns `1` if the incoming message is parsed until the last byte, and has
* to be completed by calling `llhttp_finish()` on EOF
*/
int llhttp_message_needs_eof(const llhttp_t* parser);
/* Returns `1` if there might be any other messages following the last that was
* successfuly parsed.
*/
int llhttp_should_keep_alive(const llhttp_t* parser);
/* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set
* appropriate error reason.
*
* Important: do not call this from user callbacks! User callbacks must return
* `HPE_PAUSED` if pausing is required.
*/
void llhttp_pause(llhttp_t* parser);
/* Might be called to resume the execution after the pause in user's callback.
* See `llhttp_execute()` above for details.
*
* Call this only if `llhttp_execute()` returns `HPE_PAUSED`.
*/
void llhttp_resume(llhttp_t* parser);
/* Might be called to resume the execution after the pause in user's callback.
* See `llhttp_execute()` above for details.
*
* Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE`
*/
void llhttp_resume_after_upgrade(llhttp_t* parser);
/* Returns the latest return error */
llhttp_errno_t llhttp_get_errno(const llhttp_t* parser);
/* Returns the verbal explanation of the latest returned error.
*
* Note: User callback should set error reason when returning the error. See
* `llhttp_set_error_reason()` for details.
*/
const char* llhttp_get_error_reason(const llhttp_t* parser);
/* Assign verbal description to the returned error. Must be called in user
* callbacks right before returning the errno.
*
* Note: `HPE_USER` error code might be useful in user callbacks.
*/
void llhttp_set_error_reason(llhttp_t* parser, const char* reason);
/* Returns the pointer to the last parsed byte before the returned error. The
* pointer is relative to the `data` argument of `llhttp_execute()`.
*
* Note: this method might be useful for counting the number of parsed bytes.
*/
const char* llhttp_get_error_pos(const llhttp_t* parser);
/* Returns textual name of error code */
const char* llhttp_errno_name(llhttp_errno_t err);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* INCLUDE_LLHTTP_API_H_ */
#endif /* INCLUDE_LLHTTP_H_ */

13
deps/llhttp/llhttp.gyp vendored Normal file
View File

@ -0,0 +1,13 @@
{
'targets': [
{
'target_name': 'llhttp',
'type': 'static_library',
'include_dirs': [ '.', 'include' ],
'direct_dependent_settings': {
'include_dirs': [ 'include' ],
},
'sources': [ 'src/llhttp.c', 'src/api.c', 'src/http.c' ],
},
]
}

205
deps/llhttp/src/api.c vendored Normal file
View File

@ -0,0 +1,205 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "llhttp.h"
#define CALLBACK_MAYBE(PARSER, NAME, ...) \
do { \
llhttp_settings_t* settings; \
settings = (llhttp_settings_t*) (PARSER)->settings; \
if (settings == NULL || settings->NAME == NULL) { \
err = 0; \
break; \
} \
err = settings->NAME(__VA_ARGS__); \
} while (0)
void llhttp_init(llhttp_t* parser, llhttp_type_t type,
const llhttp_settings_t* settings) {
llhttp__internal_init(parser);
parser->type = type;
parser->settings = (void*) settings;
}
llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) {
return llhttp__internal_execute(parser, data, data + len);
}
void llhttp_settings_init(llhttp_settings_t* settings) {
memset(settings, 0, sizeof(*settings));
}
llhttp_errno_t llhttp_finish(llhttp_t* parser) {
int err;
/* We're in an error state. Don't bother doing anything. */
if (parser->error != 0) {
return 0;
}
switch (parser->finish) {
case HTTP_FINISH_SAFE_WITH_CB:
CALLBACK_MAYBE(parser, on_message_complete, parser);
if (err != HPE_OK) return err;
/* FALLTHROUGH */
case HTTP_FINISH_SAFE:
return HPE_OK;
case HTTP_FINISH_UNSAFE:
parser->reason = "Invalid EOF state";
return HPE_INVALID_EOF_STATE;
default:
abort();
}
}
void llhttp_pause(llhttp_t* parser) {
if (parser->error != HPE_OK) {
return;
}
parser->error = HPE_PAUSED;
parser->reason = "Paused";
}
void llhttp_resume(llhttp_t* parser) {
if (parser->error != HPE_PAUSED) {
return;
}
parser->error = 0;
}
void llhttp_resume_after_upgrade(llhttp_t* parser) {
if (parser->error != HPE_PAUSED_UPGRADE) {
return;
}
parser->error = 0;
}
llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) {
return parser->error;
}
const char* llhttp_get_error_reason(const llhttp_t* parser) {
return parser->reason;
}
void llhttp_set_error_reason(llhttp_t* parser, const char* reason) {
parser->reason = reason;
}
const char* llhttp_get_error_pos(const llhttp_t* parser) {
return parser->error_pos;
}
const char* llhttp_errno_name(llhttp_errno_t err) {
#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME;
switch (err) {
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
default: abort();
}
#undef HTTP_ERRNO_GEN
}
/* Callbacks */
int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_message_begin, s);
return err;
}
int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_url, s, p, endp - p);
return err;
}
int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_status, s, p, endp - p);
return err;
}
int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_header_field, s, p, endp - p);
return err;
}
int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_header_value, s, p, endp - p);
return err;
}
int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_headers_complete, s);
return err;
}
int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_message_complete, s);
return err;
}
int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_body, s, p, endp - p);
return err;
}
int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_chunk_header, s);
return err;
}
int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_chunk_complete, s);
return err;
}
/* Private */
void llhttp__debug(llhttp_t* s, const char* p, const char* endp,
const char* msg) {
if (p == endp) {
fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type,
s->flags, msg);
} else {
fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s,
s->type, s->flags, *p, msg);
}
}

120
deps/llhttp/src/http.c vendored Normal file
View File

@ -0,0 +1,120 @@
#include <stdio.h>
#ifndef LLHTTP__TEST
# include "llhttp.h"
#else
# define llhttp_t llparse_t
#endif /* */
int llhttp_message_needs_eof(const llhttp_t* parser);
int llhttp_should_keep_alive(const llhttp_t* parser);
int llhttp__before_headers_complete(llhttp_t* parser, const char* p,
const char* endp) {
/* Set this here so that on_headers_complete() callbacks can see it */
if ((parser->flags & F_UPGRADE) &&
(parser->flags & F_CONNECTION_UPGRADE)) {
/* For responses, "Upgrade: foo" and "Connection: upgrade" are
* mandatory only when it is a 101 Switching Protocols response,
* otherwise it is purely informational, to announce support.
*/
parser->upgrade =
(parser->type == HTTP_REQUEST || parser->status_code == 101);
} else {
parser->upgrade = (parser->method == HTTP_CONNECT);
}
return 0;
}
/* Return values:
* 0 - No body, `restart`, message_complete
* 1 - CONNECT request, `restart`, message_complete, and pause
* 2 - chunk_size_start
* 3 - body_identity
* 4 - body_identity_eof
*/
int llhttp__after_headers_complete(llhttp_t* parser, const char* p,
const char* endp) {
int hasBody;
hasBody = parser->flags & F_CHUNKED || parser->content_length > 0;
if (parser->upgrade && (parser->method == HTTP_CONNECT ||
(parser->flags & F_SKIPBODY) || !hasBody)) {
/* Exit, the rest of the message is in a different protocol. */
return 1;
}
if (parser->flags & F_SKIPBODY) {
return 0;
} else if (parser->flags & F_CHUNKED) {
/* chunked encoding - ignore Content-Length header */
return 2;
} else {
if (!(parser->flags & F_CONTENT_LENGTH)) {
if (!llhttp_message_needs_eof(parser)) {
/* Assume content-length 0 - read the next */
return 0;
} else {
/* Read body until EOF */
return 4;
}
} else if (parser->content_length == 0) {
/* Content-Length header given but zero: Content-Length: 0\r\n */
return 0;
} else {
/* Content-Length header given and non-zero */
return 3;
}
}
}
int llhttp__after_message_complete(llhttp_t* parser, const char* p,
const char* endp) {
int should_keep_alive;
should_keep_alive = llhttp_should_keep_alive(parser);
parser->flags = 0;
parser->finish = HTTP_FINISH_SAFE;
/* NOTE: this is ignored in loose parsing mode */
return should_keep_alive;
}
int llhttp_message_needs_eof(const llhttp_t* parser) {
if (parser->type == HTTP_REQUEST) {
return 0;
}
/* See RFC 2616 section 4.4 */
if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
parser->status_code == 204 || /* No Content */
parser->status_code == 304 || /* Not Modified */
(parser->flags & F_SKIPBODY)) { /* response to a HEAD request */
return 0;
}
if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) {
return 0;
}
return 1;
}
int llhttp_should_keep_alive(const llhttp_t* parser) {
if (parser->http_major > 0 && parser->http_minor > 0) {
/* HTTP/1.1 */
if (parser->flags & F_CONNECTION_CLOSE) {
return 0;
}
} else {
/* HTTP/1.0 or earlier */
if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
return 0;
}
}
return !llhttp_message_needs_eof(parser);
}

6044
deps/llhttp/src/llhttp.c vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
'force_dynamic_crt%': 0, 'force_dynamic_crt%': 0,
'node_module_version%': '', 'node_module_version%': '',
'node_shared_zlib%': 'false', 'node_shared_zlib%': 'false',
'node_experimental_http_parser%': 'false',
'node_shared_http_parser%': 'false', 'node_shared_http_parser%': 'false',
'node_shared_cares%': 'false', 'node_shared_cares%': 'false',
'node_shared_libuv%': 'false', 'node_shared_libuv%': 'false',

View File

@ -163,9 +163,14 @@
], ],
}], }],
[ 'node_shared_http_parser=="false"', { [ 'node_experimental_http_parser=="true"', {
'dependencies': [ 'deps/http_parser/http_parser.gyp:http_parser' ], 'defines': [ 'NODE_EXPERIMENTAL_HTTP' ],
}], 'dependencies': [ 'deps/llhttp/llhttp.gyp:llhttp' ],
}, {
'conditions': [ [ 'node_shared_http_parser=="false"', {
'dependencies': [ 'deps/http_parser/http_parser.gyp:http_parser' ],
} ] ],
} ],
[ 'node_shared_cares=="false"', { [ 'node_shared_cares=="false"', {
'dependencies': [ 'deps/cares/cares.gyp:cares' ], 'dependencies': [ 'deps/cares/cares.gyp:cares' ],

View File

@ -265,6 +265,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(raw_string, "raw") \ V(raw_string, "raw") \
V(read_host_object_string, "_readHostObject") \ V(read_host_object_string, "_readHostObject") \
V(readable_string, "readable") \ V(readable_string, "readable") \
V(reason_string, "reason") \
V(refresh_string, "refresh") \ V(refresh_string, "refresh") \
V(regexp_string, "regexp") \ V(regexp_string, "regexp") \
V(rename_string, "rename") \ V(rename_string, "rename") \

24
src/http_parser_adaptor.h Normal file
View File

@ -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_ */

View File

@ -1,6 +1,6 @@
#include "inspector_socket.h" #include "inspector_socket.h"
#include "http_parser.h" #include "http_parser_adaptor.h"
#include "util-inl.h" #include "util-inl.h"
#define NODE_WANT_INTERNALS 1 #define NODE_WANT_INTERNALS 1
@ -433,8 +433,13 @@ class HttpHandler : public ProtocolHandler {
explicit HttpHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp) explicit HttpHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp)
: ProtocolHandler(inspector, std::move(tcp)), : ProtocolHandler(inspector, std::move(tcp)),
parsing_value_(false) { 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_init(&parser_, HTTP_REQUEST);
http_parser_settings_init(&parser_settings); http_parser_settings_init(&parser_settings);
#endif /* NODE_EXPERIMENTAL_HTTP */
parser_settings.on_header_field = OnHeaderField; parser_settings.on_header_field = OnHeaderField;
parser_settings.on_header_value = OnHeaderValue; parser_settings.on_header_value = OnHeaderValue;
parser_settings.on_message_complete = OnMessageComplete; parser_settings.on_message_complete = OnMessageComplete;
@ -478,9 +483,20 @@ class HttpHandler : public ProtocolHandler {
} }
void OnData(std::vector<char>* data) override { 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()); http_parser_execute(&parser_, &parser_settings, data->data(), data->size());
err = HTTP_PARSER_ERRNO(&parser_);
#endif /* NODE_EXPERIMENTAL_HTTP */
data->clear(); data->clear();
if (parser_.http_errno != HPE_OK) { if (err != HPE_OK) {
CancelHandshake(); CancelHandshake();
} }
// Event handling may delete *this // Event handling may delete *this
@ -517,14 +533,14 @@ class HttpHandler : public ProtocolHandler {
handler->inspector()->SwitchProtocol(nullptr); 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); HttpHandler* handler = From(parser);
handler->parsing_value_ = true; handler->parsing_value_ = true;
handler->headers_[handler->current_header_].append(at, length); handler->headers_[handler->current_header_].append(at, length);
return 0; 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); HttpHandler* handler = From(parser);
if (handler->parsing_value_) { if (handler->parsing_value_) {
handler->parsing_value_ = false; handler->parsing_value_ = false;
@ -534,17 +550,17 @@ class HttpHandler : public ProtocolHandler {
return 0; 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); HttpHandler* handler = From(parser);
handler->path_.append(at, length); handler->path_.append(at, length);
return 0; return 0;
} }
static HttpHandler* From(http_parser* parser) { static HttpHandler* From(parser_t* parser) {
return node::ContainerOf(&HttpHandler::parser_, 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. // Event needs to be fired after the parser is done.
HttpHandler* handler = From(parser); HttpHandler* handler = From(parser);
handler->events_.push_back( handler->events_.push_back(
@ -581,8 +597,8 @@ class HttpHandler : public ProtocolHandler {
} }
bool parsing_value_; bool parsing_value_;
http_parser parser_; parser_t parser_;
http_parser_settings parser_settings; parser_settings_t parser_settings;
std::vector<HttpEvent> events_; std::vector<HttpEvent> events_;
std::string current_header_; std::string current_header_;
std::map<std::string, std::string> headers_; std::map<std::string, std::string> headers_;

View File

@ -53,7 +53,11 @@
#include "async_wrap-inl.h" #include "async_wrap-inl.h"
#include "env-inl.h" #include "env-inl.h"
#include "handle_wrap.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 "nghttp2/nghttp2ver.h"
#include "req_wrap-inl.h" #include "req_wrap-inl.h"
#include "string_bytes.h" #include "string_bytes.h"
@ -179,6 +183,22 @@ static node_module* modlist_internal;
static node_module* modlist_linked; static node_module* modlist_linked;
static node_module* modlist_addon; 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) // Bit flag used to track security reverts (see node_revert.h)
unsigned int reverted = 0; unsigned int reverted = 0;
@ -217,17 +237,15 @@ class NodeTraceStateObserver :
auto trace_process = tracing::TracedValue::Create(); auto trace_process = tracing::TracedValue::Create();
trace_process->BeginDictionary("versions"); trace_process->BeginDictionary("versions");
const char http_parser_version[] = #ifdef NODE_EXPERIMENTAL_HTTP
NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) trace_process->SetString("llhttp", llhttp_version);
"." #else /* !NODE_EXPERIMENTAL_HTTP */
NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) trace_process->SetString("http_parser", http_parser_version);
"." #endif /* NODE_EXPERIMENTAL_HTTP */
NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH);
const char node_napi_version[] = NODE_STRINGIFY(NAPI_VERSION); const char node_napi_version[] = NODE_STRINGIFY(NAPI_VERSION);
const char node_modules_version[] = NODE_STRINGIFY(NODE_MODULE_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("node", NODE_VERSION_STRING);
trace_process->SetString("v8", V8::GetVersion()); trace_process->SetString("v8", V8::GetVersion());
trace_process->SetString("uv", uv_version_string()); trace_process->SetString("uv", uv_version_string());
@ -1344,14 +1362,16 @@ void SetupProcessObject(Environment* env,
Local<Object> versions = Object::New(env->isolate()); Local<Object> versions = Object::New(env->isolate());
READONLY_PROPERTY(process, "versions", versions); READONLY_PROPERTY(process, "versions", versions);
const char http_parser_version[] = NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) #ifdef NODE_EXPERIMENTAL_HTTP
"." READONLY_PROPERTY(versions,
NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) "llhttp",
"." FIXED_ONE_BYTE_STRING(env->isolate(), llhttp_version));
NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); #else /* !NODE_EXPERIMENTAL_HTTP */
READONLY_PROPERTY(versions, READONLY_PROPERTY(versions,
"http_parser", "http_parser",
FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version)); FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version));
#endif /* NODE_EXPERIMENTAL_HTTP */
// +1 to get rid of the leading 'v' // +1 to get rid of the leading 'v'
READONLY_PROPERTY(versions, READONLY_PROPERTY(versions,
"node", "node",

View File

@ -25,7 +25,6 @@
#include "async_wrap-inl.h" #include "async_wrap-inl.h"
#include "env-inl.h" #include "env-inl.h"
#include "http_parser.h"
#include "stream_base-inl.h" #include "stream_base-inl.h"
#include "util-inl.h" #include "util-inl.h"
#include "v8.h" #include "v8.h"
@ -33,6 +32,8 @@
#include <stdlib.h> // free() #include <stdlib.h> // free()
#include <string.h> // strdup() #include <string.h> // strdup()
#include "http_parser_adaptor.h"
// This is a binding to http_parser (https://github.com/nodejs/http-parser) // 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 // 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(). // 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 { class Parser : public AsyncWrap, public StreamListener {
public: 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), : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTPPARSER),
current_buffer_len_(0), current_buffer_len_(0),
current_buffer_data_(nullptr) { current_buffer_data_(nullptr) {
@ -172,18 +173,33 @@ class Parser : public AsyncWrap, public StreamListener {
int on_url(const char* at, size_t length) { int on_url(const char* at, size_t length) {
int rv = TrackHeader(length);
if (rv != 0) {
return rv;
}
url_.Update(at, length); url_.Update(at, length);
return 0; return 0;
} }
int on_status(const char* at, size_t length) { int on_status(const char* at, size_t length) {
int rv = TrackHeader(length);
if (rv != 0) {
return rv;
}
status_message_.Update(at, length); status_message_.Update(at, length);
return 0; return 0;
} }
int on_header_field(const char* at, size_t length) { int on_header_field(const char* at, size_t length) {
int rv = TrackHeader(length);
if (rv != 0) {
return rv;
}
if (num_fields_ == num_values_) { if (num_fields_ == num_values_) {
// start of new field name // start of new field name
num_fields_++; num_fields_++;
@ -206,6 +222,11 @@ class Parser : public AsyncWrap, public StreamListener {
int on_header_value(const char* at, size_t length) { int on_header_value(const char* at, size_t length) {
int rv = TrackHeader(length);
if (rv != 0) {
return rv;
}
if (num_values_ != num_fields_) { if (num_values_ != num_fields_) {
// start of new header value // start of new header value
num_values_++; num_values_++;
@ -222,6 +243,10 @@ class Parser : public AsyncWrap, public StreamListener {
int on_headers_complete() { int on_headers_complete() {
#ifdef NODE_EXPERIMENTAL_HTTP
header_nread_ = 0;
#endif /* NODE_EXPERIMENTAL_HTTP */
// Arguments for the on-headers-complete javascript callback. This // Arguments for the on-headers-complete javascript callback. This
// list needs to be kept in sync with the actual argument list for // list needs to be kept in sync with the actual argument list for
// `parserOnHeadersComplete` in lib/_http_common.js. // `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_MAJOR] = Integer::New(env()->isolate(), parser_.http_major);
argv[A_VERSION_MINOR] = Integer::New(env()->isolate(), parser_.http_minor); 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] = 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); argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade);
@ -332,7 +364,10 @@ class Parser : public AsyncWrap, public StreamListener {
if (r.IsEmpty()) { if (r.IsEmpty()) {
got_exception_ = true; 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; return 0;
@ -357,18 +392,33 @@ class Parser : public AsyncWrap, public StreamListener {
if (r.IsEmpty()) { if (r.IsEmpty()) {
got_exception_ = true; got_exception_ = true;
return -1; return HPE_USER;
} }
return 0; 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) { static void New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsInt32()); CHECK(args[0]->IsInt32());
http_parser_type type = parser_type_t type =
static_cast<http_parser_type>(args[0].As<Int32>()->Value()); static_cast<parser_type_t>(args[0].As<Int32>()->Value());
CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE);
new Parser(env, args.This(), type); new Parser(env, args.This(), type);
} }
@ -434,30 +484,11 @@ class Parser : public AsyncWrap, public StreamListener {
static void Finish(const FunctionCallbackInfo<Value>& args) { static void Finish(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Parser* parser; Parser* parser;
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
CHECK(parser->current_buffer_.IsEmpty()); CHECK(parser->current_buffer_.IsEmpty());
parser->got_exception_ = false; parser->Execute(nullptr, 0);
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);
}
} }
@ -467,8 +498,8 @@ class Parser : public AsyncWrap, public StreamListener {
CHECK(args[0]->IsInt32()); CHECK(args[0]->IsInt32());
CHECK(args[1]->IsBoolean()); CHECK(args[1]->IsBoolean());
bool isReused = args[1]->IsTrue(); bool isReused = args[1]->IsTrue();
http_parser_type type = parser_type_t type =
static_cast<http_parser_type>(args[0].As<Int32>()->Value()); static_cast<parser_type_t>(args[0].As<Int32>()->Value());
CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE);
Parser* parser; Parser* parser;
@ -492,7 +523,21 @@ class Parser : public AsyncWrap, public StreamListener {
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder()); ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
// Should always be called from the same context. // Should always be called from the same context.
CHECK_EQ(env, parser->env()); 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); 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; current_buffer_data_ = data;
got_exception_ = false; got_exception_ = false;
size_t nparsed = parser_errno_t err;
http_parser_execute(&parser_, &settings, data, len);
Save(); #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();
}
err = HTTP_PARSER_ERRNO(&parser_);
#endif /* NODE_EXPERIMENTAL_HTTP */
// Unassign the 'buffer_' variable // Unassign the 'buffer_' variable
current_buffer_.Clear(); current_buffer_.Clear();
@ -616,22 +697,29 @@ class Parser : public AsyncWrap, public StreamListener {
if (got_exception_) if (got_exception_)
return scope.Escape(Local<Value>()); 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 // If there was a parse error in one of the callbacks
// TODO(bnoordhuis) What if there is an error on EOF? // TODO(bnoordhuis) What if there is an error on EOF?
if (!parser_.upgrade && nparsed != len) { if (!parser_.upgrade && err != HPE_OK) {
enum http_errno err = HTTP_PARSER_ERRNO(&parser_);
Local<Value> e = Exception::Error(env()->parse_error_string()); Local<Value> e = Exception::Error(env()->parse_error_string());
Local<Object> obj = e->ToObject(env()->isolate()->GetCurrentContext()) Local<Object> obj = e->ToObject(env()->isolate()->GetCurrentContext())
.ToLocalChecked(); .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(), obj->Set(env()->code_string(),
OneByteString(env()->isolate(), http_errno_name(err))); OneByteString(env()->isolate(), http_errno_name(err)));
#endif /* NODE_EXPERIMENTAL_HTTP */
return scope.Escape(e); return scope.Escape(e);
} }
return scope.Escape(nparsed_obj);
return scope.Escape(nread_obj);
} }
Local<Array> CreateHeaders() { 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); http_parser_init(&parser_, type);
#endif /* NODE_EXPERIMENTAL_HTTP */
url_.Reset(); url_.Reset();
status_message_.Reset(); status_message_.Reset();
num_fields_ = 0; 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 fields_[32]; // header fields
StringPtr values_[32]; // header values StringPtr values_[32]; // header values
StringPtr url_; StringPtr url_;
@ -707,25 +827,37 @@ class Parser : public AsyncWrap, public StreamListener {
Local<Object> current_buffer_; Local<Object> current_buffer_;
size_t current_buffer_len_; size_t current_buffer_len_;
char* current_buffer_data_; 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 // These are helper functions for filling `http_parser_settings`, which turn
// a member function of Parser into a C-style HTTP parser callback. // a member function of Parser into a C-style HTTP parser callback.
template <typename Parser, Parser> struct Proxy; template <typename Parser, Parser> struct Proxy;
template <typename Parser, typename ...Args, int (Parser::*Member)(Args...)> template <typename Parser, typename ...Args, int (Parser::*Member)(Args...)>
struct Proxy<int (Parser::*)(Args...), Member> { 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); 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::*Call)();
typedef int (Parser::*DataCall)(const char* at, size_t length); 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<Call, &Parser::on_message_begin>::Raw,
Proxy<DataCall, &Parser::on_url>::Raw, Proxy<DataCall, &Parser::on_url>::Raw,
Proxy<DataCall, &Parser::on_status>::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<Call, &Parser::on_headers_complete>::Raw,
Proxy<DataCall, &Parser::on_body>::Raw, Proxy<DataCall, &Parser::on_body>::Raw,
Proxy<Call, &Parser::on_message_complete>::Raw, Proxy<Call, &Parser::on_message_complete>::Raw,
nullptr, // on_chunk_header #ifdef NODE_EXPERIMENTAL_HTTP
nullptr // on_chunk_complete Proxy<Call, &Parser::on_chunk_header>::Raw,
Proxy<Call, &Parser::on_chunk_complete>::Raw,
#else /* !NODE_EXPERIMENTAL_HTTP */
nullptr,
nullptr,
#endif /* NODE_EXPERIMENTAL_HTTP */
}; };

View File

@ -25,7 +25,7 @@ function flushPool() {
function demoBug(part1, part2) { function demoBug(part1, part2) {
flushPool(); flushPool();
const parser = new HTTPParser(0); const parser = new HTTPParser(HTTPParser.REQUEST);
parser.headers = []; parser.headers = [];
parser.url = ''; parser.url = '';

View File

@ -2,7 +2,7 @@
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const expected_keys = ['ares', 'http_parser', 'modules', 'node', const expected_keys = ['ares', 'modules', 'node',
'uv', 'v8', 'zlib', 'nghttp2', 'napi']; 'uv', 'v8', 'zlib', 'nghttp2', 'napi'];
if (common.hasCrypto) { if (common.hasCrypto) {
@ -16,6 +16,9 @@ if (common.hasIntl) {
expected_keys.push('unicode'); expected_keys.push('unicode');
} }
expected_keys.push(
process.versions.llhttp === undefined ? 'http_parser' : 'llhttp');
expected_keys.sort(); expected_keys.sort();
const actual_keys = Object.keys(process.versions).sort(); const actual_keys = Object.keys(process.versions).sort();
@ -24,7 +27,8 @@ assert.deepStrictEqual(actual_keys, expected_keys);
const commonTemplate = /^\d+\.\d+\.\d+(?:-.*)?$/; const commonTemplate = /^\d+\.\d+\.\d+(?:-.*)?$/;
assert(commonTemplate.test(process.versions.ares)); assert(commonTemplate.test(process.versions.ares));
assert(commonTemplate.test(process.versions.http_parser)); assert(commonTemplate.test(process.versions.llhttp === undefined ?
process.versions.http_parser : process.versions.llhttp));
assert(commonTemplate.test(process.versions.node)); assert(commonTemplate.test(process.versions.node));
assert(commonTemplate.test(process.versions.uv)); assert(commonTemplate.test(process.versions.uv));
assert(commonTemplate.test(process.versions.zlib)); assert(commonTemplate.test(process.versions.zlib));

View File

@ -36,8 +36,9 @@ proc.once('exit', common.mustCall(() => {
assert(traces.some((trace) => assert(traces.some((trace) =>
trace.name === 'node' && trace.name === 'node' &&
trace.args.process.versions.http_parser === (trace.args.process.versions.http_parser ===
process.versions.http_parser && process.versions.http_parser ||
trace.args.process.versions.llhttp === process.versions.llhttp) &&
trace.args.process.versions.node === trace.args.process.versions.node ===
process.versions.node && process.versions.node &&
trace.args.process.versions.v8 === trace.args.process.versions.v8 ===

View File

@ -149,7 +149,7 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check
{ {
const { HTTPParser } = internalBinding('http_parser'); const { HTTPParser } = internalBinding('http_parser');
testInitialized(new HTTPParser(0), 'HTTPParser'); testInitialized(new HTTPParser(HTTPParser.REQUEST), 'HTTPParser');
} }

View File

@ -58,6 +58,7 @@ else
fi fi
addlicense "libuv" "deps/uv" "$(cat ${rootdir}/deps/uv/LICENSE)" addlicense "libuv" "deps/uv" "$(cat ${rootdir}/deps/uv/LICENSE)"
addlicense "llhttp" "deps/llhttp" "$(cat deps/llhttp/LICENSE-MIT)"
addlicense "OpenSSL" "deps/openssl" \ addlicense "OpenSSL" "deps/openssl" \
"$(sed -e '/^ \*\/$/,$d' -e '/^ [^*].*$/d' -e '/\/\*.*$/d' -e '/^$/d' -e 's/^[/ ]\* *//' ${rootdir}/deps/openssl/openssl/LICENSE)" "$(sed -e '/^ \*\/$/,$d' -e '/^ [^*].*$/d' -e '/\/\*.*$/d' -e '/^$/d' -e 's/^[/ ]\* *//' ${rootdir}/deps/openssl/openssl/LICENSE)"
addlicense "Punycode.js" "lib/punycode.js" \ addlicense "Punycode.js" "lib/punycode.js" \