Asgineer reference

This page contains the API documentation of Asgineer’s functions and classes, as well as a description of Asgineer more implicit API.

How to return a response

An HTTP response consists of three things: a status code, headers, and the body. Your handler must return these as a tuple. You can also return just the body, or the body and headers; these are all equivalent:

return 200, {}, 'hello'
return {}, 'hello'
return 'hello'

If needed, the normalize_response() function can be used to turn a response (e.g. of a subhandler) into a 3-element tuple.

Asgineer automatically converts the body returned by your handler, and sets the appropriate headers:

  • A bytes object is passed unchanged.
  • A str object that starts with <!DOCTYPE html> or <html> is UTF-8 encoded, and the content-type header defaults to text/html.
  • Any other str object is UTF-8 encoded, and the content-type header defaults to text/plain.
  • A dict object is JSON-encoded, and the content-type header is set to application/json.
  • An async generator can be provided as an alternative way to send a chunked response.

See request.accept and request.send for a lower level API (for which the auto-conversion does not apply).

Requests

class asgineer.BaseRequest(scope)[source]

Base request class, defining the properties to get access to the request metadata.

headers

A dictionary representing the headers. Both keys and values are lowercase strings.

host

he requested host name, taken from the Host header, or scope['server'][0] if there is not Host header. See also scope['server'] and scope['client'].

method

The HTTP method (string). E.g. ‘HEAD’, ‘GET’, ‘PUT’, ‘POST’, ‘DELETE’.

path

The path part of the URL (a string, with percent escapes decoded).

port

The server’s port (integer).

querydict

A dictionary representing the URL query parameters.

querylist

A list with (key, value) tuples, representing the URL query parameters.

scheme

The URL scheme (string). E.g. ‘http’ or ‘https’.

scope

A dict representing the raw ASGI scope. See the ASGI reference for details.

url

The full (unquoted) url, composed of scheme, host, port, path, and query parameters (string).

class asgineer.HttpRequest(scope, receive, send)[source]

Subclass of BaseRequest to represent an HTTP request. An object of this class is passed to the request handler.

accept(status=200, headers={})[source]

Accept this http request. Sends the status code and headers.

In Asgineer, a response can be provided in two ways. The simpler (preferred) way is to let the handler return status, headers and body. Alternatively, one can use use accept() and send(). In the latter case, the handler must return None.

Using accept() and send() is mostly intended for long-lived responses such as chunked data, long polling and SSE.

Note that when using a handler return value, Asgineer automatically sets headers based on the body. This is not the case when using accept. (Except that the ASGI server will set “transfer-encoding” to “chunked” if “content-length” is not specified.)

get_body(limit=10485760)[source]

Async function to get the bytes of the body. If the end of the stream is not reached before the byte limit is reached (default 10MiB), raises an IOError.

get_json(limit=10485760)[source]

Async function to get the body as a dict. If the end of the stream is not reached before the byte limit is reached (default 10MiB), raises an IOError.

iter_body()[source]

Async generator that iterates over the chunks in the body. During iteration you should probably take measures to avoid excessive memory usage to prevent server vulnerabilities. Raises DisconnectedError when the connection is closed.

send(data, more=True)[source]

Send (a chunk of) data, representing the response. Note that accept() must be called first. See accept() for details.

sleep_while_connected(seconds)[source]

Async sleep, wake-able, and only while the connection is active. Intended for use in long polling and server side events (SSE):

  • Returns after the given amount of seconds.
  • Returns when the request wakeup() is called.
  • Raises DisconnectedError when the connection is closed.
  • Note that this drops all received data.
wakeup()[source]

Awake any tasks that are waiting in sleep_while_connected().

class asgineer.WebsocketRequest(scope, receive, send)[source]

Subclass of BaseRequest to represent a websocket request. An object of this class is passed to the request handler.

accept(subprotocol=None)[source]

Async function to accept the websocket connection. This needs to be called before any sending or receiving. Raises DisconnectedError when the client closed the connection.

close(code=1000)[source]

Async function to close the websocket connection.

receive()[source]

Async function to receive one websocket message. The result can be bytes or str (depending on how it was sent). Raises DisconnectedError when the client closed the connection.

receive_iter()[source]

Async generator to iterate over incoming messages as long as the connection is not closed. Each message can be a bytes or str.

receive_json()[source]

Async convenience function to receive a JSON message. Works on binary as well as text messages, as long as its JSON encoded. Raises DisconnectedError when the client closed the connection.

send(data)[source]

