Skip to content

Small, sharp, auditable HTTP parser.#102

Closed
lovelydinosaur wants to merge 20 commits intodevfrom
http-parser
Closed

Small, sharp, auditable HTTP parser.#102
lovelydinosaur wants to merge 20 commits intodevfrom
http-parser

Conversation

@lovelydinosaur
Copy link
Contributor

Parsers

Here's what'll be incoming here...

HTTPClientParser

writer = io.BytesIO()
reader = io.BytesIO(
    b"HTTP/1.1 200 OK\r\n"
    b"Content-Length: 23\r\n"
    b"Content-Type: application/json\r\n"
    b"\r\n"
    b'{"msg": "hello, world"}'
)
p = httpx.HTTPClientParser(writer, reader)

# Send the request...
p.send_method_line(b"GET", b"/", b"HTTP/1.1")
p.send_headers([(b"Host", b"example.com")])
p.send_body(b'')

# Receive the response...
protocol, code, reason_phase = p.recv_status_line()
headers = p.recv_headers()
body = b''
while buffer := p.recv_body():
    body += buffer

HTTPServerParser

reader = io.BytesIO(
    b"GET / HTTP/1.1 200\r\n"
    b"Host: example.com\r\n"
    b"Accept: */*\r\n"
    b"\r\n"
)
writer = io.BytesIO()
p = httpx.HTTPServerParser(reader, writer)

# Receive the request...
method, target, protocol = p.recv_method_line()
headers = p.recv_headers()
body = b''
while buffer := p.recv_body():
    body += buffer

# Send the response...
p.send_status_line(b'HTTP/1.1', b'200', b'OK')
p.send_headers([(b'Content-Length', b'23'), (b'Content-Type', b'application/json')])
p.send_body(b'{"msg": "hello, world"}')
p.send_body(b'')

MultiPartParser

p = MultiPartParser(stream, boundary)
p.recv_start()
while not p.recv_complete():
     headers = p.recv_headers()
     body = b''
     while buffer := p.recv_body():
         body += buffer

@lovelydinosaur
Copy link
Contributor Author

lovelydinosaur commented Jun 11, 2025

IDK how many folks will appreciate this, tho it's looking really neat. This swaps out h11 for a smart little HTTP parser with a similar design.

>>> import httpx
>>> import io
>>>
>>> writer = io.BytesIO()
>>> reader = io.BytesIO(
...     b"HTTP/1.1 200 OK\r\n"
...     b"Content-Length: 23\r\n"
...     b"Content-Type: application/json\r\n"
...     b"\r\n"
...     b'{"msg": "hello, world"}'
... )

>>> # Create ourselves a lil parser...
>>> p = httpx.HTTPParser(writer, reader)
>>> p
<HTTPParser [client IDLE, server IDLE]>

>>> # Send a request...
>>> p.send_method_line(b'GET', b'/', b'HTTP/1.1')
>>> p.send_headers([(b"Host", b"example.com")])
>>> p.send_body(b'')

>>> # Check the current state and output
>>> p
<HTTPParser [client DONE, server IDLE]>
>>> writer.getvalue()
b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n'

>>> # Read ourselves a lil response
>>> p.recv_status_line()
(b'HTTP/1.1', b'200', b'OK')
>>> p.recv_headers()
[(b'Content-Length', b'23'), (b'Content-Type', b'application/json')]
>>> p.recv_body()
b'{"msg": "hello, world"}'
>>> p.recv_body()
b''

>>> # Completed request/response cycle
>>> p
<HTTPParser [client DONE, server DONE]>
>>> p.start_next_cycle()
>>> p
<HTTPParser [client IDLE, server IDLE]>

We can doc out an example integrating this with https://www.encode.io/httpnext/networking
This gets us to zero dependencies, with very limited stdlib imports.

Now... I need to get things out into the public domain.

@lovelydinosaur
Copy link
Contributor Author

Resolved in #122

@lovelydinosaur lovelydinosaur deleted the http-parser branch September 16, 2025 09:07
@carltongibson
Copy link
Collaborator

This is cool. BTW.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants