carvalhoviniciusluiz /
restify-devise
| 1 | 'use strict' |
||
| 2 | |||
| 3 | import path from 'path' |
||
| 4 | import restify from 'restify' |
||
| 5 | import logger from 'winston' |
||
| 6 | import * as errors from 'restify-errors' |
||
| 7 | import chalk from 'chalk' |
||
| 8 | import config from '../config' |
||
| 9 | import router from './router' |
||
| 10 | // import favicon from 'serve-favicon' |
||
| 11 | import compression from 'compression' |
||
| 12 | import helmet from 'helmet' |
||
| 13 | import validator from 'restify-joi-middleware' |
||
| 14 | import passport from './helpers/passport' |
||
| 15 | import restifyRender from 'restify-render-middleware' |
||
| 16 | import { pug } from 'consolidate' |
||
| 17 | import acceptLanguage from 'accept-language' |
||
| 18 | import i18n, { languages } from './helpers/i18n' |
||
| 19 | import socketio from 'socket.io' |
||
| 20 | import channels from './channels' |
||
| 21 | import corsMiddleware from 'restify-cors-middleware' |
||
| 22 | |||
| 23 | // set supported languages |
||
| 24 | acceptLanguage.languages(languages) |
||
| 25 | |||
| 26 | const server = restify.createServer({ |
||
| 27 | name: 'Restify Devise', |
||
| 28 | version: '1.0.0', |
||
| 29 | // https://github.com/restify/node-restify/issues/1219#issuecomment-328499227 |
||
| 30 | // https://github.com/makeomatic/restify-formatter-jsonapi/issues/2#issuecomment-307285452 |
||
| 31 | ignoreTrailingSlash: true |
||
| 32 | }) |
||
| 33 | |||
| 34 | // set websocket app |
||
| 35 | const io = socketio.listen(server.server) |
||
| 36 | channels(io) |
||
| 37 | |||
| 38 | server.use(restify.plugins.acceptParser(server.acceptable)) |
||
| 39 | server.use(restify.plugins.queryParser()) |
||
| 40 | server.use(restify.plugins.bodyParser()) |
||
| 41 | server.use(restify.plugins.fullResponse()) |
||
| 42 | server.use(restify.plugins.gzipResponse()) |
||
| 43 | |||
| 44 | // CORS middleware |
||
| 45 | const cors = corsMiddleware({ |
||
| 46 | preflightMaxAge: 5, |
||
| 47 | allowHeaders: ['Authorization'] |
||
| 48 | }) |
||
| 49 | |||
| 50 | server.pre(cors.preflight) |
||
| 51 | server.use(cors.actual) |
||
| 52 | |||
| 53 | // https://www.npmjs.com/package/compression#filter-1 |
||
| 54 | server.use(compression({ |
||
| 55 | filter (request, response) { |
||
| 56 | if (request.headers['x-no-compression']) { |
||
| 57 | logger.warn('server', 'X-No-Compresseion') |
||
| 58 | // don't compress responses with this request header |
||
| 59 | return false |
||
| 60 | } |
||
| 61 | |||
| 62 | // fallback to standard filter function |
||
| 63 | return compression.filter(request, response) |
||
| 64 | } |
||
| 65 | })) |
||
| 66 | |||
| 67 | // https://www.npmjs.com/package/helmet#how-it-works |
||
| 68 | server.use(helmet()) |
||
| 69 | server.use(helmet.noCache()) |
||
| 70 | server.use(helmet.referrerPolicy()) |
||
| 71 | |||
| 72 | // joi validation middleware for restify |
||
| 73 | server.use(validator({ |
||
| 74 | joiOptions: { |
||
| 75 | allowUnknown: false |
||
| 76 | }, |
||
| 77 | errorTransformer: (validationInput, joiError) => { |
||
| 78 | const { type, context } = joiError.details[0] |
||
| 79 | const retError = new errors.BadRequestError() |
||
| 80 | retError.body.message = { |
||
| 81 | warn: `'${i18n.t(context.key)}' ${i18n.t(type, context)}`, |
||
| 82 | context: joiError.details[0].context |
||
| 83 | } |
||
| 84 | return retError |
||
| 85 | } |
||
| 86 | })) |
||
| 87 | |||
| 88 | // i18n middleware for restify |
||
| 89 | server.use(i18n.handler()) |
||
| 90 | |||
| 91 | // add res.render() |
||
| 92 | server.use(restifyRender({ |
||
| 93 | engine: pug, |
||
| 94 | dir: path.join(__dirname, 'views') |
||
| 95 | })) |
||
| 96 | |||
| 97 | server.pre((request, response, next) => { |
||
| 98 | logger.info('[server]', request.method, request.url) |
||
| 99 | next() |
||
| 100 | }) |
||
| 101 | |||
| 102 | server.pre((req, res, next) => { |
||
| 103 | const headerValue = req.headers['accept-language'] |
||
| 104 | |||
| 105 | if (headerValue) { |
||
| 106 | i18n.changeLanguage(acceptLanguage.get(headerValue)) |
||
| 107 | } |
||
| 108 | |||
| 109 | next() |
||
| 110 | }) |
||
| 111 | |||
| 112 | passport.initialize(server) |
||
| 113 | |||
| 114 | // asset routing added |
||
| 115 | server.get('/assets/*', restify.plugins.serveStatic({ |
||
| 116 | directory: __dirname |
||
| 117 | // default: 'style.css' |
||
| 118 | })) |
||
| 119 | |||
| 120 | // set routes app |
||
| 121 | router(server) |
||
| 122 | |||
| 123 | // TODO |
||
| 124 | // http://restify.com/docs/plugins-api/#auditlogger |
||
| 125 | // https://github.com/trentm/node-bunyan-winston/blob/master/restify-winston.js#L18-L80 |
||
| 126 | |||
| 127 | // Restify servers emit all the events from the node http.Server and has several other events you want to listen on. |
||
| 128 | // http://nodejs.org/docs/latest/api/http.html#http_class_http_server |
||
| 129 | |||
| 130 | // When a client request is sent for a URL that does not exist, restify will emit this event. |
||
| 131 | // Note that restify checks for listeners on this event, and if there are none, responds with a default 404 handler. |
||
| 132 | // It is expected that if you listen for this event, you respond to the client. |
||
| 133 | |||
| 134 | server.on('NotFound', (request, response, error, next) => { |
||
| 135 | const url = (request.isSecure()) |
||
| 136 | ? 'https' |
||
| 137 | : 'http' + '://' + request.headers.host + request.url |
||
| 138 | |||
| 139 | logger.warn('[server]', `Route ${chalk.cyan(url)} not found`) |
||
| 140 | return next(new errors.NotFoundError()) |
||
| 141 | }) |
||
| 142 | |||
| 143 | // When a client request is sent for a URL that does exist, but you have not registered a route for that HTTP verb, |
||
| 144 | // restify will emit this event. Note that restify checks for listeners on this event, and if there are none, |
||
| 145 | // responds with a default 405 handler. It is expected that if you listen for this event, you respond to the client. |
||
| 146 | // server.on('MethodNotAllowed', (request, response, next) => {}) |
||
| 147 | |||
| 148 | // When a client request is sent for a route that exists, but does not match the version(s) on those routes, |
||
| 149 | // restify will emit this event. Note that restify checks for listeners on this event, and if there are none, |
||
| 150 | // responds with a default 400 handler. It is expected that if you listen for this event, you respond to the client. |
||
| 151 | // server.on('VersionNotAllowed', (request, response, next) => {}) |
||
| 152 | |||
| 153 | // When a client request is sent for a route that exist, but has a content-type mismatch, |
||
| 154 | // restify will emit this event. Note that restify checks for listeners on this event, and if there are none, |
||
| 155 | // responds with a default 415 handler. It is expected that if you listen for this event, you respond to the client. |
||
| 156 | // server.on('UnsupportedMediaType', (request, response, next) => {}) |
||
| 157 | |||
| 158 | // Emitted after a route has finished all the handlers you registered. |
||
| 159 | // You can use this to write audit logs, etc. The route parameter will be the Route object that ran. |
||
| 160 | // server.on('after', (request, response, route, error) => {}) |
||
| 161 | |||
| 162 | // Emitted when some handler throws an uncaughtException somewhere in the chain. |
||
| 163 | // The default behavior is to just call res.send(error), and let the built-ins in restify handle transforming, |
||
| 164 | // but you can override to whatever you want here. |
||
| 165 | server.on('uncaughtException', (request, response, route, error) => { |
||
| 166 | logger.error('[server]', error.stack) |
||
| 167 | response.send(error) |
||
| 168 | }) |
||
| 169 | |||
| 170 | // error handler |
||
| 171 | server.on('error', (error) => { |
||
| 172 | onError(error) |
||
| 173 | }) |
||
| 174 | |||
| 175 | const port = normalizePort(config.server.port) |
||
| 176 | |||
| 177 | /** |
||
| 178 | * Normalize a port into a number, string, or false. |
||
| 179 | */ |
||
| 180 | function normalizePort (val) { |
||
| 181 | const port = parseInt(val, 10) |
||
| 182 | |||
| 183 | if (isNaN(port)) { |
||
| 184 | // named pipe |
||
| 185 | return val |
||
| 186 | } |
||
| 187 | |||
| 188 | if (port >= 0) { |
||
| 189 | // port number |
||
| 190 | return port |
||
| 191 | } |
||
| 192 | |||
| 193 | return false |
||
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Event listener for HTTP server "error" event. |
||
| 198 | */ |
||
| 199 | function onError (err) { |
||
| 200 | if (err.syscall !== 'listen') { |
||
| 201 | throw err |
||
| 202 | } |
||
| 203 | |||
| 204 | const bind = typeof port === 'string' |
||
| 205 | ? 'Pipe ' + port |
||
| 206 | : 'Port ' + port |
||
| 207 | |||
| 208 | // handle specific listen errors with friendly messages |
||
| 209 | /* eslint-disable no-unreachable */ |
||
| 210 | switch (err.code) { |
||
|
0 ignored issues
–
show
Coding Style
introduced
by
Loading history...
|
|||
| 211 | case 'EACCES': |
||
| 212 | err.message = `${bind} requires elevated privileges` |
||
| 213 | logger.error('[server]', err.message) |
||
| 214 | break |
||
| 215 | |||
| 216 | // lsof -i tcp:8088 |
||
| 217 | // kill -9 <PID> |
||
| 218 | case 'EADDRINUSE': |
||
| 219 | err.message = `${bind} is already in use` |
||
| 220 | logger.error('[server]', err.message) |
||
| 221 | break |
||
| 222 | } |
||
| 223 | throw err |
||
| 224 | } |
||
| 225 | |||
| 226 | module.exports = server |
||
| 227 |