Source code
Revision control
Copy as Markdown
Other Tools
/**
* A minimal Parser that mimicks a small fraction of the Node.js parser
* API.
* To run:
* - `npm run build-wasm`
* - `npx ts-node examples/wasm.ts`
*/
import { readFileSync } from 'fs';
import { resolve } from 'path';
import * as constants from '../build/wasm/constants';
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.llhttp_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: constants.TYPE) {
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',
'X: Y',
'Content-Length: 9',
'',
'uh, meow?'
].join('\r\n')));
console.log(p);
p.destroy();
}