From d9a6a063d97b642b750ac54456a367b952e44407 Mon Sep 17 00:00:00 2001 From: Daniele Belardi Date: Mon, 29 Mar 2021 18:35:22 +0200 Subject: [PATCH] examples: re-write in .ts --- examples/wasm.js | 223 ------------------------------------------ examples/wasm.ts | 249 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+), 223 deletions(-) delete mode 100644 examples/wasm.js create mode 100644 examples/wasm.ts diff --git a/examples/wasm.js b/examples/wasm.js deleted file mode 100644 index 36a7661d..00000000 --- a/examples/wasm.js +++ /dev/null @@ -1,223 +0,0 @@ -'use strict'; - -/* global WebAssembly */ - -const { readFileSync } = require('fs'); -const { resolve } = require('path'); -const constants = require('../build/wasm/constants.js'); -const bin = readFileSync(resolve(__dirname, '../build/wasm/llhttp.wasm')); -const mod = new WebAssembly.Module(bin); - -const REQUEST = constants.TYPE.REQUEST; -const RESPONSE = constants.TYPE.RESPONSE; -const kOnMessageBegin = 0; -const kOnHeaders = 1; -const kOnHeadersComplete = 2; -const kOnBody = 3; -const kOnMessageComplete = 4; -const kOnExecute = 5; - -const kPtr = Symbol('kPtr'); -const kUrl = Symbol('kUrl'); -const kStatusMessage = Symbol('kStatusMessage'); -const kHeadersFields = Symbol('kHeadersFields'); -const kHeadersValues = Symbol('kHeadersValues'); -const kBody = Symbol('kBody'); -const kReset = Symbol('kReset'); -const kCheckErr = Symbol('kCheckErr'); - -const cstr = (ptr, len) => - Buffer.from(inst.exports.memory.buffer, ptr, len).toString(); - -const wasm_on_message_begin = p => { - const i = instMap.get(p); - i[kReset](); - return i[kOnMessageBegin](); -}; - -const wasm_on_url = (p, at, length) => { - instMap.get(p)[kUrl] = cstr(at, length); - return 0; -}; - -const wasm_on_status = (p, at, length) => { - instMap.get(p)[kStatusMessage] = cstr(at, length); - return 0; -}; - -const wasm_on_header_field = (p, at, length) => { - const i= instMap.get(p) - i[kHeadersFields].push(cstr(at, length)); - return 0; -}; - -const wasm_on_header_value = (p, at, length) => { - const i = instMap.get(p); - i[kHeadersValues].push(cstr(at, length)); - return 0; -}; - -const wasm_on_headers_complete = p => { - const i = instMap.get(p); - const type = inst.exports.llhttp_get_type(p); - const versionMajor = inst.exports.llhttp_get_http_major(p); - const versionMinor = inst.exports.llhttp_get_http_minor(p); - const rawHeaders = []; - let method; - let url; - let statusCode; - let statusMessage; - const upgrade = inst.exports.llhttp_get_upgrade(p); - const shouldKeepAlive = inst.exports.llhttp_should_keep_alive(p); - - for (let c = 0; c < i[kHeadersFields].length; c++) { - rawHeaders.push(i[kHeadersFields][c], i[kHeadersValues][c]) - } - i[kOnHeaders](rawHeaders); - - if (type === HTTPParser.REQUEST) { - method = constants.METHODS[inst.exports.llhttp_get_method(p)]; - url = i[kUrl]; - } else if (type === HTTPParser.RESPONSE) { - statusCode = inst.exports.llhttp_get_status_code(p); - statusMessage = i[kStatusMessage]; - } - return i[kOnHeadersComplete](versionMajor, versionMinor, rawHeaders, method, -url, statusCode, statusMessage, upgrade, shouldKeepAlive); -}; - -const wasm_on_body = (p, at, length) => { - const i = instMap.get(p); - const body = Buffer.from(inst.exports.memory.buffer, at, length); - return i[kOnBody](body); -}; - -const wasm_on_message_complete = (p) => { - return instMap.get(p)[kOnMessageComplete](); -}; - -const instMap = new Map(); - -const inst = new WebAssembly.Instance(mod, { - env: { - wasm_on_message_begin, - wasm_on_url, - wasm_on_status, - wasm_on_header_field, - wasm_on_header_value, - wasm_on_headers_complete, - wasm_on_body, - wasm_on_message_complete, - }, -}); - -inst.exports._initialize(); // wasi reactor - -class HTTPParser { - constructor(type) { - this[kPtr] = inst.exports.llhttp_alloc(constants.TYPE[type]); - instMap.set(this[kPtr], this); - - this[kUrl] = ''; - this[kStatusMessage] = null; - this[kHeadersFields] = []; - this[kHeadersValues] = []; - this[kBody] = null; - } - - [kReset]() { - this[kUrl] = ''; - this[kStatusMessage] = null; - this[kHeadersFields] = []; - this[kHeadersValues] = []; - this[kBody] = null; - } - - [kOnMessageBegin]() { - return 0; - } - - [kOnHeaders](rawHeaders) {} - - [kOnHeadersComplete](versionMajor, versionMinor, rawHeaders, method, - url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - return 0; - } - - [kOnBody](body) { - this[kBody] = body; - return 0; - } - - [kOnMessageComplete]() { - return 0; - } - - destroy() { - instMap.delete(this[kPtr]); - inst.exports.llhttp_free(this[kPtr]); - } - - execute(data) { - // TODO(devsnek): could probably use static alloc and chunk but i'm lazy - const ptr = inst.exports.malloc(data.byteLength); - const u8 = new Uint8Array(inst.exports.memory.buffer); - u8.set(data, ptr); - const ret = inst.exports.llhttp_execute(this[kPtr], ptr, data.length); - inst.exports.free(ptr); - this[kCheckErr](ret); - return ret; - } - - [kCheckErr](n) { - if (n === constants.ERROR.OK) { - return; - } - const ptr = inst.exports.llhttp_get_error_reason(this[kPtr]); - const u8 = new Uint8Array(inst.exports.memory.buffer); - const len = u8.indexOf(0, ptr) - ptr; - throw new Error(cstr(ptr, len)); - } -} - -HTTPParser.REQUEST = REQUEST; -HTTPParser.RESPONSE = RESPONSE; -HTTPParser.kOnMessageBegin = kOnMessageBegin; -HTTPParser.kOnHeaders = kOnHeaders; -HTTPParser.kOnHeadersComplete = kOnHeadersComplete; -HTTPParser.kOnBody = kOnBody; -HTTPParser.kOnMessageComplete = kOnMessageComplete; -HTTPParser.kOnExecute = kOnExecute; - -{ - const p = new HTTPParser(HTTPParser.REQUEST); - - p.execute(Buffer.from([ - 'POST /owo HTTP/1.1', - 'X: Y', - 'Content-Length: 9', - '', - 'uh, meow?', - '', - ].join('\r\n'))); - - console.log(p); - - p.destroy(); -} - -{ - const p = new HTTPParser(HTTPParser.RESPONSE); - - p.execute(Buffer.from(`\ -HTTP/1.1 200 OK\r -X: Y\r -Content-Length: 9\r -\r -uh, meow?\r -`)); - - console.log(p); - - p.destroy(); -} diff --git a/examples/wasm.ts b/examples/wasm.ts new file mode 100644 index 00000000..3b900549 --- /dev/null +++ b/examples/wasm.ts @@ -0,0 +1,249 @@ +/** + * A minimal Parser that mimicks a small fraction of the Node.js parser + * API. + * To run: + * - `npm run build` + * - `npx ts-node examples/wasm.ts` + */ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +import constants from '../build/wasm/constants.js'; + +const bin = readFileSync(resolve(__dirname, '../build/wasm/llhttp.wasm')); +const mod = new WebAssembly.Module(bin); + +const REQUEST = constants.TYPE.REQUEST; +const RESPONSE = constants.TYPE.RESPONSE; +const kOnMessageBegin = 0; +const kOnHeaders = 1; +const kOnHeadersComplete = 2; +const kOnBody = 3; +const kOnMessageComplete = 4; +const kOnExecute = 5; + +const kPtr = Symbol('kPtr'); +const kUrl = Symbol('kUrl'); +const kStatusMessage = Symbol('kStatusMessage'); +const kHeadersFields = Symbol('kHeadersFields'); +const kHeadersValues = Symbol('kHeadersValues'); +const kBody = Symbol('kBody'); +const kReset = Symbol('kReset'); +const kCheckErr = Symbol('kCheckErr'); + + +const cstr = (ptr: number, len: number): string => + Buffer.from(memory.buffer, ptr, len).toString(); + +const wasm_on_message_begin = (p: number) => { + const i = instMap.get(p); + i[kReset](); + return i[kOnMessageBegin](); +}; + +const wasm_on_url = (p: number, at: number, length: number) => { + instMap.get(p)[kUrl] = cstr(at, length); + return 0; +}; + +const wasm_on_status = (p: number, at: number, length: number) => { + instMap.get(p)[kStatusMessage] = cstr(at, length); + return 0; +}; + +const wasm_on_header_field = (p: number, at: number, length: number) => { + const i= instMap.get(p) + i[kHeadersFields].push(cstr(at, length)); + return 0; +}; + +const wasm_on_header_value = (p: number, at: number, length: number) => { + const i = instMap.get(p); + i[kHeadersValues].push(cstr(at, length)); + return 0; +}; + +const wasm_on_headers_complete = (p: number) => { + const i = instMap.get(p); + const type = get_type(p); + const versionMajor = get_version_major(p); + const versionMinor = get_version_minor(p); + const rawHeaders = []; + let method; + let url; + let statusCode; + let statusMessage; + const upgrade = get_upgrade(p); + const shouldKeepAlive = should_keep_alive(p); + + for (let c = 0; c < i[kHeadersFields].length; c++) { + rawHeaders.push(i[kHeadersFields][c], i[kHeadersValues][c]) + } + + if (type === HTTPParser.REQUEST) { + method = constants.METHODS[get_method(p)]; + url = i[kUrl]; + } else if (type === HTTPParser.RESPONSE) { + statusCode = get_status_code(p); + statusMessage = i[kStatusMessage]; + } + return i[kOnHeadersComplete](versionMajor, versionMinor, rawHeaders, method, +url, statusCode, statusMessage, upgrade, shouldKeepAlive); +}; + +const wasm_on_body = (p: number, at: number, length: number) => { + const i = instMap.get(p); + const body = Buffer.from(memory.buffer, at, length); + return i[kOnBody](body); +}; + +const wasm_on_message_complete = (p: number) => { + return instMap.get(p)[kOnMessageComplete](); +}; + +const instMap = new Map(); + +const inst = new WebAssembly.Instance(mod, { + env: { + wasm_on_message_begin, + wasm_on_url, + wasm_on_status, + wasm_on_header_field, + wasm_on_header_value, + wasm_on_headers_complete, + wasm_on_body, + wasm_on_message_complete, + }, +}); + +const memory = inst.exports.memory as any; +const alloc = inst.exports.llhttp_alloc as CallableFunction; +const malloc = inst.exports.malloc as CallableFunction; +const execute = inst.exports.llhttp_execute as CallableFunction; +const get_type = inst.exports.get_type as CallableFunction; +const get_upgrade = inst.exports.llhttp_get_upgrade as CallableFunction; +const should_keep_alive = inst.exports.llhttp_should_keep_alive as CallableFunction; +const get_method = inst.exports.llhttp_get_method as CallableFunction; +const get_status_code = inst.exports.llhttp_get_status_code as CallableFunction; +const get_version_minor = inst.exports.llhttp_get_http_minor as CallableFunction; +const get_version_major = inst.exports.llhttp_get_http_major as CallableFunction; +const get_error_reason = inst.exports.llhttp_get_error_reason as CallableFunction; +const free = inst.exports.free as CallableFunction; +const initialize = inst.exports._initialize as CallableFunction; + +initialize(); // wasi reactor + +class HTTPParser { + static REQUEST = REQUEST; + static RESPONSE = RESPONSE; + static kOnMessageBegin = kOnMessageBegin; + static kOnHeaders = kOnHeaders; + static kOnHeadersComplete = kOnHeadersComplete; + static kOnBody = kOnBody; + static kOnMessageComplete = kOnMessageComplete; + static kOnExecute = kOnExecute; + + [kPtr]: number; + [kUrl]: string; + [kStatusMessage]: null|string; + [kHeadersFields]: []|[string]; + [kHeadersValues]: []|[string]; + [kBody]: null|Buffer; + + constructor(type: string|number) { + this[kPtr] = alloc(constants.TYPE[type]); + instMap.set(this[kPtr], this); + + this[kUrl] = ''; + this[kStatusMessage] = null; + this[kHeadersFields] = []; + this[kHeadersValues] = []; + this[kBody] = null; + } + + [kReset]() { + this[kUrl] = ''; + this[kStatusMessage] = null; + this[kHeadersFields] = []; + this[kHeadersValues] = []; + this[kBody] = null; + } + + [kOnMessageBegin]() { + return 0; + } + + [kOnHeaders](rawHeaders: [string]) {} + + [kOnHeadersComplete](versionMajor: number, versionMinor: number, rawHeaders: [string], method: string, + url: string, statusCode: number, statusMessage: string, upgrade: boolean, shouldKeepAlive: boolean) { + return 0; + } + + [kOnBody](body: Buffer) { + this[kBody] = body; + return 0; + } + + [kOnMessageComplete]() { + return 0; + } + + destroy() { + instMap.delete(this[kPtr]); + free(this[kPtr]); + } + + execute(data: Buffer) { + const ptr = malloc(data.byteLength); + const u8 = new Uint8Array(memory.buffer); + u8.set(data, ptr); + const ret = execute(this[kPtr], ptr, data.length); + free(ptr); + this[kCheckErr](ret); + return ret; + } + + [kCheckErr](n: number) { + if (n === constants.ERROR.OK) { + return; + } + const ptr = get_error_reason(this[kPtr]); + const u8 = new Uint8Array(memory.buffer); + const len = u8.indexOf(0, ptr) - ptr; + throw new Error(cstr(ptr, len)); + } +} + + +{ + const p = new HTTPParser(HTTPParser.REQUEST); + + p.execute(Buffer.from([ + 'POST /owo HTTP/1.1', + 'X: Y', + 'Content-Length: 9', + '', + 'uh, meow?', + '', + ].join('\r\n'))); + + console.log(p); + + p.destroy(); +} + +{ + const p = new HTTPParser(HTTPParser.RESPONSE); + + p.execute(Buffer.from(`\ +HTTP/1.1 200 OK\r +X: Y\r +Content-Length: 9\r +\r +uh, meow?\r +`)); + + console.log(p); + + p.destroy(); +}