bluegate v1.1.18
Minimalistic Web Application Framework as Promised
BlueGate is a simple framework to build web applications in NodeJS. It is build on top of the powerful Bluebird library to let you use the ease of Promises to its fullest extent.
Instead of a simple stack with middleware, BlueGate has a sophisticated request flow that fits both REST API's and complex multi-tier applications.
BlueGate can be extended with the following modules:
- class to use ES6 classes to write routes.
- csrf for CSRF protection.
- handlebars for template rendering.
- session for session support.
- static to serve static assets.
Installation
Install using npm install bluegate
Quick example
var BlueGate = require('bluegate');
var app = new BlueGate();
app.listen(8080); // Port or unix socket.
app.postvalidation('GET /user/<id:int>', (id) => {
if (id === 123) {
throw Error('This is not a valid user id');
}
});
app.process('GET /user/<id:int>', (id) => {
// Return page content or promise for content.
return {id: id};
});
app.process('GET /user/<id:int>/picture', (request, id) => {
request.mime = 'image/jpeg';
return new Buffer('...');
);See the Todo example for a more complete example.
Request flow
Each request follows the steps below:
initializecan be used to register request specific handlersauthenticationshould be used to identify the clientauthorisationshould be used for permission checksprevalidationdoes validation before preprocessingpreprocessdoes the preprocessing (e.g. parsing body)postvalidationdoes validation after preprocessingprocesswill generate the outputpostprocesscan alter the output (e.g. for templating)- send response to client
afteris for additional work (e.g. statistics)
All remaining steps are skipped when an error occur before sending the response, In that case, we will arrive at the error-flow:
erroris used to generate the error response for the client- send response to client
aftererroris for additional work (e.g. statistics)
The name of each step is used as function name to register handlers for it.
This can be done on the BlueGate instance (as shown in the example above) or
on the this scope within a handler. The first argument is in the form
METHOD /path and determines which requests it can handle. This argument
can be omitted to enable the handler for all requests.
Writing handlers
Input
Handler functions can accept input via both function arguments and the local
scope (this). The local scope is only accessible when using ES5 functions.
For ES6 functions you may add the request parameter. The following
examples are identical:
// ES5
app.process('GET /list', function() {
var page = this.getQuery('page', 'int', 1);
});
// ES6
app.process('GET /list', (request) {
var page = request.getQuery('page', 'int', 1);
});Input from path parameters is mapped to function arguments. Function arguments
that do not have a parameter will get undefined as value.
app.process('GET /user/<name:string>', function(type, name) {
typeof type === 'undefined';
typeof name === 'string';
});Other input is available in the local scope (this.*) or request parameter
(request.*).
The table below lists all available variables and functions.
| Name | Type | Example | Read only? |
|---|---|---|---|
| host | string | www.example.com | yes |
| path | string | /user/john | yes |
| method | string | GET | yes |
| body | * | yes | |
| mime | string | text/html | no |
| status | int | 200 | no |
| query | object | 'page' | yes |
| headers | object | {'User-Agent': '...'} | yes |
| cookies | object | 'sessionId' | yes |
| ip | string | 127.0.0.1 | yes |
| date | date | yes | |
| secure | bool | false | yes |
| parameters | object | {...} | yes |
| multipartBoundary | string | yes |
The body type is dependent on the Content-Type header sent by the client.
| Content-Type | Type |
|---|---|
| application/json | object |
| application/form-data | object |
| text/* | buffer |
| / | Readable stream |
The multipartBoundary property is only set when the Content-Type header was set to multipart/*.
Parsing multipart data is not done by this framework, since the application may stream the input directly to
files or external storage, which is beyond the scope of BlueGate. Popular modules for parsing are
busboy,
multiparty and
dicer.
See the upload example
for more information on how to handle multipart/form-data requests and file uploads.
Path parameters
Path parameters are passed as function arguments, as shown in the last code example. The following types are available:
| Type | Description |
|---|---|
| alpha | Alpha characters (a-z A-Z) |
| alphanum | Alphanumeric characters (a-z A-Z 0-9) |
| bool | Boolean (matches "1", "0", "true" and "false") |
| float | Floats (e.g. -34.3, .3 or 63) |
| int | Positive integer (1..n). Does not match 0. |
| path | Matches all characters including forward slashes ("/") |
| signed | Signed integer (e.g. -123, 0 or 123) |
| string | Matches all characters except forward slashes ("/") |
| unsigned | Unsigned integer (0..n) |
| uuid | Matches UUID versions 1 to 5 |
Accepting path parameters via function arguments should be preferred above using
this.parameters. The last object was added to allow abstract functions to
handle multiple callbacks.
It is possible to set new parameters or override existing using setParameter
inside a handler. This is illustrated in the following example:
app.initialize('GET /path/<foo:string>', function(foo) {
this.setParameter('foo', 'bar');
});
app.process('GET /path/<foo:string>', function(foo) {
// foo == "bar", independent from the actual path argument.
});The setParameter function requires two arguments; name and value. The value
is not casted to the type defined in the path.
Query arguments
Values from the path query ("/news?page=34") can be retreived using
this.getQuery. The first argument is the name and the second is the value
type. A default value can be provided as thirth argument (defaults to null).
The default value is returned when the variable is missing or its value does not
match the requested type.
app.process('GET /news', function() {
var page = this.getQuery('page', 'int', 1);
});An array of all available query variables is available in this.query. This
contains a list of names only, to enforce validation when getting the value.
Output
Output is provided as return value. This can be provided as strings, buffer, readable stream or any JSON serializable value. The MIME-type defaults to "text/html" when using strings, "application/octet-stream" for buffers and stream and "application/json" for other types. JSON output is automatically encoded.
Use this.mime to set a different MIME-type.
app.process('GET /image', function() {
this.mime = 'image/jpeg';
return fs.createReadStream('image.jpg');
});Cookies
Read cookies using getCookie. This is similar to getQuery. The names of
all provided cookies can be found in this.cookies.
app.authentication(function() {
var sessionId = this.getCookie('sessionId', 'alphanum');
});Use the setCookie function to set a cookie. Arguments are:
- Name May not contain whitespace, comma, semicolon, equals sign or non-printable characters.
- Value May not contain whitespace, comma, semicolon or non-printable characters.
- Expires
Given as JavaScript
Date-object. Optional, defaults to not expiration date (session cookie). - Path E.g. "/forum". Optional.
- Domain E.g. ".example.com". Optional.
- HttpOnly
Set HttpOnly flag. Given as boolean. Optional, defaults to
true. - Secure
Set Secure flag. Given as boolean. Optional, defaults to
truewhen visited over SSL.
Example:
app.preprocess('POST /login', function() {
var sessionId = '...';
var date = new Date();
date.setDate(date.getDate() + 14);
// Set a session cookie.
this.setCookie('sessionId', sessionId);
// Expires after 2 weeks.
this.setCookie('sessionId', sessionId, date);
// Only on /forum.
this.setCookie('sessionId', sessionId, null, '/forum');
// Set for example.com and all subdomains.
this.setCookie('sessionId', sessionId, null, null, '.example.com');
});HTTP headers
HTTP headers can be set using the setHeader function.
app.preprocess('GET /path', function() {
this.setHeader('X-Generator', 'CMS');
});An optional thirth argument can be provided to append headers instead of replacing them.
app.preprocess('GET /path', function() {
this.setHeader('Cache-Control', 'no-cache', true);
this.setHeader('Cache-Control', 'no-store', true);
});HTTP status code
The HTTP status code is 200 by default. This code is changed automatically when an error occurs. The HTTP status for errors is dependent on the phase in which the error occurred.
| Phase | Code | Message |
|---|---|---|
initialize | 500 | Internal server error |
authentication | 401 | Authentication required |
authorisation | 403 | Permission denied |
prevalidation | 400 | Bad request |
preprocess | 500 | Internal server error |
postvalidation | 400 | Bad request |
process | 500 | Internal server error |
postprocess | 500 | Internal server error |
Additionally, a 404 response ("Not found") is provided when no process
handler was found. All phases before process are still executed, because
it is possible that those will register a process handler.
It is possible to override the status code from within a handler using
this.status.
app.process('POST /object', function() {
this.status = 201;
return {messages: ['Created']);
});Logging
BlueGate will log requests to console by default. You can change this behaviour in the constructor options.
var server = new BlueGate({
log: false, // or:
log: function(message) { ... }
});The format of the log messages is:
2015-05-26T21:15:05 127.0.0.1 "GET /host-test" 200 16 143
\- Start of request | | | | |
\ Client IP | | | |
\ Request | | |
Response code / | |
Response size (bytes) / |
Response time (ms) /No settings are available to change this format. You can disable logging and
register an after / aftererror handler for custom logging.
Security
Running behind a proxy server
When you are running behind a proxy server, you should set the
trustedProxies option. This contains a list of IP-addresses used by your
proxy servers. The default value for this list is 127.0.0.1. All proxies
must add the IP-address to the X-Forwarded-For header. The this.ip
variable used in handlers will contain the client IP, even when the client tries
to spoof the IP-address by sending a false X-Forwarded-For header.
var app = new BlueGate({
trustedProxies: ['192.168.1.10', '192.168.1.11']
});Running behind SSL
The this.secure variable in handles indicates if the client is using HTTPS
for this request. This flag relies on the X-Forwarded-Proto header, which
should be set by reverse proxies (which may require extra configuration).
This value can be spoofed by the client.
Cookies are set with the Secure flag by default when running behind SSL. It's
possible to remove it by setting the 7th argument of setCookie to false.
Clickjacking
All HTML responses will include the HTTP-header X-Frame-Options: deny to
prevent clickjacking attacks.
It is set to the most strict setting by default. You can change its setting in
the constructor when you use iframes. Strings are used as header value. Use
false to completely remove this header.
var app = new BlueGate({
clickjacking: 'sameorigin'
});MIME-sniffing
All responses will include the header X-Content-Type-Options: nosniff by
default. This helps to prevent
MIME-sniffing attacks. You
should leave this header in and make sure that the MIME-type is set correctly.
However, you can disable this feature in the constructor.
var app = new BlueGate({
noMimeSniffing: false
});Maximum input size
A maxInputSize option is available. This limits the number of bytes
accepted in the request body. The default value is 1048576 (1MB). You should
lower this value if large requests aren't used to avoid DoS attacks.
var app = new BlueGate({
maxInputSize: 1024 * 64 // 64 KB
});The maximum input size does not apply to posted streams (e.g. multipart data). A process function can stop the upload by sending a response, ignoring the stream.
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago