Experimental HTTP application framework based on URI templates and Stream#pipe
A prototype HTTP application framework using URI Templates and streams for looking up, rendering, and persisting content from/to a data store.
To accomplish this, Dive defines two primary concepts: resources and routes. However, these have specific definitions, somewhat different than other HTTP frameworks:
A resource is an entity identified by a URI, that has a media type and contents, as it exists in a single point in time. There may also be other metadata that describes this resource, like caching information. The actual contents of the resource need not be stored in memory; the resource just has to know it can get at them if necessary.
Resources render representations by implementing the Resource#render()
method, which typically returns a ServerResponse
stream.
A route describes a set of similar resources, which may vary by various request parameters, like variables matched to a URI Template, or request headers.
For example, a Route may be a set of HTML blog posts, or a set of JSON documents each describing a Git repository.
Resources within a route may vary along zero dimensions; in which case the set will contain exactly one document (by the multiplicative identity).
The resource parameters (the values for the variables in the URI Template) uniquely identify a resource within the route. Formally, resources are always looked up by their URI. Internally for performance, resources are also looked up by their parameters.
Route by itself is an abstract class, there are several broad subclasses of routes:
The "lowest level" of Route is a data source. They map HTTP resources in terms of other resources, for example a filesystem, database query, or a hard-coded document.
Data sources use the parameters from the parsed URI to lookup values from a data source. For example, a file by its file path, or a database record by its stringified id.
Route
accepts a prepare
option that can be used to define hard-coded sets of resourcesRouteRedirect
always returns a 3xx redirect responseRouteFilesystem
looks up a file on the filesystemSecond are transforming routes, which defines a set of resources in terms of a 1:1 mapping onto another set via some function. For example, a template route can produce a set of HTML documents from a set of JSON documents, via an isomorphic mapping.
Transforming routes use the parameters from the parsed URI to fill in the URI template from an underlying set. For example, an HTML template might provide a set of HTML documents at http://localhost/{file}.html
, the file
variable will be extracted and filled in to find the equivalent JSON document at http://localhost/{file}.json
.
Transforming routes are easily created with new TransformRoute(opts, innerRoute)
. The TransformRoute#render_transform(resource, req, input, output)
method is used to generate the resource. The input
argument is a readable response to be transformed, and output
is the writable response to write the resource to.
Caching routes try to fill from a data source (the cache) first, forwarding the request to an inner route after a cache miss.
Caching routes are otherwise transparent, and do not perform any transformations on the data or resource URI; they copy the uriTemplate of the inner route exactly.
An aggregation routes, or simply a Collection, generates resources using the contents of multiple other resources from another route.
Examples of collections include blog archives, file listings, sitemaps, Atom/RSS feeds, and search results.
In its simplest form, it will contain a single resource that links to each resource in a route. In more complicated forms, it may represent a paginated archive, or dynamically generate search results from an index.
Finally, there are combination routes, which defines a set in terms of multiple other sets. Dive defines several of these:
First
looks (in sequence) through an ordered list of sets and returns the first Resource that it findsRouteURITemplate
uses a URI Template router to pick the most specific pattern that matches the input URINegotiate
queries all underlying sets for the specified resource and, depending on the HTTP request headers, returns a suitable matching documentCombination routes do not read parameters from the parsed URI, though they may still have an associated URI Template that's used by transforming routes.
All errors in the course of processing a request are caught and rendered into error responses, if a response has not already been written.
If the request has a URI, the error response is routed to a handler in a similar fashion to a request handler.
handle
implementation, which generate a MethodNotAllowed error.Errors always flow into a Writable side and out of a Readable side. Errors in streams are caught by the HTTP server. If a response has not already been written, one is generated from the error by calling the following functions to see which can generate one:
Resource#error
if it exists.Application#error
is called, which in turn consults the URI router and child routes to determine who has a Route#error
willing to handle the error.Application#defaultNotFound
may generate a generic "Page Not Found" error. This is primarily used for generating static websites.Application#onError
writes a test/plain response; additionally, the stack will be written if Application#debug
is true.Additionally, if the error was a 500 class error, it will be logged (by default, printed to stderr).
If Application#debug
is true, all errors are logged.
A Resource
instance has the following properties:
methods
not handled by one of the aboveerr
. May be left undefined, in which case, the route will search elsewhere for a function that can handle the error (see "Error Handling" above).The req
parameter (used in the render functions) is similar to IncomingMessage, and uses the following properties:
method
- the HTTP method being calleduri
- the full, absolute URI being requested (absolute meaning "no fragment")headers
- map of headers, pseudo-headers and hop-by-hop headers removedrawHeaders
- Array, alternating name and value of each headerA Route
instance provides the following properties:
prepare
yielded no resultserror
, when no Route#resolve call resolved a Resource (usually 404 or a 5xx error)watch takes a callback with two arguments:
Most applications are defined inside an Application. It is a type of Route that implements several features commonly implemented by a Web application:
http:
)Application uniquely has a handleRequest
method. This is called by various listeners, after being normalized down to (abstracted into) standard HTTP semantics (RFC7231). This abstraction applies the following to the standard Node.js req/res objects:
A Listener opens a server and translates requests and responses between an Application.
Runtime flags:
HTTPServer is a Listener that translates HTTP requests into calls to Application. HTTPServer is designed to be Internet-facing, and listens on as many protocols as are supported.
uriTemplate
property) to the list of routesChange the URI of a resource being routed.
For incoming requests, check several downstream routes in order.
Check several inbound routes and pick the one that best matches the client's Accept header.
Cache responses on a filesystem or other database.
Forward an HTTP response over the network.
Transform an HTTP request and/or response, e.g. apply a template.
Generate a response from a file.