From 2b39ceec8c807c04e2c6ecb20124a1ef5de51b53 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 23 Aug 2022 11:28:03 +0200 Subject: [PATCH 1/2] feat: Allow all completion callback to be pausable. --- bench/index.ts | 4 +- src/llhttp/c-headers.ts | 14 +-- src/llhttp/constants.ts | 9 +- src/llhttp/http.ts | 62 ++++++++---- src/llhttp/url.ts | 18 ++-- src/llhttp/utils.ts | 10 +- src/native/api.h | 10 +- test/fixtures/extra.c | 104 +++++++++++++++---- test/md-test.ts | 129 +++++++++++++----------- test/request/pausing.md | 212 +++++++++++++++++++++++++++++++++++++++ test/response/pausing.md | 211 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 652 insertions(+), 131 deletions(-) create mode 100644 test/request/pausing.md create mode 100644 test/response/pausing.md diff --git a/bench/index.ts b/bench/index.ts index 0fad75d9..06688045 100644 --- a/bench/index.ts +++ b/bench/index.ts @@ -65,7 +65,7 @@ if (isURL) { } if (isHTTP) { - requests.forEach((request, name) => { + for(const [request, name] of requests) { console.log('http loose: "%s" (C)', name); spawnSync('./test/tmp/http-loose-request-c', [ @@ -79,5 +79,5 @@ if (isHTTP) { 'bench', request ], { stdio: 'inherit' }); - }); + } } diff --git a/src/llhttp/c-headers.ts b/src/llhttp/c-headers.ts index 717ed6e3..81338871 100644 --- a/src/llhttp/c-headers.ts +++ b/src/llhttp/c-headers.ts @@ -65,8 +65,10 @@ export class CHeaders { res += `enum ${name} {\n`; const keys = Object.keys(map); - keys.forEach((key, i) => { - const isLast = i === keys.length - 1; + const keysLength = keys.length; + for (let i = 0; i < keysLength; i++) { + const key = keys[i]; + const isLast = i === keysLength - 1; let value: number | string = map[key]; @@ -78,7 +80,7 @@ export class CHeaders { if (!isLast) { res += ',\n'; } - }); + } res += '\n};\n'; res += `typedef enum ${name} ${name}_t;\n`; @@ -89,9 +91,9 @@ export class CHeaders { let res = ''; res += `#define ${name}_MAP(XX) \\\n`; - Object.keys(map).forEach((key) => { - res += ` XX(${map[key]!}, ${key.replace(/-/g, '')}, ${key}) \\\n`; - }); + for (const [key, value] of Object.entries(map)) { + res += ` XX(${value!}, ${key.replace(/-/g, '')}, ${key}) \\\n`; + } res += '\n'; return res; diff --git a/src/llhttp/constants.ts b/src/llhttp/constants.ts index e1ba48a8..f3b0e857 100644 --- a/src/llhttp/constants.ts +++ b/src/llhttp/constants.ts @@ -34,6 +34,11 @@ export enum ERROR { PAUSED_H2_UPGRADE = 23, USER = 24, + + CB_URL_COMPLETE = 25, + CB_STATUS_COMPLETE = 26, + CB_HEADER_FIELD_COMPLETE = 27, + CB_HEADER_VALUE_COMPLETE = 28, } export enum TYPE { @@ -189,11 +194,11 @@ export const METHODS_RTSP = [ export const METHOD_MAP = enumToMap(METHODS); export const H_METHOD_MAP: IEnumMap = {}; -Object.keys(METHOD_MAP).forEach((key) => { +for (const key of Object.keys(METHOD_MAP)) { if (/^H/.test(key)) { H_METHOD_MAP[key] = METHOD_MAP[key]; } -}); +} export enum FINISH { SAFE = 0, diff --git a/src/llhttp/http.ts b/src/llhttp/http.ts index e44c8598..91d995ef 100644 --- a/src/llhttp/http.ts +++ b/src/llhttp/http.ts @@ -183,7 +183,9 @@ export class HTTP { }; /* tslint:enable:object-literal-sort-keys */ - NODES.forEach((name) => this.nodes.set(name, p.node(name) as Match)); + for (const name of NODES) { + this.nodes.set(name, p.node(name) as Match); + } } public build(): IHTTPResult { @@ -338,14 +340,16 @@ export class HTTP { notEqual: url.entry.normal, })); - const onUrlCompleteHTTP = p.invoke(this.callback.onUrlComplete); - onUrlCompleteHTTP.otherwise(n('req_http_start')); + const onUrlCompleteHTTP = this.invokePausable( + 'on_url_complete', ERROR.CB_URL_COMPLETE, n('req_http_start'), + ); url.exit.toHTTP .otherwise(onUrlCompleteHTTP); - const onUrlCompleteHTTP09 = p.invoke(this.callback.onUrlComplete); - onUrlCompleteHTTP09.otherwise(n('header_field_start')); + const onUrlCompleteHTTP09 = this.invokePausable( + 'on_url_complete', ERROR.CB_URL_COMPLETE, n('header_field_start'), + ); url.exit.toHTTP09 .otherwise( @@ -425,8 +429,9 @@ export class HTTP { .select(SPECIAL_HEADERS, this.store('header_state', 'header_field_colon')) .otherwise(this.resetHeaderState('header_field_general')); - const onHeaderFieldComplete = p.invoke(this.callback.onHeaderFieldComplete); - onHeaderFieldComplete.otherwise(n('header_value_discard_ws')); + const onHeaderFieldComplete = this.invokePausable( + 'on_header_field_complete', ERROR.CB_HEADER_FIELD_COMPLETE, n('header_value_discard_ws'), + ); const onInvalidHeaderFieldChar = p.error(ERROR.INVALID_HEADER_TOKEN, 'Invalid header field char'); @@ -488,8 +493,9 @@ export class HTTP { n('header_value_discard_lws')); } - const onHeaderValueComplete = p.invoke(this.callback.onHeaderValueComplete); - onHeaderValueComplete.otherwise(n('header_field_start')); + const onHeaderValueComplete = this.invokePausable( + 'on_header_value_complete', ERROR.CB_HEADER_VALUE_COMPLETE, n('header_field_start'), + ); const emptyContentLengthError = p.error( ERROR.INVALID_CONTENT_LENGTH, 'Empty Content-Length'); @@ -1000,16 +1006,34 @@ export class HTTP { private invokePausable(name: string, errorCode: ERROR, next: string | Node) : Node { let cb; - if (name === 'on_message_begin') { - cb = this.callback.onMessageBegin; - } else if (name === 'on_message_complete') { - cb = this.callback.onMessageComplete; - } else if (name === 'on_chunk_header') { - cb = this.callback.onChunkHeader; - } else if (name === 'on_chunk_complete') { - cb = this.callback.onChunkComplete; - } else { - throw new Error('Unknown callback: ' + name); + + switch (name) { + case 'on_message_begin': + cb = this.callback.onMessageBegin; + break; + case 'on_url_complete': + cb = this.callback.onUrlComplete; + break; + case 'on_status_complete': + cb = this.callback.onStatusComplete; + break; + case 'on_header_field_complete': + cb = this.callback.onHeaderFieldComplete; + break; + case 'on_header_value_complete': + cb = this.callback.onHeaderValueComplete; + break; + case 'on_message_complete': + cb = this.callback.onMessageComplete; + break; + case 'on_chunk_header': + cb = this.callback.onChunkHeader; + break; + case 'on_chunk_complete': + cb = this.callback.onChunkComplete; + break; + default: + throw new Error('Unknown callback: ' + name); } const p = this.llparse; diff --git a/src/llhttp/url.ts b/src/llhttp/url.ts index 233971e6..49f816c8 100644 --- a/src/llhttp/url.ts +++ b/src/llhttp/url.ts @@ -98,10 +98,7 @@ export class URL { .match('//', this.spanStart('host', server)) .otherwise(p.error(ERROR.INVALID_URL, 'Unexpected char in url schema')); - [ - server, - serverWithAt, - ].forEach((node) => { + for (const node of [server, serverWithAt]) { node .peek('/', this.spanEnd('host', this.spanStart('path').skipTo(path))) .match('?', this.spanEnd('host', this.spanStart('query', query))) @@ -112,7 +109,7 @@ export class URL { if (node !== serverWithAt) { node.match('@', serverWithAt); } - }); + } serverWithAt .match('@', p.error(ERROR.INVALID_URL, 'Double @ in url')); @@ -142,10 +139,10 @@ export class URL { .otherwise( p.error(ERROR.INVALID_URL, 'Invalid char in url fragment start')); - [ start, schema, schemaDelim ].forEach((node) => { + for (const node of [ start, schema, schemaDelim ]) { /* No whitespace allowed here */ node.match([ ' ', '\r', '\n' ], this.errorInvalid); - }); + } // Adaptors const toHTTP = this.node('to_http'); @@ -161,10 +158,7 @@ export class URL { .match('\r\n', toHTTP09) .otherwise(p.error(ERROR.INVALID_URL, 'Expected CRLF')); - [ - server, serverWithAt, queryOrFragment, queryStart, query, - fragment, - ].forEach((node) => { + for (const node of [server, serverWithAt, queryOrFragment, queryStart, query, fragment]) { let spanName: SpanName | undefined; if (node === server || node === serverWithAt) { @@ -187,7 +181,7 @@ export class URL { node.peek('\r', endTo(skipCRLF)); node.peek('\n', endTo(skipToHTTP09)); - }); + } return { entry, diff --git a/src/llhttp/utils.ts b/src/llhttp/utils.ts index cb4605b1..7c01d663 100644 --- a/src/llhttp/utils.ts +++ b/src/llhttp/utils.ts @@ -9,19 +9,19 @@ export function enumToMap( ): IEnumMap { const res: IEnumMap = {}; - Object.keys(obj).forEach((key) => { + for (const key of Object.keys(obj)) { const value = obj[key]; if (typeof value !== 'number') { - return; + continue; } if (filter && !filter.includes(value)) { - return; + continue; } if (exceptions && exceptions.includes(value)) { - return; + continue; } res[key] = value; - }); + } return res; } diff --git a/src/native/api.h b/src/native/api.h index 0e71d1a6..54812d60 100644 --- a/src/native/api.h +++ b/src/native/api.h @@ -43,6 +43,10 @@ struct llhttp_settings_s { /* Possible return values 0, -1, `HPE_PAUSED` */ llhttp_cb on_message_complete; + llhttp_cb on_url_complete; + llhttp_cb on_status_complete; + llhttp_cb on_header_field_complete; + llhttp_cb on_header_value_complete; /* When on_chunk_header is called, the current chunk length is stored * in parser->content_length. @@ -50,12 +54,6 @@ struct llhttp_settings_s { */ llhttp_cb on_chunk_header; llhttp_cb on_chunk_complete; - - /* Information-only callbacks, return value is ignored */ - llhttp_cb on_url_complete; - llhttp_cb on_status_complete; - llhttp_cb on_header_field_complete; - llhttp_cb on_header_value_complete; }; /* Initialize the parser with specific type and user settings. diff --git a/test/fixtures/extra.c b/test/fixtures/extra.c index 64e9ae97..4ee7b78a 100644 --- a/test/fixtures/extra.c +++ b/test/fixtures/extra.c @@ -13,14 +13,21 @@ int llhttp__on_url(llparse_t* s, const char* p, const char* endp) { int llhttp__on_url_complete(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + llparse__print(p, endp, "url complete"); - return 0; + + #ifdef LLHTTP__TEST_PAUSE_ON_URL_COMPLETE + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif } int llhttp__on_url_schema(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("url.schema", p, endp); } @@ -28,6 +35,7 @@ int llhttp__on_url_schema(llparse_t* s, const char* p, const char* endp) { int llhttp__on_url_host(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("url.host", p, endp); } @@ -35,6 +43,7 @@ int llhttp__on_url_host(llparse_t* s, const char* p, const char* endp) { int llhttp__on_url_path(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("url.path", p, endp); } @@ -42,6 +51,7 @@ int llhttp__on_url_path(llparse_t* s, const char* p, const char* endp) { int llhttp__on_url_query(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("url.query", p, endp); } @@ -49,6 +59,7 @@ int llhttp__on_url_query(llparse_t* s, const char* p, const char* endp) { int llhttp__on_url_fragment(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("url.fragment", p, endp); } @@ -110,9 +121,38 @@ void llhttp__test_finish(llparse_t* s) { } +int llhttp__on_message_begin(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + + llparse__print(p, endp, "message begin"); + + #ifdef LLHTTP__TEST_PAUSE_ON_MESSAGE_BEGIN + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif +} + + +int llhttp__on_message_complete(llparse_t* s, const char* p, const char* endp) { + if (llparse__in_bench) + return 0; + + llparse__print(p, endp, "message complete"); + + #ifdef LLHTTP__TEST_PAUSE_ON_MESSAGE_COMPLETE + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif +} + + int llhttp__on_status(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("status", p, endp); } @@ -120,14 +160,21 @@ int llhttp__on_status(llparse_t* s, const char* p, const char* endp) { int llhttp__on_status_complete(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + llparse__print(p, endp, "status complete"); - return 0; + + #ifdef LLHTTP__TEST_PAUSE_ON_STATUS_COMPLETE + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif } int llhttp__on_header_field(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("header_field", p, endp); } @@ -135,14 +182,21 @@ int llhttp__on_header_field(llparse_t* s, const char* p, const char* endp) { int llhttp__on_header_field_complete(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + llparse__print(p, endp, "header_field complete"); - return 0; + + #ifdef LLHTTP__TEST_PAUSE_ON_HEADER_FIELD_COMPLETE + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif } int llhttp__on_header_value(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("header_value", p, endp); } @@ -150,8 +204,14 @@ int llhttp__on_header_value(llparse_t* s, const char* p, const char* endp) { int llhttp__on_header_value_complete(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + llparse__print(p, endp, "header_value complete"); - return 0; + + #ifdef LLHTTP__TEST_PAUSE_ON_HEADER_VALUE_COMPLETE + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif } @@ -171,29 +231,19 @@ int llhttp__on_headers_complete(llparse_t* s, const char* p, const char* endp) { } else { llparse__print(p, endp, "invalid headers complete"); } - return 0; -} - -int llhttp__on_message_begin(llparse_t* s, const char* p, const char* endp) { - if (llparse__in_bench) + #ifdef LLHTTP__TEST_PAUSE_ON_HEADERS_COMPLETE + return LLPARSE__ERROR_PAUSE; + #else return 0; - llparse__print(p, endp, "message begin"); - return 0; -} - - -int llhttp__on_message_complete(llparse_t* s, const char* p, const char* endp) { - if (llparse__in_bench) - return 0; - llparse__print(p, endp, "message complete"); - return 0; + #endif } int llhttp__on_body(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + return llparse__print_span("body", p, endp); } @@ -201,16 +251,28 @@ int llhttp__on_body(llparse_t* s, const char* p, const char* endp) { int llhttp__on_chunk_header(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + llparse__print(p, endp, "chunk header len=%d", (int) s->content_length); - return 0; + + #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_HEADER + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif } int llhttp__on_chunk_complete(llparse_t* s, const char* p, const char* endp) { if (llparse__in_bench) return 0; + llparse__print(p, endp, "chunk complete"); - return 0; + + #ifdef LLHTTP__TEST_PAUSE_ON_CHUNK_COMPLETE + return LLPARSE__ERROR_PAUSE; + #else + return 0; + #endif } #endif /* LLHTTP__TEST_HTTP */ diff --git a/test/md-test.ts b/test/md-test.ts index 20d9b3d2..9161d89d 100644 --- a/test/md-test.ts +++ b/test/md-test.ts @@ -6,6 +6,8 @@ import * as path from 'path'; import * as vm from 'vm'; import * as llhttp from '../src/llhttp'; +import {IHTTPResult} from '../src/llhttp/http'; +import {IURLResult} from '../src/llhttp/url'; import { build, FixtureResult, TestType } from './fixtures'; // @@ -13,19 +15,42 @@ import { build, FixtureResult, TestType } from './fixtures'; // (different types of tests will re-use them) // +interface INodeCacheEntry { + llparse: LLParse; + entry: IHTTPResult['entry']; +} + +interface IUrlCacheEntry { + llparse: LLParse; + entry: IURLResult['entry']['normal']; +} + +const nodeCache = new Map(); +const urlCache = new Map(); +const modeCache = new Map(); + function buildNode(mode: llhttp.HTTPMode) { + let entry = nodeCache.get(mode); + + if (entry) { + return entry; + } + const p = new LLParse(); const instance = new llhttp.HTTP(p, mode); - return { llparse: p, entry: instance.build().entry }; + entry = { llparse: p, entry: instance.build().entry }; + nodeCache.set(mode, entry); + return entry; } -const httpNode = { - loose: buildNode('loose'), - strict: buildNode('strict'), -}; - function buildURL(mode: llhttp.HTTPMode) { + let entry = urlCache.get(mode); + + if (entry) { + return entry; + } + const p = new LLParse(); const instance = new llhttp.URL(p, mode, true); @@ -35,29 +60,34 @@ function buildURL(mode: llhttp.HTTPMode) { node.exit.toHTTP.otherwise(node.entry.normal); node.exit.toHTTP09.otherwise(node.entry.normal); - return { llparse: p, entry: node.entry.normal }; + entry = { llparse: p, entry: node.entry.normal }; + urlCache.set(mode, entry); + return entry; } -const urlNode = { - loose: buildURL('loose'), - strict: buildURL('strict'), -}; - // // Build binaries using cached nodes/llparse // -async function buildMode(mode: llhttp.HTTPMode, ty: TestType) +async function buildMode(mode: llhttp.HTTPMode, ty: TestType, meta: any) : Promise { + + const cacheKey = `${mode}:${ty}:${JSON.stringify(meta || {})}`; + let entry = modeCache.get(cacheKey); + + if (entry) { + return entry; + } + let node; let prefix: string; - let extra: ReadonlyArray; + let extra: string[]; if (ty === 'url') { - node = urlNode[mode]; + node = buildURL(mode); prefix = 'url'; extra = []; } else { - node = httpNode[mode]; + node = buildNode(mode); prefix = 'http'; extra = [ '-DLLHTTP__TEST_HTTP', @@ -65,48 +95,22 @@ async function buildMode(mode: llhttp.HTTPMode, ty: TestType) ]; } - return await build(node.llparse, node.entry, `${prefix}-${mode}-${ty}`, { + if (meta.pause) { + extra.push(`-DLLHTTP__TEST_PAUSE_${meta.pause.toUpperCase()}=1`); + } + + entry = await build(node.llparse, node.entry, `${prefix}-${mode}-${ty}`, { extra, }, ty); + + modeCache.set(cacheKey, entry); + return entry; } interface IFixtureMap { [key: string]: { [key: string]: Promise }; } -const http: IFixtureMap = { - loose: { - 'none': buildMode('loose', 'none'), - 'request': buildMode('loose', 'request'), - 'request-finish': buildMode('loose', 'request-finish'), - 'request-lenient-chunked-length': buildMode('loose', 'request-lenient-chunked-length'), - 'request-lenient-headers': buildMode('loose', 'request-lenient-headers'), - 'request-lenient-keep-alive': buildMode( 'loose', 'request-lenient-keep-alive'), - 'request-lenient-transfer-encoding': buildMode('loose', 'request-lenient-transfer-encoding'), - 'request-lenient-version': buildMode( 'loose', 'request-lenient-version'), - 'response': buildMode('loose', 'response'), - 'response-finish': buildMode('loose', 'response-finish'), - 'response-lenient-keep-alive': buildMode( 'loose', 'response-lenient-keep-alive'), - 'response-lenient-version': buildMode( 'loose', 'response-lenient-version'), - 'url': buildMode('loose', 'url'), - }, - strict: { - 'none': buildMode('strict', 'none'), - 'request': buildMode('strict', 'request'), - 'request-finish': buildMode('strict', 'request-finish'), - 'request-lenient-chunked-length': buildMode('strict', 'request-lenient-chunked-length'), - 'request-lenient-headers': buildMode('strict', 'request-lenient-headers'), - 'request-lenient-keep-alive': buildMode( 'strict', 'request-lenient-keep-alive'), - 'request-lenient-transfer-encoding': buildMode('strict', 'request-lenient-transfer-encoding'), - 'request-lenient-version': buildMode( 'strict', 'request-lenient-version'), - 'response': buildMode('strict', 'response'), - 'response-finish': buildMode('strict', 'response-finish'), - 'response-lenient-keep-alive': buildMode('strict', 'response-lenient-keep-alive'), - 'response-lenient-version': buildMode( 'strict', 'response-lenient-version'), - 'url': buildMode('strict', 'url'), - }, -}; - // // Run test suite // @@ -121,7 +125,7 @@ function run(name: string): void { input: string, expected: ReadonlyArray): void { it(`should pass in mode="${mode}" and for type="${ty}"`, async () => { - const binary = await http[mode][ty]; + const binary = await buildMode(mode, ty, meta); await binary.check(input, expected, { noScan: meta.noScan === true, }); @@ -255,11 +259,11 @@ function run(name: string): void { } }); - modes.forEach((mode) => { - types.forEach((ty) => { + for (const mode of modes) { + for (const ty of types) { runSingleTest(mode, ty, meta, input, fullExpected); - }); - }); + } + } }); } @@ -267,13 +271,19 @@ function run(name: string): void { describe(group.name + ` at ${name}.md:${group.line + 1}`, function() { this.timeout(60000); - group.children.forEach((child) => runGroup(child)); + for (const child of group.children) { + runGroup(child); + } - group.tests.forEach((test) => runTest(test)); + for (const test of group.tests) { + runTest(test); + } }); } - groups.forEach((group) => runGroup(group)); + for (const group of groups) { + runGroup(group); + } } run('request/sample'); @@ -286,6 +296,7 @@ run('request/content-length'); run('request/transfer-encoding'); run('request/invalid'); run('request/finish'); +run('request/pausing'); run('response/sample'); run('response/connection'); @@ -294,4 +305,6 @@ run('response/transfer-encoding'); run('response/invalid'); run('response/finish'); run('request/lenient-version'); +run('response/pausing'); + run('url'); diff --git a/test/request/pausing.md b/test/request/pausing.md new file mode 100644 index 00000000..64ff4a8d --- /dev/null +++ b/test/request/pausing.md @@ -0,0 +1,212 @@ +Pausing +======= + +### on_message_begin + + +```http +POST / HTTP/1.1 +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=0 pause +off=5 len=1 span[url]="/" +off=7 url complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete method=3 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_message_complete + + +```http +POST / HTTP/1.1 +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=7 url complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete method=3 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +off=41 pause +``` + +### on_url_complete + + +```http +POST / HTTP/1.1 +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=7 url complete +off=7 pause +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete method=3 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_header_field_complete + + +```http +POST / HTTP/1.1 +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=7 url complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=32 pause +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete method=3 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_header_value_complete + + +```http +POST / HTTP/1.1 +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=7 url complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=36 pause +off=38 headers complete method=3 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_headers_complete + + +```http +POST / HTTP/1.1 +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=5 len=1 span[url]="/" +off=7 url complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete method=3 v=1/1 flags=20 content_length=3 +off=38 pause +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_chunk_header + + +```http +PUT / HTTP/1.1 +Transfer-Encoding: chunked + +a +0123456789 +0 + + +``` + +```log +off=0 message begin +off=4 len=1 span[url]="/" +off=6 url complete +off=16 len=17 span[header_field]="Transfer-Encoding" +off=34 header_field complete +off=35 len=7 span[header_value]="chunked" +off=44 header_value complete +off=46 headers complete method=4 v=1/1 flags=208 content_length=0 +off=49 chunk header len=10 +off=49 pause +off=49 len=10 span[body]="0123456789" +off=61 chunk complete +off=64 chunk header len=0 +off=64 pause +off=66 chunk complete +off=66 message complete +``` + +### on_chunk_complete + + +```http +PUT / HTTP/1.1 +Transfer-Encoding: chunked + +a +0123456789 +0 + + +``` + +```log +off=0 message begin +off=4 len=1 span[url]="/" +off=6 url complete +off=16 len=17 span[header_field]="Transfer-Encoding" +off=34 header_field complete +off=35 len=7 span[header_value]="chunked" +off=44 header_value complete +off=46 headers complete method=4 v=1/1 flags=208 content_length=0 +off=49 chunk header len=10 +off=49 len=10 span[body]="0123456789" +off=61 chunk complete +off=61 pause +off=64 chunk header len=0 +off=66 chunk complete +off=66 pause +off=66 message complete +``` diff --git a/test/response/pausing.md b/test/response/pausing.md new file mode 100644 index 00000000..d4898856 --- /dev/null +++ b/test/response/pausing.md @@ -0,0 +1,211 @@ +Pausing +======= + +### on_message_begin + + +```http +HTTP/1.1 200 OK +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=0 pause +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete status=200 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_message_complete + + +```http +HTTP/1.1 200 OK +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete status=200 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +off=41 pause +``` + +### on_status_complete + + +```http +HTTP/1.1 200 OK +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete status=200 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_header_field_complete + + +```http +HTTP/1.1 200 OK +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=32 pause +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete status=200 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_header_value_complete + + +```http +HTTP/1.1 200 OK +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=36 pause +off=38 headers complete status=200 v=1/1 flags=20 content_length=3 +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_headers_complete + + +```http +HTTP/1.1 200 OK +Content-Length: 3 + +abc +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=14 span[header_field]="Content-Length" +off=32 header_field complete +off=33 len=1 span[header_value]="3" +off=36 header_value complete +off=38 headers complete status=200 v=1/1 flags=20 content_length=3 +off=38 pause +off=38 len=3 span[body]="abc" +off=41 message complete +``` + +### on_chunk_header + + +```http +HTTP/1.1 200 OK +Transfer-Encoding: chunked + +a +0123456789 +0 + + +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=17 span[header_field]="Transfer-Encoding" +off=35 header_field complete +off=36 len=7 span[header_value]="chunked" +off=45 header_value complete +off=47 headers complete status=200 v=1/1 flags=208 content_length=0 +off=50 chunk header len=10 +off=50 pause +off=50 len=10 span[body]="0123456789" +off=62 chunk complete +off=65 chunk header len=0 +off=65 pause +off=67 chunk complete +off=67 message complete +``` + +### on_chunk_complete + + +```http +HTTP/1.1 200 OK +Transfer-Encoding: chunked + +a +0123456789 +0 + + +``` + +```log +off=0 message begin +off=13 len=2 span[status]="OK" +off=17 status complete +off=17 len=17 span[header_field]="Transfer-Encoding" +off=35 header_field complete +off=36 len=7 span[header_value]="chunked" +off=45 header_value complete +off=47 headers complete status=200 v=1/1 flags=208 content_length=0 +off=50 chunk header len=10 +off=50 len=10 span[body]="0123456789" +off=62 chunk complete +off=62 pause +off=65 chunk header len=0 +off=67 chunk complete +off=67 pause +off=67 message complete +``` From 1215a81b0fdbd193c7781d65c787793caaad3d27 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Tue, 23 Aug 2022 12:15:11 +0200 Subject: [PATCH 2/2] fix: Fixed enum. --- src/llhttp/constants.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/llhttp/constants.ts b/src/llhttp/constants.ts index f3b0e857..926e5ce2 100644 --- a/src/llhttp/constants.ts +++ b/src/llhttp/constants.ts @@ -35,10 +35,10 @@ export enum ERROR { USER = 24, - CB_URL_COMPLETE = 25, - CB_STATUS_COMPLETE = 26, - CB_HEADER_FIELD_COMPLETE = 27, - CB_HEADER_VALUE_COMPLETE = 28, + CB_URL_COMPLETE = 26, + CB_STATUS_COMPLETE = 27, + CB_HEADER_FIELD_COMPLETE = 28, + CB_HEADER_VALUE_COMPLETE = 29, } export enum TYPE {