{"id":16544785,"url":"https://github.com/synchronizing/httpq","last_synced_at":"2025-10-28T15:31:15.840Z","repository":{"id":103393389,"uuid":"426037529","full_name":"synchronizing/httpq","owner":"synchronizing","description":"🏃‍♂️ Parse, modify, and compile HTTP/1.1 messages.","archived":false,"fork":false,"pushed_at":"2023-10-18T05:25:03.000Z","size":134,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-09-21T06:56:33.195Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://synchronizing.github.io/httpq/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/synchronizing.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-11-09T00:24:39.000Z","updated_at":"2023-05-16T06:19:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"c1b30079-ebbf-4282-ad50-553f32b78aa8","html_url":"https://github.com/synchronizing/httpq","commit_stats":{"total_commits":21,"total_committers":1,"mean_commits":21.0,"dds":0.0,"last_synced_commit":"c98a7b939c9a2655f719e0806a97a81845625311"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synchronizing%2Fhttpq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synchronizing%2Fhttpq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synchronizing%2Fhttpq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synchronizing%2Fhttpq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/synchronizing","download_url":"https://codeload.github.com/synchronizing/httpq/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219859266,"owners_count":16556036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T19:04:38.625Z","updated_at":"2025-10-28T15:31:15.491Z","avatar_url":"https://github.com/synchronizing.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🏃‍♂️ httpq\n\n\u003cp align=\"center\"\u003e\n\n  \u003ca href=\"https://www.pepy.tech/projects/httpq\"\u003e\n    \u003cimg src=\"https://static.pepy.tech/badge/httpq\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://github.com/synchronizing/httpq/actions/workflows/pytest-cover-run.yaml\"\u003e\n    \u003cimg src=\"https://github.com/synchronizing/httpq/actions/workflows/pytest-cover-run.yaml/badge.svg\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://synchronizing.github.io/httpq/\"\u003e\n    \u003cimg src=\"https://github.com/synchronizing/httpq/actions/workflows/docs-publish.yaml/badge.svg\"\u003e\n  \u003c/a\u003e\n  \n  \u003ca href=\"https://coveralls.io/github/synchronizing/httpq?branch=master\"\u003e\n    \u003cimg src=\"https://coveralls.io/repos/github/synchronizing/httpq/badge.svg?branch=master\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nhttpq is a small (~32KB) package to parse, modify, and compile HTTP/1.1 messages with a built-in state machine.\n\n## Installing\n\n```\npip install httpq\n```\n\n## Documentation\n\nDocumentation can be found [here](https://synchronizing.github.io/httpq/).\n\n## Using\n\n`httpq` has three methods to initialize a `httpq.Request` and `httpq.Response` object.\n\n#### `__init__`\n\n```python\nimport httpq\n\nreq = httpq.Request(\n    method=\"GET\",\n    target=\"/get\",\n    protocol=\"HTTP/1.1\",\n    headers={\n        \"Host\": \"httpbin.org\",\n        \"Content-Length\": 12,\n        \"Accept\": [\"application/json\", \"text/plain\"],\n    },\n    body=\"Hello world!\",\n)\n\nresp = httpq.Response(\n    protocol=\"HTTP/1.1\",\n    status=200,\n    reason=\"OK\",\n    headers={\"Content-Length\": 12, \"Content-Type\": \"text/plain\"},\n    body=\"Hello world!\",\n)\n```\n\n#### `parse`\n\n```python\nreq = httpq.Request.parse(\n    b\"GET /get HTTP/1.1\\r\\n\"\n    b\"Host: httpbin.org\\r\\n\"\n    b\"Content-Length: 12\\r\\n\"\n    b\"Accept: application/json\\r\\n\"\n    b\"Accept: text/plain\\r\\n\"\n    b\"\\r\\n\"\n    b\"Hello world!\"\n)\n\nresp = httpq.Response.parse(\n    b\"HTTP/1.1 200 OK\\r\\n\"\n    b\"Content-Length: 12\\r\\n\"\n    b\"Content-Type: text/plain\\r\\n\"\n    b\"\\r\\n\"\n    b\"Hello world!\"\n)\n```\n\n#### `feed`\n\n```python\nreq = httpq.Request()\nreq.feed(b\"GET /get HTTP/1.1\\r\\n\")\nreq.feed(b\"Host: httpbin.org\\r\\n\")\nreq.feed(b\"Content-Length: 18\\r\\n\")\nreq.feed(b\"Accept: application/json\\r\\n\")\nreq.feed(b\"Accept: text/plain\\r\\n\")\nreq.feed(b\"\\r\\n\")\nreq.feed(b\"Hello world!\")\n\nresp = httpq.Response()\nresp.feed(b\"HTTP/1.1 200 OK\\r\\n\")\nresp.feed(b\"Content-Length: 12\\r\\n\")\nresp.feed(b\"Content-Type: text/plain\\r\\n\")\nresp.feed(b\"\\r\\n\")\nresp.feed(b\"Hello world!\")\n```\n\nThe feed mechanism comes with a simple built-in state machine. The state machine can be in one of three states:\n\n* `TOP`: The feed cursor is at the top of the message.\n* `HEADER`: The feed cursor is at the headers.\n* `BODY`: The feed cursor is at the body.\n\nOnce at the body it's the user's responsibility to keep track of the message length.\n\n```python\nimport socket\nimport httpq\n\ns = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\ns.connect((\"httpbin.org\", 80))\n\nreq = httpq.Request(\n    method=\"GET\",\n    target=\"/get\",\n    protocol=\"HTTP/1.1\",\n    headers={\"Host\": \"httpbin.org\"},\n)\ns.sendall(req.raw)\n\nresp = httpq.Response()\nwhile resp.state != httpq.state.BODY:\n    resp.feed(s.recv(10))\n\n# At this stage we have a response that has read the top line and headers. It's the user's\n# responsibility to keep track of the rest of the message's length. In this case, we'll just\n# use the `Content-Length` header.\nwhile len(resp.body) != resp.headers[\"Content-Length\"]:\n    resp.feed(s.recv(10))\n\nprint(resp)\n```\n\nOutputs:\n\n```\n← HTTP/1.1 200 OK\n← Date: Sun, 12 Mar 2023 03:05:55 GMT\n← Content-Type: application/json\n← Content-Length: 197\n← Connection: keep-alive\n← Server: gunicorn/19.9.0\n← Access-Control-Allow-Origin: *\n← Access-Control-Allow-Credentials: true\n← \n← {\n←   \"args\": {}, \n←   \"headers\": {\n←     \"Host\": \"httpbin.org\", \n←     \"X-Amzn-Trace-Id\": \"Root=1-640d4193-650c50825ec4415732dacde8\"\n←   }, \n←   \"origin\": \"xx.xx.xx.xxx\", \n←   \"url\": \"http://httpbin.org/get\"\n← }\n```\n\nNote 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.\n\n### Modifying and Comparisons\n\n`httpq` also comes with an intuitive method to modify and compare message values:\n\n```python\nimport httpq\n\nreq = httpq.Request(\n    method=\"GET\",\n    target=\"/get\",\n    protocol=\"HTTP/1.1\",\n    headers={\"Host\": \"httpbin.org\", \"Content-Length\": 12},\n    body=\"Hello world!\",\n)\n\nresp = httpq.Response(\n    protocol=\"HTTP/1.1\",\n    status=404,\n    reason=\"Not Found\",\n    headers={\"Content-Length\": 12},\n    body=\"Hello world!\",\n)\n\n# string, bytes, and int are all valid values for any field.\nreq.method = \"POST\"\nreq.target = b\"/\"\n\nresp.status = 200\nresp.reason = \"OK\"\nresp.headers += {\"Accept\": \"*/*\"}\n```\n\nInternally 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.\n\n```python\nresp.status == 200      # \u003e\u003e\u003e True\nresp.status == \"200\"    # \u003e\u003e\u003e True\nresp.status == b\"200\"   # \u003e\u003e\u003e True\n```\n\nOnce the object is modified to the user's preference utilizing the `Request` and `Response` object is as easy as calling a property (specifically `.raw`):\n\n```python\nprint(req.raw)\nprint(resp.raw)\n```\n\n```\nb'POST / HTTP/1.1\\r\\nHost: httpbin.org\\r\\nContent-Length: 12\\r\\n\\r\\nHello world!'\nb'HTTP/1.1 200 OK\\r\\nContent-Length: 12\\r\\nAccept: */*\\r\\n\\r\\nHello world!'\n```\n\nUniquely, the `__str__` method returns the objects with arrows to make obvious of its type:\n\n```python\nprint(req)\nprint(resp)\n```\n\n```\n→ POST / HTTP/1.1\n→ Host: httpbin.org\n→ Content-Length: 12\n→ \n→ Hello world!\n\n← HTTP/1.1 200 OK\n← Content-Length: 12\n← Accept: */*\n← \n← Hello world!\n```\n\nCheckout the documentation for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynchronizing%2Fhttpq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsynchronizing%2Fhttpq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynchronizing%2Fhttpq/lists"}