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.

111 lines
3.1 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
  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) {
  17. const httpProxy = require('http-proxy')
  18. const proxy = httpProxy.createProxyServer({
  19. changeOrigin: true
  20. })
  21. return function proxyMiddleware () {
  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 + 1] || '')
  29. })
  30. debug('proxy request', `from: ${this.path}, to: ${url.parse(route.new).href}`)
  31. return new Promise((resolve, reject) => {
  32. proxy.once('error', err => {
  33. err.message = `[PROXY] Error: ${err.message} Target: ${route.new}`
  34. reject(err)
  35. })
  36. proxy.once('proxyReq', function (proxyReq) {
  37. proxyReq.path = url.parse(route.new).path
  38. })
  39. proxy.once('close', resolve)
  40. proxy.web(this.req, this.res, { target: route.new })
  41. })
  42. }
  43. }
  44. function blacklist (forbid) {
  45. return function blacklist (ctx, next) {
  46. if (forbid.some(expression => pathToRegexp(expression).test(ctx.path))) {
  47. ctx.throw(403, http.STATUS_CODES[403])
  48. } else {
  49. return next()
  50. }
  51. }
  52. }
  53. function mime (mimeTypes) {
  54. return function mime (ctx, next) {
  55. return next().then(() => {
  56. const reqPathExtension = path.extname(ctx.path).slice(1)
  57. Object.keys(mimeTypes).forEach(mimeType => {
  58. const extsToOverride = mimeTypes[mimeType]
  59. if (extsToOverride.indexOf(reqPathExtension) > -1) ctx.type = mimeType
  60. })
  61. })
  62. }
  63. }
  64. function mockResponses (route, targets) {
  65. targets = arrayify(targets)
  66. debug('mock route: %s, targets: %s', route, targets.length)
  67. const pathRe = pathToRegexp(route)
  68. return function mockResponse (ctx, next) {
  69. if (pathRe.test(ctx.path)) {
  70. const testValue = require('test-value')
  71. /* find a mock with compatible method and accepts */
  72. let target = targets.find(target => {
  73. return testValue(target, {
  74. request: {
  75. method: [ ctx.method, undefined ],
  76. accepts: type => ctx.accepts(type)
  77. }
  78. })
  79. })
  80. /* else take the first target without a request (no request means 'all requests') */
  81. if (!target) {
  82. target = targets.find(target => !target.request)
  83. }
  84. if (target) {
  85. if (t.isFunction(target.response)) {
  86. const pathMatches = ctx.path.match(pathRe).slice(1)
  87. return target.response.apply(null, [ctx].concat(pathMatches))
  88. } else if (t.isPlainObject(target.response)) {
  89. Object.assign(ctx.response, target.response)
  90. } else {
  91. throw new Error(`Invalid response: ${JSON.stringify(target.response)}`)
  92. }
  93. }
  94. } else {
  95. return next()
  96. }
  97. }
  98. }