minihttp
minihttp is a small HTTP server framework for Python applications running on trusted networks or behind a more developed web server such as nginx. It provides typed request handlers, JSON serialization, persistent sessions, and a deliberately small HTTP feature set.
Features
Creating a server
Create a Server and call run() to start it.
from minihttp import Server
server = Server()
server.run()
By default, the server listens on 127.0.0.1:2000.
You can also specify a persistent SQLite database:
server = Server("minihttp.db")
This initializes relix for the application and also provides storage for sessions.
To listen on a different address:
server.run("0.0.0.0", 8000)
Routes
Use method decorators to register handlers.
from minihttp import Server
server = Server()
@server.get("/")
def index():
return {
"message": "Hello, world!",
}
@server.post("/users")
def create_user():
return {
"created": True,
}
server.run()
minihttp supports:
@server.get("/example")
@server.post("/example")
@server.put("/example")
@server.patch("/example")
@server.delete("/example")
@server.head("/example")
@server.options("/example")
You can also register a method explicitly:
@server.route("GET", "/hello")
def hello():
return "Hello"
Path variables
Prefix part of a route with : to create a path variable.
@server.get("/users/:id")
def get_user(id: int):
return {
"id": id,
}
A request for:
GET /users/42
calls the handler with id as the integer 42.
Multiple path variables can be used:
@server.get("/users/:user_id/posts/:post_id")
def get_post(user_id: int, post_id: int):
return {
"user_id": user_id,
"post_id": post_id,
}
Path variables without a type annotation are passed as strings.
Query parameters
For GET, DELETE, HEAD, and OPTIONS requests, a dataclass handler argument is populated from the query string.
from dataclasses import dataclass
@dataclass
class Search:
query: str
page: int = 1
exact: bool = False
@server.get("/search")
def search(options: Search):
return {
"query": options.query,
"page": options.page,
"exact": options.exact,
}
A request such as:
GET /search?query=python&page=2&exact=true
becomes:
Search(
query="python",
page=2,
exact=True,
)
Dataclass defaults and Optional values can be used normally.
from dataclasses import dataclass
from typing import Optional
@dataclass
class Options:
page: int = 1
search: Optional[str] = None
A handler may contain at most one dataclass request argument.
JSON request bodies
For POST, PUT, and PATCH, the handler's dataclass argument is populated from the JSON request body.
from dataclasses import dataclass
@dataclass
class NewUser:
name: str
email: str
is_admin: bool = False
@server.post("/users")
def create_user(user: NewUser):
return {
"name": user.name,
"email": user.email,
"is_admin": user.is_admin,
}
A request containing:
{
"name": "Alice",
"email": "alice@example.com"
}
is converted into a NewUser before the handler is called.
Nested dataclasses are supported:
@dataclass
class Address:
city: str
country: str
@dataclass
class NewUser:
name: str
address: Address
@server.post("/users")
def create_user(user: NewUser):
return user
Invalid values result in a 400 response.
Request headers
Add a Headers argument when a handler needs access to request headers.
from minihttp import Headers
@server.get("/headers")
def show_headers(headers: Headers):
return {
"user_agent": headers.get("User-Agent"),
}
Header names are case-insensitive:
headers["Content-Type"]
headers["content-type"]
headers["CONTENT-TYPE"]
all refer to the same value.
Headers are stored internally using lowercase names.
A handler may contain at most one Headers argument.
Sessions
Add a Session argument to a handler to use server-side session data.
from minihttp import Session
@server.get("/login")
def login(session: Session):
session.username = "alice"
return {
"logged_in": True,
}
Session values support both attribute and dictionary access:
session.username = "alice"
session["username"] = "alice"
print(session.username)
print(session["username"])
Nested dictionaries also support attribute access:
session.preferences = {
"theme": "dark",
"notifications": True,
}
session.preferences.theme = "light"
Changes are tracked recursively.
minihttp automatically saves a changed session after the request finishes, so handlers normally do not need to call session.save() themselves.
Sessions are persisted using relix.
Session keys
Each session has a cryptographically random key containing 128 bits of entropy.
@server.get("/session")
def show_session(session: Session):
return {
"key": session.key,
}
The key is stored in the client's key cookie:
key=...
If the client does not have a valid session key, minihttp creates a new session with a new random key.
A supplied key that does not exist in the database is never reused to create a session.
Sessions are only loaded when the handler actually declares a Session argument.
relix initialization
minihttp can initialize relix automatically.
from minihttp import Server
server = Server("app.db")
This is equivalent to using app.db as the relix database for models and sessions.
For an in-memory database:
server = Server()
Models should be defined before the Server is constructed so their tables can be created during database initialization.
relix can also be used independently of minihttp:
from relix import Database
database = Database.init("app.db")
Returning JSON
Handlers can return native Python values directly.
@server.get("/status")
def status():
return {
"running": True,
"workers": 4,
}
Lists work too:
@server.get("/numbers")
def numbers():
return [1, 2, 3]
Strings, integers, floats, booleans, and None are also serialized as JSON.
Returning dataclasses
Dataclass instances are serialized automatically.
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
@server.get("/user")
def user():
return User(
name="Alice",
email="alice@example.com",
)
relix models are dataclasses too, so they can also be returned directly from handlers.
Custom responses
Return a Response when you need control over the status, headers, content type, or raw body.
from minihttp import Response
@server.get("/raw")
def raw():
return Response(
b"hello",
status=200,
headers={
"X-Example": "value",
},
content_type="application/octet-stream",
)
JSON responses
Use JSONResponse when you need JSON with a custom status or headers.
from minihttp import JSONResponse
@server.post("/users")
def create_user():
return JSONResponse(
{
"id": 42,
},
status=201,
headers={
"X-Created": "yes",
},
)
Text responses
Use TextResponse for plain text.
from minihttp import TextResponse
@server.get("/hello.txt")
def hello():
return TextResponse("Hello, world!")
HTML responses
Use HTMLResponse for HTML.
from minihttp import HTMLResponse
@server.get("/")
def index():
return HTMLResponse("""
<!doctype html>
<html>
<body>
<h1>Hello!</h1>
</body>
</html>
""")
Files can be read normally:
from pathlib import Path
from minihttp import HTMLResponse
@server.get("/")
def index():
path = Path(__file__).parent / "index.html"
return HTMLResponse(
path.read_text(encoding="utf-8")
)
CSS responses
Use CSSResponse for stylesheets.
from minihttp import CSSResponse
@server.get("/style.css")
def stylesheet():
return CSSResponse("""
body {
font-family: sans-serif;
}
""")
JavaScript responses
Use JavaScriptResponse for JavaScript.
from minihttp import JavaScriptResponse
@server.get("/app.js")
def javascript():
return JavaScriptResponse("""
console.log("Hello from minihttp");
""")
File responses
Use FileResponse to send a file.
from pathlib import Path
from minihttp import FileResponse
@server.get("/example.tar.gz")
def download():
path = Path(__file__).parent / "example.tar.gz"
return FileResponse(path)
The content type is inferred from the filename when possible.
Response headers
Response helpers accept custom headers.
from minihttp import TextResponse
@server.get("/example")
def example():
return TextResponse(
"Hello",
headers={
"Cache-Control": "no-store",
"X-Example": "value",
},
)
Response status codes
Response helpers accept custom status codes.
from minihttp import JSONResponse
@server.post("/users")
def create_user():
return JSONResponse(
{
"id": 42,
},
status=201,
)
Combining handler arguments
Path variables, a dataclass, Headers, and Session can be combined in the same handler.
from dataclasses import dataclass
from minihttp import Headers, Session
@dataclass
class Options:
include_details: bool = False
@server.get("/users/:id")
def get_user(
id: int,
options: Options,
headers: Headers,
session: Session,
):
session.last_user = id
return {
"id": id,
"include_details": options.include_details,
"user_agent": headers.get("User-Agent"),
}
The handler signature describes exactly which parts of the request the handler needs.
Persistent connections
HTTP/1.1 connections are kept alive by default, allowing multiple requests to use the same TCP connection.
Clients can explicitly close the connection with:
Connection: close
HTTP/1.0 connections close by default unless the client requests:
Connection: keep-alive
minihttp handles response framing using Content-Length, so multiple requests and responses can share the same connection safely.
A small application
minihttp and relix can be used together with very little setup.
from dataclasses import dataclass
from minihttp import JSONResponse, Server, Session
from relix import Model
@dataclass
class User(Model):
username: str
display_name: str
@dataclass
class Login:
username: str
server = Server("app.db")
@server.post("/login")
def login(data: Login, session: Session):
user = User.where(
User.username == data.username
).first()
if user is None:
return JSONResponse(
{
"error": "unknown user",
},
status=404,
)
session.user_id = user.id
return {
"logged_in": True,
}
@server.get("/profile")
def profile(session: Session):
user_id = session.get("user_id")
if user_id is None:
return JSONResponse(
{
"error": "not logged in",
},
status=401,
)
user = User.get(user_id)
if user is None:
return JSONResponse(
{
"error": "user not found",
},
status=404,
)
return user
server.run()
minihttp deliberately supports only a subset of HTTP. It is intended for trusted networks or deployments where a reverse proxy such as nginx handles the broader public-facing HTTP concerns.
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 minihttp-0.2.0.tar.gz.
File metadata
- Download URL: minihttp-0.2.0.tar.gz
- Upload date:
- Size: 19.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8af9692f04a22d84971768abab7c9326ce54f8b15c8d80ee2c9ca4c4ceedfd5d
|
|
| MD5 |
aab194b9df7a3b82c5d07b7a9c27be13
|
|
| BLAKE2b-256 |
a9934a4c6d2d28af183dfa493c162e47ff7b0460820676ff08f70dc8dc0d9046
|
Provenance
The following attestation bundles were made for minihttp-0.2.0.tar.gz:
Publisher:
publish.yml on mizuki-hikaru/minihttp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minihttp-0.2.0.tar.gz -
Subject digest:
8af9692f04a22d84971768abab7c9326ce54f8b15c8d80ee2c9ca4c4ceedfd5d - Sigstore transparency entry: 2733930243
- Sigstore integration time:
-
Permalink:
mizuki-hikaru/minihttp@d37731d6f46060fa5fa00fd69e4428ed6f017bfd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mizuki-hikaru
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d37731d6f46060fa5fa00fd69e4428ed6f017bfd -
Trigger Event:
push
-
Statement type:
File details
Details for the file minihttp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: minihttp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7da236dec7153a264813375682c70323bc9440910a6d9d5fb167dca9425a878a
|
|
| MD5 |
0b3c9bf8edd037d7ad150f5412f71542
|
|
| BLAKE2b-256 |
43e7e07fb5358f3145b051f7afc99fadcf2aa3eb6e1c0f376496e7a1107b8878
|
Provenance
The following attestation bundles were made for minihttp-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on mizuki-hikaru/minihttp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minihttp-0.2.0-py3-none-any.whl -
Subject digest:
7da236dec7153a264813375682c70323bc9440910a6d9d5fb167dca9425a878a - Sigstore transparency entry: 2733930274
- Sigstore integration time:
-
Permalink:
mizuki-hikaru/minihttp@d37731d6f46060fa5fa00fd69e4428ed6f017bfd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mizuki-hikaru
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d37731d6f46060fa5fa00fd69e4428ed6f017bfd -
Trigger Event:
push
-
Statement type: