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.

222 lines
6.8 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
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
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 url = require('url')
  4. const arrayify = require('array-back')
  5. /**
  6. * @module local-web-server
  7. */
  8. module.exports = localWebServer
  9. /**
  10. * Returns a Koa application you can launch or mix into an existing app.
  11. *
  12. * @param [options] {object} - options
  13. * @param [options.static] {object} - koa-static config
  14. * @param [options.static.root=.] {string} - root directory
  15. * @param [options.static.options] {string} - [options](https://github.com/koajs/static#options)
  16. * @param [options.serveIndex] {object} - koa-serve-index config
  17. * @param [options.serveIndex.path=.] {string} - root directory
  18. * @param [options.serveIndex.options] {string} - [options](https://github.com/expressjs/serve-index#options)
  19. * @param [options.forbid] {string[]} - A list of forbidden routes, each route being an [express route-path](http://expressjs.com/guide/routing.html#route-paths).
  20. * @param [options.spa] {string} - specify an SPA file to catch requests for everything but static assets.
  21. * @param [options.log] {object} - [morgan](https://github.com/expressjs/morgan) config
  22. * @param [options.log.format] {string} - [log format](https://github.com/expressjs/morgan#predefined-formats)
  23. * @param [options.log.options] {object} - [options](https://github.com/expressjs/morgan#options)
  24. * @param [options.compress] {boolean} - Serve gzip-compressed resources, where applicable
  25. * @param [options.mime] {object} - A list of mime-type overrides, passed directly to [mime.define()](https://github.com/broofa/node-mime#mimedefine)
  26. * @param [options.rewrite] {module:local-web-server~rewriteRule[]} - One or more rewrite rules
  27. * @param [options.verbose] {boolean} - Print detailed output, useful for debugging
  28. *
  29. * @alias module:local-web-server
  30. * @return {external:KoaApplication}
  31. * @example
  32. * const localWebServer = require('local-web-server')
  33. * localWebServer().listen(8000)
  34. */
  35. function localWebServer (options) {
  36. options = Object.assign({
  37. static: {},
  38. serveIndex: {},
  39. spa: null,
  40. log: {},
  41. compress: false,
  42. mime: {},
  43. forbid: [],
  44. rewrite: [],
  45. verbose: false,
  46. mocks: []
  47. }, options)
  48. if (options.verbose) {
  49. process.env.DEBUG = '*'
  50. }
  51. const log = options.log
  52. log.options = log.options || {}
  53. if (options.verbose && !log.format) {
  54. log.format = 'none'
  55. }
  56. if (!options.static.root) options.static.root = process.cwd()
  57. if (!options.serveIndex.path) options.serveIndex.path = process.cwd()
  58. options.rewrite = arrayify(options.rewrite)
  59. options.forbid = arrayify(options.forbid)
  60. options.mocks = arrayify(options.mocks)
  61. const debug = require('debug')('local-web-server')
  62. const Koa = require('koa')
  63. const convert = require('koa-convert')
  64. const cors = require('kcors')
  65. const _ = require('koa-route')
  66. const json = require('koa-json')
  67. const bodyParser = require('koa-bodyparser')
  68. const mw = require('./middleware')
  69. const app = new Koa()
  70. const _use = app.use
  71. app.use = x => _use.call(app, convert(x))
  72. /* CORS: allow from any origin */
  73. app.use(cors())
  74. /* pretty print JSON */
  75. app.use(json())
  76. /* rewrite rules */
  77. if (options.rewrite && options.rewrite.length) {
  78. options.rewrite.forEach(route => {
  79. if (route.to) {
  80. if (url.parse(route.to).host) {
  81. debug('proxy rewrite', `${route.from} -> ${route.to}`)
  82. app.use(_.all(route.from, mw.proxyRequest(route, app)))
  83. } else {
  84. const rewrite = require('koa-rewrite')
  85. const rmw = rewrite(route.from, route.to)
  86. rmw._name = 'rewrite'
  87. app.use(rmw)
  88. }
  89. }
  90. })
  91. }
  92. /* must come after rewrite. See https://github.com/nodejitsu/node-http-proxy/issues/180. */
  93. app.use(bodyParser())
  94. /* path blacklist */
  95. if (options.forbid.length) {
  96. debug('forbid', options.forbid.join(', '))
  97. app.use(mw.blacklist(options.forbid))
  98. }
  99. /* cache */
  100. if (!options['no-cache']) {
  101. const conditional = require('koa-conditional-get')
  102. const etag = require('koa-etag')
  103. app.use(conditional())
  104. app.use(etag())
  105. }
  106. /* mime-type overrides */
  107. if (options.mime) {
  108. debug('mime override', JSON.stringify(options.mime))
  109. app.use(mw.mime(options.mime))
  110. }
  111. /* compress response */
  112. if (options.compress) {
  113. const compress = require('koa-compress')
  114. debug('compression', 'enabled')
  115. app.use(compress())
  116. }
  117. /* Logging */
  118. if (log.format !== 'none') {
  119. const morgan = require('koa-morgan')
  120. if (!log.format) {
  121. const streamLogStats = require('stream-log-stats')
  122. log.options.stream = streamLogStats({ refreshRate: 500 })
  123. app.use(morgan('common', log.options))
  124. } else if (log.format === 'logstalgia') {
  125. morgan.token('date', logstalgiaDate)
  126. app.use(morgan('combined', log.options))
  127. } else {
  128. app.use(morgan(log.format, log.options))
  129. }
  130. }
  131. /* Mock Responses */
  132. options.mocks.forEach(mock => {
  133. if (mock.module) {
  134. mock.responses = require(path.resolve(path.join(options.static.root, mock.module)))
  135. }
  136. if (mock.responses) {
  137. app.use(mw.mockResponses(mock.route, mock.responses))
  138. } else if (mock.response) {
  139. mock.target = {
  140. request: mock.request,
  141. response: mock.response
  142. }
  143. app.use(mw.mockResponses(mock.route, mock.target))
  144. }
  145. })
  146. /* serve static files */
  147. if (options.static.root) {
  148. const serve = require('koa-static')
  149. app.use(serve(options.static.root, options.static.options))
  150. }
  151. /* serve directory index */
  152. if (options.serveIndex.path) {
  153. const serveIndex = require('koa-serve-index')
  154. app.use(serveIndex(options.serveIndex.path, options.serveIndex.options))
  155. }
  156. /* for any URL not matched by static (e.g. `/search`), serve the SPA */
  157. if (options.spa) {
  158. const send = require('koa-send')
  159. debug('SPA', options.spa)
  160. app.use(_.all('*', function spa (ctx, route, next) {
  161. const root = path.resolve(options.static.root) || process.cwd()
  162. return send(ctx, options.spa, { root: root }).then(next)
  163. }))
  164. }
  165. return app
  166. }
  167. function logstalgiaDate () {
  168. var d = new Date()
  169. return (`${d.getDate()}/${d.getUTCMonth()}/${d.getFullYear()}:${d.toTimeString()}`).replace('GMT', '').replace(' (BST)', '')
  170. }
  171. process.on('unhandledRejection', (reason, p) => {
  172. throw reason
  173. })
  174. /**
  175. * The `from` and `to` routes are specified using [express route-paths](http://expressjs.com/guide/routing.html#route-paths)
  176. *
  177. * @example
  178. * ```json
  179. * {
  180. * "rewrite": [
  181. * { "from": "/css/*", "to": "/build/styles/$1" },
  182. * { "from": "/npm/*", "to": "http://registry.npmjs.org/$1" },
  183. * { "from": "/:user/repos/:name", "to": "https://api.github.com/repos/:user/:name" }
  184. * ]
  185. * }
  186. * ```
  187. *
  188. * @typedef rewriteRule
  189. * @property from {string} - request route
  190. * @property to {string} - target route
  191. */
  192. /**
  193. * @external KoaApplication
  194. * @see https://github.com/koajs/koa/blob/master/docs/api/index.md#application
  195. */