如何编写一个 HTTP 反向代理服务器

如何编写一个 HTTP 反向代理服务器,第1张

如何编写一个HTTP反向代理服务

如果您经常使用Node.js编写Web服务方法,那么您必须熟悉使用Nginx作为代理服务。在目前的消费情况下,我们往往需要在内网的多个服务器上布置法式风格,而在一个多核服务器中,为了充实和 *** 纵所有的CPU资本,我们还需要启动多个服务进程,这些服务进程需要监控差异。然后,Nginx被用作代理服务器,来自用户阅读器的请求被接收并被转发到所有的Web服务器。下图显示了事情的流程:

Node.js上显示了一个简单的http代理,法语代理的长度通常很简单。本文中例子的中心代码只需要60多个,你只需要知道内置HTTP模块的基本用法。详情请看下文。

取创意,取相关技术。

使用http.createServer()创建的http服务器,处理请求的函数模式一般是function(req,res){}(以下简称requestHandler),它接收两个参数,即HTTP。IncomingMessage和http。服务器响应工具。我们可以通过这两个工具获得所有关于请求的疑问,并停止对它们的响应。

一般来说,支流Node.jsWeb框架的中心组件(像connect)有两种情况:

如果中心不需要任何初始化参数,它的导出结果是requestHandler。

如果需要中心零件的初始化参数,其导出结果就是中心零件的初始化函数。实现初始化函数时,会传入一个选项工具,实现后会返回一个requestHandler。

为了使代码更加标准,在本例中,我们将反向代理方法想象为一种中间件模式,并利用上述第二种连接方式:

//作为中心件而死

consthandler=reverseProxy({

//初始化参数,用于设置目的服务器列表。

服务器:["127.0.0.1:3001","127.0.0.1:3002","127.0.0.1:3003"]

});

//可以在http模块中间接使用

constserver=http.createserver(handler);

//作为核心部分,它被用在连接模块中

app.use(处理程序);

澄清:

在中的代码中,reverseProxy是代理服务器中心的初始化函数,它带有一个工具参数。servers是后端服务器位置的列表,每个位置都是IPlocation:end-center。

执行reverseProxy()后,会返回一个类似function(req,res){}的函数,用于处理HTTP请求。可以作为connect中间件的http.createServer()和app.use()的处理函数。

当接收到来自客户端的请求时,根据第二周期从服务器阵列中选择服务器位置,并将请求代理发送到该位置的服务器。

服务器收到HTTP请求后,需要向作为代理的目标服务器发起新的HTTP请求,并可以使用http.request()接收请求:

constreq=http.request(

{

主机名:“目标服务器位置”,

端口:“80”,

路径:“恳求方式”,

标题:{

《x-y-z》:“乞讨头”

}

},

功能(资源){

//res是一个echo工具。

console.log(RES.statuscode);

}

);

//如果需要收集一个恳求者,用write()打end()

req.end();

要将客户的整个恳求者(正文部门,POST和PUT中会有恳求者)转移到另一个服务器,可以使用Stream工具的pipe()方法,例如:

//req和res是对客户的全部恳求和回应工具。

//req2warningres2作为服务器主动方代理的恳求工具。

//将数据传输到req2。

req.pipe(请求2);

//将数据从res2传输到res

res2.pipe(RES);

澄清:

req工具是一个可读的流,通过数据加扰的过程接收数据,当到达数据加扰结束时,表示数据接收结束。

Res工具是一个可写流(WritableStream),通过write()的过程输出数据,由end()完成输出。

为了通过监视来自可读流的数据并使用可写流的write()方法输出数据来简化数据的获取,我们可以使用可读流的pipe()方法。

以上只是提到了实现HTTP代理需求的关键技术。具体文档请参考以下地方:https://nodejs.org/api/http.html#http_http_request_options_callback。

当然,为了实现一种友谊,我们往往需要很多额外的东西。详情请看下文。

简单版本

下面是一个简单HTTP反向代理服务器的实际文件战代码(有任何第三轮库可以依赖)。为了使代码更加简洁,使用了一些最新的ES语法特性,需要运行最新版本的Nodev8.x:

文件proxy.js:

consthttp=require("http");

constassert=require("assert");

constlog=require("。/log");

/**充当背面代理的中心部件*/

