Skip to main content

Requests

Every handler receives a Request object as its first argument. It exposes the method, URI, headers, body, and parsed data.

Method and URI​

from oxapy import get


@get("/debug")
def debug(request):
return {
"method": request.method, # "GET"
"uri": request.uri, # full URI including query string
}

Headers​

Headers are available as a plain dictionary:

@get("/hello")
def hello(request):
user_agent = request.headers.get("user-agent")
return f"Hello from {user_agent}"

Query parameters​

request.query parses the query string of the URI into a dictionary. Unknown keys fall back to a default with dict.get.

from oxapy import get


@get("/search")
def search(request):
q = request.query.get("q", "")
limit = int(request.query.get("limit", "20"))
return {"q": q, "limit": limit}

For /search?q=rust&limit=10 this returns {"q": "rust", "limit": 10}.

JSON bodies​

Use request.json() to parse a JSON request body. It is the standard way to read POST/PUT payloads.

from oxapy import post


@post("/api/data")
def create_data(request):
data = request.json()
return {"status": "success", "received": data}

request.data contains the raw body as a string, when present.

Forms​

For application/x-www-form-urlencoded bodies, request.form gives you a dictionary of the submitted fields.

from oxapy import post


@post("/login")
def login(request):
form = request.form
return {"username": form["username"]}

File uploads​

Multipart form data is exposed through request.files, a dictionary mapping field names to File objects.

from oxapy import post


@post("/upload")
def upload(request):
files_info = {}
for name, file in request.files.items():
files_info[name] = {
"filename": file.name,
"content_type": file.content_type,
"size": len(file.content),
}
return {"files": files_info, "form": dict(request.form)}

Saving an uploaded file​

:::warning Sanitize the filename before saving

File.save(path) writes to exactly the path you give it — it does no validation whatsoever. The name attribute comes straight from the client, so image.save(f"uploads/{image.name}") lets an attacker choose where the file lands, including escaping the upload directory with a crafted name like ../../etc/cron.d/evil.

Strip any directory component from the name, then resolve the result inside a fixed base directory with secure_join():

import os

from oxapy import secure_join

UPLOADS = "./uploads"


@post("/upload")
def upload(request):
if "profile_image" in request.files:
image = request.files["profile_image"]
# os.path.basename drops any directory part from the client-supplied name;
# secure_join rejects anything that still escapes UPLOADS.
safe_name = os.path.basename(image.name)
image.save(secure_join(UPLOADS, safe_name))
return {"status": "success", "filename": safe_name}
return {"status": "error", "message": "No file uploaded"}

secure_join() answers "is this path inside the base directory?" — it does not make a name safe to create, so both steps are needed. Prefer generating your own filename (a UUID, for example) over reusing the client's. See the Static Files API reference.

:::

Cookies​

Read cookies with request.get_cookie(name), which returns None when the cookie is absent.

@get("/")
def index(request):
theme = request.get_cookie("theme") or "light"
return {"theme": theme}

Application data​

request.app_data returns the object you set with HttpServer.app_data(). It is shared across all requests, which makes it the right place for counters, pools, or other shared resources. See the Application State guide.

@get("/count")
def count(request):
state = request.app_data
state.counter += 1
return {"count": state.counter}

Dynamic attributes​

Middleware and handlers can attach arbitrary attributes to a request. For example, an authentication middleware may store the current user:

def auth_middleware(request, next, **kw):
request.user_name = "John Doe"
return next(request, **kw)


@get("/profile")
def profile(request):
return {"user": request.user_name}

See the Middleware guide for details.

Next steps​