creationix/http-parser-js

test failed in chrome browser and nodejs

masx200 opened this issue · 5 comments

chrome 版本 102.0.1245.44 (正式版本) (64 位)

Node.js v18.3.0

https://cdn.skypack.dev/http-parser-js@0.5.6/http-parser.js?dts

https://esm.sh/http-parser-js@0.5.6/http-parser.js?dts

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script type="module" src="./test-http-parser-js.mjs"></script>
    </body>
</html>
import {
    // HeaderObject,
    HTTPParser,
} from "https://esm.sh/http-parser-js@0.5.6/http-parser.js?dts";
console.log(HTTPParser);
export async function parseRequest(input /* : Uint8Array */) {
    const parser = new HTTPParser(HTTPParser.REQUEST);
    console.log(parser);
    let complete = false;
    let shouldKeepAlive;
    let upgrade;
    let method;
    let url;
    let versionMajor;
    let versionMinor;
    let headers /* : never[] | HeaderObject */ = [];
    let trailers /* : string[]  */ = [];
    const bodyChunks /* : Uint8Array[]  */ = [];

    parser[HTTPParser.kOnHeadersComplete] = function (req) {
        console.log("kOnHeadersComplete", req);
        shouldKeepAlive = req.shouldKeepAlive;
        upgrade = req.upgrade;
        // deno-lint-ignore ban-ts-comment
        //@ts-ignore
        method = HTTPParser.methods[req.method];
        url = req.url;
        versionMajor = req.versionMajor;
        versionMinor = req.versionMinor;
        headers = req.headers;
    };

    parser[HTTPParser.kOnBody] = function (chunk, offset, length) {
        console.log("kOnBody", chunk, offset, length);
        bodyChunks.push(chunk.slice(offset, offset + length));
    };

    // This is actually the event for trailers, go figure.
    parser[HTTPParser.kOnHeaders] = function (t) {
        console.log("kOnHeaders", t);
        trailers = t;
    };

    parser[HTTPParser.kOnMessageComplete] = function () {
        console.log("kOnMessageComplete");
        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).
    const error = parser.execute(input);
    if (error instanceof Error) {
        throw error;
    }
    parser.finish();

    if (!complete) {
        throw new Error("Could not parse request");
    }

    const body = await new Blob(bodyChunks).arrayBuffer();

    return {
        shouldKeepAlive,
        upgrade,
        method,
        url,
        versionMajor,
        versionMinor,
        headers,
        body,
        trailers,
    };
}

console.log("Example: basic GET request:");
const input =
    'GET /v.gif?t=1655787151198&pid=103&referrer=https%3A%2F%2Fbaike.baidu.com%2Fitem%2FCRLF%2F7659459&type=10010009&index=1655787089090&_dTm=62108&_did=1655787151198&lemmaId=1157566&newLemmaId=7659459&subLemmaId=1157566&lemmaTitle=CRLF&url=https%3A%2F%2Fbaike.baidu.com%2Fitem%2FCRLF%2F7659459 HTTP/1.1\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\r\nCache-Control: max-age=0\r\nConnection: keep-alive\r\nCookie: __yjs_duid=1_fa1b0419ac25df147d4e4501b94dabba1645767195236; BAIDUID=1C8DAF19769B90116DDCA39B63310EE5:FG=1; BIDUPSID=1C8DAF19769B90116DDCA39B63310EE5; PSTM=1648793481; BAIDUID_BFESS=6A3143B22B963455BAA872B8D3C6DBEA:FG=1; BDUSS=k1WmxpOElFaFUzM0Q5ZnQyWGxuQTRuMzA2RERIQmpXWjhKMU1PZUZoZm1MTUZpSVFBQUFBJCQAAAAAAAAAAAEAAADPRYIEbWFzeDIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOafmWLmn5liVk; BDUSS_BFESS=k1WmxpOElFaFUzM0Q5ZnQyWGxuQTRuMzA2RERIQmpXWjhKMU1PZUZoZm1MTUZpSVFBQUFBJCQAAAAAAAAAAAEAAADPRYIEbWFzeDIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOafmWLmn5liVk; ab_sr=1.0.1_OTRjNjg1ZTI1YjllNjQwOTdmMGI3M2EwYTEzNmI1NzM3NzZlMjVlNGFmODljNzZjYWI2MGI5OGViOTA1NjYyZTc5ZjI0ZjliNWFhMTU5MTFjZWExNDNmOWYzNWJmOGQ1MjcxOWM0ODMyNGI4MjliMDJiNjNhNTk0NTMzNTM3ZjU4YzNmN2I1OGEzMmQ1NGUxNDJmMjkwNDdkMTMwOThiMTk4NTNkY2E1NDQxYTVkZWI4YjRmZGI4YTBjNzYxNWNh; RT="z=1&dm=baidu.com&si=qoeulsf2old&ss=l4novq29&sl=5&tt=1yr&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=b0z"\r\nDNT: 1\r\nHost: nsclick.baidu.com\r\nReferer: https://baike.baidu.com/item/CRLF/7659459\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: no-cors\r\nSec-Fetch-Site: same-site\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44\r\nsec-ch-ua: " Not A;Brand";v="99", "Chromium";v="102", "Microsoft Edge";v="102"\r\nsec-ch-ua-mobile: ?0\r\nsec-ch-ua-platform: "Windows"\r\n\r\n';
