Skip to content

Commit

Permalink
Fixing API by moving functions into utility (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
avoidwork committed Jun 14, 2018
1 parent c5be994 commit acd5e28
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 134 deletions.
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
const path = require("path"),
Woodland = require(path.join(__dirname, "lib", "woodland.js"));

function factory ({cacheSize = 1000, cacheTTL = 0, coerce = true, defaultHeaders = {}, seed = Math.floor(Math.random() * 1000) + 1, http2 = false} = {}) {
const router = new Woodland(defaultHeaders, cacheSize, seed, coerce, http2, cacheTTL);
function factory ({cacheSize = 1000, cacheTTL = 0, coerce = true, defaultHeaders = {}, http2 = false} = {}) {
const router = new Woodland(defaultHeaders, cacheSize, coerce, http2, cacheTTL);

router.route = (...args) => router._route.apply(router, args);

Expand Down
128 changes: 122 additions & 6 deletions lib/utility.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"use strict";

const http = require("http"),
path = require("path"),
coerce = require("tiny-coerce"),
regex = require(path.join(__dirname, "regex.js"));

function clone (arg) {
return JSON.parse(JSON.stringify(arg));
}
Expand All @@ -15,22 +20,133 @@ function each (arr, fn) {
return arr;
}

function http2Normalize (arg) {
const obj = clone(arg);

obj.headers.host = obj.headers[":authority"].replace(/:.*$/, "");
obj.method = obj.headers[":method"];
obj.connection = {remoteAddress: "0.0.0.0"};
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 http2Send (req, res, body, nstatus = 200, headers = null) {
const status = res.statusCode < nstatus ? nstatus : res.statusCode,
output = {":status": status},
empty = regex.isHead.test(req.method) || status === 204 || status === 304 || body === "";

if (res.statusCode < status) {
res.statusCode = status;
}

// Calling this for http1 middleware that's hooked the method
res.writeHead(res.statusCode);

each(Object.keys(res._headers).filter(i => regex.invalid.test(i) === false), i => {
output[i] = res._headers[i];
});

if (headers !== null) {
each(Object.keys(headers).filter(i => regex.invalid.test(i) === false), i => {
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 normalize (...args) {
let [path, fn, method] = args;
let [route, fn, method] = args;

if (typeof path !== "string") {
if (typeof route !== "string") {
method = fn;
fn = path;
path = "/.*";
fn = route;
route = "/.*";
}

method = method !== void 0 ? method.toUpperCase() : "GET";

return [path, fn, method];
return [route, fn, method];
}

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

if (err === void 0) {
reject(new Error(regex.hasGet.test(req.allow || "") ? 405 : 404));
} else if (isNaN(status) === false && status >= 400) {
reject(err);
} else {
reject(new Error(status >= 400 ? status : isNaN(err.message) === false ? err.message : http.STATUS_CODES[err.message || err] || 500));
}
}

function params (req, pos = []) {
if (pos.length > 0) {
const uri = req.parsed.pathname.replace(regex.slashEnd, "").split("/");

each(pos, i => {
req.params[i[1]] = coerce(uri[i[0]]);
});
}
}

function reduce (uri, map, arg) {
if (map !== void 0) {
each(Array.from(map.keys()).filter(route => {
let now = false,
valid;

if (regex.hasParam.test(route) === true && regex.braceLeft.test(route) === false && arg.params === false) {
arg.params = true;
now = true;

each(route.replace(regex.slashEnd, "").replace(regex.slashEnd, "").split("/"), (i, idx) => {
if (regex.isParam.test(i) === true) {
arg.pos.push([idx, i.replace(regex.isParam, "")]);
}
});

route = route.replace(/\/:(\w*)/g, "/(.*)");
}

valid = new RegExp("^" + route + "$", "i").test(uri);

if (now === true && valid === false) {
arg.params = false;
arg.pos = [];
}

return valid;
}), route => each(map.get(route), i => arg.middleware.push(i)));
}
}

module.exports = {
clone,
each,
normalize
http2Normalize,
http2Send,
normalize,
last,
params,
reduce
};
135 changes: 11 additions & 124 deletions lib/woodland.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
"use strict";

const path = require("path"),
tcoerce = require("tiny-coerce"),
lru = require("tiny-lru"),
http = require("http"),
parse = require("tiny-parse"),
mmh3 = require("murmurhash3js").x86.hash32,
methods = http.METHODS,
regex = require(path.join(__dirname, "regex.js")),
{all, delimiter} = require(path.join(__dirname, "const.js")),
{clone, each, normalize} = require(path.join(__dirname, "utility.js"));
{clone, each, http2Normalize, http2Send, normalize, params, last, reduce} = require(path.join(__dirname, "utility.js"));

class Woodland {
constructor (defaultHeaders, cacheSize, seed, coerce, http2, cacheTTL) {
constructor (defaultHeaders, cacheSize, coerce, http2, cacheTTL) {
this.blacklisted = new Set();
this.cache = lru(cacheSize, false, 0, cacheTTL);
this.coerce = coerce;
Expand All @@ -21,7 +19,6 @@ class Woodland {
this.permissions = lru(cacheSize, false, 0, cacheTTL);
this.methods = [];
this.middleware = new Map();
this.seed = seed;
}

allowed (method, uri, override = false) {
Expand Down Expand Up @@ -78,7 +75,7 @@ class Woodland {
res._headers[key] = value;
};

res.send = (...args) => this.http2Send(req, res, ...args);
res.send = (...args) => http2Send(req, res, ...args);

res.writeHead = (status = res.statusCode, headers) => {
res.statusCode = status;
Expand Down Expand Up @@ -149,56 +146,6 @@ class Woodland {
}
}

hash (arg) {
return mmh3(arg, this.seed);
}

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

if (res.statusCode < status) {
res.statusCode = status;
}

// Calling this for http1 middleware that's hooked the method
res.writeHead(res.statusCode);

each(Object.keys(res._headers).filter(i => regex.invalid.test(i) === false), i => {
output[i] = res._headers[i];
});

if (headers !== null) {
each(Object.keys(headers).filter(i => regex.invalid.test(i) === false), i => {
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);
}
}

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

if (err === void 0) {
reject(new Error(regex.hasGet.test(req.allow || "") ? 405 : 404));
} else if (isNaN(status) === false && status >= 400) {
reject(err);
} else {
reject(new Error(status >= 400 ? status : isNaN(err.message) === false ? err.message : http.STATUS_CODES[err.message || err] || 500));
}
}

list (method = "get", type = "array") {
let result;

Expand All @@ -215,25 +162,6 @@ class Woodland {
return result;
}

normalize (arg) {
const obj = clone(arg);

obj.headers.host = obj.headers[":authority"].replace(/:.*$/, "");
obj.method = obj.headers[":method"];
obj.connection = {remoteAddress: "0.0.0.0"};
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;
}

onclose () {}

onconnect () {}
Expand All @@ -244,9 +172,7 @@ class Woodland {
status = isNaN(res.statusCode) === false && res.statusCode >= 400 ? res.statusCode : numeric ? Number(err.message) : 500,
output = numeric ? http.STATUS_CODES[status] : err.message;

res.statusCode = status;
res.writeHead(status, {"content-type": "text/plain", "content-length": Buffer.byteLength(output)});
res.end(output);
res.send(output, status, {"content-type": "text/plain", "content-length": Buffer.byteLength(output)});
} else {
const numeric = isNaN(err.message) === false,
status = numeric ? Number(err.message) : 500,
Expand All @@ -258,47 +184,8 @@ class Woodland {

onfinish () {}

params (req, pos = []) {
const uri = req.parsed.pathname.replace(regex.slashEnd, "").split("/");

each(pos, i => {
req.params[i[1]] = tcoerce(uri[i[0]]);
});
}

reduce (uri, map, arg) {
if (map !== void 0) {
each(Array.from(map.keys()).filter(route => {
let now = false,
valid;

if (regex.hasParam.test(route) === true && regex.braceLeft.test(route) === false && arg.params === false) {
arg.params = true;
now = true;

each(route.replace(regex.slashEnd, "").replace(regex.slashEnd, "").split("/"), (i, idx) => {
if (regex.isParam.test(i) === true) {
arg.pos.push([idx, i.replace(regex.isParam, "")]);
}
});

route = route.replace(/\/:(\w*)/g, "/(.*)");
}

valid = new RegExp("^" + route + "$", "i").test(uri);

if (now === true && valid === false) {
arg.params = false;
arg.pos = [];
}

return valid;
}), route => each(map.get(route), i => arg.middleware.push(i)));
}
}

_route (...args) {
const [req, res] = this.http2 === false ? args : [this.normalize({headers: args[1]}), args[0]];
const [req, res] = this.http2 === false ? args : [http2Normalize({headers: args[1]}), args[0]];

return new Promise((resolve, reject) => {
let method = regex.isHead.test(req.method) ? "GET" : req.method,
Expand All @@ -317,13 +204,13 @@ class Woodland {
if (obj.done === false) {
obj.value(err, req, res, next);
} else {
this.last(req, res, reject, err);
last(req, res, reject, err);
}
} else {
obj.value(req, res, next);
}
} else {
this.last(req, res, reject, err);
last(req, res, reject, err);
}
});

Expand All @@ -344,13 +231,13 @@ class Woodland {
result = this.routes(req.parsed.pathname, method);

if (result.params !== void 0) {
this.params(req, result.pos);
params(req, result.pos);
}

middleware = result.middleware[Symbol.iterator]();
next();
} else {
this.last(req, res, reject);
last(req, res, reject);
}
}).then(() => this.onfinish(req, res)).catch(err => {
this.onerror(req, res, err);
Expand All @@ -367,8 +254,8 @@ class Woodland {
result = cached;
} else {
result = {middleware: [], params: false, pos: []};
this.reduce(uri, this.middleware.get(all.toUpperCase()), result);
this.reduce(uri, this.middleware.get(method), result);
reduce(uri, this.middleware.get(all.toUpperCase()), result);
reduce(uri, this.middleware.get(method), result);
this.cache.set(key, result);
}

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit acd5e28

Please sign in to comment.