Package: httpq
🏃‍♂️ httpqhttpq is a small (~32KB) package to parse, modify, and compile HTTP/1.1 messages with a built-in state machine.Installingpip install httpqDocumentationDocumentation can be foundhere.Usinghttpqhas three methods to initialize ahttpq.Requestandhttpq.Responseobject.__init__importhttpqreq=httpq.Request(method="GET",target="/get",protocol="HTTP/1.1",headers={"Host":"httpbin.org","Content-Length":12,"Accept":["Accept: application/json","Accept: text/plain"],},body="Hello world!",)resp=httpq.Response(protocol="HTTP/1.1",status=200,reason="OK",headers={"Content-Length":12,"Content-Type":"text/plain"},body="Hello world!",)parsereq=httpq.Request.parse(b"GET /get HTTP/1.1\r\n"b"Host: httpbin.org\r\n"b"Content-Length: 12\r\n"b"Accept: application/json\r\n"b"Accept: text/plain\r\n"b"\r\n"b"Hello world!")resp=httpq.Response.parse(b"HTTP/1.1 200 OK\r\n"b"Content-Length: 12\r\n"b"Content-Type: text/plain\r\n"b"\r\n"b"Hello world!")feedreq=httpq.Request()req.feed(b"GET /get HTTP/1.1\r\n")req.feed(b"Host: httpbin.org\r\n")req.feed(b"Content-Length: 18\r\n")req.feed(b"Accept: application/json\r\n")req.feed(b"Accept: text/plain\r\n")req.feed(b"\r\n")req.feed(b"Hello world!")resp=httpq.Response()resp.feed(b"HTTP/1.1 200 OK\r\n")resp.feed(b"Content-Length: 12\r\n")resp.feed(b"Content-Type: text/plain\r\n")resp.feed(b"\r\n")resp.feed(b"Hello world!")The feed mechanism comes with a simple built-in state machine. The state machine can be in one of three states:TOP: The feed cursor is at the top of the message.HEADER: The feed cursor is at the headers.BODY: The feed cursor is at the body.Once at the body it's the user's responsibility to keep track of the message length.importsocketimporthttpqs=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect(("httpbin.org",80))req=httpq.Request(method="GET",target="/get",protocol="HTTP/1.1",headers={"Host":"httpbin.org"},)s.sendall(req.raw)resp=httpq.Response()whileresp.state!=httpq.state.BODY:resp.feed(s.recv(10))# At this stage we have a response that has read the top line and headers. It's the user's# responsibility to keep track of the rest of the message's length. In this case, we'll just# use the `Content-Length` header.whilelen(resp.body)!=resp.headers["Content-Length"]:resp.feed(s.recv(10))print(resp)Outputs:← HTTP/1.1 200 OK
← Date: Sun, 12 Mar 2023 03:05:55 GMT
← Content-Type: application/json
← Content-Length: 197
← Connection: keep-alive
← Server: gunicorn/19.9.0
← Access-Control-Allow-Origin: *
← Access-Control-Allow-Credentials: true
← 
← {
←   "args": {}, 
←   "headers": {
←     "Host": "httpbin.org", 
←     "X-Amzn-Trace-Id": "Root=1-640d4193-650c50825ec4415732dacde8"
←   }, 
←   "origin": "xx.xx.xx.xxx", 
←   "url": "http://httpbin.org/get"
← }Note that the feed mechanism is used in conjunction with thestateproperty. We can use this parse until the body of the message, and then use the captured headers to parse the body.Modifying and Comparisonshttpqalso comes with an intuitive method to modify and compare message values:importhttpqreq=httpq.Request(method="GET",target="/get",protocol="HTTP/1.1",headers={"Host":"httpbin.org","Content-Length":12},body="Hello world!",)resp=httpq.Response(protocol="HTTP/1.1",status=404,reason="Not Found",headers={"Content-Length":12},body="Hello world!",)# string, bytes, and int are all valid values for any field.req.method="POST"req.target=b"/"resp.status=200resp.reason="OK"resp.headers+={"Accept":"*/*"}Internally every value of a request or response is saved as anItem, a special object type that allows easy setting and comparisons on the fly.resp.status==200# >>> Trueresp.status=="200"# >>> Trueresp.status==b"200"# >>> TrueOnce the object is modified to the user's preference utilizing theRequestandResponseobject is as easy as calling a property (specifically.raw):print(req.raw)print(resp.raw)b'POST / HTTP/1.1\r\nHost: httpbin.org\r\nContent-Length: 12\r\n\r\nHello world!'
b'HTTP/1.1 200 OK\r\nContent-Length: 12\r\nAccept: */*\r\n\r\nHello world!'Uniquely, the__str__method returns the objects with arrows to make obvious of its type:print(req)print(resp)→ POST / HTTP/1.1
→ Host: httpbin.org
→ Content-Length: 12
→ 
→ Hello world!

← HTTP/1.1 200 OK
← Content-Length: 12
← Accept: */*
← 
← Hello world!Checkout the documentation for more details.