Async function to send a websocket message. The value can be bytes, str or dict. In the latter case, the message is encoded with JSON (and UTF-8).

class asgineer.RequestSet[source]

A set of request objects that are currenlty active.

This class can help manage long-lived connections such as with long polling, SSE or websockets. All requests in as set can easily be awoken at once, and requests are automatically removed from the set when they’re done.

add(request)[source]

Add a request object to the set.

clear()[source]

Remove all request objects from the set.

discard(request)[source]

Remove the given request object from the set. If not present, it is ignored.

class asgineer.DisconnectedError[source]

An error raised when the connection is disconnected by the client. Subclass of IOError. You don’t need to catch these - it is considered ok for a handler to exit by this.

Entrypoint functions

asgineer.to_asgi(handler)[source]

Convert a request handler (a coroutine function) to an ASGI application, which can be served with an ASGI server, such as Uvicorn, Hypercorn, Daphne, etc.

asgineer.run(app, server, bind='localhost:8080', **kwargs)[source]

Run the given ASGI app with the given ASGI server. (This works for any ASGI app, not just Asgineer apps.) This provides a generic programatic API as an alternative to the standard ASGI-way to start a server.

Arguments:

  • app (required): The ASGI application object, or a string "module.path:appname".
  • server (required): The name of the server to use, e.g. uvicorn/hypercorn/etc.
  • kwargs: additional arguments to pass to the underlying server.

Utility functions

The asgineer.utils module provides a few utilities for common tasks.

asgineer.utils.sleep(seconds)[source]

An async sleep function. Uses asyncio. Can be extended to support Trio once we support that.

asgineer.utils.make_asset_handler(assets, max_age=0, min_compress_size=256)[source]

Get a coroutine function for efficiently serving in-memory assets. The resulting handler functon takes care of setting the appropriate content-type header, sending compressed responses when possible/sensible, and applying appropriate HTTP caching (using etag and cache-control headers). Usage:

assets = ... # a dict mapping filenames to asset bodies (str/bytes)

asset_handler = make_asset_handler(assets)

async def some_handler(request):
    path = request.path.lstrip("/")
    return await asset_handler(request, path)

Parameters for make_asset_handler():

  • assets (dict): The assets to serve. The keys represent “file names” and must be str. The values must be bytes or str.
  • max_age (int): The maximum age of the assets. This is used as a hint for the client (e.g. the browser) for how long an asset is “fresh” and can be used before validating it. The default is zero. Can be set higher for assets that hardly ever change (e.g. images and fonts).
  • min_compress_size (int): The minimum size of the body for compressing an asset. Default 256.

Parameters for the handler:

  • request (Request): The Asgineer request object (for the request headers).
  • path (str): A key in the asset dictionary. Case insensitive. If not given or None, request.path.lstrip("/") is used.

Handler behavior:

  • If the given path is not present in the asset dict (case insensitive), a 404-not-found response is returned.
  • The etag header is set to a (sha256) hash of the body of the asset.
  • The cache-control header is set to “public, must-revalidate, max-age=xx”.
  • If the request has a if-none-match header that matches the etag, the handler responds with 304 (indicating to the client that the resource is still up-to-date).
  • Otherwise, the asset body is returned, setting the content-type header based on the filename extensions of the keys in the asset dicts. If the key does not contain a dot, the content-type will be based on the body of the asset.
  • If the asset is over min_compress_size bytes, is not a video, the request has a accept-encoding header that contains “gzip”, and the compressed data is less that 90% of the raw data, the data is send in compressed form.
asgineer.utils.normalize_response(response)[source]

Normalize the given response, by always returning a 3-element tuple (status, headers, body). The body is not “resolved”; it is safe to call this function multiple times on the same response.

asgineer.utils.guess_content_type_from_body(body)[source]

Guess the content-type based of the body.

  • “text/html” for str bodies starting with <!DOCTYPE html> or <html>.
  • “text/plain” for other str bodies.
  • “application/json” for dict bodies.
  • “application/octet-stream” otherwise.

Details on Asgineer’s behavior

Asgineer will invoke your main handler for each incoming request. If an exception is raised inside the handler, this exception will be logged (including exc_info) using the logger that can be obtained with logging.getLogger("asgineer"), which by default writes to stderr. If possible, a status 500 (internal server error) response is sent back that includes the error message (without traceback).

Similarly, when the returned response is flawed, a (slightly different) error message is logged and included in the response.

In fact, Asgineer handles all exceptions, since the ASGI servers log errors in different ways (some just ignore them). If an error does fall through, it can be considered a bug.