70行node.js代码实现一个类nginx的http代理(且支持身份验证)

先说说背景。node

有时咱们须要把一个自身不带有权限验证的服务暴露出去,为了提升安全性,咱们但愿用户访问该服务时,提供一个校验令牌。无令牌就拒绝访问。nginx

因此我决定挑战下本身,写一个能知足上述需求的http代理,基于Node.js和TypeScript。chrome


 

咱们先看看使用效果。代码已经上传到npm仓库。npm

# 安装npm i -g @youmoo/lets-proxy# 运行PORT=9999 lets-proxy "https://httpbin.org" "hello world"

上述命令先是安装了 @youmoo/lets-proxy 模块,该模块会自动往你的 PATH 中添加一个名为 lets-proxy 的命令;而后执行它。PORT 参数指定监听的端口,第一个参数 https://httpbin.org 指定要代理哪一个服务;第二个参数 hello world 是校验码。浏览器

既然上面咱们代理了 https://httpbin.org 的网站,咱们对比一下经过代理访问和经过原网站访问的实际效果。安全

访问首页:服务器

 

咱们看到本地出现了 Forbidden 提示。这是由于咱们没有在浏览器中设置校验码。咱们打开左边页面的chrome控制台,加入一个带有 hello world 字样的 cookiecookie

cookie 设置好后,咱们再刷新此页面:网站

能够看到代理成功了。url

httpbin.org 提供了一个获取客户端ip的页面:httpbin.org/ip,咱们访问试试:

也是成功的。说明代理是正常的。接下来简单说说代理的原理。


 

代理的原理

在以前的文章中有介绍过 http 请求格式(回放:说说几种Spring MVC支持的客户端传参方式)。

代理要作的即是,让用户(客户端)不直接访问源网站(本例中是httpbin.og),而是访问代理,代理经过解析用户的请求,以用户的身份将请求几乎原封不动的转发给源网站,而后再将源网站的响应信息回送传给客户端。代理服务器对于客户端和源网站是透明的(两者意识不到代理的存在)。但代理自己其实充当着2种角色:对于客户端来讲代理充当了服务器(源网站)的角色;对于源网站而言代理又充当了用户(客户端)的角色。

站在http的角度说,代理就是拦截客户端的 request headers 和 request body ,再将其转发给源网站。而后将源网站的 response headers 和 response body 回写给客户端。


 

接下来是晾代码的时刻。关键代码不到10行,重要的部分已注释。

#!/usr/bin/env node​import http from 'http';import https from 'https';​​// 监听的端口const { PORT = 9999 } = process.env;// 代理的上游地址和验证码,验证码为空时,表示关闭验证const [upstream = 'https://httpbin.org', auth = null] = process.argv.slice(2);​const server = http.createServer((req, res) => {​const {method,url,headers} = req;​if (auth) {const { cookie } = headers;// 若是开启的验证但cookie中未有此验证信息,报403if (!cookie || !cookie.includes(auth)) {​res.writeHead(403).end('Forbidden');return;}}​// 组装上游urlconst proxied = new URL(upstream + url);​const { protocol, host, pathname, search, hash } = proxied;headers.host = host;​const _http = protocol === 'https:' ? https : http;​// 向上游发出请求const proxyReq = _http.request({method,protocol,host,path: pathname,search,hash,// 将客户端的headers也传给上游headers: headers}, proxyRes => {​// 将上游返回的headers传给客户端res.writeHead(proxyRes.statusCode, proxyRes.statusMessage, proxyRes.headers);​// 将上游的response body返给客户端proxyRes.pipe(res);}).on('error', err => {// 若是请求出错,直接返回500res.statusCode = 500;res.statusMessage = err.message;res.end(err.message);});​// 将客户端request body传给上游req.pipe(proxyReq);​});​server.listen(PORT, () => {console.log('监听:', server.address());console.log('代理:', upstream);});​

 


 

欢迎关注或留言:)

注意,其实nginx自己支持 basic auth 和 auth_request 指令,也能知足文章开头提到的需求。

相关文章
相关标签/搜索