Skip to content

Commit cdb30c6

Browse files
bushidocodesclaude
andcommitted
feat: replace deprecated http-parser with llhttp (#331)
Node's http-parser (vendored at v2.9.4) is deprecated and frozen. Its maintained successor is llhttp, which is ~2x faster and keeps the same incremental, callback-based parsing model. To avoid pulling Node/npm/llparse into the build, we vendor llhttp's *pre-generated* C release artifacts (the `release` branch of nodejs/llhttp, tag release/v9.4.2): include/llhttp.h plus src/{llhttp,api,http}.c. These compile with the normal C toolchain, so the build toolchain is unchanged. - thirdparty: drop the http-parser git submodule, vendor llhttp sources, and compile the three release .c files into dist/ (see VENDOR.md). - http_parser_settings: port callbacks to the llhttp_t / llhttp_settings_t API (llhttp_settings_init, settings bound at init). The signatures are otherwise identical. - on_headers_complete: http-parser's "return 2" (skip body) maps to llhttp's "return 1"; on_message_complete now returns HPE_PAUSED so the parser stops cleanly after one message instead of parsing trailing bytes as a pipelined request (which would re-enter on_message_begin and trip its asserts). This matches http-parser, which returned immediately after firing on_message_complete for our requests. - http_session: llhttp_execute returns an errno rather than a byte count; derive bytes-consumed from the buffer length (HPE_OK) or llhttp_get_error_pos (HPE_PAUSED / HPE_PAUSED_UPGRADE). - .vscode: repoint include paths/associations at the new llhttp sources. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1e7bd47 commit cdb30c6

16 files changed

Lines changed: 12522 additions & 52 deletions

File tree

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
url = https://github.com/gwsystems/aWsm
44
ignore = dirty
55
branch = master
6-
[submodule "http-parser"]
7-
path = runtime/thirdparty/http-parser
8-
url = https://github.com/gwsystems/http-parser.git
96
[submodule "ck"]
107
path = runtime/thirdparty/ck
118
url = https://github.com/gwsystems/ck.git

.vscode/c_cpp_properties.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"/usr/include/",
88
"${workspaceFolder}/runtime/include/",
99
"${workspaceFolder}/runtime/thirdparty/ck/include/",
10-
"${workspaceFolder}/runtime/thirdparty/http-parser/",
10+
"${workspaceFolder}/runtime/thirdparty/llhttp/include/",
1111
"${workspaceFolder}/runtime/thirdparty/jsmn/",
1212
"${workspaceFolder}/awsm/runtime/libc/wasi/include/",
1313
"${workspaceFolder}/libsledge/include"

.vscode/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"http_total.h": "c",
6767
"http_request.h": "c",
6868
"http.h": "c",
69-
"http_parser.h": "c",
69+
"llhttp.h": "c",
7070
"http_parser_settings.h": "c",
7171
"client_socket.h": "c",
7272
"context.h": "c",
@@ -205,7 +205,7 @@
205205
"**/awsm/target/**": true,
206206
"**/runtime/thirdparty/**": true,
207207
"**/runtime/thirdparty/ck/**": true,
208-
"**/runtime/thirdparty/http-parser/**": true,
208+
"**/runtime/thirdparty/llhttp/**": true,
209209
"**/runtime/thirdparty/jsmn/**": true,
210210
"**/runtime/thirdparty/dist/**": true,
211211
"*.o": true,

runtime/Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ INCLUDES += -Iinclude/ -Ithirdparty/dist/include/ -I../libsledge/include/
127127
CFILES += src/*.c
128128
CFILES += src/arch/${ARCH}/*.c
129129
CFILES += src/libc/*.c
130-
CFILES += thirdparty/dist/lib/http_parser.o
130+
CFILES += thirdparty/dist/lib/llhttp.o
131+
CFILES += thirdparty/dist/lib/llhttp_api.o
132+
CFILES += thirdparty/dist/lib/llhttp_http.o
131133

132134
# Configuring Jasmine
133135
JSMNCFLAGS += -DJSMN_STATIC
@@ -157,7 +159,9 @@ runtime.clean:
157159
@rm -f bin/${BINARY_NAME}
158160

159161
# Thirdparty Dependency Rules
160-
thirdparty/dist/lib/http_parser.o: thirdparty
162+
thirdparty/dist/lib/llhttp.o: thirdparty
163+
thirdparty/dist/lib/llhttp_api.o: thirdparty
164+
thirdparty/dist/lib/llhttp_http.o: thirdparty
161165
thirdparty/dist/include/*.h: thirdparty
162166

163167
.PHONY: thirdparty

runtime/include/http_parser_settings.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#pragma once
22

3-
#include "http_parser.h"
3+
#include "llhttp.h"
44

5-
extern http_parser_settings runtime_http_parser_settings;
5+
extern llhttp_settings_t runtime_http_parser_settings;
66

77
void http_parser_settings_initialize(void);
88

9-
static inline http_parser_settings *
9+
static inline llhttp_settings_t *
1010
http_parser_settings_get()
1111
{
1212
return &runtime_http_parser_settings;

runtime/include/http_session.h

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include "auto_buf.h"
1313
#include "debuglog.h"
1414
#include "epoll_tag.h"
15-
#include "http_parser.h"
15+
#include "llhttp.h"
1616
#include "http_parser_settings.h"
1717
#include "http_request.h"
1818
#include "http_route_total.h"
@@ -44,7 +44,7 @@ struct http_session {
4444
enum http_session_state state;
4545
struct sockaddr client_address; /* client requesting connection! */
4646
int socket;
47-
struct http_parser http_parser;
47+
llhttp_t http_parser;
4848
struct http_request http_request;
4949
struct auto_buf request_buffer;
5050
struct auto_buf response_header;
@@ -73,8 +73,9 @@ static inline void
7373
http_session_parser_init(struct http_session *session)
7474
{
7575
memset(&session->http_request, 0, sizeof(struct http_request));
76-
http_parser_init(&session->http_parser, HTTP_REQUEST);
77-
/* Set the session as the data the http-parser has access to */
76+
/* llhttp binds the callback settings at init time (unlike http-parser, which took them at execute) */
77+
llhttp_init(&session->http_parser, HTTP_REQUEST, http_parser_settings_get());
78+
/* Set the session as the data the parser has access to */
7879
session->http_parser.data = &session->http_request;
7980
}
8081

@@ -293,22 +294,27 @@ http_session_parse(struct http_session *session, ssize_t bytes_received)
293294
assert(session != 0);
294295
assert(bytes_received > 0);
295296

296-
const http_parser_settings *settings = http_parser_settings_get();
297+
const char *parse_start = (const char *)&session->request_buffer.data[session->http_request.length_parsed];
298+
size_t parse_length = (size_t)session->request_buffer.size - session->http_request.length_parsed;
297299

298300
#ifdef LOG_HTTP_PARSER
299-
debuglog("http_parser_execute(%p, %p, %p, %zu\n)", &session->http_parser, settings,
300-
&session->request_buffer.buffer[session->request_buffer.length], bytes_received);
301+
debuglog("llhttp_execute(%p, %p, %zu)\n", &session->http_parser, parse_start, parse_length);
301302
#endif
302-
size_t bytes_parsed =
303-
http_parser_execute(&session->http_parser, settings,
304-
(const char *)&session->request_buffer.data[session->http_request.length_parsed],
305-
(size_t)session->request_buffer.size - session->http_request.length_parsed);
306-
307-
if (session->http_parser.http_errno != HPE_OK) {
308-
debuglog("Error: %s, Description: %s\n",
309-
http_errno_name((enum http_errno)session->http_parser.http_errno),
310-
http_errno_description((enum http_errno)session->http_parser.http_errno));
311-
debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received);
303+
304+
/* Unlike http_parser_execute (which returned a byte count), llhttp_execute returns an error code and
305+
* binds its callback settings at init time. On HPE_OK the whole buffer was consumed. */
306+
enum llhttp_errno err = llhttp_execute(&session->http_parser, parse_start, parse_length);
307+
308+
size_t bytes_parsed;
309+
if (likely(err == HPE_OK)) {
310+
bytes_parsed = parse_length;
311+
} else if (err == HPE_PAUSED || err == HPE_PAUSED_UPGRADE) {
312+
/* Parser stopped early (e.g. on an upgrade); count only the bytes it consumed. */
313+
bytes_parsed = (size_t)(llhttp_get_error_pos(&session->http_parser) - parse_start);
314+
} else {
315+
debuglog("Error: %s, Description: %s\n", llhttp_errno_name(err),
316+
llhttp_get_error_reason(&session->http_parser));
317+
debuglog("Length Read %zu\n", (size_t)bytes_received);
312318
debuglog("Error parsing socket %d\n", session->socket);
313319
return -1;
314320
}

runtime/src/http_parser_settings.c

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include "http_request.h"
88
#include "likely.h"
99

10-
http_parser_settings runtime_http_parser_settings;
10+
llhttp_settings_t runtime_http_parser_settings;
1111

1212
/***********************************************************************
1313
* http-parser Callbacks in lifecycle order *
@@ -22,7 +22,7 @@ http_parser_settings runtime_http_parser_settings;
2222
* @returns 0
2323
*/
2424
int
25-
http_parser_settings_on_url(http_parser *parser, const char *at, size_t length)
25+
http_parser_settings_on_url(llhttp_t *parser, const char *at, size_t length)
2626
{
2727
struct http_request *http_request = (struct http_request *)parser->data;
2828

@@ -84,7 +84,7 @@ http_parser_settings_on_url(http_parser *parser, const char *at, size_t length)
8484
* @param parser
8585
*/
8686
int
87-
http_parser_settings_on_message_begin(http_parser *parser)
87+
http_parser_settings_on_message_begin(llhttp_t *parser)
8888
{
8989
struct http_request *http_request = (struct http_request *)parser->data;
9090

@@ -111,7 +111,7 @@ http_parser_settings_on_message_begin(http_parser *parser)
111111
* @returns 0
112112
*/
113113
int
114-
http_parser_settings_on_header_field(http_parser *parser, const char *at, size_t length)
114+
http_parser_settings_on_header_field(llhttp_t *parser, const char *at, size_t length)
115115
{
116116
struct http_request *http_request = (struct http_request *)parser->data;
117117

@@ -153,7 +153,7 @@ http_parser_settings_on_header_field(http_parser *parser, const char *at, size_t
153153
* @returns 0
154154
*/
155155
int
156-
http_parser_settings_on_header_value(http_parser *parser, const char *at, size_t length)
156+
http_parser_settings_on_header_value(llhttp_t *parser, const char *at, size_t length)
157157
{
158158
struct http_request *http_request = (struct http_request *)parser->data;
159159

@@ -187,7 +187,7 @@ http_parser_settings_on_header_value(http_parser *parser, const char *at, size_t
187187
* @param parser
188188
*/
189189
int
190-
http_parser_settings_on_header_end(http_parser *parser)
190+
http_parser_settings_on_header_end(llhttp_t *parser)
191191
{
192192
struct http_request *http_request = (struct http_request *)parser->data;
193193

@@ -207,7 +207,9 @@ http_parser_settings_on_header_end(http_parser *parser)
207207
return 0;
208208
} else {
209209
http_request->body_length = 0;
210-
return 2;
210+
/* Returning 1 tells llhttp to assume no body and proceed straight to
211+
* on_message_complete (http-parser used 2 for the same intent). */
212+
return 1;
211213
}
212214
}
213215

@@ -223,7 +225,7 @@ const char *http_methods[] = {"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
223225
* @returns 0
224226
*/
225227
int
226-
http_parser_settings_on_body(http_parser *parser, const char *at, size_t length)
228+
http_parser_settings_on_body(llhttp_t *parser, const char *at, size_t length)
227229
{
228230
struct http_request *http_request = (struct http_request *)parser->data;
229231

@@ -260,7 +262,7 @@ http_parser_settings_on_body(http_parser *parser, const char *at, size_t length)
260262
* @returns 0
261263
*/
262264
int
263-
http_parser_settings_on_msg_end(http_parser *parser)
265+
http_parser_settings_on_msg_end(llhttp_t *parser)
264266
{
265267
struct http_request *http_request = (struct http_request *)parser->data;
266268

@@ -273,7 +275,11 @@ http_parser_settings_on_msg_end(http_parser *parser)
273275

274276
http_request->message_end = true;
275277

276-
return 0;
278+
/* Pause once a full message is parsed so llhttp stops cleanly instead of trying to parse any trailing
279+
* bytes as a pipelined next message (which would re-enter on_message_begin and trip its asserts). This
280+
* mirrors http-parser, which returned immediately after firing on_message_complete for our requests.
281+
* http_session_parse treats HPE_PAUSED as a normal stop. */
282+
return HPE_PAUSED;
277283
}
278284

279285
/***********************************************************************
@@ -284,7 +290,7 @@ http_parser_settings_on_msg_end(http_parser *parser)
284290
* The settings global with the Callback Functions for HTTP Events
285291
*/
286292
static inline void
287-
http_parser_settings_register_callbacks(http_parser_settings *settings)
293+
http_parser_settings_register_callbacks(llhttp_settings_t *settings)
288294
{
289295
settings->on_url = http_parser_settings_on_url;
290296
settings->on_message_begin = http_parser_settings_on_message_begin;
@@ -301,6 +307,6 @@ http_parser_settings_register_callbacks(http_parser_settings *settings)
301307
void
302308
http_parser_settings_initialize()
303309
{
304-
http_parser_settings_init(&runtime_http_parser_settings);
310+
llhttp_settings_init(&runtime_http_parser_settings);
305311
http_parser_settings_register_callbacks(&runtime_http_parser_settings);
306312
}

runtime/thirdparty/Makefile

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ DIST_PREFIX=${CURR_DIR}/dist/
55
all: clean build
66

77
.PHONY: build
8-
build: ck jsmn http-parser
8+
build: ck jsmn llhttp
99

1010
# Concurrency Kit
1111
ck/Makefile: ck/Makefile.in ck/configure
@@ -17,20 +17,28 @@ ${DIST_PREFIX}/lib/libck.so: ck/Makefile
1717
.PHONY: ck
1818
ck: ${DIST_PREFIX}/lib/libck.so
1919

20-
# HTTP Parser
21-
${DIST_PREFIX}/include/http_parser.h: http-parser/http_parser.h
22-
mkdir -p ${DIST_PREFIX}
20+
# llhttp HTTP Parser (pre-generated C release of nodejs/llhttp, vendored in llhttp/)
21+
# We compile the release's three C sources directly so the build needs no Node/npm/llparse toolchain.
22+
LLHTTP_CFLAGS = -O3 -Illhttp/include
23+
24+
${DIST_PREFIX}/include/llhttp.h: llhttp/include/llhttp.h
2325
mkdir -p ${DIST_PREFIX}/include/
24-
cp http-parser/http_parser.h ${DIST_PREFIX}/include/
26+
cp llhttp/include/llhttp.h ${DIST_PREFIX}/include/
2527

26-
${DIST_PREFIX}/lib/http_parser.o: http-parser/http_parser.c http-parser/http_parser.h
27-
mkdir -p ${DIST_PREFIX}
28+
${DIST_PREFIX}/lib/llhttp.o: llhttp/src/llhttp.c llhttp/include/llhttp.h
29+
mkdir -p ${DIST_PREFIX}/lib/
30+
$(CC) $(CFLAGS) $(LLHTTP_CFLAGS) -c llhttp/src/llhttp.c -o $@
31+
32+
${DIST_PREFIX}/lib/llhttp_api.o: llhttp/src/api.c llhttp/include/llhttp.h
33+
mkdir -p ${DIST_PREFIX}/lib/
34+
$(CC) $(CFLAGS) $(LLHTTP_CFLAGS) -c llhttp/src/api.c -o $@
35+
36+
${DIST_PREFIX}/lib/llhttp_http.o: llhttp/src/http.c llhttp/include/llhttp.h
2837
mkdir -p ${DIST_PREFIX}/lib/
29-
cd http-parser; $(CC) $(CFLAGS) -I. -c http_parser.c
30-
mv http-parser/http_parser.o ${DIST_PREFIX}/lib/
38+
$(CC) $(CFLAGS) $(LLHTTP_CFLAGS) -c llhttp/src/http.c -o $@
3139

32-
.PHONY: http-parser
33-
http-parser: ${DIST_PREFIX}/lib/http_parser.o ${DIST_PREFIX}/include/http_parser.h
40+
.PHONY: llhttp
41+
llhttp: ${DIST_PREFIX}/lib/llhttp.o ${DIST_PREFIX}/lib/llhttp_api.o ${DIST_PREFIX}/lib/llhttp_http.o ${DIST_PREFIX}/include/llhttp.h
3442

3543
# Jasmine JSON Parser
3644
${DIST_PREFIX}/include/jsmn.h: jsmn/jsmn.h

runtime/thirdparty/http-parser

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
This software is licensed under the MIT License.
2+
3+
Copyright Fedor Indutny, 2018.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a
6+
copy of this software and associated documentation files (the
7+
"Software"), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to permit
10+
persons to whom the Software is furnished to do so, subject to the
11+
following conditions:
12+
13+
The above copyright notice and this permission notice shall be included
14+
in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
19+
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
22+
USE OR OTHER DEALINGS IN THE SOFTWARE.

0 commit comments

Comments
 (0)