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.

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