快速实现一个node代理服务器趟坑记

node如今作为大前端开发的基建工程,可以说无处不在,作为一个前端码畜,我们可以不懂后台开发,但是node是必须要略懂一二的,尤其对于前端开发,node可以说是通往服务器的捷径。牛逼戛然而止,上正题。

前端开发绕不来测试环境接口本地联调的主题,这时候就需要借助一个本地代理服务器解决跨域问题,这里结合最近的工作经历,分享一次简易代理服务器的趟坑之旅。 我们假设本地开发使用了类似express的devserver,既然要使用代理服务,必然要先拦截到目标请求,然后对请求进行转发。思路就是这么简单,我这里使用node原生http模块,当然可以根据简单的配置选择使用http或者https,第一步就是转发参数的设置,其中转发url和请求头直接从req对象中继承即可,需要配置的参数主要有host,port,以及是否使用https,有些业务服务器可能会过滤请求的来源,这时候可能需要配置referer和origin,设置完毕接下来就可以转发了,一般请求都会有参数,也就是请求实体,所以这里转发我直接使用了req.pipe(_proxy),然后从_proxy获取的resonse直接继续pipe到客户端。就这么简单的玩意儿,结果踩坑了,请求发出去后半天没响应,过会儿直接就抛出socket hang up。

请求被挂起

显然这是请求被挂起了,猜猜莫非是也许服务器限制了?可能性不大,但是总共就做了这么点东西,有啥问题?调试一把,我们很好奇看看请求两端输出的到底是什么,首先看看转发的请求头,好像挺正常的,那就再看看请求实体,因为我们使用了pipe直接转发,不太好看,这里先改一下,使用如下方式

req.on(data,(chunk)=>{ console.log(String(chunk)); })

把chunk打印出来看看,竟然是空的。但是客户端的请求确实是带了参数的,所以正常来说实体不应该为空,那么这里是空的到底意味着什么呢?即使是空的,最多也只是参数丢了,为何请求会被挂起呢?请求被挂起说明了目标服务器未回应,那么何种情况下会出现这种问题?结合刚刚请求实体丢失的情况,我们打印一下转发请求头看看,

本地测试请求头

如果细心一点熟悉http请求头规范的同学可能已经恍然大悟,大家仔细看前面截图中的content-length,content-length如果非空,说明请求实体非空,目标服务器会持续等待请求实体的发送,而在这里,我们的请求实体就这么巧完美失踪,所以导致了目标服务器进入了死等模式。到这个socket hang up 的谜团解开了,那么让我们再回到前面消失的请求实体,流有个区别于普通数据结构最大的特点,那就是数据流动性,普通对象可以重复读取数据,但是如果流数据在之前就被读取了,那么管道就空了,后面就无法继续读取了。到这里应该谜底就都能揭晓了,node相关的服务器,都有类似的请求实体解析的包,例如express就有json数据对应的解析包border-parser,form-data对应的解析器connect-multipart等等。果不其然,这里使用了body-parser解析器。

var bodyParser = require(body-parser);app.use(bodyParser.json());app.use(bodyParser.urlencoded({ extended: false }));

那么像这种情况请求流已经被读取了怎么办呢?也好办,那只是不能方便地使用pipe流的方式了。我们还是可以这么干:

var body = req.body; res.write(body); res.end();

就像这样使用最原始的传参方式完美解决。

接下来总结一下,首先是http请求头content-length的规范,如果服务器使用的是http/1.1,并且请求中带上了实体,正常情况下会要求必须带上content-length,否则服务器会返回4xx,不过这是为了兼容http/1.0,现代服务器即使没有带上content-length,也能够正常处理请求实体,所以上述socket hang up的情况,如果我们不考虑请求实体丢失的问题,直接删掉content-length,socket hang up 的问题还是可以解决掉的,只不过丢了参数对我们的请求而言就没有意义了。

再就是实体的转发方式了,如果实体已经被解析了,那就需要使用正常请求的发送方式进行write,如果实体尚未解析,那么我们直接可以使用流的pipe方式进行发送。

前面具体的关于content-length的详细规范,有兴趣大家可以翻阅权威http协议指南深入了解。

HTTP/1.1: HTTP Message​www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4

大家有兴趣的可以上github看看具体的代码,也就是个几十行代码的小轮子。

最近也开始花点时间造造小轮子,惊讶地发现一个大神们喜欢重复造轮子的背后逻辑。造轮子有助于我们更加清晰地认识自己,帮助我们形成知识体系。而且,造一个可用的轮子能让我们的代码变得更加专业。轮子哥的存在是很有道理的,哈哈。不要再说再信造轮子没用的话了,至少对你自己有用,做得好还能服务社区,所以大家就可劲儿的造吧。

本次趟坑之旅愉快落幕,如有疏误,请拍砖。