Issues (3)

src/server.js (1 issue)

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
As per coding-style, switch statements should have a default case.
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