prepareProxy.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file at
  6. * https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
  7. */
  8. const fs = require('fs')
  9. const url = require('url')
  10. const path = require('path')
  11. const { chalk } = require('@vue/cli-shared-utils')
  12. const address = require('address')
  13. const defaultConfig = {
  14. logLevel: 'silent',
  15. secure: false,
  16. changeOrigin: true,
  17. ws: true,
  18. xfwd: true
  19. }
  20. module.exports = function prepareProxy (proxy, appPublicFolder) {
  21. // `proxy` lets you specify alternate servers for specific requests.
  22. // It can either be a string or an object conforming to the Webpack dev server proxy configuration
  23. // https://webpack.github.io/docs/webpack-dev-server.html
  24. if (!proxy) {
  25. return undefined
  26. }
  27. if (Array.isArray(proxy) || (typeof proxy !== 'object' && typeof proxy !== 'string')) {
  28. console.log(
  29. chalk.red(
  30. 'When specified, "proxy" in package.json must be a string or an object.'
  31. )
  32. )
  33. console.log(
  34. chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".')
  35. )
  36. console.log(
  37. chalk.red(
  38. 'Either remove "proxy" from package.json, or make it an object.'
  39. )
  40. )
  41. process.exit(1)
  42. }
  43. // If proxy is specified, let it handle any request except for
  44. // files in the public folder and requests to the WebpackDevServer socket endpoint.
  45. // https://github.com/facebook/create-react-app/issues/6720
  46. function mayProxy (pathname) {
  47. const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1))
  48. const isPublicFileRequest = fs.existsSync(maybePublicPath)
  49. const isWdsEndpointRequest = pathname.startsWith('/sockjs-node') // used by webpackHotDevClient
  50. return !(isPublicFileRequest || isWdsEndpointRequest)
  51. }
  52. function createProxyEntry (target, usersOnProxyReq, context) {
  53. // #2478
  54. // There're a little-known use case that the `target` field is an object rather than a string
  55. // https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/https.md
  56. if (typeof target === 'string' && process.platform === 'win32') {
  57. target = resolveLoopback(target)
  58. }
  59. return {
  60. target,
  61. context (pathname, req) {
  62. // is a static asset
  63. if (!mayProxy(pathname)) {
  64. return false
  65. }
  66. if (context) {
  67. // Explicit context, e.g. /api
  68. return pathname.match(context)
  69. } else {
  70. // not a static request
  71. if (req.method !== 'GET') {
  72. return true
  73. }
  74. // Heuristics: if request `accept`s text/html, we pick /index.html.
  75. // Modern browsers include text/html into `accept` header when navigating.
  76. // However API calls like `fetch()` won’t generally accept text/html.
  77. // If this heuristic doesn’t work well for you, use a custom `proxy` object.
  78. return (
  79. req.headers.accept &&
  80. req.headers.accept.indexOf('text/html') === -1
  81. )
  82. }
  83. },
  84. onProxyReq (proxyReq, req, res) {
  85. if (usersOnProxyReq) {
  86. usersOnProxyReq(proxyReq, req, res)
  87. }
  88. // Browsers may send Origin headers even with same-origin
  89. // requests. To prevent CORS issues, we have to change
  90. // the Origin to match the target URL.
  91. if (!proxyReq.agent && proxyReq.getHeader('origin')) {
  92. proxyReq.setHeader('origin', target)
  93. }
  94. },
  95. onError: onProxyError(target)
  96. }
  97. }
  98. // Support proxy as a string for those who are using the simple proxy option
  99. if (typeof proxy === 'string') {
  100. if (!/^http(s)?:\/\//.test(proxy)) {
  101. console.log(
  102. chalk.red(
  103. 'When "proxy" is specified in package.json it must start with either http:// or https://'
  104. )
  105. )
  106. process.exit(1)
  107. }
  108. return [
  109. Object.assign({}, defaultConfig, createProxyEntry(proxy))
  110. ]
  111. }
  112. // Otherwise, proxy is an object so create an array of proxies to pass to webpackDevServer
  113. return Object.keys(proxy).map(context => {
  114. const config = proxy[context]
  115. if (!config.hasOwnProperty('target')) {
  116. console.log(
  117. chalk.red(
  118. 'When `proxy` in package.json is an object, each `context` object must have a ' +
  119. '`target` property specified as a url string'
  120. )
  121. )
  122. process.exit(1)
  123. }
  124. const entry = createProxyEntry(config.target, config.onProxyReq, context)
  125. return Object.assign({}, defaultConfig, config, entry)
  126. })
  127. }
  128. function resolveLoopback (proxy) {
  129. const o = url.parse(proxy)
  130. o.host = undefined
  131. if (o.hostname !== 'localhost') {
  132. return proxy
  133. }
  134. // Unfortunately, many languages (unlike node) do not yet support IPv6.
  135. // This means even though localhost resolves to ::1, the application
  136. // must fall back to IPv4 (on 127.0.0.1).
  137. // We can re-enable this in a few years.
  138. /* try {
  139. o.hostname = address.ipv6() ? '::1' : '127.0.0.1';
  140. } catch (_ignored) {
  141. o.hostname = '127.0.0.1';
  142. }*/
  143. try {
  144. // Check if we're on a network; if we are, chances are we can resolve
  145. // localhost. Otherwise, we can just be safe and assume localhost is
  146. // IPv4 for maximum compatibility.
  147. if (!address.ip()) {
  148. o.hostname = '127.0.0.1'
  149. }
  150. } catch (_ignored) {
  151. o.hostname = '127.0.0.1'
  152. }
  153. return url.format(o)
  154. }
  155. // We need to provide a custom onError function for httpProxyMiddleware.
  156. // It allows us to log custom error messages on the console.
  157. function onProxyError (proxy) {
  158. return (err, req, res) => {
  159. const host = req.headers && req.headers.host
  160. console.log(
  161. chalk.red('Proxy error:') +
  162. ' Could not proxy request ' +
  163. chalk.cyan(req.url) +
  164. ' from ' +
  165. chalk.cyan(host) +
  166. ' to ' +
  167. chalk.cyan(proxy) +
  168. '.'
  169. )
  170. console.log(
  171. 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
  172. chalk.cyan(err.code) +
  173. ').'
  174. )
  175. console.log()
  176. // And immediately send the proper error response to the client.
  177. // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
  178. if (res.writeHead && !res.headersSent) {
  179. res.writeHead(500)
  180. }
  181. res.end(
  182. 'Proxy error: Could not proxy request ' +
  183. req.url +
  184. ' from ' +
  185. host +
  186. ' to ' +
  187. proxy +
  188. ' (' +
  189. err.code +
  190. ').'
  191. )
  192. }
  193. }