https://github.com/synchronizing/httpq
🏃♂️ Parse, modify, and compile HTTP/1.1 messages.
https://github.com/synchronizing/httpq
Last synced: 8 months ago
JSON representation
🏃♂️ Parse, modify, and compile HTTP/1.1 messages.
- Host: GitHub
- URL: https://github.com/synchronizing/httpq
- Owner: synchronizing
- License: mit
- Created: 2021-11-09T00:24:39.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2023-10-18T05:25:03.000Z (over 2 years ago)
- Last Synced: 2024-09-21T06:56:33.195Z (almost 2 years ago)
- Language: Python
- Homepage: https://synchronizing.github.io/httpq/
- Size: 131 KB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 🏃♂️ httpq
httpq is a small (~32KB) package to parse, modify, and compile HTTP/1.1 messages with a built-in state machine.
## Installing
```
pip install httpq
```
## Documentation
Documentation can be found [here](https://synchronizing.github.io/httpq/).
## Using
`httpq` has three methods to initialize a `httpq.Request` and `httpq.Response` object.
#### `__init__`
```python
import httpq
req = httpq.Request(
method="GET",
target="/get",
protocol="HTTP/1.1",
headers={
"Host": "httpbin.org",
"Content-Length": 12,
"Accept": ["application/json", "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!",
)
```
#### `parse`
```python
req = 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!"
)
```
#### `feed`
```python
req = 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.
```python
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))
# 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.
while len(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 the `state` property. We can use this parse until the body of the message, and then use the captured headers to parse the body.
### Modifying and Comparisons
`httpq` also comes with an intuitive method to modify and compare message values:
```python
import httpq
req = 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 = 200
resp.reason = "OK"
resp.headers += {"Accept": "*/*"}
```
Internally every value of a request or response is saved as an `Item`, a special object type that allows easy setting and comparisons on the fly.
```python
resp.status == 200 # >>> True
resp.status == "200" # >>> True
resp.status == b"200" # >>> True
```
Once the object is modified to the user's preference utilizing the `Request` and `Response` object is as easy as calling a property (specifically `.raw`):
```python
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:
```python
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.