console.log(input);
const parsed = await parseRequest(new TextEncoder().encode(input));

console.log(parsed);

in browser chrome


Uncaught Error: Parse Error
    at f (http-parser.js:2:7310)
    at s.REQUEST_LINE (http-parser.js:2:3922)
    at s.execute (http-parser.js:2:2430)
    at parseRequest (test-http-parser-js.mjs:53:26)
    at test-http-parser-js.mjs:82:22

in nodejs

node C:\Documents\test\test-http-parser-js.mjs
C:\Documents\test\node_modules\http-parser-js\http-parser.js:484
  var err = new Error("Parse Error");
            ^

Error: Parse Error
    at parseErrorCode (C:\Documents\test\node_modules\http-parser-js\http-parser.js:484:13)
    at HTTPParser.REQUEST_LINE (C:\Documents\test\node_modules\http-parser-js\http-parser.js:244:11)
    at HTTPParser.execute (C:\Documents\test\node_modules\http-parser-js\http-parser.js:130:27)
    at parseRequest (file:///C:/Documents/test/test-http-parser-js.mjs:55:24)
    at file:///C:/Documents/test/test-http-parser-js.mjs:84:22
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:409:24)
    at async loadESM (node:internal/process/esm_loader:85:5)
    at async handleMainPromise (node:internal/modules/run_main:61:12) {
  code: 'HPE_INVALID_CONSTANT'
}

Node.js v18.3.0
{
  "dependencies": {
    "http-parser-js": "^0.5.6"
  }
}

PR's welcome if the cause is identified, and add the request to test-http-parser-durability.js for future testing.

var line = this.line + chunk.toString(HTTPParser.encoding, this.offset, i);

I know the reason for the error. There is no buffer object in the browser. You can only use uint8array. Its tostring method is different from that of buffer.

https://developer.mozilla.org/zh-CN/docs/Web/API/TextDecoder

Decoding strings in browsers should use TextDecoder

Yeah, this is a Node module and takes a Buffer as input, if you want to use it in the browser, easiest is probably to use an appropriate polyfill.

import { HTTPParser } from "https://esm.sh/http-parser-js@0.5.6/http-parser.js?dts";

