/** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file at * https://github.com/facebookincubator/create-react-app/blob/master/LICENSE */ const fs = require('fs') const url = require('url') const path = require('path') const { chalk } = require('@vue/cli-shared-utils') const address = require('address') const defaultConfig = { logLevel: 'silent', secure: false, changeOrigin: true, ws: true, xfwd: true } module.exports = function prepareProxy (proxy, appPublicFolder) { // `proxy` lets you specify alternate servers for specific requests. // It can either be a string or an object conforming to the Webpack dev server proxy configuration // https://webpack.github.io/docs/webpack-dev-server.html if (!proxy) { return undefined } if (Array.isArray(proxy) || (typeof proxy !== 'object' && typeof proxy !== 'string')) { console.log( chalk.red( 'When specified, "proxy" in package.json must be a string or an object.' ) ) console.log( chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".') ) console.log( chalk.red( 'Either remove "proxy" from package.json, or make it an object.' ) ) process.exit(1) } // If proxy is specified, let it handle any request except for // files in the public folder and requests to the WebpackDevServer socket endpoint. // https://github.com/facebook/create-react-app/issues/6720 function mayProxy (pathname) { const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1)) const isPublicFileRequest = fs.existsSync(maybePublicPath) const isWdsEndpointRequest = pathname.startsWith('/sockjs-node') // used by webpackHotDevClient return !(isPublicFileRequest || isWdsEndpointRequest) } function createProxyEntry (target, usersOnProxyReq, context) { // #2478 // There're a little-known use case that the `target` field is an object rather than a string // https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/https.md if (typeof target === 'string' && process.platform === 'win32') { target = resolveLoopback(target) } return { target, context (pathname, req) { // is a static asset if (!mayProxy(pathname)) { return false } if (context) { // Explicit context, e.g. /api return pathname.match(context) } else { // not a static request if (req.method !== 'GET') { return true } // Heuristics: if request `accept`s text/html, we pick /index.html. // Modern browsers include text/html into `accept` header when navigating. // However API calls like `fetch()` won’t generally accept text/html. // If this heuristic doesn’t work well for you, use a custom `proxy` object. return ( req.headers.accept && req.headers.accept.indexOf('text/html') === -1 ) } }, onProxyReq (proxyReq, req, res) { if (usersOnProxyReq) { usersOnProxyReq(proxyReq, req, res) } // Browsers may send Origin headers even with same-origin // requests. To prevent CORS issues, we have to change // the Origin to match the target URL. if (!proxyReq.agent && proxyReq.getHeader('origin')) { proxyReq.setHeader('origin', target) } }, onError: onProxyError(target) } } // Support proxy as a string for those who are using the simple proxy option if (typeof proxy === 'string') { if (!/^http(s)?:\/\//.test(proxy)) { console.log( chalk.red( 'When "proxy" is specified in package.json it must start with either http:// or https://' ) ) process.exit(1) } return [ Object.assign({}, defaultConfig, createProxyEntry(proxy)) ] } // Otherwise, proxy is an object so create an array of proxies to pass to webpackDevServer return Object.keys(proxy).map(context => { const config = proxy[context] if (!config.hasOwnProperty('target')) { console.log( chalk.red( 'When `proxy` in package.json is an object, each `context` object must have a ' + '`target` property specified as a url string' ) ) process.exit(1) } const entry = createProxyEntry(config.target, config.onProxyReq, context) return Object.assign({}, defaultConfig, config, entry) }) } function resolveLoopback (proxy) { const o = url.parse(proxy) o.host = undefined if (o.hostname !== 'localhost') { return proxy } // Unfortunately, many languages (unlike node) do not yet support IPv6. // This means even though localhost resolves to ::1, the application // must fall back to IPv4 (on 127.0.0.1). // We can re-enable this in a few years. /* try { o.hostname = address.ipv6() ? '::1' : '127.0.0.1'; } catch (_ignored) { o.hostname = '127.0.0.1'; }*/ try { // Check if we're on a network; if we are, chances are we can resolve // localhost. Otherwise, we can just be safe and assume localhost is // IPv4 for maximum compatibility. if (!address.ip()) { o.hostname = '127.0.0.1' } } catch (_ignored) { o.hostname = '127.0.0.1' } return url.format(o) } // We need to provide a custom onError function for httpProxyMiddleware. // It allows us to log custom error messages on the console. function onProxyError (proxy) { return (err, req, res) => { const host = req.headers && req.headers.host console.log( chalk.red('Proxy error:') + ' Could not proxy request ' + chalk.cyan(req.url) + ' from ' + chalk.cyan(host) + ' to ' + chalk.cyan(proxy) + '.' ) console.log( 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' + chalk.cyan(err.code) + ').' ) console.log() // And immediately send the proper error response to the client. // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side. if (res.writeHead && !res.headersSent) { res.writeHead(500) } res.end( 'Proxy error: Could not proxy request ' + req.url + ' from ' + host + ' to ' + proxy + ' (' + err.code + ').' ) } }