/* The following is an example of how to use the parser as a standalone module. Both parseRequest and parseResponse can be used to parse a raw HTTP request/response from a Buffer. */ const { deepStrictEqual, throws } = require('assert'); // Replace the require when using the module from npm. //const { HTTPParser } = require('http-parser-js'); const { HTTPParser } = require('./http-parser.js'); function parseRequest(input) { const parser = new HTTPParser(HTTPParser.REQUEST); let complete = false; let shouldKeepAlive; let upgrade; let method; let url; let versionMajor; let versionMinor; let headers = []; let trailers = []; let bodyChunks = []; parser[HTTPParser.kOnHeadersComplete] = function (req) { shouldKeepAlive = req.shouldKeepAlive; upgrade = req.upgrade; method = HTTPParser.methods[req.method]; url = req.url; versionMajor = req.versionMajor; versionMinor = req.versionMinor; headers = req.headers; }; parser[HTTPParser.kOnBody] = function (chunk, offset, length) { bodyChunks.push(chunk.slice(offset, offset + length)); }; // This is actually the event for trailers, go figure. parser[HTTPParser.kOnHeaders] = function (t) { trailers = t; }; parser[HTTPParser.kOnMessageComplete] = function () { complete = true; }; // Since we are sending the entire Buffer at once here all callbacks above happen synchronously. // The parser does not do _anything_ asynchronous. // However, you can of course call execute() multiple times with multiple chunks, e.g. from a stream. // But then you have to refactor the entire logic to be async (e.g. resolve a Promise in kOnMessageComplete and add timeout logic). parser.execute(input); parser.finish(); if (!complete) { throw new Error('Could not parse request'); } let body = Buffer.concat(bodyChunks); return { shouldKeepAlive, upgrade, method, url, versionMajor, versionMinor, headers, body, trailers, }; } function parseResponse(input) { const parser = new HTTPParser(HTTPParser.RESPONSE); let complete = false; let shouldKeepAlive; let upgrade; let statusCode; let statusMessage; let versionMajor; let versionMinor; let headers = []; let trailers = []; let bodyChunks = []; parser[HTTPParser.kOnHeadersComplete] = function (res) { shouldKeepAlive = res.shouldKeepAlive; upgrade = res.upgrade; statusCode = res.statusCode; statusMessage = res.statusMessage; versionMajor = res.versionMajor; versionMinor = res.versionMinor; headers = res.headers; }; parser[HTTPParser.kOnBody] = function (chunk, offset, length) { bodyChunks.push(chunk.slice(offset, offset + length)); }; // This is actually the event for trailers, go figure. parser[HTTPParser.kOnHeaders] = function (t) { trailers = t; }; parser[HTTPParser.kOnMessageComplete] = function () { complete = true; }; // Since we are sending the entire Buffer at once here all callbacks above happen synchronously. // The parser does not do _anything_ asynchronous. // However, you can of course call execute() multiple times with multiple chunks, e.g. from a stream. // But then you have to refactor the entire logic to be async (e.g. resolve a Promise in kOnMessageComplete and add timeout logic). parser.execute(input); parser.finish(); if (!complete) { throw new Error('Could not parse'); } let body = Buffer.concat(bodyChunks); return { shouldKeepAlive, upgrade, statusCode, statusMessage, versionMajor, versionMinor, headers, body, trailers, }; } let parsed; console.log('Example: basic GET request:'); parsed = parseRequest( Buffer.from(`GET / HTTP/1.1 Host: www.example.com `) ); console.log(parsed); deepStrictEqual(parsed.shouldKeepAlive, true); deepStrictEqual(parsed.upgrade, false); deepStrictEqual(parsed.method, 'GET'); deepStrictEqual(parsed.url, '/'); deepStrictEqual(parsed.versionMajor, 1); deepStrictEqual(parsed.versionMinor, 1); deepStrictEqual(parsed.headers, ['Host', 'www.example.com']); deepStrictEqual(parsed.body.toString(), ''); deepStrictEqual(parsed.trailers, []); console.log('Example: POST request with body:'); parsed = parseRequest( Buffer.from(`POST /memes HTTP/1.1 Host: www.example.com Content-Length: 7 Content-Type: text/plain foo bar `) ); console.log(parsed); deepStrictEqual(parsed.shouldKeepAlive, true); deepStrictEqual(parsed.upgrade, false); deepStrictEqual(parsed.method, 'POST'); deepStrictEqual(parsed.url, '/memes'); deepStrictEqual(parsed.versionMajor, 1); deepStrictEqual(parsed.versionMinor, 1); deepStrictEqual(parsed.headers, ['Host', 'www.example.com', 'Content-Length', '7', 'Content-Type', 'text/plain']); deepStrictEqual(parsed.body.toString(), 'foo bar'); deepStrictEqual(parsed.trailers, []); console.log('Example: basic HTML response'); parsed = parseResponse( Buffer.from(`HTTP/1.1 404 Not Found Content-Type: text/html Content-Length: 33 Computer says no `) ); console.log(parsed); deepStrictEqual(parsed.shouldKeepAlive, true); deepStrictEqual(parsed.upgrade, false); deepStrictEqual(parsed.statusCode, 404); deepStrictEqual(parsed.statusMessage, 'Not Found'); deepStrictEqual(parsed.versionMajor, 1); deepStrictEqual(parsed.versionMinor, 1); deepStrictEqual(parsed.headers, ['Content-Type', 'text/html', 'Content-Length', '33']); deepStrictEqual(parsed.body.toString(), 'Computer says no'); deepStrictEqual(parsed.trailers, []); console.log('Example: chunked response with trailers'); parsed = parseResponse( Buffer.from(`HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked Trailer: Expires 7 Mozilla 9 Developer 7 Network 0 Expires: Wed, 21 Oct 2015 07:28:00 GMT `) ); console.log(parsed); deepStrictEqual(parsed.shouldKeepAlive, true); deepStrictEqual(parsed.upgrade, false); deepStrictEqual(parsed.statusCode, 200); deepStrictEqual(parsed.statusMessage, 'OK'); deepStrictEqual(parsed.versionMajor, 1); deepStrictEqual(parsed.versionMinor, 1); deepStrictEqual(parsed.headers, ['Content-Type', 'text/plain', 'Transfer-Encoding', 'chunked', 'Trailer', 'Expires']); deepStrictEqual(parsed.body.toString(), 'MozillaDeveloperNetwork'); deepStrictEqual(parsed.trailers, ['Expires', 'Wed, 21 Oct 2015 07:28:00 GMT']); throws(function () { parseResponse( Buffer.from(`HTTP/1.1 200 OK Content-Length: 1 `) ); });