You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
4.5 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. 'use strict'
  2. const path = require('path')
  3. const http = require('http')
  4. const url = require('url')
  5. const Koa = require('koa')
  6. const serve = require('koa-static')
  7. const convert = require('koa-convert')
  8. const serveIndex = require('koa-serve-index')
  9. const morgan = require('koa-morgan')
  10. const compress = require('koa-compress')
  11. const streamLogStats = require('stream-log-stats')
  12. const cors = require('kcors')
  13. const conditional = require('koa-conditional-get');
  14. const etag = require('koa-etag');
  15. const _ = require('koa-route')
  16. const mount = require('koa-mount')
  17. const httpProxy = require('http-proxy')
  18. const pathToRegexp = require('path-to-regexp')
  19. const send = require('koa-send')
  20. /**
  21. * @module local-web-server
  22. */
  23. module.exports = localWebServer
  24. /**
  25. * Returns a Koa application
  26. *
  27. * @param [options] {object} - options
  28. * @param [options.forbid] {regexp[]} - a list of forbidden routes.
  29. * @alias module:local-web-server
  30. * @example
  31. * const localWebServer = require('local-web-server')
  32. */
  33. function localWebServer (options) {
  34. options = Object.assign({
  35. static: {},
  36. serveIndex: {},
  37. log: {},
  38. compress: false,
  39. forbid: [],
  40. directories: [],
  41. proxyRoutes: []
  42. }, options)
  43. const log = options.log
  44. log.options = log.options || {}
  45. const app = new Koa()
  46. const _use = app.use
  47. app.use = x => _use.call(app, convert(x))
  48. const proxy = httpProxy.createProxyServer({
  49. changeOrigin: true
  50. })
  51. /* Proxy routes */
  52. options.proxyRoutes.forEach(route => {
  53. app.use(_.all(route.from, function * () {
  54. const keys = []
  55. route.re = pathToRegexp(route.from, keys)
  56. route.new = route.to
  57. this.response = false
  58. keys.forEach((key, index) => {
  59. const re = {
  60. token: RegExp('\\$\\{' + key.name + '\\}', 'g'),
  61. index: RegExp('\\$\\{' + index + '\\}', 'g')
  62. }
  63. route.new = route.new
  64. .replace(re.token, arguments[index] || '')
  65. .replace(re.index, arguments[index] || '')
  66. })
  67. proxy.once('proxyReq', function (proxyReq) {
  68. proxyReq.path = url.parse(route.new).path;
  69. })
  70. proxy.web(this.req, this.res, { target: route.new })
  71. }))
  72. })
  73. /* CORS: allow from any origin */
  74. app.use(cors())
  75. /* path blacklist */
  76. if (options.forbid.length) {
  77. app.use(function blacklist (ctx, next) {
  78. if (options.forbid.some(regexp => regexp.test(ctx.path))) {
  79. ctx.throw(403, http.STATUS_CODES[403])
  80. } else {
  81. return next()
  82. }
  83. })
  84. }
  85. /* Cache */
  86. if (!options['no-cache']) {
  87. app.use(conditional())
  88. app.use(etag())
  89. }
  90. /* mime-type overrides */
  91. if (options.mime) {
  92. app.use((ctx, next) => {
  93. return next().then(() => {
  94. const reqPathExtension = path.extname(ctx.path).slice(1)
  95. Object.keys(options.mime).forEach(mimeType => {
  96. const extsToOverride = options.mime[mimeType]
  97. if (extsToOverride.indexOf(reqPathExtension) > -1) ctx.type = mimeType
  98. })
  99. })
  100. })
  101. }
  102. /* compress response */
  103. if (options.compress) {
  104. app.use(compress())
  105. }
  106. /* special case log formats */
  107. if (log.format) {
  108. if (log.format === 'none'){
  109. log.format = undefined
  110. } else if (log.format === 'logstalgia') {
  111. morgan.token('date', logstalgiaDate)
  112. log.format = 'combined'
  113. }
  114. /* if no specific log format was requested, show log stats */
  115. } else {
  116. log.format = 'common'
  117. log.options.stream = streamLogStats({ refreshRate: 500 })
  118. }
  119. if (log.format) app.use(morgan.middleware(log.format, log.options))
  120. // options.static.root = [
  121. // { route: '/one', root: 'lib' },
  122. // { route: '/two', root: 'node_modules' }
  123. // ]
  124. /* serve static files */
  125. if (options.static.root) {
  126. app.use(serve(options.static.root, options.static.options))
  127. // options.static.root.forEach(config => {
  128. // app.use(mount(config.route, serve(config.root)))
  129. // app.use(mount(config.route, serveIndex(config.root)))
  130. // })
  131. }
  132. /* serve directory index */
  133. if (options.serveIndex.path) {
  134. app.use(serveIndex(options.serveIndex.path, options.serveIndex.options))
  135. }
  136. /* for any URL not matched by static (e.g. `/search`), serve the SPA */
  137. if (options.spa) {
  138. app.use(_.all('*', function * () {
  139. yield send(this, options.spa, { root: process.cwd() })
  140. }))
  141. }
  142. return app
  143. }
  144. function logstalgiaDate () {
  145. var d = new Date()
  146. return (`${d.getDate()}/${d.getUTCMonth()}/${d.getFullYear()}:${d.toTimeString()}`)
  147. .replace('GMT', '')
  148. .replace(' (BST)', '')
  149. }
  150. process.on('unhandledRejection', (reason, p) => {
  151. throw reason
  152. })