module.exports=functionreverseproxy(选项){

assert(array.isarray(options.servers),“options.servers必须是数组”);

assert(options.servers.length>;0,“options.servers的最小程度必须大于0”);

//解析服务器位置,死磕主机名和端口。

constservers=options.servers.map(str=>;{

consts=str.split(":");

return{hostname:s[0],port:s[1]||"80"};

});

//弄个后端服务器,第二个循环。

设ti=0;

函数getTarget(){

constt=servers[ti];

ti++;

if(ti>;=服务器.长度){

ti=0;

}

returnt;

}

//Die监控错误事故功能,下降时回显500。

函数绑定错误(req,res,id){

返回函数(错误){

constmsg=String(err.stack||err);

日志("[%s]攻击:%s",id,msg);

如果(!RES.headersent){

res.writeHead(500,{"content-type":"text/plain"});

}

res.end(消息);

};

}

返回函数代理(req,res){

//代理,求疑息

consttarget=gettarget();

常量信息={

...目标,

方法:req.method

路径:req.url,

头:req.headers

};

constid=`${req.method}${req.URL}=>;${target.hostname}:${target.port}`;

日志("[%s]代理抗辩",id);

//收款代理人的代理抗辩

constreq2=http.request(info,res2=>{

res2.on("error",bindError(req,res,id));

Log("[%s]echo:%s",id,res2.statuscode);

res.writeHead(res2.statusCode,res2.headers);

res2.pipe(RES);

});

req.pipe(请求2);

req2.on("error",bindError(req,res,id));

};

};

log.js文件:

constutil=require("util");

/**打印日记*/

module.exports=函数日志(...args){

consttime=新日期()。toLocaleString();

console.log(time,util.format(...args));

};

澄清:

log.js文件真正展示了一个用来打印日记的函数log()。可以支持console.log()的相同用法,在输出前主动减少被骗前的日期和战争时间,方便我们看日记。

reverseProxy()函数导入使用assert模块来停止基本参数检查。如果参数模式不符合要求,它将被抛出。可以保证接收方第一时间知道,而不是在运行期间出现各种不可预知的问题。

get()函数用于递归返回目标服务器的位置。

BindError()函数用于监控错误无序,防止所有法式水果因抓取和收集而崩溃,同时可以向客户端返回损坏的信息。

为了测试代码 *** 作的结果,我编写了一个简单的法语版本,文件server.js:

consthttp=require("http");

constlog=require("。/log");

constreverseProxy=require("。/proxy");

//创建反向代理服务器。

函数startProxyServer(端口){

返回新承诺((resolve,reject)=>;{

constserver=http.createServer(

反向Proxy({

服务器:["127.0.0.1:3001","127.0.0.1:3002","127.0.0.1:3003"]

})

);

server.listen(port,()=>{

Log("背靠背代理服务器已启动:%s",端口);

解析(服务器);

});

server.on("错误",拒绝);

});

}

//创建演示服务器

函数开始示例服务器(端口){

返回新承诺((解决,拒绝)=>{

constserver=http.createserver(function(req,res){

constchunks=[];

req.on("data",chunk=>chunks.push(chunk));

req.on("end",()=>{

constbuf=buffer.concat(chunks);

RES.end(`${port}:${req.method}${req.URL}${buf.tostring()}`.trim());

});

});

server.listen(port,()=>{

Log("服务器启动:%s",端口);

解析(服务器);

});

server.on("错误",拒绝);

});

}

(异步函数(){

awaitstartExampleServer(3001);

awaitstartExampleServer(3002);

awaitstartExampleServer(3003);

awaitstartProxyServer(3000);

})();

执行以下命令开始:

nodeserver.js

然后,我们可以通过processcurl命令检查返回的结果:

curlhttp://127.0.0.1:3000/hello/world

重复执行命令,如果输出结果是偶然的,应该是这样的(输出内容结束部门要按照第二个周期):

3001:GET/hello/world

3002:GET/hello/world

3003:GET/hello/world

3001:GET/hello/world

3002:GET/hello/world

3003:GET/hello/world

注意:如果使用阅读器打开网站,可以看到不同的结果。因此,读者会主动测试考试请求/favicon。事实上,一旦页面被更新,就会收到两个请求。

单元测试

上面,我们已经完成了一个基本的HTTP反向代理方法,并通过一个简单的过程验证了它可以做普通的事情。但是我们没有足够的测试,比如只验证GET请求,验证POST请求等恳求方式。而且通过流程步法做更多的测试,费力不讨好,干脆省略。所以,接下来我们要从中减去活动单元测试。

本文选择Node.js世界中广泛使用的mocha作为单元测试框架,反汇编使用supertest停止HTTP连接请求的测试。因为supertest以前带了一些基本面的断行方法,所以暂时不需要柴应该的第三圈断行库。

最后,npminit初始化一个package.json文件,然后执行以下命令:devicemocha和supertest:

npm安装mocha超级测试-保存-开发

然后创建一个新文件test.js:

consthttp=require("http");

constlog=require("。/log");

constreverseProxy=require("。/proxy");

const{expect}=require("chai");

constrequest=require("supertest");

//创建反向代理服务器。

函数startProxyServer(){

返回新承诺((resolve,reject)=>;{

constserver=http.createServer(

反向Proxy({

服务器:["127.0.0.1:3001","127.0.0.1:3002","127.0.0.1:3003"]

})

);

Log("备份代理的代理服务器已经启动");

解析(服务器);

});

}

//创建演示服务器

