deno_fcgi
FastCGI implementation for Deno.
Example
import {Server} from 'https://deno.land/x/fcgi/mod.ts';
const listener = Deno.listen({port: 8080});
const server = new Server(listener);
console.log(`Started on ${(listener.addr as Deno.NetAddr).port}`);
for await (let req of server)
{ Deno.readAll(req.body).then
( async postData =>
{ let postStr = new TextDecoder().decode(postData);
console.log(`URL=${req.url} GET=${[...req.get.entries()]} POST=${postStr}`);
await req.respond({body: 'Hello'});
}
);
}
Usage
First need to proxy HTTP requests from a FastCGI-capable web server, like Apache or Nginx. Iāll show a simplest setup example for Apache, to be used as starting point.
<VirtualHost *:80>
ServerName deno-server.loc
DocumentRoot /var/www/deno-server-root
DirectoryIndex index.html
SetInputFilter DEFLATE
Protocols h2 h2c http/1.1
SetHandler "proxy:fcgi://localhost:8080"
</VirtualHost>
In this configuration i assume that the following Apache modules are enabled: proxy_fcgi, http2, deflate.
DocumentRoot directory must exist, and only requests to existing files from that path will be forwarded to Deno. If thereās file /var/www/deno-server-root/index.html
it can be accessed as http://deno-server.loc/index.html
or http://deno-server.loc/
.
To use fake domain name deno-server.loc
from localhost, add it to /etc/hosts
:
127.0.0.1 deno-server.loc
Run Deno application like this:
deno run --unstable --allow-net main.ts
Using unix-domain socket
import {Server} from 'https://deno.land/x/fcgi/mod.ts';
const listener = Deno.listen({transport: 'unix', path: '/run/deno-server/main.sock'});
const server = new Server(listener);
console.log(`Started on ${(listener.addr as Deno.UnixAddr).path}`);
for await (let req of server)
{ Deno.readAll(req.body).then
( async postData =>
{ let postStr = new TextDecoder().decode(postData);
console.log(`URL=${req.url} GET=${[...req.get.entries()]} POST=${postStr}`);
await req.respond({body: 'Hello'});
}
);
}
<VirtualHost *:80>
ServerName deno-server.loc
DocumentRoot /var/www/deno-server-root
DirectoryIndex index.html
SetInputFilter DEFLATE
Protocols h2 h2c http/1.1
SetHandler "proxy:unix:/run/deno-server/main.sock|fcgi://localhost"
</VirtualHost>
We want to use a socket file, and both Apache and our application must have access permission to it.
sudo mkdir /run/deno-server
sudo chown "$USER:" /run/deno-server
In this directory our application will create socket file main.sock
, so it must have write permission to the directory. It will create the file with aplicationās owner, so Apache will have no write access to it. Therefore itās necessary to change file owner after creating it (after our application executed Deno.listen()
). We could do so in the application itself, but for this to be possible we need to give to our application root privileges. Instead i suggest to use a starter script that will first start our application, and then will change the ownership of the created file.
APACHE_USER=www-data
deno run --unstable --allow-read --allow-write main.ts & sleep 3 && sudo chown "$APACHE_USER:$USER" /run/deno-server/main.sock; fg
Using the API
First thing to do is to create Server
object.
const listener = Deno.listen({port: 8080});
const options =
{ maxConns: 128,
structuredParams: true,
};
const server = new Server(listener, options);
listener
can be any Deno.Listener
object.
There are the following options that modify server behavior:
maxConns
- Maximum number of simultaneous connections to accept (default is 128).structuredParams
- Parse GET and POST parameters like PHP does. Query strings likeitems[]=a&items[]=b
oritems[a][b]=c
will be parsed toMap
objects, soreq.get.get('item')
will be of typeMap<string, ...>
.
ServerRequest
object
The Asynchronous iteration over Server
object yields incoming HTTP requests. Each request is a ServerRequest
object, that contains the request information sent from FastCGI server.
url
(string) - Like/index.html
.method
(string) - LikeGET
.proto
(string) - LikeHTTP/1.1
orHTTP/2
.protoMinor
(number)protoMajor
(number)params
(Headers) - Environment parameters that usually includeDOCUMENT_ROOT
, and can includeCONTEXT_DOCUMENT_ROOT
if using apache MultiViews.headers
(Headers) - Request HTTP headers.get
(Map) - Lazy-parsed query string.post
(Map) - Lazy-parsed POST body, that can contain uploaded files.cookies
(Map) - Lazy-parsed request cookies. Adding and deleting them adds corresponding response HTTP headers.body
(Deno.Reader) - Allows to read raw POST body before accessingpost
. The body can be also read from theServerRequest
object itself, as it implementsDeno.Reader
(req.body == req
).responseStatus
(number) - Set this to HTTP status code before callingrespond()
. However status given torespond()
(if given) overrides this one.responseHeaders
(Headers) - Set response HTTP headers here, before callingrespond()
, or pass them torespond()
(the latter have precedence).headersSent
(boolean) - Indicates that response headers are already sent. They will be sent byrespond()
or earlier if you write data to theServerRequest
object (it implementsDeno.Writer
).
Itās your responsibility to call respond()
when youāre finished with this request. respond()
sends all the pending data to the FastCGI server, and terminates the request, freeing all the resources, and deleting all the uploaded files (you need to move them to different location to keep them). The object will be not usable after calling respond()
.
Response headers and data can be set before calling respond()
, or they can be given to the response()
.
import {Server} from 'https://deno.land/x/fcgi/mod.ts';
const listener = Deno.listen({port: 8080});
const server = new Server(listener);
console.log(`Started on ${(listener.addr as Deno.NetAddr).port}`);
for await (let req of server)
{ Deno.readAll(req.body).then
( async postData =>
{ let postStr = new TextDecoder().decode(postData);
console.log(`URL=${req.url} GET=${[...req.get.entries()]} POST=${postStr}`);
req.respond({body: 'Hello', headers: new Headers([['Content-Type', 'text/html']])});
}
);
}
Or:
import {Server} from 'https://deno.land/x/fcgi/mod.ts';
const listener = Deno.listen({port: 8080});
const server = new Server(listener);
console.log(`Started on ${(listener.addr as Deno.NetAddr).port}`);
for await (let req of server)
{ Deno.readAll(req.body).then
( async postData =>
{ let postStr = new TextDecoder().decode(postData);
console.log(`URL=${req.url} GET=${[...req.get.entries()]} POST=${postStr}`);
req.responseHeaders.set('Content-Type', 'text/html');
await Deno.writeAll(req, new TextEncoder().encode('Hello'));
req.respond();
}
);
}