Skip to content

Latest commit

 

History

History
301 lines (211 loc) · 7.62 KB

README.md

File metadata and controls

301 lines (211 loc) · 7.62 KB

Examples

You can run the examples from the section below, simply by running the server given in the ./examples directory:

python3 examples/main.py

You can then send HTTP queries to the server with the command curl:

curl -X $HTTP_METHOD -d "$BODY" -w "\n\nStatus code:%{http_code}\n" "$URL"

For development purposes, you can execute the tests with :

deno task --cwd examples/ test

Usage

To create a new HTTP server, just call the function startHTTPServer():

import asyncio
from VSHS import startHTTPServer, rootDir

# Put the bytecode-cache in the same directory to keep the project clean
import sys
sys.pycache_prefix = rootDir() + "/__pycache__"

asyncio.run( startHTTPServer(hostname="localhost", port=8080, routes="/routes") )

Routes and handlers

The routes parameter is a directory containing the differents routes your HTTP server will answer to. In this directory, each subdirectory corresponds to a route, and each files, to a supported HTTP method for this route.

For example, the file ./routes/hello-world/GET.ts defines how your server will answer to a GET /hello-world HTTP query. In order to do so, GET.py default exports an asynchronous function whose return value is the answer to the received HTTP query.

async def default(url, body, route):
    return {"message": "Hello World"}
curl -w "\n" -X GET http://localhost:8080/hello-world

Output:

{
    "message": "Hello World"
}

Handler parameters

In fact, the handler function takes 3 parameters:

from VSHS import get_query

async def default(
                    url,      # yarl.URL: the requested URL
                    body,      # any|None: json.loads( body ) or None if empty body.
                    route     # Route: cf next section
                ):

    return {
        "urlParams" : get_query(url),
        "bodyParams": body,
        "pathParams": route.vars # dict[string, string]
    }
curl -w "\n" -X POST -d '{"body": "A"}' http://localhost:8080/params/C?url=B

Output:

{
    "urlParams": {
        "url": "B"
    },
    "bodyParams": {
        "body": "A"
    },
    "pathParams": {
        "route": "C"
    }
}

⚠ Some brower might forbid to add body to GET queries.

Routes variables

The route parameter has two components:

  • path is the route path, e.g. /params/{name}/GET.ts. Letters in between braces represents a variable, corresponding to set of letters (except /). Hence a single route path can match several URL, e.g.:

    • /params/faa
    • /params/fuu
  • vars is an object whose keys are the path variables names and whose values their values in the current URL, e.g.:

    • {name: "faa"}
    • {name: "fuu"}

HTTP Error Code

If an exception is thrown inside an handlers, the server will automatically send an HTTP 500 status code (Internal Server Error).

async def default(url, body, route):
    raise Exception('Oups...')
curl -w "\n\nStatus code: %{http_code}\n" -X GET http://localhost:8080/exception

Output:

Oups...

Status code: 500

You can send other HTTP status code, by throwing an instance of HTTPError:

from VSHS import HTTPError

async def default(url, body, route):
    raise HTTPError(403, "Forbidden Access")
curl -w "\n\nStatus code: %{http_code}\n" -X GET http://localhost:8080/http-error

Output:

Forbidden Access

Status code: 403

💡 If it exists, errors are redirected to the /errors/{error_code} route, with body containing the error message.

Mime-type

In the response

We infer the mime type from the handler return value :

Return Mime
str text/plain
MultiDict application/x-www-form-urlencoded
bytes application/octet-stream
Blob blob.type
or
application/octet-stream
any application/json
SSEResponse text/event-stream

💡 We provide a Blob class to mimic JS Blob :

blob = Blob(content, type="text/plain")

await blob.text()
await blob.bytes()

In the query

We automatically perform the following conversions on the query body:

Mime Result
No body None
text/plain str or dict
application/x-www-form-urlencoded dict
application/json dict
application/octet-stream bytes
others Blob

⚠ For text/plain and application/x-www-form-urlencoded, we first try to parse it with JSON.parse().

💡 The default mime-types set by the client are :

Source (JS) Mime-type
string text/plain
URLSearchParams application/x-www-form-urlencoded
FormData application/x-www-form-urlencoded
Uint8Array None
Blob blob.type or none
curl -d application/x-www-form-urlencoded

💡 To provide an explicit mime-type in the query :

fetch('...', {body: ..., headers: {"Content-Type", "..."})
curl -d "..." -H "Content-Type: ..."

Static ressources

You can also provide a directory containing static files

import asyncio
from VSHS import startHTTPServer, rootDir

# Put the bytecode-cache in the same directory to keep the project clean
import sys
sys.pycache_prefix = rootDir() + "/__pycache__"

asyncio.run( startHTTPServer(hostname="localhost",
                             port=8080,
                             routes="/routes",
                             static="/assets") )
curl -w "\n\nType: %{content_type}\n" -X GET http://localhost:8080/

Output:

<b>Hello world</b>

Type: text/html

Server-Sent Events

If you want to return Server-Sent Events, you just have to return an instance of SSEResponse:

import asyncio
import traceback
from VSHS import SSEResponse

async def run(self):

    try:
        i = 0
        while True:
            await asyncio.sleep(1)
            i += 1
            await self.send(data={"count": i}, event="event_name")

    except Exception as e:
        print("Connection closed")
        traceback.print_exc()

async def default(url, body, route):
    return SSEResponse( run )

The method send(message: any, event?: str) sends a new event to the client. Once the client closes the connection, an exception is raised:

curl -X GET http://localhost:8080/server-sent-events

Output:

event: event_name
data: {"count":0}

event: event_name
data: {"count":1}

event: event_name
data: {"count":2}

Demo Messages.html

We also provide an additionnal demonstration in ./examples/demo/.

This webpage sends two HTTP queries :

  • GET /demo/website to receive Server-Sent Events at each modification of ./examples/messages.txt.
  • POST /demo/website to append a new line into ./examples/messages.txt at each submission of the formular.