本文同时参与
「掘力星计划」
,赢取创作大礼包,挑战创作激励金
上一章我们讲到了Vue 本地开发请求跨域的解决方法以及基本的实现原理,这章我们继续深入服务器代理的实现原理
对上一章感兴趣的人可以前往查看。
Vue 本地开发请求跨域的解决以及服务器代理的原理,永远不要做只用不学的人【一】
上一章我们再总结的时候讲
proxyTable
就是webpack在开发环境给我们提供的一个代理服务器,(使用的是
http-proxy-middleware
)。而讲
http-proxy-middleware
之前我们要先讲一下
node-http-proxy
。
一、node-http-proxy
从官网我们可以了解到
node-http-proxy
是一个HTTP可编程的,支持 websockets的代理库。它适合于实现诸如反向代理和负载均衡之类的组件。
1.1 什么是代理
下面给出一幅图说说我的简单理解:
如上图所示,代理服务器挡在了服务器的面前。对于用户来说,是不知道这层代理的存在的,因为它是针对服务器的,所以称之为
反向代理
。如果是用户在客户端主动设置代理,则为
正向代理
。
正向代理针对客户端,反向代理针对服务端
那么在服务器前面加设一层代理服务器有什么用呐?
如图所示,服务器A监听
192.168.100.162
的
8001
端口(假设主要存放的是 /api 开头的后台接口);服务器B监听
192.168.100.162
的
8002
端口(假设主要存放的都是 / 开头的静态文件)。
那么我们在客户端访问
192.168.100.162:8001/api/xxx
就可以获得接口的返回数据;访问
192.168.100.162:8002/${static_file_path}
就可以获取到对应的静态文件。
这样就实现了页面和接口的分离,但是真正使用的时候,我们还是需要将它们统一起来。
那么如何将他们统一起来呐?这就需要代理服务器来帮我们做些工作了。
假设使用下面这个配置代理服务器(代理服务器监听 8003 端口)。客户端访问
192.168.100.162:8003/api/*
的时候代理服务器就帮我们访问
192.168.100.162:8001/api/*
;访问
192.168.100.162:8003/*
的时候代理服务器就帮我们访问
192.168.100.162:8002/*
。这样的话,就解决了上面我们遇到的问题。
1.2 node-http-proxy 源码学习
好了,大致了解了什么是代理后,我们开始着手学习
node-http-proxy
源码吧。
其实
node-http-proxy
就是根据我们输入的配置来帮我们实现请求的代发送。
我自己将源码抽离出来一个最基本的内容方便学习,其他内容需要深入了解可以查看
node-http-proxy
学习。
项目目录如下(和源码一样):
src/http-proxy.js
const ProxyServer = require('./http-proxy/index.js').Server;
function createProxyServer(options) {
return new ProxyServer(options);
ProxyServer.createProxyServer = createProxyServer;
ProxyServer.createServer = createProxyServer;
ProxyServer.createProxy = createProxyServer;
module.exports = ProxyServer;
src/http-proxy/index.js
const httpProxy = module.exports;
const parse_url = require('url').parse;
const http = require("http");
const https = require('https');
const EventEmitter = require('events');
const web = require('./passes/web-incoming');
function createRightProxy(options) {
return function(req, res, ) {
const passes = this.webPasses;
const args = Array.prototype.slice.call(arguments);
const cntr = args.length - 1;
const requestOptions = options;
if (args[cntr] !== res) {
Object.assign(requestOptions, args[cntr]);
['target'].forEach((e) => {
if (typeof requestOptions[e] === 'string') {
requestOptions[e] = parse_url(requestOptions[e]);
if (!requestOptions.target) {
return this.emit('error', new Error('Must provide a proper URL as target'));
for(let i=0; i < passes.length; i++) {
if(passes[i](req, res, requestOptions, this)) {
break;
class ProxyServer extends EventEmitter {
constructor(options) {
super();
this.web = createRightProxy(options);
this.options = options || {};
this.webPasses = Object.keys(web).map(function(pass) {
return web[pass];
this.on('error', this.onError);
onError(err) {
throw err;
listen(port) {
const self = this;
const closure = function(req, res) {
self.web(req, res);
this._server = this.options.ssl ?
https.createServer(this.options.ssl, closure) :
http.createServer(closure);
this._server.listen(port);
return this;
httpProxy.Server = ProxyServer;
src/http-proxy/common.js
const common = exports;
const url = require('url');
const isSSL = /^https|wss/;
common.setupOutgoing = (outgoing, options, req) => {
outgoing.port = options['target'].port || (isSSL.test(options['target'].protocol)? 443 : 80);
['host', 'hostname'].forEach(
function(e) { outgoing[e] = options['target'][e]; }
outgoing.method = options.method || req.method;
outgoing.headers = Object.assign({}, req.headers);
if (options.headers) {
Object.assign(outgoing.headers, options.headers);
outgoing.path = url.parse(req.url).path || '';
return outgoing;
src/http-proxy/passes/web-incoming.js
const httpNative = require('http');
const httpsNative = require('https');
const common = require('../common');
const webOutgoing = require('./web-outgoing');
const web_o = Object.keys(webOutgoing).map(function(pass) {
return webOutgoing[pass];
const nativeAgents = {
http: httpNative,
https: httpsNative
module.exports = {
stream: (req, res, options, server) => {
server.emit('start', req, res, options.target);
const { http, https } = nativeAgents;
const proxyReq = (options.target.protocol === 'https:'? https : http).request(
common.setupOutgoing(options.ssl || {}, options, req)
proxyReq.on('socket', (socket) => {
if(server) { server.emit('proxyReq', proxyReq, req, res, options) };
req.on('aborted', () => {
proxyReq.abort();
const proxyError = createErrorHandler(proxyReq, options.target);
req.on('error', proxyError);
proxyReq.on('error', proxyError);
function createErrorHandler(proxyReq, url) {
return function proxyError(err) {
if (req.socket.destroyed && err.code === 'ECONNRESET') {
server.emit('econnreset', err, req, res, url);
return proxyReq.abort();
server.emit('error', err, req, res, url);
req.pipe(proxyReq);
proxyReq.on('response', (proxyRes) => {
if(server) { server.emit('proxyRes', proxyReq, req, res, options); }
if(!res.headersSent && !options.selfHandleResponse) {
for(var i=0; i < web_o.length; i++) {
if(web_o[i](req, res, proxyRes, options)) { break; }
if (!res.finished) {
proxyRes.on('end', function () {
if (server) server.emit('end', req, res, proxyRes);
proxyRes.pipe(res);
} else {
if (server) server.emit('end', req, res, proxyRes);
src/http-proxy/passes/web-outgoing.js
module.exports = {
writeStatusCode: function writeStatusCode(req, res, proxyRes) {
if(proxyRes.statusMessage) {
res.statusCode = proxyRes.statusCode;
res.statusMessage = proxyRes.statusMessage;
} else {
res.statusCode = proxyRes.statusCode;
1.3 例子
好了,源码的内容基本上就是上面的样子,实现了 node-http-proxy
最核心基本的功能。下面让我们写个例子测一测:
examples/test.js
const httpProxy = require('../src/http-proxy');
const http = require('http');
const fs = require('fs');
httpProxy.createServer({
target:'http://localhost:8001'
}).listen(8003);
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(8001);
console.log('proxy server start at 8003');
console.log('server start at 8001');
命令行输入 node examples/test.js
启动脚本。然后打开页面访问 localhost:8003
,页面成功返回如下:
成功!!!
1.4 其他使用方法
自定义请求逻辑,如下:
const httpProxy = require('../src/http-proxy');
const http = require('http');
const proxy = httpProxy.createProxyServer({});
const proxyServer = http.createServer((req, res) => {
proxy.web(req, res, { target:'http://localhost:8001' });
proxyServer.listen(8003);
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(8001);
console.log('proxy server start at 8003');
console.log('server start at 8001');
代理请求 headers 重写
const httpProxy = require('../src/http-proxy');
const http = require('http');
const proxy = httpProxy.createProxyServer({});
proxy.on('proxyReq', function(proxyReq, req, res, options) {
proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
const proxyServer = http.createServer((req, res) => {
proxy.web(req, res, { target:'http://localhost:8001' });
proxyServer.listen(8003);
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(8001);
console.log('proxy server start at 8003');
console.log('server start at 8001');
请求返回如下:
request successfully proxied to: /
"host": "localhost:8003",
"connection": "keep-alive",
"cache-control": "max-age=0",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
"sec-fetch-user": "?1",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"sec-fetch-site": "cross-site",
"sec-fetch-mode": "navigate",
"accept-encoding": "gzip, deflate, br",
"accept-language": "zh-CN,zh;q=0.9",
"cookie": "_ga=GA1.1.1152336717.1566564547",
"x-special-proxy-header": "foobar"
这一章我们先讲到这里,下一章我们继续讲服务器代理的原理这块的内容。感谢大家的观看