An opt-in, stream-based, tree-processing approach to Web Hosting with NodeJS.
- Supports HTTP
- Supports HTTPS
- Supports HTTP/2
By default, the framework does nothing. It parses no headers. It writes no headers. It never writes to or reads from any stream. All processing is handled by middleware. All middleware is built to provide maximum throughput and do as little as possible.
npm install webhoster
** Coming soon! **
For now, take a look at /test/index.js
Class that handles the logic for handling requests and responses
.defaultInstance- (HttpHandler) - Returns a instance ofHttpHandlerthat can be accessed staticly..middleware- (Middleware[]) - An array of middleware operations to iterate through when handling a request. It is recommended to create isolated branches (eg:/images/;/api/;/views/; etc.)..errorHandlers- (MiddlewareErrorHandler[]) - An array ofMiddlewareErrorHandlerthat will handle errors and respond appropriately (eg:res.status = 500).handleRequest- (function(MiddlewareFunctionParams):Promise<HttpResponse>) - handles logic for calling middleware and error handlers. Unlikely to be used directly..handleHttp1Request- (function(IncomingMessage, ServerResponse):Promise<HttpResponse>) - constructs a newHttpRequestandHttpResponsebased on the HTTP1 parameters and passes it tohandleRequest.handleHttp2Stream- (function(ServerHttp2Stream, IncomingHttpHeaders, HttpResponseOptions):Promise<HttpResponse>) - constructs a newHttpRequestandHttpResponsebased on the HTTP2 parameters and passes it tohandleRequest
const handler = HttpHandler.defaultInstance;
handler.middleware.push(
new ContentDecoderMiddleware(), // Automatically decodes content
new SendStringMiddleware(), // Auto convert strings to Buffer
new SendJsonMiddleware(), // Auto converts objects to JSON
new ContentEncoderMiddleware(), // Compress anything after
new HashMiddleware(), // Hash anything after
new ContentLengthMiddleware(), // Calculate length of anything after
new AutoHeadersMiddleware(), // Send headers automatically
new HeadMethodMiddleware(), // Discard body content
);
handler.middleware.push(
imagesMiddleware,
return404Middleware,
);
handler.errorHandlers.push(
errorLoggerMiddleware,
return500Middleware,
);
http1Server.addListener('request', handler.handleHttp1Request);
http2Server.addListener('stream', handler.handleHttp2Stream);Class that provides the bare-minimum to bridge different protocols for client requests
.read()-Promise<any>- Returns content as handled by request's content handlers. Returns.raw()if no compatible handler found..stream- (Readable) - Allows for direct interaction with tail-end of the request stream pipeline. With no middleware, it emitsBufferchunks..body- (ReadableStream) - Returns request's body asReadableStream(if supported)..bodyUsed- (boolean) - Returns whether request's body has been read from..arrayBuffer()- (Promise<ArrayBuffer>) - Returns a promise fulfilled with request's body asArrayBuffer..blob()- (Promise<Blob>) - Returns a promise fulfilled with request's body asBlob..formData()- (Promise<FormData>) - Returns a promise fulfilled with request's body asFormData. Not implemented by default..json()- (Promise<any>) - Returns a promise fulfilled with request's body parsed asJSON..text()- (Promise<string>) - Returns a promise fulfilled with request's body asstring..headers- (IncomingHttpHeaders) - The response headers exactly as presented to the NodeJS Server with no modifications..locals- (Object<string,any>) - Object that gets passed in every step of the middleware tree. Application-level variables should be presented here..addDownstream()- (function(stream:Readable):Readable) - adds a downstream to the current pipeline. Used by preprocessor middleware for the purpose of transforming data (eg: JSON-parsing) before reaching logic middleware.
async function onPostComment({ request }) {
const content = await request.read();
let comment;
try {
comment = new UserComment(content);
} catch {
return 400;
}
try {
await insertComment(comment);
} catch {
return 500;
}
return { status: 'OK' };
}Class that provides the bare-minimum to bridge different protocols for client responses
end(content:any) => HttpHandler.END- Content to be sent to the client based on the Content Handler configured. The function will call.stream.endand returnHttpHandler.END.headers- (OutgoingHttpHeaders) - The response headers exactly as presented to the NodeJS Server with no modifications.content- (any) - Content that will be sent to client.status- (number) - The response status codesetStatus(statusCode:number) => this- Sets.statuswhile returningthis(HttpResponse). Will throw anErrorif headers have already been sent.code(statusCode:number) => this- Sets.statuswhile returningthis(`HttpResponse).async send(content:any)- Similar toend, but asynchronous. Return when the client stream ends, confirming the data was sent, or throws an error on failure. Useful for ensuring client received the data.stream- (Writable) - Used for interacting with the stream. With no custom middleware, it accepts aBufferorstring.pipeFrom(source:any)- Creates a pipeline starting with source. May insert.pipeProcessorsinto pipeline. ReturnsHttpHandler.END;pipelineFrom(source:any) => Promise<HttpHandler.END>- Similar topipeFrom, but asynchronous. Return when the client stream ends, confirming the data was sent, or throws an error on failure. Useful for ensuring client received the data..finalizers- Array of body processors that may read and transform.body, or simply analyze on.body..canPushPath- (boolean) -trueon HTTP/2 streams that support push.pushPath- (function(path:string):Promise) - Push a new HTTP/2 stream simulating a request forpath
async function onGetIndexPage(transaction) {
if (transaction.canPushPath) {
transaction.pushPath('/script.js').catch(() => { /* Ignore push failure */ });
transaction.pushPath('/styles.css').catch(() => { /* Ignore push failure */ });;
}
transaction.response.header['content-type'] = 'text/html';
return await getIndexPage();
}Middleware logic flows in a tree structure, allowing for continue, break, or end.
A MiddlewareFunction is a function that accepts a MiddlewareFunctionParams object structured as { res: HttpRequest, res: HttpResponse }. The function can return a instruction with the step in the tree-based logic, status code, or content body to be handled. It maybe return any of these instructions with any of the values as a literal, a Promise, or PromiseLike:
HttpHandler.CONTINUE: Continues on the current branch to the next middleware, or moves to the next branch if there are no siblings left.alias: true|void|null|undefinedHttpHandler.BREAK: Breaks from the current middleware branch, and continues to the next branch.alias: falseHttpHandler.END: Terminates the entire middleware tree.alias: 0number: Sets status code and then ends stream.alias: res.code(statusCode).end()Set|Map: Add an inline middleware branch.Array: Explicitly passed toHttpResponse.end(). This is to support sending anArrayobject instead having it becoming an inline middleware branch.any: Any other value returned would automatically be passed toHttpResponse.end()which, in turn, uses it's own content handlers (eg:JSON;Readable), and finally terminates the middleware tree.
A MiddlewareFilter is a function that accepts a MiddlewareFunctionParams and returns a boolean or Promise<boolean> signaling whether to continue in the branch. true translates to HttpHandler.CONTINUE. false translates to HttpHandler.BREAK. There is no support for HttpHandler.END logic in a MiddlewareFilter by design.
A MiddlewareErrorHandler is an Object with a onError property. onError is like a MiddlewareFunction, but includes an err item in its parameter object. When the handler is in an error state, it will bubble upwards while searching for the next MiddlewareErrorHandler.
Middleware can be a MiddlewareFunction or MiddlewareFilter. It can also a compatible response value of either: HttpHandler.CONTINUE|true|null|void|undefined, HttpHandler.BREAK|false, HttpHandler.END|0. The response can be the value or a Promise.
To support branching, Middleware can also be a Iterable<Middleware> (eg: Set) or Map<any, Middleware>. The HttpHandler will iterate through each and flow based on the break, continue, or end instruction returned by each entry.
- AuthHeaders - Automatically sends response headers before writing or ending a response stream
- ContentLength - Sets
Content-Lengthbased on response stream content writes - Hash - Sets
ETag,Digest, andContent-MD5response headers automatically - ContentEncoder - Applies
Content-Encodingto response based onAccept-Encodingrequest header - SendJson - Adds response content processor that encodes objects and arrays to JSON string. Sets
application/json;charset=utf-8, if content-type not set. - SendString - Adds response content processor that encodes a string. Uses
charsetor usesutf-8, if not set.
- ContentDecoder - Decodes
Content-Encodingfrom request streams
- Path - Creates logic filter based on URL pathname
- Method - Creates logic filter based on request method
- CORS - Handles preflight
OPTIONrequests and sets necessary response headers for other methods
HttpHandler.defaultInstance.middleware.push(
new AutoHeadersMiddleware(),
new ContentLengthMiddleware(),
hash: (USE_HASH ? new HashMiddleware() : HttpHandler.CONTINUE),
);
HttpHandler.defaultInstance.middleware.push(
[
PathMiddleware.SUBPATH('/api'),
new CORSMiddleware(),
[MethodMiddleware.GET, myAPIGetFunctions],
[MethodMiddleware.POST, myAPIPostFunctions],
],
[
new PathMiddleware(/^\/(index\.html?)?$/),
indexPageMiddleware
],
arrayToBePopulatedLater,
404 // Equivalient of ({response}) => response.code(404).end()
);
HttpHandler.defaultInstance.errorHandlers.push({
onError({error}) {
console.error(error);
return 500;
},
});async function checkToken({request, response, locals}) {
const content = await req.read();
try {
const decoded = await decodeJWT(content.token);
locals.jwt = decoded;
} catch {
return 401;
}
/**
* Since we want the logic to continue to the next step,
* We can either allow the function to implicitly return `undefined`
* or explicitly use any of the following:
* * return undefined;
* * return true;
* * return HttpHandler.CONTINUE;
*/
}