danmaru
v1.3.1
Published
No dependency, convenience HTTTP/HTTPS server wrapper
Downloads
19
Readme
danmaru
No dependency, convenience HTTTP/HTTPS server wrapper in TypeScript. For complete TypeScript code see https://github.com/sdrsdr/danmaru
How to use
import * as http from 'http';
import {compose,log_all,codes} from 'danmaru';
const log=log_all();
const server= new http.Server();
compose(
server,[
{prefix:"/hello_json?", do: (req,resp)=>{
let who=req.full_url.searchParams.get('who')??"<who param not found in searchParams>";
log.mark("Hello "+who+" of JSON!");
resp.json_response(codes.OK,{say:'Hello '+who+' of JSON',method:req.method});
}}
],{log}
);
server.listen(1234,()=>{
log.mark("danmaru HTTP server started at port 1234");
});
compose
function compose(
server:http_Server|https_Server,
http_actions:http_action_t[],
options?:options_t
):boolean ;
Hookup server
to handle requests via http_actions
various options like log functions, global maximal body size and global method filters goes in options
http_action_t
interface http_action_t {
prefix:string;
do:http_action_cb;
m?:string[]; //allowed methods
max_body_size?:number; //if not set max_body_size from options or MAX_BODY_SIZE will be enforced
exact_match?:boolean; //default false; if true prefix must exact-match
error_catcher?:error_catcher_cb; //catch failing requests
}
Array of this interface goes in compose
to describe the urls handled by the server.
prefix
is the start of the url to match. You can have sameprefix
in thecompose
array multiple time with different allowed methods and differentdo
. The match lookup folows array order ofcompose
http_actions
param. First match handles the request completely.do
is the callback that will genrate the responcem
Is optional string array that explicityl allows only mentioned HTTP methods. If this is not set the method filter fromcompose
'soptins
kick in. It is possible to have multiplehttp_action
with sameprefix
but differentm
filters and different domax_body_size
is optional limiter for http resuest body size once the request matches. You can set global limit incompose
'soptions
. There is some hard coded limit if none is set explicityl.exact_match
is assumed false if missing. This changes how the incoming request url is matched againsthttp_action
. It is possible to have multiplehttp_action
with sameprefix
but differentexact_match
and different doerror_catcher
a callback that will receive all request about to be rejected by the library.
options_t
interface options_t {
log?:logger_t;
indexer?:http_action_cb; //default handler; if not set a 404 is send back
auto_headers?:OutgoingHttpHeaders; //this headers are preset for sending for each response
auto_handle_OPTIONS?:boolean; //default is false. Just do resp.simple_response(200) and return for all non 404 urls and OPTIONS method (passing the content of auto_headers to the browser)
max_body_size?:number; //in characters if not set MAX_BODY_SIZE will be enforced
allowed_methods?:string[]; // default is no-filtering;
catch_to_500?:boolean; //catch exceptions in http_action_t.do, log in err, respond with error code 500 (if possible)
error_catcher?:error_catcher_cb; //catch failing requests
}
options for compose
:
log
log4js inspired interface to logging facility to be used in the server. Some helper functions are provided:function log_all():logger_t
andfunction log_none():logger_t
to help with faster setup.indexer
is a url handler for unmatched requests. It can do custom 404 pages or index direcotories somehow. It's all up to you. The helper functionhttp_action_gone
is provided that just returns cacheable410 GONE
responses.auto_headers
headers specified here will be automatically send with every response. Primary target is...auto_headers:{"Access-Control-Allow-Origin":"*"}, ...
auto_handle_OPTIONS
is of by default. If set totrue
it allows for unmatched requests withOPTIONS
method to be automatically replied with200 OK
plus theauto_headers
to allow some browsers to check for CORS headers.max_body_size
global request body size limiter. if not set MAX_BODY_SIZE will be enforced.allowed_methods
states what HTTP methods your server will handle. If you want to handle onlyGET
and/orPOST
request you can state so here and keephttp_action
param ofcompose
cleaner or you can intermix all HTTP method filtering to you likecatch_to_500
makes error handling easy in.do
callback by puting atry {...} catch ( ... ) { ... }
block so an exception thrown in your code will cause internal server error code 500 to be send. To proper catch async errors you need to return the (hidden) promise from from the first async function called. See the example in http_action_cb belowerror_catcher
a callback that will receive all request about to be rejected by the library.
http_action_cb
interface http_action_cb {
(req:CompleteIncomingMessage, resp:SimpleServerResponse) :void|Promise<any>;
}
The http request handling callback in form
function handle_a_request(req:CompleteIncomingMessage, resp:SimpleServerResponse) {
...
}
or
async function handle_a_request(req:CompleteIncomingMessage, resp:SimpleServerResponse) {
...
}
this is the do
in http_action_t
and indexer
in options_t
as usual use the information in req
to craft a resp
if you're using catch_to_500
option and mixing sync and async functions make really sure you're returing the tail call functions. For example if you're using a authentication middleware you should do some strict returns:
type chain_cb_t=(req: CompleteIncomingMessage, resp: SimpleServerResponse, auth_artefact:string)=>void|Promise<any>;
//chack auth call chain if auth ok
function auth (req: CompleteIncomingMessage, resp: SimpleServerResponse, chain:chain_cb_t){
let auth_artefact=req......
....
return chain(req,resp,auth_artefact)
}
async function dothis(req: CompleteIncomingMessage, resp: SimpleServerResponse, auth_artefact:string) {
await ....
}
...
compose(
server,
[
...
{prefix:"/api/dothis?", do: (req,resp)=>{return auth(req,resp,dothis)}},
{prefix:"/api/dothat?", do: (req,resp)=>{return auth(req,resp,dothat)}},
....
],
options
);
CompleteIncomingMessage
The danmaru request object extending from NodeJS IncomingMessage
interface CompleteIncomingMessage extends IncomingMessage {
//assert this from parent
url:string;
method:string;
//expand a bit
action:http_action_t;
full_url:URL;
body_string:string;
is_damaged:boolean;
}
url
,method
these are optional in originalIncomingMessage
but as we're working with HTTP/HTTPS servers we can assert them in danmaruaction
this is thehttp_action_t
that is assigned to handle the request. This might come handy in somelayered
designfull_url
as the originalIncomingMessage
haveurl
that is just a string danmaru creates a full_url object from URL class factoring inHost
header and allowing for easy search params (GET params) access wiareq.full_url.searchParams.get('paramname')
body_string
in case of request with a body the text is collected here fully before the call tohttp_action_cb
is done. If the body goes above limitingmax_body_size
ofhttp_action_t
oroptions_t
danmaru will return400 BAD REQUEST
andhttp_action_cb
will NOT be calledis_damaged
is flag set and used internally by danmaru to mark aIncomingMessage
as damaged by error, cancellation ormax_body_size
violation such request should not reachhttp_action_cb
SimpleServerResponse
The danmaru request object extending from NodeJS ServerResponse
export interface SimpleServerResponse extends ServerResponse {
action:http_action_t;
req_url:string;
logger:logger_t;
auto_headers:OutgoingHttpHeaders;
simple_response:(code:number,data?:any, headers?:OutgoingHttpHeaders, reason?:string)=>boolean;
json_response:(code:number,data:string|object, headers?:OutgoingHttpHeaders, reason?:string)=>boolean;
}
action
this is thehttp_action_t
that is assigned to handle the request. This might come handy in somelayered
designreq_url
a copy of the associatedIncomingMessage
.url
kept for easy logging.logger
the logger from associatedcompose
auto_headers
from thecompose
options
simple_response
method for "one line response":resp.simple_response(200)
is all it take to "confirm" ahttp_action_cb
you can specify the response body data indata
, set additional headers inheaders
or customise the response text viareason
if you like.json_response
method for "one line JSON response":resp.simple_response(200,{ok:true})
is all it take to create return some JSON to the requester. It resolves tosimple_response
with proper stringification, if needed, plus aContent-Type: application/json; charset=UTF-8
header to make the standard committee happy.
#logger_t
the log4js inspired logging facility
interface logger_t {
debug:logfunction_t;
info:logfunction_t;
warn:logfunction_t;
error:logfunction_t;
mark:logfunction_t;
}
this provided utility function explains it all:
function log_all():logger_t {
return {
debug:console.log,
info:console.log,
warn:console.log,
error:console.log,
mark:console.log,
}
}
For static files serving we recoomend using serve-handler and calling it in indexer function
import {compose} from 'danmaru';
import serveHandler from 'serve-handler'
....
compose(server,[...],{
indexer:(req,resp)=>{
if (req.url.startsWith('/api/')) {
log.info("unhandled api call at "+req.url);
resp.simple_response(codes.NOT_FOUND);
return;
}
serveHandler(req,resp,{public:"./fe/dist", rewrites:[{source:'/',destination:'/index.html'}]});
}
});
For more (advanced) examples take a look at our test files :)