1.3.1 • Published 2 years ago

danmaru v1.3.1

Weekly downloads
62
License
LGPL-3.0
Repository
github
Last release
2 years ago

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 same prefix in the compose array multiple time with different allowed methods and different do. The match lookup folows array order of compose http_actions param. First match handles the request completely.
  • do is the callback that will genrate the responce
  • m Is optional string array that explicityl allows only mentioned HTTP methods. If this is not set the method filter from compose's optins kick in. It is possible to have multiple http_action with same prefix but different m filters and different do
  • max_body_size is optional limiter for http resuest body size once the request matches. You can set global limit in compose's options. 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 against http_action. It is possible to have multiple http_action with same prefix but different exact_match and different do
  • error_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 and function 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 function http_action_gone is provided that just returns cacheable 410 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 to true it allows for unmatched requests with OPTIONS method to be automatically replied with 200 OK plus the auto_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 only GET and/or POST request you can state so here and keep http_action param of compose cleaner or you can intermix all HTTP method filtering to you like
  • catch_to_500 makes error handling easy in .do callback by puting a try {...} 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 below
  • error_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 original IncomingMessage but as we're working with HTTP/HTTPS servers we can assert them in danmaru
  • action this is the http_action_t that is assigned to handle the request. This might come handy in some layered design
  • full_url as the original IncomingMessage have url that is just a string danmaru creates a full_url object from URL class factoring in Host header and allowing for easy search params (GET params) access wia req.full_url.searchParams.get('paramname')
  • body_string in case of request with a body the text is collected here fully before the call to http_action_cb is done. If the body goes above limiting max_body_size of http_action_t or options_t danmaru will return 400 BAD REQUEST and http_action_cb will NOT be called
  • is_damaged is flag set and used internally by danmaru to mark a IncomingMessage as damaged by error, cancellation or max_body_size violation such request should not reach http_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 the http_action_t that is assigned to handle the request. This might come handy in some layered design
  • req_url a copy of the associated IncomingMessage .url kept for easy logging.
  • logger the logger from associated compose
  • auto_headers from the compose options
  • simple_response method for "one line response": resp.simple_response(200) is all it take to "confirm" a http_action_cb you can specify the response body data in data, set additional headers in headers or customise the response text via reason 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 to simple_response with proper stringification, if needed, plus a Content-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 :)

1.3.1

2 years ago

1.3.0

2 years ago

1.2.1

2 years ago

1.2.0

3 years ago

1.1.0

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago