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 指令,也能知足文章開頭提到的需求。

相關文章
相關標籤/搜索