Socket
Socket
Sign inDemoInstall

aldo-http

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aldo-http - npm Package Compare versions

Comparing version 0.2.2 to 1.0.0-alpha

lib/factory.d.ts

33

lib/index.js
"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"
}
}
`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';
}
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc