Skip to main content

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

minihttp-0.2.0.tar.gz (19.0 kB view details)

Uploaded Source

Built Distribution

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

minihttp-0.2.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

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

Hashes for minihttp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8af9692f04a22d84971768abab7c9326ce54f8b15c8d80ee2c9ca4c4ceedfd5d
MD5 aab194b9df7a3b82c5d07b7a9c27be13
BLAKE2b-256 a9934a4c6d2d28af183dfa493c162e47ff7b0460820676ff08f70dc8dc0d9046

See more details on using hashes here.

Provenance

The following attestation bundles were made for minihttp-0.2.0.tar.gz:

Publisher: publish.yml on mizuki-hikaru/minihttp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for minihttp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7da236dec7153a264813375682c70323bc9440910a6d9d5fb167dca9425a878a
MD5 0b3c9bf8edd037d7ad150f5412f71542
BLAKE2b-256 43e7e07fb5358f3145b051f7afc99fadcf2aa3eb6e1c0f376496e7a1107b8878

See more details on using hashes here.

Provenance

The following attestation bundles were made for minihttp-0.2.0-py3-none-any.whl:

Publisher: publish.yml on mizuki-hikaru/minihttp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page