You can run the examples from the section below, simply by running the server given in the ./examples
directory:
deno task --cwd examples/ start
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
To create a new HTTP server, just call the function startHTTPServer()
:
import startHTTPServer from "VSHS";
startHTTPServer({
port: 8080,
hostname: 'localhost',
routes: '/routes'
});
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.ts
default exports an asynchronous function whose return value is the answer to the received HTTP query.
export default async function() {
return {message: "Hello World"};
}
curl -w "\n" -X GET http://localhost:8080/hello-world
Output:
{
"message": "Hello World"
}
In fact, the handler function takes a HandlerParams
parameter:
import { HandlerParams } from "VSHS";
export default async function(
{
url, // URL: the requested URL
// ( cf https://developer.mozilla.org/en-US/docs/Web/API/URL )
body, // any|null: JSON.parse( query.body ) or null if empty body.
route: {// cf next section
path // string
vars // Record<string, string>
}
}: HandlerParams
) {
return {
urlParams : Object.fromEntries(url.searchParams.entries()),
bodyParams: body,
pathParams: vars
};
}
curl -w "\n" -X POST -d '{"body": "A"}' http://localhost:8080/params/C?url=B
Output:
{
"urlParams": {
"url": "B"
},
"bodyParams": {
"body": "A"
},
"pathParams": {
"name": "C"
}
}
⚠ Some brower might forbid to add body to GET queries.
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"}
If an exception is thrown inside an handlers, the server will automatically send an HTTP 500 status code (Internal Server Error).
export default async function() {
throw new Error('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
:
import {HTTPError} from "VSHS";
export default async function() {
throw new 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.
We infer the response's Content-Type
from the handler return value :
Return | Mime |
---|---|
string |
text/plain |
URLSearchParams |
application/x-www-form-urlencoded |
FormData |
application/x-www-form-urlencoded |
Uint8Array |
application/octet-stream |
Blob |
blob.type or application/octet-stream |
any |
application/json |
SSEResponse |
text/event-stream |
We automatically perform the following conversions on the query body:
Mime | Result |
---|---|
No body | null |
text/plain |
string or Object |
application/x-www-form-urlencoded |
Object |
application/json |
Object |
application/octet-stream |
Uint8Array |
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 | 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: ..."
You can also provide a directory containing static files
startHTTPServer({
port: 8080,
hostname: 'localhost',
routes: '/routes',
static: '/assets'
});
curl -w "\n\nType: %{content_type}\n" -X GET http://localhost:8080/
Output:
<b>Hello world</b>
Type: text/html
If you want to return Server-Sent Events, you just have to return an instance of SSEResponse
:
import { SSEResponse } from "../../../HTTPServer.ts";
export default async function() {
return new SSEResponse( async (self) => {
self.onConnectionClosed = () => {
clearInterval(timer);
}
let i = 0;
let timer = setInterval( async () => {
await self.send({count: i++}, "event_name")
}, 1000);
});
}
The method send(message: any, event?: string)
sends a new event to the client. Once the client closes the connection, the callback registered in self.onConnectionClosed
is called.
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}
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.