http-server-tools
v1.0.7
Published
Some wrappers around the standard node:http API
Downloads
4
Readme
http-server-tools
is a thin wrapper around the standard 'node:http'
library for developing Web services with as few external dependencies as possible, without resorting to a full blown middleware framework. Or to build such frameworks.
The main class here, HttpRequestContext
, incapsulates the ClientRequest
/ServerResponse
pair and implements an OO API hiding some transport protocol details while keeping low level operation totally available.
HttpRequestContext
don't assume subclassing, but is highly configurable and is meant to be used together with companion objects carrying sets of options for its constructor. Such objects should be singletons representing local application's specifics as opposed to one-off multi purpose HttpRequestContext
instances.
To illustrate HttpRequestContext
API in action, a trivial embeddable file directory sharing Web server, HttpStaticSite
, is included in the library.
Installation
npm install http-server-tools
Usage
const createError = require ('http-errors')
const {HttpRequestContext} = require ('http-server-tools')
async function handle (response) {
const ctx = new HttpRequestContext (response, {
// parse : str => JSON.parse (str), // for .bodyParams
// stringify : obj => JSON.stringify (obj), // for .write ({...})
// createError : err => createError (500, err, {expose: false}),
// maxBodySize : 10 * 1024 * 1024,
// pathBase : 0,
// pathMapping : // e. g. ([type, id]) => ({type, id})
// keepBody : // e. g. function () {return this.path [0] === 'huge'}
// statusCode : 200,
// charset : 'utf-8',
// contentType : // e. g. 'text/xml', 'application/soap+xml'
})
const {pathParams, searchParams} = ctx // from the URL
const {sessionId} = ctx.cookieParams
await ctx.readBody () // if the method is 'GET' or `keepBody` returns true, does nothing
const {bodyParams} = ctx // see the `parse` option
try {
const result = await invokeMyBusinessMethod ({...pathParams,...searchParams, ...bodyParams}, sessionId)
ctx.setCookie ('sessionId', sessionId, {httpOnly: true})
await ctx.write (result)
}
catch (err) {
await ctx.writeError (err)
}
}
http.createServer ({...})
.on ('request', (_, response) =>
handle (response)
.then (..., ...)
)
.listen ({...})
In essence, in most cases, to serve a request, one should:
- create an
HttpRequestContext
instance; - fetch the request completely with
await ctx.readBody ()
; - calculate the result based on
{...pathParams, ...searchParams, ...bodyParams}
and, probably,cookieParams
; ctx.write ()
it out.
Reading Incoming Data
In most cases, to acquire the totality of data sent from the client, the boilerplate code
await ctx.readBody ()
const {pathParams, searchParams, bodyParams, cookieParams: {sessionId}} = ctx
// now do something with {...pathParams, ...searchParams, ...bodyParams} and sessionId
should fit right. Details are explained below in this section.
.searchParams
This property's value is an object composed from url.searchParams. For /?type=users&id=1
, it's {type: 'users', 'id': '1'}
.
.path
This property presents the url.pathname
, split by '/'
, with all empty strings filtered away, with first pathBase
stripped off. For example, for http://127.0.0.1/api/v1.0//users/1/?show=1#help
it will be
|pathBase
|path
|
| - | - |
|0
(default)|['api', 'v1.0', 'users', '1']
|
|1
|['v1.0', 'users', '1']
|
|2
|['users', '1']
|
.pathParams
In many Web applications, components of the path
with fixed positions have a clear business sense: for example, the root part means entity (name it type
) and the second is the unique identifier (name it id
): /{type}/{id}
.
In such cases, HttpRequestContext
lets configure a mapping function
pathMapping: ([type, id]) => ({type, id})
and obtain the corresponding named parameters, in the form of a plain Object, via the .pathParams
property, similar to .searchParams
.
Without pathMapping
defined (which is the default), .pathParams
is always an empty object {}
.
.body
Initially undefined, after await ctx.readBody ()
this property becomes a Buffer containing the whole request body.
If the HTTP Request Method doesn't assume sending any body (GET
, HEAD
etc.), .body
remains undefined.
The same happens for 'POST'
, 'PUT'
etc. when .keepBody ()
returns true
. By default, it never does but the developer might opt to alter it to read the .request
stream explicitly. For example:
keepBody: function () {return this.searchParams.type === 'special'},
will keep .request
unread for /type=special
even after await ctx.readBody ()
is done.
If the maxBodySize
(10 Mb by default) limit gets exceeded, a 413 Content Too Large http error is thrown.
Overall, it's always safe to call await ctx.readBody ()
, once per ctx
instance.
.bodyText
This computed property returns the .body
as a string, decoded with Content-Type
's charset
, 'utf-8' by default.
Exception: for an undefined .body
, .bodyText
returns the zero length string: ''
.
.bodyParams
This computed property returns the result of applying the parse
option (JSON.parse
by default) to .bodyText
.
Exception: for a zero length .bodyText
, .bodyParams
returns the empty object: {}
.
So, for instance, {...searchParams, ...bodyParams}
is OK for GET
requests without additional checks.
For SOAP and other XML based services, it takes to redefine the parse
option using something like XMLParser.
.cookieParams
This property's getter is the shortcut to cookie.parse ().
Writing Results
In short, to send business data
(normally, a plain object or, in special cases, a stream) to the client, the application should call
await ctx.write (data)
once per HTTP request. This high level all purpose method actually just checks for its argument type and forwards it to one of the specific methods described hereafter.
Any of write...
methods is presumed to be called once per request lifecycle, at its end. All custom HTTP headers (including Set-Cookie
, see below) must be already set at this point.
The response status code is set from ctx.statusCode
which is initially set by configuration, 200
by default.
writeEmpty
Invoked by write
for undefined
incoming value. Writes an empty 204 No Content response.
writeStream
This method is directly invoked by write
for a Readable incoming value. It basically just pipes the argument into .response
, but, first, it writes HTTP headers, ContentType
specifically.
To overwrite the default application/octet-stream
type, there are three options:
- add the
[HttpRequestContext.CONTENT_TYPE]
property to the stream instance; - set
ctx.contentType
; - lowest level: explicitly call
ctx.response.setHeader ('content-length', ...)
.
writeBuffer
Invoked by write
for a Buffer incoming value, writes the binary content supplied into .response
.
The ContentType
is controlled the same 3 ways as for writeStream
, including the [HttpRequestContext.CONTENT_TYPE]
property for the buffer.
writeText
Invoked by write
for a string
incoming value. Unlike aforementioned methods, defaults ContentType
to text/plain
. Moreover, appends the ; charset=${charset}
unless it's already there. The same charset
is used to translate the string into a Buffer to actually write out. One should have a really good reason to overwrite the default charset: 'utf-8'
.
writeObject
Invoked by write
for a plain (not Readable nor Buffer) object. Executes stringify
to obtain the string representation and then uses writeText
to do the rest. The ContentType
is set to 'application/json'.
To build an SOAP service, serialize
should be implemented with XMLSchemata or like; and contentType
must be set accordingly.
Reporting Errors
The writeError
method requires an Error
object as an argument. Normally, it should be created with http-errors
. Otherwise, the local createError
option is used to wrap it up first — it absolutely must return an http-errors
augmented object.
The result is written out with writeText
. For a true expose
value the text is the error's message
; otherwise, just the status text is written.
As for any text, the Content-Type
is text/plain
by default, but it may me altered by setting {headers: {'Content-Type': ...}}
.
For writeError
, the statusCode
is copied from the error object, overriding what was set for the context instance.
Working with Cookies
HttpRequestContext
wraps around the ultra popular cookie
module featuring:
- the
cookieParams
property for reading the.request
'sCookie
header; - the
setCookie (name, value, options)
method for writing the.response
'sSet-Cookie
header.
Going Low Level
To perform any tasks uncovered by the methods described, HttpRequestContext
provides the next properties:
Name | Type | Description | Possible use
-|-|-|-
request
| ClientRequest
| The raw request | Processing the body as a stream; see keepBody
option
response
| ServerResponse
| The raw response | Setting headers
url
| URL
| Parsed request.url
| Reading non standard parameters