函数开始示例服务器(端口){

返回新承诺((解决,拒绝)=>{

constserver=http.createserver(function(req,res){

constchunks=[];

req.on("data",chunk=>chunks.push(chunk));

req.on("end",()=>{

constbuf=buffer.concat(chunks);

RES.end(`${port}:${req.method}${req.URL}${buf.tostring()}`.trim());

});

});

server.listen(port,()=>{

Log("服务器启动:%s",端口);

解析(服务器);

});

server.on("错误",拒绝);

});

}

Describe("测试后台代理",function(){

let服务器;

让exampleservers=[];

//测试开始前启动服务器。

before(异步函数(){

exampleservers.push(awaitstartexampleserver(3001));

exampleservers.push(awaitstartexampleserver(3002));

exampleservers.push(awaitstartexampleserver(3003));

server=awaitstartProxyServer();

});

//测试结束后关闭服务器。

after(异步函数(){

for(示例服务器的常量服务器){

server.close();

}

});

It("第二周期返回目的地",asyncfunction(){

等待请求(服务器)

。get("/hello")

。预期(200)

。expect(`3001:GET/hello`);

等待请求(服务器)

。get("/hello")

。预期(200)

。expect(`3002:GET/hello`);

等待请求(服务器)

。get("/hello")

。预期(200)

。expect(`3003:GET/hello`);

等待请求(服务器)

。get("/hello")

。预期(200)

。expect(`3001:GET/hello`);

});

It("支持POST请求",异步函数(){

等待请求(服务器)

。发布("/xyz")

。发送({

甲:123,

乙:456

})

。预期(200)

。expect(`3002:POST/xyz{"a":123,"b":456}`);

});

});

澄清:

就在单元测试开始之前,需要通过processbefore()注册回调函数,以便在测试用例正在执行的时候先启动服务器。

同样,通过processafter()注册回调函数,这样就可以在所有测试用例正在执行之后关闭服务器释放资金(否则mocha进程不会退出)。

使用supertest接收请求时,代理服务器不需要监听客户端,只需要使用服务器实例作为挪用参数即可。

然后修复package.json文件的脚本部分:

{

"脚本":{

"test":"mochatest.js"

}

}

执行以下命令开始测试:

npm测试

如果一切正常,我们应该会看到这样的输出结果,其中类似passing的提醒暗示我们的测试已经经历了整个过程:

测试代理代理

2017-12-1218:28:15服务器启动时间:3001

2017-12-1218:28:15服务器启动时间:3002

2017-12-1218:28:15服务器启动时间:3003

2017-12-1218:28:15,后台代理的代理服务器已经启动。

2017-12-1218:28:15[GET/hello=>代理人的代理辩护

2017-12-1218:28:15[GET/hello=>回声:200

2017-12-1218:28:15[GET/hello=>为代理人辩护

2017-12-1218:28:15[GET/hello=>回声:200

2017-12-1218:28:15[GET/hello=>代理人的代理辩护

2017-12-1218:28:15[GET/hello=>回声:200

2017-12-1218:28:15[GET/hello=>代理人的代理辩护

2017-12-1218:28:15[GET/hello=>回声:200

第二轮返回目的地

2017-12-1218:28:15[POST/xyz=>为代理人辩护

2017-12-1218:28:15[POST/xyz=>回声:200

支持后恳求

2次通过(45毫秒)

虽然上面的测试代码还不够,剩下的就留给读者了。

心脏连接改善

如果我们想想象一个通用的背靠背代理,我们可以提供一个作为http死去的函数。ClientRequest通过进程实现代理的静态修正请求:

反向Proxy({

服务器:["127.0.0.1:3001","127.0.0.1:3002","127.0.0.1:3003"],

请求:函数(req,info){

//info是一个死请求选项工具。

//我们可以静态删除恳求头,像当前的恳求时间戳。

info.headers["X-Request-Timestamp"]=date.now();

//返回http。客户端请求工具

返回http.request(info);

}

});

然后,原来的http.request(info,(res2)=>:{})部门可以更改为监视对紧急事件的响应:

constreq2=http.request(options.request(info));

req2.on("response",res2=>{});

同样,我们也可以通过流程提供一个函数来纠正部门的回声内容:

反向Proxy({

服务器:["127.0.0.1:3001","127.0.0.1:3002","127.0.0.1:3003"],

响应:函数(res,info){

//info是收款代理在代理征集时使用的请求选项工具。

//我们可以静态设置一些echo头,比如练习代理代理的模板服务器的位置。

RES.setheader("X-backend-Server",`${info.hostname}:${info.port}`);

}

});

这里只收集我的想法,关于详细的方法和代码就不赘述了。

摘要

主要介绍如何使用内置的http模块创建HTTP服务器,发起HTTP请求,并简单介绍如何停止测试HTTP连接。在HTTP恳求代理过程中,主要使用流工具的pipe()方法,关键部门代码只需要检查几次。Node.js中的很多法式风格都采用了Stream的思想,将数据视为一个流,通过使用管道将一个流转换成另一个流。可见Stream在Node.js中的重要性。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/768637.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-02
下一篇 2022-05-02

发表评论

登录后才能评论

评论列表(0条)

保存