Micro HTTP client for Python and MicroPython
Project description
uHTTP Client: micro HTTP client
Features
- MicroPython and CPython compatible
- Select-based async (no async/await, no threading)
- Keep-alive connections with automatic reuse
- Fluent API:
response = client.get('/path').wait() - URL parsing with automatic SSL detection
- Base path support for API versioning
- JSON support (auto-encode request, lazy decode response)
- Binary data support
- Cookies persistence
- HTTP Basic and Digest authentication
- SSL/TLS support for HTTPS
Usage
URL-based initialization (recommended)
import uhttp.client
# HTTPS with automatic SSL context
client = uhttp.client.HttpClient('https://api.example.com')
response = client.get('/users').wait()
client.close()
# With base path for API versioning
client = uhttp.client.HttpClient('https://api.example.com/v1')
response = client.get('/users').wait() # requests /v1/users
client.close()
# HTTP
client = uhttp.client.HttpClient('http://localhost:8080')
Traditional initialization
import uhttp.client
client = uhttp.client.HttpClient('httpbin.org', port=80)
response = client.get('/get').wait()
client.close()
# With explicit SSL context
import ssl
ctx = ssl.create_default_context()
client = uhttp.client.HttpClient('api.example.com', port=443, ssl_context=ctx)
Context manager
import uhttp.client
with uhttp.client.HttpClient('https://httpbin.org') as client:
response = client.get('/get').wait()
print(response.status)
JSON API
client = uhttp.client.HttpClient('https://api.example.com/v1')
# GET with query parameters
response = client.get('/users', query={'page': 1, 'limit': 10}).wait()
# POST with JSON body
response = client.post('/users', json={'name': 'John'}).wait()
# PUT
response = client.put('/users/1', json={'name': 'Jane'}).wait()
# DELETE
response = client.delete('/users/1').wait()
client.close()
Custom headers
response = client.get('/protected', headers={
'Authorization': 'Bearer token123',
'X-Custom-Header': 'value'
}).wait()
Binary data
# Send binary
response = client.post('/upload', data=b'\x00\x01\x02\xff').wait()
# Receive binary
response = client.get('/image.png').wait()
image_bytes = response.data
HTTPS
Automatic (with URL)
import uhttp.client
# SSL context created automatically for https:// URLs
client = uhttp.client.HttpClient('https://api.example.com')
response = client.get('/secure').wait()
client.close()
Manual SSL context
import ssl
import uhttp.client
ctx = ssl.create_default_context()
client = uhttp.client.HttpClient('api.example.com', port=443, ssl_context=ctx)
response = client.get('/secure').wait()
client.close()
MicroPython HTTPS
import ssl
import uhttp.client
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client = uhttp.client.HttpClient('api.example.com', port=443, ssl_context=ctx)
response = client.get('/secure').wait()
client.close()
Async (non-blocking) mode
Default mode is async. Use with external select loop:
import select
import uhttp.client
client = uhttp.client.HttpClient('http://httpbin.org')
# Start request (non-blocking)
client.get('/delay/2')
# Manual select loop
while True:
r, w, _ = select.select(
client.read_sockets,
client.write_sockets,
[], 10.0
)
response = client.process_events(r, w)
if response:
print(response.status)
break
client.close()
Parallel requests
import select
import uhttp.client
clients = [
uhttp.client.HttpClient('http://httpbin.org'),
uhttp.client.HttpClient('http://httpbin.org'),
uhttp.client.HttpClient('http://httpbin.org'),
]
# Start all requests
for i, client in enumerate(clients):
client.get('/delay/1', query={'n': i})
# Wait for all
results = {}
while len(results) < len(clients):
read_socks = []
write_socks = []
for c in clients:
read_socks.extend(c.read_sockets)
write_socks.extend(c.write_sockets)
r, w, _ = select.select(read_socks, write_socks, [], 10.0)
for i, client in enumerate(clients):
if i not in results:
resp = client.process_events(r, w)
if resp:
results[i] = resp
for client in clients:
client.close()
Combined with HttpServer
import select
import uhttp.server
import uhttp.client
server = uhttp.server.HttpServer(port=8080)
backend = uhttp.client.HttpClient('http://api.example.com')
while True:
r, w, _ = select.select(
server.read_sockets + backend.read_sockets,
server.write_sockets + backend.write_sockets,
[], 1.0
)
# Handle incoming requests
incoming = server.process_events(r, w)
if incoming:
backend.get('/data', query=incoming.query)
# Handle backend response
response = backend.process_events(r, w)
if response:
incoming.respond(data=response.data)
API
Function parse_url
uhttp.client.parse_url(url)
Parse URL into components. Returns (host, port, path, ssl, auth) tuple.
import uhttp.client
uhttp.client.parse_url('https://api.example.com/v1/users')
# → ('api.example.com', 443, '/v1/users', True, None)
uhttp.client.parse_url('http://localhost:8080/api')
# → ('localhost', 8080, '/api', False, None)
uhttp.client.parse_url('https://user:pass@api.example.com')
# → ('api.example.com', 443, '', True, ('user', 'pass'))
uhttp.client.parse_url('example.com')
# → ('example.com', 80, '', False, None)
Class HttpClient
uhttp.client.HttpClient(url_or_host, port=None, ssl_context=None, auth=None, connect_timeout=10, timeout=30, max_response_length=1MB)
Can be initialized with URL or host/port:
import uhttp.client
# URL-based (recommended)
uhttp.client.HttpClient('https://api.example.com/v1')
# With auth in URL
uhttp.client.HttpClient('https://user:pass@api.example.com/v1')
# Traditional
uhttp.client.HttpClient('api.example.com', port=443, ssl_context=ctx)
Parameters:
url_or_host- Full URL (http://... or https://...) or hostnameport- Server port (auto-detected from URL: 80 for http, 443 for https)ssl_context- Optionalssl.SSLContext(auto-created for https:// URLs)auth- Optional (username, password) tuple for HTTP authenticationconnect_timeout- Connection timeout in seconds (default: 10)timeout- Response timeout in seconds (default: 30)max_response_length- Maximum response size (default: 1MB)
Properties
host- Server hostnameport- Server portbase_path- Base path from URL (prepended to all request paths)is_connected- True if socket is connectedstate- Current state (STATE_IDLE, STATE_SENDING, etc.)auth- Authentication credentials tuple (username, password) or Nonecookies- Cookies dict (persistent across requests)read_sockets- Sockets to monitor for reading (for select)write_sockets- Sockets to monitor for writing (for select)
Methods
request(method, path, headers=None, data=None, query=None, json=None, auth=None, timeout=None)
Start HTTP request (async). Returns self for chaining.
method- HTTP method (GET, POST, PUT, DELETE, etc.)path- Request path (base_path is prepended automatically)headers- Optional headers dictdata- Request body (bytes, str, or dict/list for JSON)query- Optional query parameters dictjson- Shortcut for data with JSON encodingauth- Optional (username, password) tuple, overrides client's default authtimeout- Optional timeout in seconds, overrides client's default timeout
get(path, **kwargs) - Send GET request
post(path, **kwargs) - Send POST request
put(path, **kwargs) - Send PUT request
delete(path, **kwargs) - Send DELETE request
head(path, **kwargs) - Send HEAD request
patch(path, **kwargs) - Send PATCH request
wait(timeout=None)
Wait for response (blocking). Returns HttpResponse when complete.
timeout- Max time to spend in wait() call. IfNone, uses request timeout.- Returns
Noneif wait timeout expires (connection stays open, can call again). - Raises
HttpTimeoutErrorif request timeout expires (connection closed).
process_events(read_sockets, write_sockets)
Process select events. Returns HttpResponse when complete, None otherwise.
- First processes any ready data, then checks request timeout.
- Raises
HttpTimeoutErrorif request timeout has expired and no complete response.
close()
Close connection.
Class HttpResponse
Properties
status- HTTP status code (int)status_message- HTTP status message (str)headers- Response headers dict (keys are lowercase)data- Response body as bytescontent_type- Content-Type header valuecontent_length- Content-Length header value
Methods
json()
Parse response body as JSON. Lazy evaluation, cached.
Authentication
Basic Auth
HTTP Basic authentication via URL or auth parameter:
import uhttp.client
# Via URL
client = uhttp.client.HttpClient('https://user:password@api.example.com')
response = client.get('/protected').wait()
# Via parameter
client = uhttp.client.HttpClient('https://api.example.com', auth=('user', 'password'))
response = client.get('/protected').wait()
# Change auth at runtime
client.auth = ('new_user', 'new_password')
# Per-request auth (overrides client's default)
client = uhttp.client.HttpClient('https://api.example.com')
response = client.get('/admin', auth=('admin', 'secret')).wait()
response = client.get('/public').wait() # no auth
Digest Auth
HTTP Digest authentication is handled automatically. On 401 response with
WWW-Authenticate: Digest header, the client retries with digest credentials:
import uhttp.client
# Same API as Basic auth - digest is automatic
client = uhttp.client.HttpClient('https://api.example.com', auth=('user', 'password'))
# First request gets 401, client automatically retries with digest auth
response = client.get('/protected').wait()
print(response.status) # 200 (after automatic retry)
Supported digest features:
- MD5 and MD5-sess algorithms
- qop (quality of protection) with auth mode
- Nonce counting for multiple requests
Cookies
Cookies are automatically:
- Stored from
Set-Cookieresponse headers - Sent with subsequent requests
import uhttp.client
client = uhttp.client.HttpClient('https://example.com')
# Login - server sets session cookie
client.post('/login', json={'user': 'admin', 'pass': 'secret'}).wait()
# Subsequent requests include the cookie automatically
response = client.get('/dashboard').wait()
# Access cookies
print(client.cookies) # {'session': 'abc123'}
client.close()
Keep-Alive
Connections are reused automatically (HTTP/1.1 keep-alive).
import uhttp.client
client = uhttp.client.HttpClient('https://httpbin.org')
# All requests use the same connection
for i in range(10):
response = client.get('/get', query={'n': i}).wait()
print(f"Request {i}: {response.status}")
client.close()
Timeouts
Two types of timeouts:
Request timeout
Total time allowed for the request. Set via timeout parameter on client or per-request.
When expired, raises HttpTimeoutError and closes connection.
import uhttp.client
# Client-level timeout (default for all requests)
client = uhttp.client.HttpClient('https://example.com', timeout=30)
# Per-request timeout (overrides client default)
response = client.get('/slow', timeout=60).wait()
Wait timeout
Time to spend in wait() call. When expired, returns None but keeps connection open.
Useful for polling or interleaving with other work.
import uhttp.client
client = uhttp.client.HttpClient('https://example.com', timeout=60) # request timeout
client.get('/slow')
# Try for 5 seconds, then do something else
response = client.wait(timeout=5)
if response is None:
print("Still waiting, doing other work...")
# Can call wait() again
response = client.wait(timeout=10)
Error handling
import uhttp.client
client = uhttp.client.HttpClient('https://example.com')
try:
response = client.get('/api').wait()
except uhttp.client.HttpConnectionError as e:
print(f"Connection failed: {e}")
except uhttp.client.HttpTimeoutError as e:
print(f"Timeout: {e}")
except uhttp.client.HttpResponseError as e:
print(f"Invalid response: {e}")
except uhttp.client.HttpClientError as e:
print(f"Client error: {e}")
finally:
client.close()
Configuration constants
CONNECT_TIMEOUT = 10 # seconds
TIMEOUT = 30 # seconds
MAX_RESPONSE_HEADERS_LENGTH = 4KB
MAX_RESPONSE_LENGTH = 1MB
Examples
See examples/ directory:
client_basic.py- Basic blocking examplesclient_https.py- HTTPS examplesclient_async.py- Async select loop examplesclient_with_server.py- Combined server + client examples
Run examples from project root:
PYTHONPATH=./server:./client python examples/client_basic.py
CLI Tool
After installing the package, uhttp command is available:
pip install uhttp-client
Basic usage
# GET request (default)
uhttp https://httpbin.org/get
# POST with JSON data
uhttp https://httpbin.org/post -j '{"key": "value"}'
# POST with form data (method auto-detected from data)
uhttp https://httpbin.org/post -d "name=john&age=30"
# Explicit HTTP method
uhttp PUT https://httpbin.org/put -j '{"update": true}'
uhttp DELETE https://httpbin.org/delete
uhttp PATCH https://httpbin.org/patch -d "field=value"
Options
# Custom headers
uhttp https://httpbin.org/get -H "Authorization: Bearer token"
# Save response to file
uhttp https://httpbin.org/image/png -o image.png
# Send file content
uhttp https://httpbin.org/post -f document.pdf
# JSON from file
uhttp https://httpbin.org/post -j @data.json
# Verbose mode (show headers and timing)
uhttp https://httpbin.org/get -v
# Skip SSL verification
uhttp https://self-signed.example.com -k
# Custom timeout
uhttp https://slow-api.example.com -t 60
Method detection
- No data →
GET - With
-d,-j, or-f→POST - Explicit method before URL → uses that method
uhttp example.com/api # GET
uhttp example.com/api -d "x=1" # POST (auto)
uhttp GET example.com/api -d "" # GET (explicit, ignores data rule)
Run without installation
python -m uhttp.cli https://httpbin.org/get
See uhttp --help for all options.
IPv6 Support
Client supports both IPv4 and IPv6:
- Automatically tries all addresses returned by
getaddrinfo()(IPv4 and IPv6) - Works with hostnames like
localhoston all systems
import uhttp.client
# Works on all systems (IPv4 or IPv6)
client = uhttp.client.HttpClient('http://localhost:8080')
# Explicit IPv4
client = uhttp.client.HttpClient('http://127.0.0.1:8080')
# Explicit IPv6
client = uhttp.client.HttpClient('http://[::1]:8080')
Development
Running tests
../.venv/bin/pip install -e .
../.venv/bin/python -m unittest discover -v tests/
For running tests from meta-repo, see uhttp README.
CI
Tests run automatically on push/PR via GitHub Actions (Ubuntu + Windows, Python 3.10 + 3.14).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file uhttp_client-2.2.1.tar.gz.
File metadata
- Download URL: uhttp_client-2.2.1.tar.gz
- Upload date:
- Size: 31.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e06ddfec0aae6dc0ee2ed79c9dad0913e82e7653cb2f6a96a8ec8bb234a84e4
|
|
| MD5 |
935c50cc602ececa291012c5f18499c9
|
|
| BLAKE2b-256 |
72d970ccd5b3f7c85a7072350224b950ebcfc49d918796e0e30997871bab6f40
|
Provenance
The following attestation bundles were made for uhttp_client-2.2.1.tar.gz:
Publisher:
publish.yml on pavelrevak/uhttp-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uhttp_client-2.2.1.tar.gz -
Subject digest:
8e06ddfec0aae6dc0ee2ed79c9dad0913e82e7653cb2f6a96a8ec8bb234a84e4 - Sigstore transparency entry: 1008348937
- Sigstore integration time:
-
Permalink:
pavelrevak/uhttp-client@a3c794b918b62b8c5a34c70e6738ad463231bf42 -
Branch / Tag:
refs/tags/v2.2.1 - Owner: https://github.com/pavelrevak
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a3c794b918b62b8c5a34c70e6738ad463231bf42 -
Trigger Event:
release
-
Statement type:
File details
Details for the file uhttp_client-2.2.1-py3-none-any.whl.
File metadata
- Download URL: uhttp_client-2.2.1-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4904d564445ebdcd6f4a22a33eb1bc7bc2f2430844aac76cec727ae9cb7b3985
|
|
| MD5 |
bcdb7ff3dbe83ea4108b1842cde86b6c
|
|
| BLAKE2b-256 |
1f2c36a2ece4531b7dc5dec2ed3f310ad3dca4303d081a03b9f4189951041f04
|
Provenance
The following attestation bundles were made for uhttp_client-2.2.1-py3-none-any.whl:
Publisher:
publish.yml on pavelrevak/uhttp-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uhttp_client-2.2.1-py3-none-any.whl -
Subject digest:
4904d564445ebdcd6f4a22a33eb1bc7bc2f2430844aac76cec727ae9cb7b3985 - Sigstore transparency entry: 1008348939
- Sigstore integration time:
-
Permalink:
pavelrevak/uhttp-client@a3c794b918b62b8c5a34c70e6738ad463231bf42 -
Branch / Tag:
refs/tags/v2.2.1 - Owner: https://github.com/pavelrevak
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a3c794b918b62b8c5a34c70e6738ad463231bf42 -
Trigger Event:
release
-
Statement type: