Skip to content

Commit

Permalink
Initial removal of http2 support (kinda useless IRL)
Browse files Browse the repository at this point in the history
  • Loading branch information
avoidwork committed Dec 21, 2020
1 parent 3ff9778 commit ba7f774
Show file tree
Hide file tree
Showing 9 changed files with 1,927 additions and 3,503 deletions.
28 changes: 0 additions & 28 deletions Gruntfile.js

This file was deleted.

23 changes: 1 addition & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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

Lightweight HTTP/HTTP2 router with automatic `Allow` & `CORS` headers. Routes can use parameter syntax, i.e. `/users/:id`, or `RegExp` syntax. Route parameters are not sanitized. If 2+ routes with parameters match a request the first route will be used to extract parameters. All HTTP methods are supported.
Lightweight HTTP router with automatic headers. Routes can use parameter syntax, i.e. `/users/:id`, or `RegExp` syntax. Route parameters are not sanitized. If 2+ routes with parameters match a request the first route will be used to extract parameters. All HTTP methods are supported.

`CORS` (Cross Origin Resource Sharing) is automatically handled, and indicated with `cors` Boolean on the `request` Object for middleware.

Expand All @@ -30,27 +30,6 @@ router.get("/:user", (req, res) => res.send(`Hello ${req.params.user}!`));
http.createServer(router.route).listen(8000);
```

#### HTTP2
```javascript
"use strict";

const http2 = require("http2"),
fs = require("fs"),
router = require("woodland")({
defaultHeaders: {"Cache-Control": "no-cache", "Content-Type": "text/plain"},
http2: true
});

router.get("/", "Hello World!");
router.get("/:user", (req, res) => res.send(`Hello ${req.params.user}!`));

http2.createSecureServer({
key: fs.readFileSync("./ssl/localhost.key"),
cert: fs.readFileSync("./ssl/localhost.crt")
}).on("stream", router.route).listen(8443);

```

## Helpers
`req` & `res` are decorated with helper functions to simplify responding.

Expand Down
11 changes: 0 additions & 11 deletions lib/request.js

This file was deleted.

59 changes: 0 additions & 59 deletions lib/utility.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"use strict";

const {STATUS_CODES} = require("http"),
path = require("path"),
coerce = require("tiny-coerce"),
request = require(path.join(__dirname, "request.js")),
{URL} = require("url"),
all = "*",
delimiter = ":";
Expand All @@ -12,61 +10,6 @@ function clone (arg) {
return JSON.parse(JSON.stringify(arg));
}

function http2Normalize (arg, stream) {
const obj = request(stream.session.socket);

obj.headers = clone(arg.headers);
obj.headers.host = arg.headers[":authority"].replace(/:.*$/, "");
obj.method = arg.headers[":method"];
obj.connection = {remoteAddress: stream.session.socket.server._connectionKey.split(":")[1]};
obj.url = obj.headers[":path"]; //`${obj.headers[":scheme"]}//${obj.headers[":authority"]}${obj.headers[":path"]}`;
obj.httpVersion = "2.0";
obj.httpVersionMajor = 2;
obj.httpVersionMinor = 0;

delete obj.headers[":authority"];
delete obj.headers[":method"];
delete obj.headers[":path"];
delete obj.headers[":scheme"];

return obj;
}

function invalid (arg) {
return arg === "transfer-encoding" || arg === "connection";
}

function http2Send (req, res, body, nstatus = 200, headers = null) {
const status = res.statusCode < nstatus ? nstatus : res.statusCode,
output = {":status": status},
empty = req.method === "HEAD" || status === 204 || status === 304 || body === "";

for (const i of Object.keys(res._headers)) {
if (invalid(i) === false) {
output[i] = res._headers[i];
}
}

if (headers !== null) {
for (const i of Object.keys(headers)) {
if (invalid(i) === false) {
output[i] = headers[i];
res._headers[i] = headers[i];
}
}
}

if (req.file === void 0 || empty) {
res.respond(output);

if (res.writable) {
res.end(empty === false ? body : void 0);
}
} else {
res.respondWithFile(req.file.path, output);
}
}

function last (req, res, e, err) {
const status = res.statusCode || 0;

Expand Down Expand Up @@ -189,8 +132,6 @@ module.exports = {
all,
clone,
delimiter,
http2Normalize,
http2Send,
last,
ms,
next,
Expand Down
169 changes: 34 additions & 135 deletions lib/woodland.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
"use strict";

const path = require("path"),
fifo = require("tiny-fifo"),
lru = require("tiny-lru"),
http = require("http"),
precise = require("precise"),
{METHODS, STATUS_CODES} = http,
EventEmitter = require("events"),
dtrace = require(path.join(__dirname, "dtrace.js")),
{all, clone, delimiter, http2Normalize, http2Send, next, ms, params, parse, partial, pipeable, last, reduce, writeHead} = require(path.join(__dirname, "utility.js"));
{all, clone, delimiter, next, ms, params, parse, partial, pipeable, last, reduce, writeHead} = require(path.join(__dirname, "utility.js"));

class Woodland extends EventEmitter {
constructor (defaultHeaders, cacheSize, http2, cacheTTL, dtp, origins) {
constructor (defaultHeaders, cacheSize, cacheTTL, dtp, origins) {
super();
this.blacklisted = new Set();
this.cache = fifo(cacheSize, cacheTTL);
this.cache = lru(cacheSize, cacheTTL);
this.defaultHeaders = Object.keys(defaultHeaders).map(key => [key.toLowerCase(), defaultHeaders[key]]);
this.dtrace = dtp === true;
this.dtp = this.dtrace ? dtrace("woodland") : null;
this.http2 = http2 === true;
this.permissions = fifo(cacheSize, cacheTTL);
this.permissions = lru(cacheSize, cacheTTL);
this.methods = [];
this.middleware = new Map();
this.origins = clone(origins);
Expand Down Expand Up @@ -124,127 +123,43 @@ class Woodland extends EventEmitter {
return res;
};

if (this.http2) {
const write = res.write.bind(res);
res.header = res.setHeader;
res.send = (body, status = 200, headers = {}) => {
if (res.headersSent === false) {
let output = body;

res._headers = {};
res.statusCode = 200;
res.hasHeader = key => key in res._headers;
res.getHeader = key => res.hasHeader(key) ? clone(res._headers[key]) : void 0;
res.getHeaders = () => clone(res._headers);
this.emit("send", req, res, output, status, headers);

res.write = (...args) => {
if (res.headersSent === false) {
let options = args[1];

if (options === void 0) {
options = {":status": res.statusCode};

for (const i of Object.keys(res._headers)) {
options[i] = res._headers[i];
}
if (pipeable(req.method, output)) {
writeHead(res, status, headers);
output.on("error", () => void 0).pipe(res);
} else {
if (typeof output !== "string" && "toString" in output) {
output = output.toString();
}

res.respond(options);
}

write(args[0]);
};

res.removeHeader = key => {
if (res.hasHeader(key)) {
delete res._headers[key];
}
};

res.setHeader = (key, value) => {
res._headers[key] = value;
};

res.header = res.setHeader;

res.send = (body, status = 200, headers = {}) => {
let error = false,
output = body;

this.emit("send", req, res, body, status, headers);

if (pipeable(req.method, output) === false) {
if (req.headers.range !== void 0) {
const buffered = Buffer.from(output);

partial(req, res, buffered, status, headers);

if (req.range !== void 0) {
writeHead(res, status, headers);
output = buffered.slice(req.range.start, req.range.end + 1).toString();
res.end(buffered.slice(req.range.start, req.range.end + 1).toString());
} else {
delete req.headers.range;
error = true;
res.error(416);
}
} else if (typeof output !== "string" && "toString" in output) {
output = output.toString();
}
}

if (error === false && res.destroyed === false) {
writeHead(res, status, headers);
http2Send(req, res, output, status, headers);
}
};

res.writeHead = (status = res.statusCode, reason = STATUS_CODES[res.statusCode], headers = null) => {
res.statusCode = status;
res.statusMessage = reason;

if (headers !== null) {
for (const i of Object.keys(headers)) {
res.setHeader(i, headers[i]);
}
}
};
} else {
res.header = res.setHeader;
res.send = (body, status = 200, headers = {}) => {
if (res.headersSent === false) {
let output = body;

this.emit("send", req, res, output, status, headers);

if (pipeable(req.method, output)) {
writeHead(res, status, headers);
output.on("error", () => void 0).pipe(res);
} else {
if (typeof output !== "string" && "toString" in output) {
output = output.toString();
}

if (req.headers.range !== void 0) {
const buffered = Buffer.from(output);

partial(req, res, buffered, status, headers);

if (req.range !== void 0) {
writeHead(res, status, headers);
res.end(buffered.slice(req.range.start, req.range.end + 1).toString());
} else {
delete req.headers.range;
res.error(416);
}
} else {
writeHead(res, status, headers);
res.end(output);
}
writeHead(res, status, headers);
res.end(output);
}
}
};
}

if (this.defaultHeaders.length > 0) {
for (const i of this.defaultHeaders) {
res.header(i[0], i[1]);
}
};

for (const i of this.defaultHeaders) {
res.header(i[0], i[1]);
}

const allow = req.allow !== "";
Expand Down Expand Up @@ -290,35 +205,19 @@ class Woodland extends EventEmitter {
timer = precise().start();
}

const headers = {"content-type": "text/plain", "cache-control": "no-cache"};

if (this.http2 === false) {
const numeric = isNaN(err.message) === false,
status = isNaN(res.statusCode) === false && res.statusCode >= 400 ? res.statusCode : numeric ? Number(err.message) : 500,
output = numeric ? http.STATUS_CODES[status] : err.message;
const headers = {"content-type": "text/plain", "cache-control": "no-cache"},
numeric = isNaN(err.message) === false,
status = isNaN(res.statusCode) === false && res.statusCode >= 400 ? res.statusCode : numeric ? Number(err.message) : 500,
output = numeric ? http.STATUS_CODES[status] : err.message;

headers["content-length"] = Buffer.byteLength(output);
headers["content-length"] = Buffer.byteLength(output);

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

res.send(output, status, headers);
} else {
const numeric = isNaN(err.message) === false,
status = isNaN(res.statusCode) === false && res.statusCode >= 400 ? res.statusCode : numeric ? Number(err.message) : 500,
output = numeric ? http.STATUS_CODES[status] : err.message;

headers["content-length"] = Buffer.byteLength(output);

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

res.send(output, status, headers);
if (this.dtrace) {
timer.stop();
this.probes.get("error").fire(() => [req.parsed.href, status, ms(timer.diff())]);
}

res.send(output, status, headers);
}

get (...args) {
Expand Down Expand Up @@ -372,7 +271,7 @@ class Woodland extends EventEmitter {
timer = precise().start();
}

const [req, res] = this.http2 === false ? args : [http2Normalize({headers: args[1]}, args[0]), args[0]];
const [req, res] = args;
let method = req.method === "HEAD" ? "GET" : req.method;

const e = err => this.emit("error", req, res, err);
Expand Down
Loading

0 comments on commit ba7f774

Please sign in to comment.