Skip to content

Commit

Permalink
Initial changes to support new woodland router (no http2 support) -…
Browse files Browse the repository at this point in the history
… WIP
  • Loading branch information
avoidwork committed Dec 26, 2020
1 parent 5c35adb commit b8c90ce
Show file tree
Hide file tree
Showing 10 changed files with 23 additions and 1,397 deletions.
38 changes: 17 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Tensō
Tenso
=====

[![build status](https://secure.travis-ci.org/avoidwork/tenso.svg)](http://travis-ci.org/avoidwork/tenso)

Tensō is an HTTP/HTTP2 REST API framework, that will handle the serialization & creation of hypermedia links; all you have to do is give it `Arrays` or `Objects`.
Tenso is an HTTP REST API framework, that will handle the serialization & creation of hypermedia links; all you have to do is give it `Arrays` or `Objects`.

## Example
Creating an API with Tensō can be this simple:
Creating an API with Tenso can be this simple:

```javascript
const path = require('path'),
Expand Down Expand Up @@ -45,9 +45,9 @@ Unprotected routes are routes that do not require authorization for access, and
The `/assets/*` route is reserved for the HTML browsable interface assets; please do not try to reuse this for data.

### Request Helpers
Tensō decorates `req` with "helpers" such as `req.ip`, & `req.parsed`. `PATCH`, `PUT`, & `POST` payloads are available as `req.body`. Sessions are available as `req.session` when using `local` authentication.
Tenso decorates `req` with "helpers" such as `req.ip`, & `req.parsed`. `PATCH`, `PUT`, & `POST` payloads are available as `req.body`. Sessions are available as `req.session` when using `local` authentication.

Tensō decorates `res` with "helpers" such as `res.send()`, `res.status()`, & `res.json()`.
Tenso decorates `res` with "helpers" such as `res.send()`, `res.status()`, & `res.json()`.

## Responses
Responses will have a standard shape, and will be utf-8 by default. The result will be in `data`. Hypermedia (pagination & links) will be in `links:[ {"uri": "...", "rel": "..."}, ...]`, & also in the `Link` HTTP header.
Expand All @@ -68,26 +68,26 @@ Sort order can be specified via then `order-by` which accepts `[field ]asc|desc`
Final modifications can be made to a response body after `hypermedia()` by overriding `tenso.final(req, res, body)`.

## REST / Hypermedia
Hypermedia is a prerequisite of REST, and is best described by the [Richardson Maturity Model](http://martinfowler.com/articles/richardsonMaturityModel.html). Tensō will automagically paginate Arrays of results, or parse Entity representations for keys that imply
Hypermedia is a prerequisite of REST, and is best described by the [Richardson Maturity Model](http://martinfowler.com/articles/richardsonMaturityModel.html). Tenso will automagically paginate Arrays of results, or parse Entity representations for keys that imply
relationships, and create the appropriate Objects in the `link` Array, as well as the `Link` HTTP header. Object keys that match this pattern: `/_(guid|uuid|id|uri|url)$/` will be considered
hypermedia links.

For example, if the key `user_id` was found, it would be mapped to `/users/:id` with a link `rel` of `related`.

Tensō will bend the rules of REST when using authentication strategies provided by passport.js, or CSRF if is enabled, because they rely on a session. Session storage is in memory, or Redis. You have the option of a stateless or stateful API.
Tenso will bend the rules of REST when using authentication strategies provided by passport.js, or CSRF if is enabled, because they rely on a session. Session storage is in memory, or Redis. You have the option of a stateless or stateful API.

Hypermedia processing of the response body can be disabled as of `10.2.0`, by setting `req.hypermedia = false` via middleware.

## Browsable API / Renderers / Serializers
Tensō 1.4.0 added a few common format renderers, such as CSV, HTML, YAML, & XML. The HTML interface is a browsable API! You can use it to verify requests & responses, or simply poke around your API to see how it behaves.
Tenso 1.4.0 added a few common format renderers, such as CSV, HTML, YAML, & XML. The HTML interface is a browsable API! You can use it to verify requests & responses, or simply poke around your API to see how it behaves.

Custom renderers can be registered with `server.renderer('mimetype', fn);` or directly on `server.renderers`. The parameters for a renderer are `(req, res, arg)`. Custom serializes can be registered with `server.serializer('mimetype', fn);` or directly on `server.serializers`. The parameters for a serializer are `(arg, err, status = 200, stack = false)`; if `arg` is `null` then `err` must be an `Error` & `stack` determines if the response body is the `Error.message` or `Error.stack` property.

## Cache
ETags are built in! Caching can be disabled by setting the `cache-control` header to a "private" or "no cache" directive (see the above `/uuid` example).

## Configuration
This is a sample configuration for Tensō, without authentication or SSL. This would be ideal for development, but not production! Enabling SSL is as easy as providing file paths for the two keys.
This is a sample configuration for Tenso, without authentication or SSL. This would be ideal for development, but not production! Enabling SSL is as easy as providing file paths for the two keys.

```
{
Expand All @@ -99,7 +99,6 @@ This is a sample configuration for Tensō, without authentication or SSL. This w
"dtrace": false, /* Optional, enables DTrace probes (see dtrace.sh) */
"headers": {}, /* Optional, custom headers */
"hostname": "localhost", /* Optional, default is 'localhost' */
"http2": false, /* Middleware signatures do not change, see woodland */
"index": ["index.htm", "index.html"], /* Files served when accessing a static assets folder */
"json": 0, /* Optional, default indent for 'pretty' JSON */
"logging": {
Expand Down Expand Up @@ -227,7 +226,7 @@ OAuth2 authentication will create `/auth`, `/auth/oauth2`, & `/auth/oauth2/callb
### SAML
SAML authentication will create `/auth`, `/auth/saml`, & `/auth/saml/callback` routes. `auth(profile, callback)` must execute `callback(err, user)`.

Tensō uses [passport-saml](https://github.com/bergie/passport-saml), for configuration options please visit it's homepage.
Tenso uses [passport-saml](https://github.com/bergie/passport-saml), for configuration options please visit it's homepage.

```
{
Expand Down Expand Up @@ -288,17 +287,14 @@ If the session `secret` is not provided, a version 4 `UUID` will be used.


## Security
Tensō uses [lusca](https://github.com/krakenjs/lusca#api) for security as a middleware. Please see it's documentation for how to configure it; each method & argument is a key:value pair for `security`.
Tenso uses [lusca](https://github.com/krakenjs/lusca#api) for security as a middleware. Please see it's documentation for how to configure it; each method & argument is a key:value pair for `security`.

```
{
"security": { ... }
}
```

## Compression
Compression is enabled by default, for Clients that support `gzip` or `deflate`. Compression will be disabled if `SSL` is enabled.

## Rate Limiting
Rate limiting is controlled by configuration, and is disabled by default. Rate limiting is based on `token`, `session`, or `ip`, depending upon authentication method.

Expand Down Expand Up @@ -331,11 +327,11 @@ Standard log levels are supported, and are emitted to `stdout` & `stderr`. Stack

```
{
"logging": {
"level": "warn",
"enabled": true,
"stack": true
}
"logging": {
"level": "warn",
"enabled": true,
"stack": true
}
}
```

Expand All @@ -349,7 +345,7 @@ The browsable template can be overridden with a custom HTML document.
```

## Static assets folder
The browsable template can load assets from this folder. assets.
The browsable template can load assets from this folder.

```
{
Expand Down
13 changes: 0 additions & 13 deletions lib/dtrace.js

This file was deleted.

79 changes: 4 additions & 75 deletions lib/tenso.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const path = require("path"),
precise = require("precise"),
moment = require("moment"),
eventsource = require("tiny-eventsource"),
dtrace = require(path.join(__dirname, "dtrace.js")),
middleware = require(path.join(__dirname, "middleware.js")),
regex = require(path.join(__dirname, "regex.js")),
{hasBody} = require(path.join(__dirname, "shared.js")),
Expand Down Expand Up @@ -78,7 +77,6 @@ class Tenso extends Base {
catchAll: true,
coerce: true,
corsExpose: "",
dtrace: false,
etags: {
enabled: false,
ignore: [],
Expand All @@ -90,7 +88,6 @@ class Tenso extends Base {
"vary": "accept, accept-encoding, accept-language, origin"
},
host: "0.0.0.0",
http2: false,
httpVersion: "1.1",
index: ["index.htm", "index.html"],
json: 0,
Expand Down Expand Up @@ -166,12 +163,10 @@ class Tenso extends Base {
static: "/assets/.*",
staticCache: 300,
template: "",
title: "Tensō",
title: "Tenso",
uid: 0
};
this.etags = null;
this.dtrace = this.config.dtrace;
this.dtp = null;
this.probes = new Map();
this.rates = new Map();
this.renderers = renderers;
Expand Down Expand Up @@ -208,20 +203,7 @@ class Tenso extends Base {
}

canETag (pathname, method, header) {
let result, timer;

if (this.dtrace) {
timer = precise().start();
}

result = this.config.etags.enabled && method === "GET" && (header !== void 0 ? this.etags.valid({"cache-control": header}) : true) && this.config.etags.invalid.filter(i => i.test(pathname)).length === 0;

if (this.dtrace) {
timer.stop();
this.probes.get("etag").fire(() => [pathname, method, ms(timer.diff())]);
}

return result;
return this.config.etags.enabled && method === "GET" && (header !== void 0 ? this.etags.valid({"cache-control": header}) : true) && this.config.etags.invalid.filter(i => i.test(pathname)).length === 0;
}

canModify (arg) {
Expand Down Expand Up @@ -311,12 +293,7 @@ class Tenso extends Base {
headers (req, res, body) {
const status = res.statusCode,
cache = res.getHeader("cache-control") || "";
let csrf = false,
timer;

if (this.dtrace) {
timer = precise().start();
}
let csrf = false;

if (req.protect && cache.includes("private") === false) {
let lcache = cache.replace(/(private|public)(,\s)?/g, "");
Expand Down Expand Up @@ -362,11 +339,6 @@ class Tenso extends Base {

res.header("x-response-time", ((req.timer.stopped.length === 0 ? req.timer.stop() : req.timer).diff() / 1e6).toFixed(2) + " ms");

if (this.dtrace) {
timer.stop();
this.probes.get("headers").fire(() => [req.parsed.pathname, req.method, status, ms(timer.diff())]);
}

this.log("Generated headers", "debug");
}

Expand All @@ -383,12 +355,6 @@ class Tenso extends Base {
}

rate (req, fn) {
let timer;

if (this.dtrace) {
timer = precise().start();
}

const config = this.config.rate,
id = req.sessionID || req.ip;
let valid = true,
Expand Down Expand Up @@ -423,21 +389,10 @@ class Tenso extends Base {
valid = false;
}

if (this.dtrace) {
timer.stop();
this.probes.get("rate").fire(() => [req.parsed.pathname, req.method, valid, limit, remaining, reset, ms(timer.diff())]);
}

return [valid, limit, remaining, reset];
}

render (req, res, arg) {
let timer;

if (this.dtrace) {
timer = precise().start();
}

if (arg === null) {
arg = "null";
}
Expand All @@ -463,11 +418,6 @@ class Tenso extends Base {
res.header("content-type", format);
result = renderer(req, res, arg, this.config.template);

if (this.dtrace) {
timer.stop();
this.probes.get("render").fire(() => [req.parsed.pathname, req.method, ms(timer.diff())]);
}

return result;
}

Expand All @@ -478,11 +428,7 @@ class Tenso extends Base {
}

send (req, res, body = "") {
let result, timer;

if (this.dtrace) {
timer = precise().start();
}
let result;

this.headers(req, res, body);

Expand All @@ -506,11 +452,6 @@ class Tenso extends Base {
}
}

if (this.dtrace) {
timer.stop();
this.probes.get("send").fire(() => [req.parsed.pathname, req.method, res.statusCode, ms(timer.diff())]);
}

return result;
}

Expand Down Expand Up @@ -543,18 +484,6 @@ class Tenso extends Base {
}).on("stream", this.router.route).listen(this.config.port, this.config.host, () => this.drop());
}

this.dtrace = this.config.dtrace;

if (this.dtrace) {
this.dtp = dtrace("tenso");
this.probes.set("etag", this.dtp.addProbe("etag", "char *", "char *", "char *"));
this.probes.set("headers", this.dtp.addProbe("headers", "char *", "char *", "int", "char *"));
this.probes.set("rate", this.dtp.addProbe("rate", "char *", "char *", "bool", "int", "int", "int", "char *"));
this.probes.set("render", this.dtp.addProbe("render", "char *", "char *", "char *"));
this.probes.set("send", this.dtp.addProbe("send", "char *", "char *", "int", "char *"));
this.dtp.enable();
}

this.log(`Started server on port ${this.config.host}:${this.config.port}`, "debug");
}

Expand Down
2 changes: 0 additions & 2 deletions lib/utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,6 @@ function bootstrap (obj) {
cacheSize: obj.config.cacheSize,
cacheTTL: obj.config.cacheTTL,
defaultHeaders: obj.config.headers,
http2: obj.config.http2,
dtrace: obj.config.dtrace,
origins: obj.config.origins
});

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tenso",
"description": "Tensō is an HTTP/HTTP2 REST API framework",
"version": "15.0.15",
"description": "Tenso is an HTTP REST API framework",
"version": "16.0.0",
"homepage": "https://github.com/avoidwork/tenso",
"author": "Jason Mulligan <[email protected]>",
"repository": {
Expand Down
Loading

0 comments on commit b8c90ce

Please sign in to comment.