Comparing version 0.2.2 to 1.0.0-alpha
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const http = require("http"); | ||
const server_1 = require("./server"); | ||
exports.Server = server_1.default; | ||
const https = require("https"); | ||
const request_1 = require("./request"); | ||
exports.Request = request_1.default; | ||
const response_1 = require("./response"); | ||
exports.Response = response_1.default; | ||
function createServer(options = {}, fn) { | ||
if (typeof options === 'function') { | ||
fn = options; | ||
options = {}; | ||
} | ||
var server = new server_1.default(_createServer(options.tls), options); | ||
fn && server.on('request', fn); | ||
return server; | ||
} | ||
exports.createServer = createServer; | ||
/** | ||
* Create native server | ||
* | ||
* @param tls secure server options | ||
* @private | ||
*/ | ||
function _createServer(tls) { | ||
return tls ? https.createServer(tls) : http.createServer(); | ||
} | ||
var server_1 = require("./server"); | ||
exports.Server = server_1.Server; | ||
var response_1 = require("./response"); | ||
exports.Response = response_1.Response; | ||
var factory_1 = require("./factory"); | ||
exports.createServer = factory_1.createServer; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const assert = require("assert"); | ||
const mime = require("mime-types"); | ||
const is_1 = require("@sindresorhus/is"); | ||
const statuses = require("statuses"); | ||
const cookie = require("./support/cookie"); | ||
const ct = require("./support/content-type"); | ||
const HTML_TAG_RE = /^\s*</; | ||
const SEPARATOR_RE = /\s*,\s*/; | ||
const mime_types_1 = require("mime-types"); | ||
class Response { | ||
/** | ||
* Construct a new response instance | ||
* Initialize a new response builder | ||
* | ||
* @param stream | ||
* @param options | ||
* @param content | ||
*/ | ||
constructor(stream, options = {}) { | ||
this.stream = stream; | ||
constructor(content) { | ||
/** | ||
* Response internal body | ||
* The response status code | ||
*/ | ||
this._body = null; | ||
// | ||
this.statusCode = 204; | ||
/** | ||
* The response status message | ||
*/ | ||
this.statusMessage = 'No Content'; | ||
/** | ||
* The response body | ||
*/ | ||
this.body = null; | ||
/** | ||
* The response headers | ||
*/ | ||
this.headers = {}; | ||
if (content != null) { | ||
this.body = content; | ||
this.statusCode = 200; | ||
this.statusMessage = 'OK'; | ||
} | ||
} | ||
/** | ||
* Response headers | ||
* Set the response status code | ||
* | ||
* Shortcut to `this.stream.getHeaders()` | ||
* @param code The status code | ||
* @param message The status message | ||
*/ | ||
get headers() { | ||
return this.stream.getHeaders(); | ||
status(code, message) { | ||
assert('number' === typeof code, 'The status code must be a number'); | ||
assert(code >= 100 && code <= 999, 'Invalid status code'); | ||
// no content status code | ||
if (this.body && statuses.empty[code]) | ||
this.body = null; | ||
this.statusMessage = message || statuses[code] || ''; | ||
this.statusCode = code; | ||
return this; | ||
} | ||
/** | ||
* Checks if the request is writable. | ||
*/ | ||
get writable() { | ||
// can't write any more after response finished | ||
if (this.stream.finished) | ||
return false; | ||
// pending writable outgoing response | ||
if (!this.stream.connection) | ||
return true; | ||
return this.stream.connection.writable; | ||
} | ||
/** | ||
* Check if a header has been written to the socket | ||
*/ | ||
get headersSent() { | ||
return this.stream.headersSent; | ||
} | ||
/** | ||
* Set the response status code | ||
*/ | ||
set status(code) { | ||
if (!this.headersSent) { | ||
assert('number' === typeof code, 'The status code must be a number'); | ||
assert(code >= 100 && code <= 999, 'Invalid status code'); | ||
this.stream.statusCode = code; | ||
this.message = statuses[code] || ''; | ||
if (this.body && statuses.empty[code]) | ||
this.body = null; | ||
} | ||
} | ||
/** | ||
* Get the response status code | ||
*/ | ||
get status() { | ||
return this.stream.statusCode; | ||
} | ||
/** | ||
* Set the response status message | ||
*/ | ||
set message(value) { | ||
if (!this.headersSent) | ||
this.stream.statusMessage = value; | ||
} | ||
/** | ||
* Get the response status message | ||
*/ | ||
get message() { | ||
return this.stream.statusMessage || statuses[this.status] || ''; | ||
} | ||
/** | ||
* Set `Content-Type` response header. | ||
@@ -90,102 +59,30 @@ * | ||
* | ||
* response.type = 'application/json' | ||
* response.type = '.html' | ||
* response.type = 'html' | ||
* response.type = 'json' | ||
* response.type = 'png' | ||
* response.type('application/json') | ||
* response.type('.html') | ||
* response.type('html') | ||
* response.type('json') | ||
* response.type('png') | ||
*/ | ||
set type(value) { | ||
var ct = mime.contentType(value); | ||
if (ct) | ||
this.set('Content-Type', ct); | ||
type(value) { | ||
let type = mime_types_1.contentType(value); | ||
if (type) { | ||
this.set('Content-Type', type); | ||
} | ||
return this; | ||
} | ||
/** | ||
* Return the response mime type void of the "charset" parameter, or undefined | ||
*/ | ||
get type() { | ||
return ct.extract(this.get('Content-Type')); | ||
} | ||
/** | ||
* Set `Content-Length` reponse header | ||
*/ | ||
set length(value) { | ||
this.set('Content-Length', value); | ||
length(value) { | ||
return this.set('Content-Length', value); | ||
} | ||
/** | ||
* Get the response content length or NaN otherwise. | ||
*/ | ||
get length() { | ||
return this.get('Content-Length') || NaN; | ||
} | ||
/** | ||
* Get the response body | ||
*/ | ||
get body() { | ||
return this._body; | ||
} | ||
/** | ||
* Set the response body | ||
*/ | ||
set body(value) { | ||
// skip | ||
if (!this.writable) | ||
return; | ||
this._body = value; | ||
// empty body | ||
if (value == null) { | ||
if (!statuses.empty[this.status]) | ||
this.status = 204; | ||
this.remove('Transfer-Encoding'); | ||
this.remove('Content-Length'); | ||
this.remove('Content-type'); | ||
return; | ||
} | ||
// status code | ||
if (!this.status) | ||
this.status = 200; | ||
// string | ||
if (typeof value === 'string') { | ||
if (!this.has('Content-Type')) { | ||
let type = HTML_TAG_RE.test(value) ? 'html' : 'plain'; | ||
this.set('Content-Type', `text/${type}; charset=utf-8`); | ||
} | ||
this.length = Buffer.byteLength(value); | ||
return; | ||
} | ||
// buffer | ||
if (Buffer.isBuffer(value)) { | ||
if (!this.has('Content-Type')) { | ||
this.set('Content-Type', 'application/octet-stream'); | ||
} | ||
this.length = value.length; | ||
return; | ||
} | ||
// stream | ||
if (_isStream(value)) { | ||
if (!this.has('Content-Type')) { | ||
this.set('Content-Type', 'application/octet-stream'); | ||
} | ||
return; | ||
} | ||
// json | ||
this._body = value = JSON.stringify(value); | ||
this.length = Buffer.byteLength(value); | ||
if (!this.has('Content-Type')) { | ||
this.set('Content-Type', 'application/json; charset=utf-8'); | ||
} | ||
} | ||
/** | ||
* Set the `Last-Modified` response header | ||
*/ | ||
set lastModified(value) { | ||
this.set('Last-Modified', value.toUTCString()); | ||
lastModified(value) { | ||
if (is_1.default.string(value)) | ||
value = new Date(value); | ||
return this.set('Last-Modified', value.toUTCString()); | ||
} | ||
/** | ||
* Get the `Last-Modified` date, or undefined if not present | ||
*/ | ||
get lastModified() { | ||
var date = this.get('Last-Modified'); | ||
return date ? new Date(date) : undefined; | ||
} | ||
/** | ||
* Set the `ETag` of the response. | ||
@@ -197,56 +94,37 @@ * | ||
* | ||
* response.etag = 'md5hashsum' | ||
* response.etag = '"md5hashsum"' | ||
* response.etag = 'W/"123456789"' | ||
* response.etag('md5hashsum') | ||
* response.etag('"md5hashsum"') | ||
* response.etag('W/"123456789"') | ||
*/ | ||
set etag(value) { | ||
etag(value) { | ||
if (!/^(W\/)?"/.test(value)) | ||
value = `"${value}"`; | ||
this.set('ETag', value); | ||
return this.set('ETag', value); | ||
} | ||
/** | ||
* Get the `ETag` of the response. | ||
*/ | ||
get etag() { | ||
return this.get('ETag'); | ||
} | ||
/** | ||
* Set the `Location` response header | ||
*/ | ||
set location(url) { | ||
this.set('Location', encodeURI(url)); | ||
location(url) { | ||
return this.set('Location', encodeURI(url)); | ||
} | ||
/** | ||
* Get the `Location` response header | ||
*/ | ||
get location() { | ||
return this.get('Location'); | ||
} | ||
/** | ||
* Append `field` to the `Vary` header | ||
* | ||
* @param field | ||
*/ | ||
vary(field) { | ||
if (this.headersSent) | ||
return this; | ||
vary(...headers) { | ||
// match all | ||
if (field.includes('*')) { | ||
this.stream.setHeader('Vary', '*'); | ||
return this; | ||
if (headers.includes('*')) { | ||
return this.set('Vary', '*'); | ||
} | ||
// first time | ||
if (!this.stream.hasHeader('Vary')) { | ||
this.stream.setHeader('Vary', String(field)); | ||
return this; | ||
if (!this.has('Vary')) { | ||
return this.set('Vary', String(headers)); | ||
} | ||
var value = this.get('Vary') || ''; | ||
let value = this.get('Vary') || ''; | ||
// existing | ||
if (value !== '*') { | ||
let array = Array.isArray(field) ? field : field.split(SEPARATOR_RE); | ||
for (let item of array) { | ||
if (!value.includes(item)) | ||
value += `, ${item}`; | ||
for (let name of headers) { | ||
if (!value.includes(name)) | ||
value += `, ${name}`; | ||
} | ||
this.stream.setHeader('Vary', value); | ||
this.set('Vary', value); | ||
} | ||
@@ -256,13 +134,6 @@ return this; | ||
/** | ||
* Check if the incoming request contains the "Content-Type" | ||
* header field, and it contains any of the give mime `type`s. | ||
* | ||
* It returns the first matching type or false otherwise. | ||
* | ||
* Pretty much the same as `Request.is()` | ||
* | ||
* @param types | ||
* Append to the `Set-Cookie` header | ||
*/ | ||
is(...types) { | ||
return ct.is(this.type, types); | ||
setCookie(cookie) { | ||
return this.append('Set-Cookie', cookie); | ||
} | ||
@@ -275,45 +146,15 @@ /** | ||
get(header) { | ||
return this.stream.getHeader(header); | ||
return this.headers[header.toLowerCase()]; | ||
} | ||
set(header, value) { | ||
if (!this.headersSent) { | ||
if (typeof header === 'string') { | ||
this.stream.setHeader(header, value); | ||
} | ||
else if (_isObject(header)) { | ||
for (let name in header) { | ||
this.stream.setHeader(name, header[name]); | ||
} | ||
} | ||
if (is_1.default.object(header)) { | ||
for (let name in header) | ||
this.set(name, header[name]); | ||
} | ||
else { | ||
this.headers[header.toLowerCase()] = value; | ||
} | ||
return this; | ||
} | ||
/** | ||
* Set cookie `name` to `value`, with the given `options`. | ||
* | ||
* Examples: | ||
* | ||
* // "Remember Me" for 15 minutes | ||
* res.cookie('remember', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }) | ||
* | ||
* // same as above | ||
* res.cookie('remember', '1', { maxAge: 900000, httpOnly: true }) | ||
* | ||
* @param name | ||
* @param value | ||
* @param options | ||
*/ | ||
setCookie(name, value, options) { | ||
return this.append('Set-Cookie', cookie.serialize(name, value, options)); | ||
} | ||
/** | ||
* Unset the cookie `name`. | ||
* | ||
* @param name | ||
* @param options | ||
*/ | ||
clearCookie(name, options) { | ||
return this.setCookie(name, '', Object.assign({ expires: new Date(0) }, options)); | ||
} | ||
/** | ||
* Append additional header name | ||
@@ -331,12 +172,10 @@ * | ||
append(header, value) { | ||
if (!this.headersSent) { | ||
if (this.stream.hasHeader(header)) { | ||
let oldValue = this.stream.getHeader(header); | ||
if (!Array.isArray(oldValue)) { | ||
oldValue = [String(oldValue)]; | ||
} | ||
value = oldValue.concat(value); | ||
if (this.has(header)) { | ||
let oldValue = this.get(header); | ||
if (!Array.isArray(oldValue)) { | ||
oldValue = [String(oldValue)]; | ||
} | ||
this.stream.setHeader(header, value); | ||
value = oldValue.concat(value); | ||
} | ||
this.set(header, value); | ||
return this; | ||
@@ -350,3 +189,3 @@ } | ||
has(header) { | ||
return this.stream.hasHeader(header); | ||
return this.get(header) !== undefined; | ||
} | ||
@@ -359,5 +198,3 @@ /** | ||
remove(header) { | ||
if (!this.headersSent) { | ||
this.stream.removeHeader(header); | ||
} | ||
delete this.headers[header.toLowerCase()]; | ||
return this; | ||
@@ -370,67 +207,96 @@ } | ||
*/ | ||
reset(headers) { | ||
if (!this.headersSent) { | ||
for (let header of this.stream.getHeaderNames()) { | ||
this.stream.removeHeader(header); | ||
} | ||
if (headers) | ||
this.set(headers); | ||
} | ||
reset(headers = {}) { | ||
this.headers = headers; | ||
return this; | ||
} | ||
/** | ||
* Send and end the response stream | ||
* Send the response and terminate the stream | ||
* | ||
* @param content | ||
* @param res The response stream | ||
* @public | ||
*/ | ||
send(content) { | ||
// already sent | ||
if (!this.writable) | ||
send(res) { | ||
let { statusCode, statusMessage, body: content, headers } = this; | ||
// not writable | ||
if (!_isWritable(res)) | ||
return; | ||
// body | ||
if (content !== undefined) | ||
this.body = content; | ||
var { body, status, stream: res } = this; | ||
// no content | ||
if (!status) | ||
status = 204; | ||
// status | ||
res.statusCode = statusCode; | ||
res.statusMessage = statusMessage || statuses[statusCode] || ''; | ||
// headers | ||
for (let field in headers) { | ||
res.setHeader(field, headers[field]); | ||
} | ||
// ignore body | ||
if (statuses.empty[status]) { | ||
this.body = null; | ||
if (statuses.empty[statusCode] || content == null) { | ||
res.removeHeader('Transfer-Encoding'); | ||
res.removeHeader('Content-Length'); | ||
res.removeHeader('Content-type'); | ||
res.end(); | ||
return; | ||
} | ||
// content type | ||
if (!res.hasHeader('Content-Type')) { | ||
res.setHeader('Content-Type', _guessType(content)); | ||
} | ||
// stream | ||
if (_isStream(body)) { | ||
body.pipe(res); | ||
if (_isStream(content)) { | ||
content.pipe(res); | ||
return; | ||
} | ||
// status body | ||
if (body == null) { | ||
body = this.message || String(status); | ||
this.set('Content-Type', 'text/plain; charset=utf-8'); | ||
this.length = Buffer.byteLength(body); | ||
// json | ||
if (!is_1.default.string(content) && !is_1.default.buffer(content)) { | ||
content = JSON.stringify(content); | ||
} | ||
// content length | ||
if (!res.hasHeader('Content-Length')) { | ||
res.setHeader('Content-Length', Buffer.byteLength(content)); | ||
} | ||
// finish | ||
res.end(body); | ||
this._body = null; | ||
res.end(content); | ||
} | ||
} | ||
exports.default = Response; | ||
exports.Response = Response; | ||
/** | ||
* Check if the argument is a stream instance | ||
* Check if the outgoing response is yet writable | ||
* | ||
* @param stream | ||
* @param res The server response stream | ||
* @private | ||
*/ | ||
function _isStream(stream) { | ||
return _isObject(stream) && typeof stream.pipe === 'function'; | ||
function _isWritable(res) { | ||
// can't write any more after response finished | ||
if (res.finished) | ||
return false; | ||
// pending writable outgoing response | ||
if (!res.connection) | ||
return true; | ||
return res.connection.writable; | ||
} | ||
const HTML_TAG_RE = /^\s*</; | ||
/** | ||
* Check if the argument is an object | ||
* Guess the content type, default to `application/json` | ||
* | ||
* @param content The response body | ||
* @private | ||
*/ | ||
function _guessType(content) { | ||
// string | ||
if (is_1.default.string(content)) { | ||
return `text/${HTML_TAG_RE.test(content) ? 'html' : 'plain'}; charset=utf-8`; | ||
} | ||
// buffer or stream | ||
if (is_1.default.buffer(content) || _isStream(content)) { | ||
return 'application/octet-stream'; | ||
} | ||
// json | ||
return 'application/json; charset=utf-8'; | ||
} | ||
/** | ||
* Check if the argument is a stream instance | ||
* | ||
* @param obj | ||
* @private | ||
*/ | ||
function _isObject(obj) { | ||
return obj && typeof obj === 'object'; | ||
function _isStream(obj) { | ||
return obj && typeof obj.pipe === 'function'; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const request_1 = require("./request"); | ||
const is_1 = require("@sindresorhus/is"); | ||
const timers_1 = require("timers"); | ||
const response_1 = require("./response"); | ||
const timers_1 = require("timers"); | ||
class default_1 { | ||
class Server { | ||
/** | ||
* Initialize a Server instance | ||
* Initialize a `Server` instance | ||
* | ||
* @param server HTTP(S) server instance | ||
* @param options | ||
* @param native The native HTTP(S) server | ||
*/ | ||
constructor(server, options = {}) { | ||
this._options = options; | ||
this.native = server; | ||
constructor(native) { | ||
this.native = native; | ||
} | ||
on(event, handler) { | ||
if (event === 'request') | ||
handler = this._wrap(handler); | ||
this.native.on(event, _defer(handler)); | ||
return this; | ||
} | ||
/** | ||
* Add a `listener` for the given `event` | ||
* Start a server listening for requests | ||
* | ||
* @param event | ||
* @param fn listener | ||
* @public | ||
*/ | ||
on(event, fn) { | ||
this.native.on(event, this._wrap(event, fn)); | ||
return this; | ||
} | ||
start(options) { | ||
start(portOrOptions) { | ||
return new Promise((resolve, reject) => { | ||
@@ -32,3 +31,3 @@ // attach the error listener | ||
// listening | ||
this.native.listen(options, () => { | ||
this.native.listen(portOrOptions, () => { | ||
// remove the unecessary error listener | ||
@@ -43,30 +42,72 @@ this.native.removeListener('error', reject); | ||
* Stops the server from accepting new requests | ||
* | ||
* @public | ||
*/ | ||
stop() { | ||
return new Promise((resolve, reject) => { | ||
this.native.close((err) => { | ||
err ? reject(err) : resolve(); | ||
}); | ||
this.native.close((e) => e ? reject(e) : resolve()); | ||
}); | ||
} | ||
/** | ||
* Wrap the `request` event listener | ||
* Wrap the request event listener | ||
* | ||
* @param event | ||
* @param fn listener | ||
* @param handler The request event listener | ||
* @private | ||
*/ | ||
_wrap(event, fn) { | ||
var opts = this._options; | ||
switch (event) { | ||
case 'request': | ||
case 'checkContinue': | ||
case 'checkExpectation': | ||
return (req, res) => { | ||
timers_1.setImmediate(fn, new request_1.default(req, opts), new response_1.default(res, opts)); | ||
}; | ||
_wrap(handler) { | ||
return (req, res) => { | ||
this._getResponse(handler, req).then((response) => response.send(res)); | ||
}; | ||
} | ||
/** | ||
* Invoke the request handler and return the response | ||
* | ||
* @param fn The request handler | ||
* @param request The incoming message | ||
* @private | ||
*/ | ||
async _getResponse(fn, request) { | ||
try { | ||
let output = await fn(request); | ||
if (output instanceof response_1.Response) | ||
return output; | ||
return new response_1.Response(output); | ||
} | ||
return () => { timers_1.setImmediate(fn, ...arguments); }; | ||
catch (err) { | ||
// normalize | ||
if (!(err instanceof Error)) { | ||
err = new Error(`Non-error thrown: "${is_1.default(err)}"`); | ||
} | ||
// delegate | ||
this.native.emit('error', err); | ||
let status = err.status || err.statusCode; | ||
let body = err.expose ? err.message : 'Internal Server Error'; | ||
// support ENOENT | ||
if (err.code === 'ENOENT') | ||
status = 404; | ||
return new response_1.Response(body) | ||
.status(_isValid(status) ? status : 500) | ||
.type('text/plain; charset=utf-8') | ||
.set(err.headers || {}); | ||
} | ||
} | ||
} | ||
exports.default = default_1; | ||
exports.Server = Server; | ||
/** | ||
* Defer the function invocation to the next tick | ||
* | ||
* @param fn The event listener | ||
* @private | ||
*/ | ||
function _defer(fn) { | ||
return (...args) => timers_1.setImmediate(fn, ...args); | ||
} | ||
/** | ||
* Check if the status code is a valid number | ||
* | ||
* @param status | ||
* @private | ||
*/ | ||
function _isValid(status) { | ||
return typeof status === 'number' && status >= 100 && status <= 999; | ||
} |
{ | ||
"name": "aldo-http", | ||
"version": "0.2.2", | ||
"description": "Enhanced HTTP createServer module for Node.js", | ||
"version": "1.0.0-alpha", | ||
"description": "Enhanced HTTP `createServer` module for Node.js", | ||
"main": "lib/index.js", | ||
"engines": { | ||
"node": ">=8.9" | ||
"node": ">=8" | ||
}, | ||
"files": [ | ||
"lib", | ||
"index.d.ts" | ||
"lib" | ||
], | ||
@@ -23,2 +22,3 @@ "scripts": { | ||
"keywords": [ | ||
"aldo", | ||
"http", | ||
@@ -37,22 +37,17 @@ "server", | ||
"devDependencies": { | ||
"@types/cookie": "^0.3.1", | ||
"@types/mime-types": "^2.1.0", | ||
"@types/mocha": "^2.2.48", | ||
"@types/node": "^8.9.4", | ||
"@types/parseurl": "^1.3.1", | ||
"@types/sinon": "^4.3.0", | ||
"@types/statuses": "^1.3.0", | ||
"@types/type-is": "^1.6.2", | ||
"mocha": "^5.0.0", | ||
"sinon": "^4.4.2", | ||
"ts-node": "^4.1.0", | ||
"typescript": "^2.6.2" | ||
"typescript": "^2.8.3" | ||
}, | ||
"dependencies": { | ||
"cookie": "^0.3.1", | ||
"mime-types": "^2.1.17", | ||
"parseurl": "^1.3.2", | ||
"statuses": "^1.4.0", | ||
"type-is": "^1.6.15" | ||
"@sindresorhus/is": "^0.9.0", | ||
"mime-types": "^2.1.18", | ||
"statuses": "^1.5.0" | ||
} | ||
} |
174
README.md
`Aldo-http` is an enhanced HTTP `createServer` module for Node.js. | ||
It provides a decorated versions of `IncomingMessage` and `ServerResponse` objects which are mostly similar to `Koa`'s request and response objects. | ||
## Installation | ||
```sh | ||
npm add aldo-http | ||
``` | ||
```js | ||
const { createServer } = require('aldo-http') | ||
## Testing | ||
```sh | ||
npm test | ||
// server | ||
const server = createServer(() => "Hello world!") | ||
// start | ||
server.start(3000) | ||
``` | ||
## Usage | ||
## createServer | ||
`Aldo-http` exposes `createServer` function to create both HTTP and HTTPS servers. | ||
```ts | ||
declare function createServer(options?: ServerOptions, fn?: RequestListener): Server; | ||
declare function createServer (options: Options, fn: RequestHandler): Server; | ||
declare function createServer (fn: RequestHandler): Server; | ||
declare function createServer (options: Options): Server; | ||
declare function createServer (): Server; | ||
declare interface ServerOptions { | ||
proxy?: boolean; | ||
declare interface Options { | ||
tls?: https.ServerOptions; | ||
@@ -28,30 +27,4 @@ } | ||
### HTTP | ||
```js | ||
const { createServer } = require('aldo-http') | ||
### HTTPS server | ||
// make a new HTTP server | ||
const server = createServer((request, response) => { | ||
// Set the `Content-Type` and `Content-Length` headers, | ||
// write `Hello world!` response body | ||
// Set the status code to `200 OK` | ||
// and finally, terminate the response stream | ||
response.send('Hello world!') | ||
}) | ||
(async () => { | ||
try { | ||
// start listening on port 3000 | ||
await server.start(3000) | ||
console.log('The server is ready') | ||
} | ||
catch (error) { | ||
// log the error | ||
console.error(error) | ||
} | ||
}) | ||
``` | ||
### HTTPS | ||
```js | ||
@@ -71,100 +44,53 @@ const { readFileSync } = require('fs') | ||
// make a HTTPS server using the TLS options | ||
const server = createServer(options, (request, response) => { | ||
response.send('Hello world!') | ||
}) | ||
const server = createServer(options, () => 'Hello world!') | ||
(async () => { | ||
// start listening on port 443 | ||
await server.start({ | ||
port: 443, | ||
exclusive: true, | ||
host: 'example.com' | ||
}) | ||
server.start({ | ||
port: 443, | ||
exclusive: true, | ||
host: 'example.com' | ||
}) | ||
``` | ||
### Trust proxy | ||
To enable parsing `X-Forwarded-*` request headers, the `proxy` flag can be set to `true` | ||
### Request handler | ||
```js | ||
const { createServer } = require('aldo-http') | ||
The `request` event handler could be a common or an async function. | ||
// make a new HTTP server | ||
const server = createServer({ proxy: true }, (request, response) => { | ||
response.send('Hello world!') | ||
}) | ||
Each handler will receive the `http.IncomingMessage` object as a single input, and could return anything as a response. | ||
(async () => { | ||
// start listening on port 3000 | ||
await server.start(3000) | ||
}) | ||
``` | ||
### Request listener callback | ||
The `request` event listener can be a simple or an async function. | ||
It will reveice a `Request` and a `Response` instances instead of the default `IncompingMessage` and `ServerResponse` objects. | ||
```ts | ||
declare type RequestListener = (req: Request, res: Response) => void; | ||
declare type RequestHandler = (request: http.IncomingMessage) => any; | ||
``` | ||
## Request | ||
The request is an `IncomingMessage` decorator. | ||
```ts | ||
declare class Request { | ||
body: any; | ||
stream: http.IncomingMessage; | ||
The handler's output could be anything: | ||
- `streams` will be piped | ||
- `strings` and `buffers` will be sent as is | ||
- `nulls` and `undefined` values will be considered as empty responses (Status code 204) | ||
- anything else will be serialized as `JSON`. | ||
readonly url: string; // URL pathname | ||
readonly ips: string[]; | ||
readonly method: string; | ||
readonly length: number; | ||
readonly origin: string; | ||
readonly secure: boolean; | ||
readonly protocol: string; | ||
readonly querystring: string; | ||
readonly ip: string | undefined; | ||
readonly host: string | undefined; | ||
readonly type: string | undefined; | ||
readonly charset: string | undefined; | ||
readonly headers: http.IncomingHttpHeaders; | ||
readonly cookies: { [name: string]: string | undefined; }; | ||
readonly query: { [key: string]: string | string[] | undefined; }; | ||
To get more control over the response to send, [Response](#response) instances could be returned. | ||
constructor(req: http.IncomingMessage, options?: { proxy?: boolean; }); | ||
## Response | ||
has(header: string): boolean; | ||
is(...types: string[]): string | false; | ||
get(header: string): string | string[] | undefined; | ||
accept(...types: string[]): string | false | string[]; | ||
acceptCharset(...args: string[]): string | false | string[]; | ||
acceptEncoding(...args: string[]): string | false | string[]; | ||
acceptLanguage(...args: string[]): string | false | string[]; | ||
} | ||
``` | ||
The response instance let you construct a complex response (status code, body and headers) | ||
## Response | ||
The response is a `ServerResponse` decorator. | ||
```ts | ||
declare class Response { | ||
body: any; | ||
etag: string; | ||
type: string; | ||
length: number; | ||
status: number; | ||
message: string; | ||
location: string; | ||
lastModified: Date; | ||
stream: http.ServerResponse; | ||
statusCode: number; | ||
statusMessage: string; | ||
headers: http.OutgoingHttpHeaders; | ||
readonly writable: boolean; | ||
readonly headersSent: boolean; | ||
readonly headers: http.OutgoingHttpHeaders; | ||
constructor(body?: any); | ||
constructor(res: http.ServerResponse, options?: {}); | ||
send(content?: any): void; | ||
type(value: string): this; | ||
etag(value: string): this; | ||
length(value: number): this; | ||
location(url: string): this; | ||
has(header: string): boolean; | ||
remove(header: string): this; | ||
vary(field: string | string[]): this; | ||
is(...types: string[]): string | false; | ||
setCookie(value: string): this; | ||
vary(...headers: string[]): this; | ||
send(res: http.ServerResponse): void; | ||
lastModified(value: string | Date): this; | ||
status(code: number, message?: string): this; | ||
append(header: string, value: string | string[]): this; | ||
@@ -175,15 +101,3 @@ get(header: string): string | number | string[] | undefined; | ||
reset(headers?: { [field: string]: string | number | string[]; }): this; | ||
setCookie(name: string, value: string, options?: SerializeCookieOptions): this; | ||
clearCookie(name: string, options?: SerializeCookieOptions): this; | ||
} | ||
declare interface SerializeCookieOptions { | ||
path?: string; | ||
expires?: Date; | ||
domain?: string; | ||
maxAge?: number; | ||
secure?: boolean; | ||
httpOnly?: boolean; | ||
sameSite?: boolean | 'lax' | 'strict'; | ||
} | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
3
9
22667
11
682
101
+ Added@sindresorhus/is@^0.9.0
+ Added@sindresorhus/is@0.9.0(transitive)
+ Addedsymbol-observable@1.2.0(transitive)
- Removedcookie@^0.3.1
- Removedparseurl@^1.3.2
- Removedtype-is@^1.6.15
- Removedcookie@0.3.1(transitive)
- Removedmedia-typer@0.3.0(transitive)
- Removedparseurl@1.3.3(transitive)
- Removedtype-is@1.6.18(transitive)
Updatedmime-types@^2.1.18
Updatedstatuses@^1.5.0