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.

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