console.log(HTTPParser);
import { Buffer } from "https://esm.sh/buffer@6.0.3?dts";
export async function parseRequest(input /* : Uint8Array */) {
    input = Buffer.from(input);
    const parser = new HTTPParser(HTTPParser.REQUEST);
    console.log(parser);
    let complete = false;
    let shouldKeepAlive;
    let upgrade;
    let method;
    let url;
    let versionMajor;
    let versionMinor;
    let headers /* : never[] | HeaderObject */ = [];
    let trailers /* : string[]  */ = [];
    const bodyChunks /* : Uint8Array[]  */ = [];

    parser[HTTPParser.kOnHeadersComplete] = function (req) {
        console.log("kOnHeadersComplete", req);
        shouldKeepAlive = req.shouldKeepAlive;
        upgrade = req.upgrade;
        // deno-lint-ignore ban-ts-comment
        //@ts-ignore
        method = HTTPParser.methods[req.method];
        url = req.url;
        versionMajor = req.versionMajor;
        versionMinor = req.versionMinor;
        headers = req.headers;
    };

    parser[HTTPParser.kOnBody] = function (chunk, offset, length) {
        console.log("kOnBody", chunk, offset, length);
        bodyChunks.push(chunk.slice(offset, offset + length));
    };

    // This is actually the event for trailers, go figure.
    parser[HTTPParser.kOnHeaders] = function (t) {
        console.log("kOnHeaders", t);
        trailers = t;
    };

    parser[HTTPParser.kOnMessageComplete] = function () {
        console.log("kOnMessageComplete");
        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).
    const error = parser.execute(input);
    if (error instanceof Error) {
        throw error;
    }
    parser.finish();

    if (!complete) {
        throw new Error("Could not parse request");
    }

    const body = await new Blob(bodyChunks).arrayBuffer();

    return {
        shouldKeepAlive,
        upgrade,
        method,
        url,
        versionMajor,
        versionMinor,
        headers,
        body,
        trailers,
    };
}

console.log("Example: basic GET request:");
const input =
    'GET /v.gif?t=1655787151198&pid=103&referrer=https%3A%2F%2Fbaike.baidu.com%2Fitem%2FCRLF%2F7659459&type=10010009&index=1655787089090&_dTm=62108&_did=1655787151198&lemmaId=1157566&newLemmaId=7659459&subLemmaId=1157566&lemmaTitle=CRLF&url=https%3A%2F%2Fbaike.baidu.com%2Fitem%2FCRLF%2F7659459 HTTP/1.1\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\r\nCache-Control: max-age=0\r\nConnection: keep-alive\r\nCookie: __yjs_duid=1_fa1b0419ac25df147d4e4501b94dabba1645767195236; BAIDUID=1C8DAF19769B90116DDCA39B63310EE5:FG=1; BIDUPSID=1C8DAF19769B90116DDCA39B63310EE5; PSTM=1648793481; BAIDUID_BFESS=6A3143B22B963455BAA872B8D3C6DBEA:FG=1; BDUSS=k1WmxpOElFaFUzM0Q5ZnQyWGxuQTRuMzA2RERIQmpXWjhKMU1PZUZoZm1MTUZpSVFBQUFBJCQAAAAAAAAAAAEAAADPRYIEbWFzeDIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOafmWLmn5liVk; BDUSS_BFESS=k1WmxpOElFaFUzM0Q5ZnQyWGxuQTRuMzA2RERIQmpXWjhKMU1PZUZoZm1MTUZpSVFBQUFBJCQAAAAAAAAAAAEAAADPRYIEbWFzeDIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOafmWLmn5liVk; ab_sr=1.0.1_OTRjNjg1ZTI1YjllNjQwOTdmMGI3M2EwYTEzNmI1NzM3NzZlMjVlNGFmODljNzZjYWI2MGI5OGViOTA1NjYyZTc5ZjI0ZjliNWFhMTU5MTFjZWExNDNmOWYzNWJmOGQ1MjcxOWM0ODMyNGI4MjliMDJiNjNhNTk0NTMzNTM3ZjU4YzNmN2I1OGEzMmQ1NGUxNDJmMjkwNDdkMTMwOThiMTk4NTNkY2E1NDQxYTVkZWI4YjRmZGI4YTBjNzYxNWNh; RT="z=1&dm=baidu.com&si=qoeulsf2old&ss=l4novq29&sl=5&tt=1yr&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=b0z"\r\nDNT: 1\r\nHost: nsclick.baidu.com\r\nReferer: https://baike.baidu.com/item/CRLF/7659459\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: no-cors\r\nSec-Fetch-Site: same-site\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44\r\nsec-ch-ua: " Not A;Brand";v="99", "Chromium";v="102", "Microsoft Edge";v="102"\r\nsec-ch-ua-mobile: ?0\r\nsec-ch-ua-platform: "Windows"\r\n\r\n';
console.log(input);
const parsed = await parseRequest(new TextEncoder().encode(input));

console.log(parsed);