huncwot
Version:
Fast, opinionated and minimal Node.js web framework built for ES6/7 era
207 lines (166 loc) • 5.09 kB
JavaScript
// Copyright 2017 Zaiste & contributors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const debug = require('debug')('huncwot:core');
const http = require('http');
const Emitter = require('events');
const Stream = require('stream');
const assert = require('assert');
const rawBody = require('raw-body');
const querystring = require('querystring');
const extractParams = require('path-to-regexp');
const { pick, compose, match } = require('./util');
class Huncwot extends Emitter {
constructor() {
super();
this.middlewareList = [];
}
listen() {
debug('listen');
const server = http.createServer(async (request, response) => {
const context = {
params: {},
request
};
try {
await compose(
handleRoute(response),
handleError,
...this.middlewareList,
notFound
)(context);
} catch (e) {
response.statusCode = 500;
response.end();
}
})
return server.listen.apply(server, arguments);
}
use(func) {
if (typeof func !== 'function') {
throw new TypeError('middleware must be a function');
}
debug(`use: '%s'`, func.name || '-');
this.middlewareList.push(func);
return this;
}
get(path, func) {
this.use(route('GET', path, func));
}
post(path, func) {
this.use(route('POST', path, func));
}
put(path, func) {
this.use(route('PUT', path, func));
}
patch(path, func) {
this.use(route('PATCH', path, func));
}
delete(path, func) {
this.use(route('DELETE', path, func));
}
}
function route(method, path, func) {
return async (context, next) => {
const { pathname, query } = require('url').parse(context.request.url, true)
const params = match()(path)(pathname);
if (context.request.method === method && params) {
Object.assign(context.params, params, query);
return await func(context)
} else {
return await next();
}
}
}
async function handleError(context, next) {
try {
return await next()
} catch (error) {
assert(error instanceof Error, `non-error thrown: ${error}`);
if (error instanceof HTTPError) {
return { statusCode: error.statusCode, body: error.message }
} else {
return { statusCode: 500, body: error.message }
}
}
}
function handleRoute(response) {
return async (context, next) => {
const buffer = await rawBody(context.request);
if (buffer.length > 0) {
const contentType = context.request.headers['content-type'].split(';')[0];
switch (contentType) {
case 'application/x-www-form-urlencoded':
Object.assign(context.params, querystring.parse(buffer.toString()))
break;
case 'application/json':
Object.assign(context.params, JSON.parse(buffer))
break;
default:
}
}
let r = await next();
if (!r) throw new Error('One of your routes does not return a value. You probably forgot a `return` statement.');
let body;
let headers;
let type;
let encoding;
response.statusCode = r.statusCode || 200;
if (typeof r === 'string' || r instanceof Stream) {
body = r;
} else {
body = r.body;
headers = r.headers;
type = r.type;
encoding = r.encoding;
}
for (var key in headers) {
response.setHeader(key, headers[key]);
}
if (encoding) response.setHeader('Content-Encoding', encoding);
if (body === null) {
response.end();
return;
}
if (Buffer.isBuffer(body)) {
response.setHeader('Content-Type', type || 'application/octet-stream');
response.setHeader('Content-Length', body.length);
response.end(body);
return;
}
if (body instanceof Stream) {
if (! response.getHeader('Content-Type'))
response.setHeader('Content-Type', type || 'text/html');
body.pipe(response);
return;
}
let str = body;
if (typeof body === 'object' || typeof body === 'number') {
str = JSON.stringify(body);
response.setHeader('Content-Type', 'application/json');
} else {
response.setHeader('Content-Type', type || 'text/plain');
}
response.setHeader('Content-Length', Buffer.byteLength(str));
response.end(str);
}
}
async function notFound() {
throw new HTTPError(404, `There's no such route.`);
}
class HTTPError extends Error {
constructor(statusCode, message) {
super(message);
this.statusCode = statusCode;
}
}
module.exports = Huncwot;