Repository: aio-libs/aiohttp

Issue:
Switch http_parser to llhttp
Nodejs decide to move from their hard-coded C parser to this one.

https://github.com/nodejs/http-parser/pull/285#issuecomment-456025694

*Upd:* 
proposed parser is https://llhttp.org



Discussion:
[GitMate.io](https://gitmate.io) thinks the contributor most likely able to help you is @asvetlov.

Possibly related issues are https://github.com/aio-libs/aiohttp/issues/33 (HTTPS Support), https://github.com/aio-libs/aiohttp/issues/390 (404 on http://aiohttp.readthedocs.org/en/v0.16.3/), https://github.com/aio-libs/aiohttp/issues/320 (HTTP/2 support), https://github.com/aio-libs/aiohttp/issues/72 (HTTP POST not working), and https://github.com/aio-libs/aiohttp/issues/858 (replace http parser?).
Seems too good to be true, but must be worth trying.

Might be worth putting the link in the body of the issue so it's rendered as a link.
https://llhttp.org
It worth to try.
API seems the same but I expect complications of our build process.
Don't have any personal estimation of the issue:
1. It *would be nice to have* but not urgent, existing parser *just works*
2. I have a long list of higher priority items in my todo list
Interesting, suricata decide to write their own HTTP-parser on Rust :) https://redmine.openinfosecfoundation.org/issues/2787#change-10814
I found a problem with this port to llhttp: there is no API in llhttp that parses url.
These functions are needed by `cdef class HttpRequestParser(HttpParser)`
Does anyone have an idea how to workaround it?

[my llhttp branch](https://github.com/derlih/aiohttp/tree/llhttp)
They are not required I guess, `yarl.URL` can be constructed directly as we do for pure-Python parser.
@socketpair 
I saw this project, but

> This project is a toy, started to reacquaint myself with Python c-api modules.
@derlih That's why I removed the link :) before your comment.
https://github.com/pallas/pyllhttp

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -18,7 +18,7 @@
     NO_EXTENSIONS = True
 
 
-if IS_GIT_REPO and not (HERE / "vendor/http-parser/README.md").exists():
+if IS_GIT_REPO and not (HERE / "vendor/llhttp/README.md").exists():
     print("Install submodules when building from git clone", file=sys.stderr)
     print("Hint:", file=sys.stderr)
     print("  git submodule update --init", file=sys.stderr)
@@ -33,10 +33,13 @@
         "aiohttp._http_parser",
         [
             "aiohttp/_http_parser.c",
-            "vendor/http-parser/http_parser.c",
             "aiohttp/_find_header.c",
+            "vendor/llhttp/build/c/llhttp.c",
+            "vendor/llhttp/src/native/api.c",
+            "vendor/llhttp/src/native/http.c",
         ],
-        define_macros=[("HTTP_PARSER_STRICT", 0)],
+        define_macros=[("LLHTTP_STRICT_MODE", 0)],
+        include_dirs=["vendor/llhttp/build"],
     ),
     Extension("aiohttp._helpers", ["aiohttp/_helpers.c"]),
     Extension("aiohttp._http_writer", ["aiohttp/_http_writer.c"]),


Test patch:
diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
--- a/tests/test_http_parser.py
+++ b/tests/test_http_parser.py
@@ -2,7 +2,7 @@
 # Tests for aiohttp/protocol.py
 
 import asyncio
-from typing import Any
+from typing import Any, List
 from unittest import mock
 from urllib.parse import quote
 
@@ -13,6 +13,7 @@
 import aiohttp
 from aiohttp import http_exceptions, streams
 from aiohttp.http_parser import (
+    NO_EXTENSIONS,
     DeflateBuffer,
     HttpPayloadParser,
     HttpRequestParserPy,
@@ -42,7 +43,14 @@ def protocol():
     return mock.Mock()
 
 
-@pytest.fixture(params=REQUEST_PARSERS)
+def _gen_ids(parsers: List[Any]) -> List[str]:
+    return [
+        "py-parser" if parser.__module__ == "aiohttp.http_parser" else "c-parser"
+        for parser in parsers
+    ]
+
+
+@pytest.fixture(params=REQUEST_PARSERS, ids=_gen_ids(REQUEST_PARSERS))
 def parser(loop: Any, protocol: Any, request: Any):
     # Parser implementations
     return request.param(
@@ -55,13 +63,13 @@ def parser(loop: Any, protocol: Any, request: Any):
     )
 
 
-@pytest.fixture(params=REQUEST_PARSERS)
+@pytest.fixture(params=REQUEST_PARSERS, ids=_gen_ids(REQUEST_PARSERS))
 def request_cls(request: Any):
     # Request Parser class
     return request.param
 
 
-@pytest.fixture(params=RESPONSE_PARSERS)
+@pytest.fixture(params=RESPONSE_PARSERS, ids=_gen_ids(RESPONSE_PARSERS))
 def response(loop: Any, protocol: Any, request: Any):
     # Parser implementations
     return request.param(
@@ -74,7 +82,7 @@ def response(loop: Any, protocol: Any, request: Any):
     )
 
 
-@pytest.fixture(params=RESPONSE_PARSERS)
+@pytest.fixture(params=RESPONSE_PARSERS, ids=_gen_ids(RESPONSE_PARSERS))
 def response_cls(request: Any):
     # Parser implementations
     return request.param
@@ -85,6 +93,14 @@ def stream():
     return mock.Mock()
 
 
+@pytest.mark.skipif(NO_EXTENSIONS, reason="Extentions available but not imported")
+def test_c_parser_loaded():
+    assert "HttpRequestParserC" in dir(aiohttp.http_parser)
+    assert "HttpResponseParserC" in dir(aiohttp.http_parser)
+    assert "RawRequestMessageC" in dir(aiohttp.http_parser)
+    assert "RawResponseMessageC" in dir(aiohttp.http_parser)
+
+
 def test_parse_headers(parser: Any) -> None:
     text = b"""GET /test HTTP/1.1\r
 test: line\r