// prepareProxy.js
const fs = require('fs');
const url = require('url');
const path = require('path');
const address = require('address');
const defaultConfig = {
logLevel: 'silent',
secure: false,
changeOrigin: true,
ws: true,
xfwd: true,
};
/**
* @param proxy
* @param appPublicFolder
*/
function prepareProxy(proxy, appPublicFolder) {
if (!proxy) {
return undefined;
}
if (typeof proxy !== 'string') {
console.log('proxy must be a string');
process.exit(1);
}
/**
* @param pathname
*/
function mayProxy(pathname) {
const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1));
const isPublicFileRequest = fs.existsSync(maybePublicPath) && fs.statSync(maybePublicPath).isFile();
const isWdsEndpointRequest = pathname.startsWith('/sockjs-node'); // used by webpackHotDevClient
return !(isPublicFileRequest || isWdsEndpointRequest);
}
/**
* @param target
* @param usersOnProxyReq
* @param context
*/
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),
};
}
if (!/^http(s)?:\/\//.test(proxy)) {
console.log('When "proxy" is specified in package.json it must start with either http:// or https://');
process.exit(1);
}
return [{ ...defaultConfig, ...createProxyEntry(proxy) }];
}
/**
* @param proxy
*/
function resolveLoopback(proxy) {
const o = new url.URL(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.
/**
* @param proxy
*/
function onProxyError(proxy) {
return (err, req, res) => {
const host = req.headers && req.headers.host;
console.log('Proxy error:' + ' Could not proxy request ' + req.url + ' from ' + host + ' to ' + proxy + '.');
console.log(
'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
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 +
').'
);
};
}
module.exports.prepareProxy = prepareProxy;
问题
在给一个 react 项目配置本地代理服务的时候,发现 webpack 本身的
devServer.proxy
不像 vue-cli 的devServer.proxy
那样支持配置一个指向开发环境 API 服务器的字符串。当配置代理服务器字符串的时候,根据 vue-cli 文档的说法:这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到开发环境 API 服务器
。不幸的是,我现在就是要用这个,所以看了下 vue-cli 的实现,把这段代码抄过来了。当然由于 webpack 代理服务器配置实际上用的是
http-proxy-middleware
,而 vue-cli 只是在 webpack 上又包了一层,所以直接按照http-proxy-middleware
文档肯定也能写,但是怎么想还是抄 vue-cli 的代码更靠谱。最后实现