/*jshint laxcomma: true, smarttabs: true, node: true, esnext: true*/ 'use strict' /** * Simple router class for directing requests * @module skyring/lib/server/router * @author Eric Satterwhite * @since 1.0.0 * @requires skyring/lib/server/route * @requires skyring/lib/server/request * @requires skyring/lib/server/response */ const Route = require('./route') const Request = require('./request') const Response = require('./response') const debug = require('debug')('skyring:server:router') /** * @constructor * @alias module:skyring/lib/server/router * @param {module:skyring/lib/server/node} node The node linked to the application hashring to pass with each request * @param {module:skyring/lib/timer} timer A timer instance associated with the application hashring to pass with each request * @example var x = new Router(node, timers) router.handle(req, res) */ function Router( node, timers ) { this.routes = new Map() this.route_options = new Map() this.node = node this.timers = timers } /** * Adds a new get handler to the router a new get handler to the router * @param {String} path The url path to route on * @param {Function} handler The handler function to call when the route is matched **/ Router.prototype.get = function get( path, fn ) { return this.route( path, 'GET', fn ) } /** * Adds a new put handler to the router * @param {String} path The url path to route on * @param {Function} handler The handler function to call when the route is matched **/ Router.prototype.put = function put( path, fn ) { return this.route( path, 'PUT', fn) } /** * Adds a new post handler to the router * @param {String} path The url path to route on * @param {Function} handler The handler function to call when the route is matched **/ Router.prototype.post = function post( path, fn ) { return this.route( path, 'POST', fn) } /** * Adds a new patch handler to the router * @param {String} path The url path to route on * @param {Function} handler The handler function to call when the route is matched **/ Router.prototype.patch = function patch( path, fn ) { return this.route( path, 'PATCH', fn) } /** * Adds a new delete handler to the router * @param {String} path The url path to route on * @param {Function} handler The handler function to call when the route is matched **/ Router.prototype.delete = function( path, fn ) { return this.route( path, 'DELETE', fn ) } /** * Adds a new opts handler to the router * @param {String} path The url path to route on * @param {Function} handler The handler function to call when the route is matched **/ Router.prototype.options = function options( path, fn ) { return this.route( path, 'OPTIONS', fn ) } /** * Adds a new route handler to the router * @param {String} path The url path to route on * @param {String} m handlerethod The http method to associate to the route * @param {Function} The handler function to call when the route is matched * @returns {module:skyring/lib/server/route} **/ Router.prototype.route = function route(path, method, fn) { const _method = method.toUpperCase() const map = this.routes.get(_method) || new Map() if (map.has(path)) { const rte = map.get(path) rte.use(fn) return rte } const rte = new Route(path, _method) rte.use(fn) map.set(path, rte) this.routes.set(_method, map) return rte } /** * Entrypoint for an incoming request * Customer properties are attached to an `$` object on the request rather than the request * itself to avoid V8 deopts / perf penalties * @param {http.IncomingMessage} req * @param {http.ServerResponse} res * @example http.createServer((req, res) => { router.handle(req, res) }) **/ Router.prototype.handle = function handle(req, res) { req.$ = new Request(req) res.$ = new Response(res) req.$.timers = this.timers const path = req.$.path const method = req.method.toUpperCase() const map = this.routes.get(method) if (map) { let rte = map.get(path) if (rte) { req.$.params = Object.create(null) return this.handleRoute(rte, req, res) } for (const route of map.values()) { const params = route.match(path) if (params) { req.$.params = params return this.handleRoute(route, req, res) } } } return notFound(req, res) } /** * Responsible for executing the middleware stack on the route ( including the end handler ) * @param {module:skyring/lib/server/route} route * @param {http.IncomingMessage} req * @param {http.ServerResponse} res **/ Router.prototype.handleRoute = function handleRoute(route, req, res) { debug('routing ', route.method, route.path) route.process(req, res, this.node, (err) => { if (err) return res.$.error(err) if (res.$.body) return res.$.json(res.$.body) return res.$.end() }) } function notFound( req, res ) { res.writeHead(404,{ 'Content-Type': 'application/json' }) res.end(JSON.stringify({message: 'Not Found' })) } module.exports = Router