Adapters¶
tornadoapi-guard is a thin layer over guard-core, a framework-agnostic security engine. guard-core is written against a small set of protocol types (GuardRequest, GuardResponse, GuardResponseFactory) so that it can be reused by different web frameworks. The adapter module translates Tornado's concrete types (RequestHandler, HTTPServerRequest, HTTPHeaders) into those protocols.
All adapter classes live in tornadoapi_guard.adapters and are re-exported from the top-level package:
from tornadoapi_guard import (
TornadoGuardRequest,
TornadoGuardResponse,
TornadoResponseFactory,
apply_guard_response,
unwrap_response,
)
Most users never touch these classes directly — SecurityMiddleware builds them under the hood. You do need them when writing a custom_request_check, a custom_response_modifier, or a custom validator that needs to return a typed response.
TornadoGuardRequest¶
Wraps a tornado.web.RequestHandler and its HTTPServerRequest. Implements the GuardRequest protocol from guard-core. The middleware builds one of these per request inside run_pre_processing, so you normally do not instantiate them by hand.
TornadoGuardRequest Properties¶
| Property | Return type | Description |
|---|---|---|
url_path |
str |
handler.request.path |
url_scheme |
str |
handler.request.protocol (for example "http" or "https") |
url_full |
str |
handler.request.full_url() |
method |
str |
HTTP method, defaulting to "GET" if Tornado reports None |
client_host |
str \| None |
handler.request.remote_ip — the IP that Tornado resolved for the peer |
headers |
Mapping[str, str] |
Case-insensitive header mapping (Tornado's HTTPHeaders) |
query_params |
Mapping[str, str] |
Query-string decoded to str values; repeated keys collapse to the first value |
state |
SimpleNamespace |
Per-handler state container — see Per-handler state below |
scope |
dict[str, Any] |
Dict exposing handler, app, application, settings, path, and method; compatible with guard-core code that inspects scope.get("app") |
TornadoGuardRequest Methods¶
Returns a rebuilt URL with the given scheme swapped in (useful for HTTPS enforcement redirects).
Returns the raw request body. Tornado already buffers the body on the request object, so this is effectively synchronous from an IO perspective — the async signature exists to match the GuardRequest protocol, which is shared with ASGI adapters where body access is genuinely async.
TornadoGuardResponse¶
class TornadoGuardResponse:
def __init__(
self,
status_code: int,
headers: MutableMapping[str, str] | None = None,
body: bytes | None = None,
) -> None: ...
A standalone, framework-agnostic response snapshot. Implements the GuardResponse protocol. Unlike FastAPI, Tornado does not have a first-class response object — responses are written incrementally via handler.write() / handler.set_header() / handler.finish(). TornadoGuardResponse bridges that gap by providing a single container that guard-core can inspect and modify.
TornadoGuardResponse Properties¶
| Property | Return type | Description |
|---|---|---|
status_code |
int |
HTTP status code |
headers |
MutableMapping[str, str] |
Mutable header mapping (guard-core mutates this to add security headers, CORS headers, and custom-response-modifier headers) |
body |
bytes \| None |
Raw response body |
TornadoGuardResponse.from_handler¶
Snapshots the current state of a RequestHandler into a TornadoGuardResponse. It reads:
handler.get_status()for the status code.handler._headers(Tornado's internalHTTPHeaderscontainer) for the accumulated response headers.handler._write_bufferfor the accumulated response body.
Used by SecurityMiddleware.run_post_processing to build a response object from a handler that has already written its reply, so that behavioral return rules and metrics collectors can see the actual status / headers / body that went on the wire.
TornadoResponseFactory¶
class TornadoResponseFactory:
def create_response(
self, content: str, status_code: int
) -> TornadoGuardResponse: ...
def create_redirect_response(
self, url: str, status_code: int
) -> TornadoGuardResponse: ...
Implements the GuardResponseFactory protocol. guard-core uses this factory whenever it needs to construct a blocking response (for example, a 403 ban, a 429 rate-limit, or a 301 HTTPS redirect). The factory produces typed TornadoGuardResponse instances with sensible default headers:
create_response(...)UTF-8 encodes the content, setsContent-Type: text/plain; charset=utf-8, and fills inContent-Length.create_redirect_response(...)setsLocation: <url>andContent-Length: 0with an empty body.
The middleware stores a singleton instance on itself and exposes it through middleware.guard_response_factory.
apply_guard_response¶
Writes a GuardResponse back through a Tornado RequestHandler. The procedure is:
handler.clear()— discard any previously buffered output.handler.set_status(guard_response.status_code)— set the status line.handler.set_header(key, value)for every entry inguard_response.headers.handler.write(body)if a body was provided.handler.finish()— terminate the request.
SecurityHandler.prepare calls this helper when the security pipeline returns a blocking response, and you may call it yourself from a custom validator if you construct a TornadoGuardResponse and want to write it out immediately.
unwrap_response¶
Returns its input unchanged if it is already a TornadoGuardResponse. Otherwise, it constructs a new TornadoGuardResponse from any GuardResponse-shaped object (copying status_code, headers as a fresh dict, and body). Symmetric with fastapi-guard's helper of the same name, and useful when you receive a response of unknown concrete type from a guard-core hook and want to guarantee a Tornado-friendly container.
Writing Custom Hooks¶
SecurityConfig accepts two user-provided callables:
custom_request_check(request)— runs at the end of the security pipeline. Return aGuardResponseto short-circuit the request, orNoneto let it through.custom_response_modifier(response)— runs during post-processing. Receives the built response and may mutate its headers (or construct a new response) before the middleware hands it back.
Use TornadoGuardResponse for return values so that SecurityMiddleware can apply headers, CORS, and the custom modifier uniformly. The pattern below mirrors examples/simple_app/main.py:
import json
from guard_core.protocols.request_protocol import GuardRequest
from guard_core.protocols.response_protocol import GuardResponse
from tornadoapi_guard import SecurityConfig, TornadoGuardResponse
async def custom_request_check(request: GuardRequest) -> GuardResponse | None:
if request.query_params.get("debug") == "true":
return TornadoGuardResponse(
status_code=403,
headers={"Content-Type": "application/json; charset=utf-8"},
body=json.dumps({"detail": "Debug mode not allowed"}).encode("utf-8"),
)
return None
async def custom_response_modifier(response: GuardResponse) -> GuardResponse:
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
return response
config = SecurityConfig(
custom_request_check=custom_request_check,
custom_response_modifier=custom_response_modifier,
)
A few notes:
- Inside
custom_request_checkyou can read the request through theGuardRequestprotocol (request.query_params,request.headers,request.client_host,await request.body()). You do not need to know that the concrete type isTornadoGuardRequest— stick to the protocol and the same hook will work against any guard-core adapter. - Inside
custom_response_modifieryou receive aGuardResponse(concretely aTornadoGuardResponsewhen running undertornadoapi-guard). Mutatingresponse.headersis safe because the mapping is declaredMutableMapping[str, str]in the protocol. - If you need a typed
TornadoGuardResponsefrom an unknownGuardResponse, callunwrap_response(response).
Per-handler State¶
TornadoGuardRequest.state returns a SimpleNamespace that the middleware uses to stash per-request data: the guard request itself, the resolved route config, the extracted client IP, and the request start time. Behavioral hooks, post-processing, and custom validators read from this namespace.
The storage is backed by a module-level WeakKeyDictionary keyed by the handler instance:
_guard_state_store: WeakKeyDictionary[Any, SimpleNamespace] = WeakKeyDictionary()
def get_guard_state(handler: RequestHandler) -> SimpleNamespace:
state = _guard_state_store.get(handler)
if state is None:
state = SimpleNamespace()
_guard_state_store[handler] = state
return state
The WeakKeyDictionary is an implementation detail — do not rely on it. What you should know:
- State is scoped to a single handler instance, which in Tornado means a single request.
- The entry is cleaned up automatically when the handler is garbage-collected at the end of the request.
- Two different handlers never share state, so it is safe to stash mutable data there.
- Inside a
custom_request_check, userequest.state.<attr>to read and write.
If you need to survive beyond a single request (for example, cross-request counters keyed by IP), use Redis through the configured RedisManager, not request.state.
See Also¶
- SecurityHandler — the base class that invokes the middleware through the adapters.
- SecurityMiddleware — orchestrates the adapters and the security pipeline.
- Core Architecture — details on the guard-core protocols these adapters implement.
- API Overview — complete API reference.