State Machine#
httpq
comes with a state machine that can be used to manage a data stream coming in from a socket, or socket-like object (io). A httpq.httpq.Message
has an internal variable called state
that is used to keep track of the state of the message. There are three possible states the message can be in:
- class state(value)[source]
States of the HTTP request.
TOP
: Message is at the top line.HEADERS
: Message is in the headers.BODY
: Message is in the body.
These states are used to determine how to handle the data that comes in from an io. Note that the state the message is in is its current state - that is to say, a Message
starts in the TOP
state, then moves on to HEADERS
, and finally BODY
.
Since httpq
is not a complete HTTP/1.1 client it does not know when the body of the message has reached its end. It is the job of the developer to determine when the message is complete by either looking at the Content-Length
or Transfer-Encoding
header.
Examples#
Say you would like to send a request to a server, and receive back its response in full. To do this, you would do the following:
import socket
import httpq
s = 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()
while resp.state != httpq.state.BODY:
resp.feed(s.recv(10))
while len(resp.body) != resp.headers["Content-Length"]:
resp.body += s.recv(10)
Take note of a few things. First, note how we receive the response in chunks (bytes of 10 for sake of example), and how we keep track of when we reach the body:
...
resp = httpq.Response()
while resp.state != httpq.state.BODY:
resp.feed(s.recv(10))
...
The property state
returns the current state of the message, and can be used to determine how to handle the data as it comes in. For the sake of example, letβs print out the current state of the message and its internal buffer:
...
resp = httpq.Response()
while resp.state != httpq.state.BODY:
print(resp.state, resp.buffer)
resp.feed(s.recv(10))
print(resp.state, resp.buffer)
...
state.TOP b''
state.TOP b'HTTP/1.1 2'
state.HEADER b'HTTP/1.1 200 OK\r\nDat'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 '
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 G'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nConten'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: ap'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nCont'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nCon'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: k'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: g'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAcce'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Ori'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Origin: *\r\nAc'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Origin: *\r\nAccess-Contr'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-C'
state.HEADER b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Credentials'
state.BODY b'HTTP/1.1 200 OK\r\nDate: Tue, 09 Nov 2021 18:29:18 GMT\r\nContent-Type: application/json\r\nContent-Length: 197\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Credentials: true\r\n\r\n'
Once we have reached the BODY
state, there are no further states. This is useful because it allows the user to dictate how much data to read from the io, and if need be, how to handle the incoming data. This leads us into the second note: notice how the body is then gathered:
while len(resp.body) != resp.headers["Content-Length"]:
resp.body += s.recv(10)
In this specific case we have decided to store the body into memory and assumed the server will always respond with the Content-Length
header.