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.

113 lines
3.1 KiB

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 arrayify = require('array-back')
  6. const pathToRegexp = require('path-to-regexp')
  7. const debug = require('debug')('local-web-server')
  8. /**
  9. * @module middleware
  10. */
  11. exports.proxyRequest = proxyRequest
  12. exports.blacklist = blacklist
  13. exports.mockResponses = mockResponses
  14. exports.mime = mime
  15. function proxyRequest (route, app) {
  16. const httpProxy = require('http-proxy')
  17. const proxy = httpProxy.createProxyServer({
  18. changeOrigin: true
  19. })
  20. return function * proxyMiddleware () {
  21. const next = arguments[arguments.length - 1]
  22. const keys = []
  23. route.re = pathToRegexp(route.from, keys)
  24. route.new = this.url.replace(route.re, route.to)
  25. keys.forEach((key, index) => {
  26. const re = RegExp(`:${key.name}`, 'g')
  27. route.new = route.new
  28. .replace(re, arguments[index] || '')
  29. })
  30. /* test no keys remain in the new path */
  31. keys.length = 0
  32. pathToRegexp(url.parse(route.new).path, keys)
  33. if (keys.length) {
  34. this.throw(500, `[PROXY] Invalid target URL: ${route.new}`)
  35. return next()
  36. }
  37. this.response = false
  38. debug('proxy request', `from: ${this.path}, to: ${url.parse(route.new).href}`)
  39. proxy.once('error', err => {
  40. this.throw(500, `[PROXY] ${err.message}: ${route.new}`)
  41. })
  42. proxy.once('proxyReq', function (proxyReq) {
  43. proxyReq.path = url.parse(route.new).path
  44. })
  45. proxy.web(this.req, this.res, { target: route.new })
  46. }
  47. }
  48. function blacklist (forbid) {
  49. return function blacklist (ctx, next) {
  50. if (forbid.some(expression => pathToRegexp(expression).test(ctx.path))) {
  51. ctx.throw(403, http.STATUS_CODES[403])
  52. } else {
  53. return next()
  54. }
  55. }
  56. }
  57. function mockResponses (options) {
  58. options = options || { root: process.cwd() }
  59. return function mockResponses (ctx, next) {
  60. if (/\.mock.js$/.test(ctx.path)) {
  61. const mocks = arrayify(require(path.join(options.root, ctx.path)))
  62. const testValue = require('test-value')
  63. const t = require('typical')
  64. /* find a mock with compatible method and accepts */
  65. let mock = mocks.find(mock => {
  66. return testValue(mock, {
  67. request: {
  68. method: [ ctx.method, undefined ],
  69. accepts: type => ctx.accepts(type)
  70. }
  71. })
  72. })
  73. /* else take the first mock without a request (no request means 'all requests') */
  74. if (!mock) {
  75. mock = mocks.find(mock => !mock.request)
  76. }
  77. if (mock) {
  78. let mockedResponse = mock.response
  79. if (t.isFunction(mock.response)) {
  80. mockedResponse = new mock.response(ctx)
  81. }
  82. debug('mock response: %j', mockedResponse)
  83. Object.assign(ctx.response, mockedResponse)
  84. }
  85. } else {
  86. return next()
  87. }
  88. }
  89. }
  90. function mime (mimeTypes) {
  91. return function mime (ctx, next) {
  92. return next().then(() => {
  93. const reqPathExtension = path.extname(ctx.path).slice(1)
  94. Object.keys(mimeTypes).forEach(mimeType => {
  95. const extsToOverride = mimeTypes[mimeType]
  96. if (extsToOverride.indexOf(reqPathExtension) > -1) ctx.type = mimeType
  97. })
  98. })
  99. }
  100. }