Skip to main content

Netizen is a minimalist HTTP client with a symmetrical interface between async and sync modes.

Project description

Netizen

Netizen is a minimalist HTTP client with a symmetrical interface between async and sync modes. It doesn't aim to be feature-complete like requests or httpx.

Netizen is just enough for poking API endpoints and performing basic HTTP operations or testing. It suits me, as I prefer working closely with sockets and don't need high-level abstraction.

Features

  • Symmetrical interface, e.g. client.send() vs await client.send()
  • The retries parameter makes it resilient and prevents flaky tests
  • ~500 lines of code
  • No dependencies other than the Python Standard Library

Installation

pip install git+https://github.com/nggit/netizen.git

Handling a JSON response body

import asyncio

from netizen import HTTPClient


# sync
with HTTPClient('ip-api.com', 80) as client:
    response = client.send(b'GET /json HTTP/1.1')

    print(response.json())

# async
async def main():
    async with HTTPClient('ip-api.com', 80) as client:
        response = await client.send(b'GET /json HTTP/1.1')

        print(await response.json())

asyncio.run(main())

Handling a raw response body with streaming

import asyncio

from netizen import HTTPClient

client = HTTPClient('example.com', 80)


# sync
with client:
    response = client.send(b'GET / HTTP/1.1')

    for data in response:
        print('Received:', len(data), 'Bytes')

# async
async def main():
    async with client:
        response = await client.send(b'GET / HTTP/1.1')

        async for data in response:
            print('Received:', len(data), 'Bytes')

asyncio.run(main())

Append request headers via *args also body parameter

with HTTPClient('example.com', 80) as client:
    response = client.send(
        b'POST / HTTP/1.1',
        b'Content-Type: application/json',
        b'Content-Length: 14',
        body=b'{"foo": "bar"}'
    )

    print('Status code:', response.status)  # 403
    print('Reason phrase:', response.message)  # b'Forbidden'

# out of context, close the connection without reading the entire response body

If you don't specify any headers, then Content-Length will be automatically inserted along with Content-Type: application/x-www-form-urlencoded.

with HTTPClient('example.com', 80) as client:
    response = client.send(b'POST / HTTP/1.1', body=b'foo=bar')

Send multiple requests within the same context/connection

with HTTPClient('ip-api.com', 80) as client:
    # first request
    response = client.send(b'GET /json HTTP/1.1')

    # the first response body must be consumed before sending another one
    print(response.json())

    # second request
    response = client.send(b'GET /json HTTP/1.1')

    print(response.body())

Handling URL redirects

from urllib.parse import urlparse


with HTTPClient('google.com', 443, ssl=True) as client:
    response = client.send(b'GET / HTTP/1.1')

    print('1. Status code:', response.status)  # 301
    print('1. Reason phrase:', response.message)  # b'Moved Permanently'
    print('1. Location:', response.url)  # b'http://www.google.com/'

    for data in response:
        pass

    if response.url:
        url = urlparse(response.url)

        if url.netloc:  # b'www.google.com' (different host)
            with HTTPClient(url.netloc.decode(), 443, ssl=True) as client:
                response = client.send(b'GET %s HTTP/1.1' % url.path)

                print('2. Status code:', response.status)  # 200
                print('2. Reason phrase:', response.message)  # b'OK'

                for data in response:
                    pass
        else:
            pass

Working directly with socket using client.sendall() and client.recv()

with HTTPClient('localhost', 8000, timeout=10, retries=10) as client:
    response = client.send(
        b'GET /chat HTTP/1.1',
        b'Upgrade: WebSocket',
        b'Connection: Upgrade',
        b'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==',
        b'Sec-WebSocket-Version: 13'
    )

    if response.status == 101:
        client.sendall(b'\x81\x0dHello, World!\x88\x02\x03\xe8')
        print('Received:', client.recv(4096))

Manually craft a bad request with client.sendall() and client.end()

with HTTPClient('localhost', 8000) as client:
    client.sendall(
        b'POST /upload HTTP/1.1\r\n'
        b'Host: localhost\r\n'
        b'Content-Length: 5\r\n'
        b'Transfer-Encoding: chunked\r\n\r\n'
    )
    client.sendall(b'0\r\n\r\n')

    # we are not using the `client.send()`, but we need the response object?
    response = client.end()

    print(response.body())

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

netizen-0.0.2.tar.gz (9.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

netizen-0.0.2-py3-none-any.whl (8.5 kB view details)

Uploaded Python 3

File details

Details for the file netizen-0.0.2.tar.gz.

File metadata

  • Download URL: netizen-0.0.2.tar.gz
  • Upload date:
  • Size: 9.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.11

File hashes

Hashes for netizen-0.0.2.tar.gz
Algorithm Hash digest
SHA256 01c99426d58fa7319cbbd6f222caf72978ac18e9d977e5d0a860b7c5c94a1f8f
MD5 0619da2e67d42d1ceed9f28c18ac4099
BLAKE2b-256 dd2ec7a4ea11b704d5cdbdf271b04aaa2f8e95d206fb812552633bdbc2b73f18

See more details on using hashes here.

File details

Details for the file netizen-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: netizen-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 8.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.11

File hashes

Hashes for netizen-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c6846dd227dea3613fff0b276046d5aee8f89ea4d4cbc739c06938d8e67b7883
MD5 1c67d7c21c21c3f95fb594d91382ee9d
BLAKE2b-256 ad5f25f52d7ab9e627707cbf94e6bb455c1614c97350b04baae9db49e05abe44

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page