express中是如何處理IP的?
express獲取client_ip
req.ip // 獲取客戶端ip
req.ips // 獲取請求經過的客戶端與代理伺服器的Ip列表
檢視原始碼
定義獲取ip的入口,
// 原始碼 request.js defineGetter(req, 'ip', function ip(){ var trust = this.app.get('trust proxy fn'); let add = proxyaddr(this, trust); return add }); defineGetter(req, 'ips', function ips() { var trust = this.app.get('trust proxy fn'); var addrs = proxyaddr.all(this, trust); addrs.reverse().pop() return addrs });
defineGetter
是 Object.defineProperty
的封裝,所以我們能在req物件上獲取到ip。
trust proxy 的意義
trust proxy :當用戶設定trust proxy的值代表使用者認為這些值是代理伺服器。那麼在獲取真實客戶端ip的時候就需要進行將這些代理ip過濾掉,
而this.app.get('trust proxy fn')會生成對應的過濾器,這裡的過濾器就是函式,類似於陣列的filter的callback。
如果使用者沒有設定'trust proxy'
,this.app.get('trust proxy fn')
的返回值如下:
function trustNone () { return false }
如果使用者沒有設定'trust proxy'
,返回值類似如下:
function trust (addr) { if (!isip(addr)) return false var ip = parseip(addr) var kind = ip.kind() if (kind !== subnetkind) { if (subnetisipv4 && !ip.isIPv4MappedAddress()) { // Incompatible IP addresses return false } // Convert IP to match subnet IP kind ip = subnetisipv4 ? ip.toIPv4Address() : ip.toIPv4MappedAddress() } return ip.match(subnetip, subnetrange) }
這裡牽扯到express中的get set方法,原始碼一併列出:
// 原始碼 application.js
// 初始化express的是後執行,預設設定trust proxy 為false,
this.set('trust proxy', false);
// 定義get方法
methods.forEach(function(method){
app[method] = function(path){
if (method === 'get' && arguments.length === 1) {
return this.set(path); // 呼叫get執行到這裡,相當於呼叫了set方法
}
......
};
});
// 定義set方法
app.set = function set(setting, val) {
if (arguments.length === 1) {
return this.settings[setting]; //呼叫set方法執行到這裡,相當於返回了settings這個物件中的某個配置
}
.....
this.settings[setting] = val;
......
case 'trust proxy':
this.set('trust proxy fn', compileTrust(val));
....
};
經過以上程式碼的執行,
則兩個方法的作用是用來過濾
proxyaddr
這是npm包proxy-addr
,express文件中提到的linklocal,loopback,uniquelocal
是在這裡定義的,上面提到的ip過濾方法也是這個包來生成。
由函式forward來處理獲取ip,事實上使用x-forwarded-for 和 req.connection.remoteAddress的類獲取ip。
function forwarded (req) {
....
var proxyAddrs = parse(req.headers['x-forwarded-for'] || '')
var socketAddr = req.connection.remoteAddress
var addrs = [socketAddr].concat(proxyAddrs)
return addrs
}
例項
var express = require('express')
var app = express()
app.set('trust proxy','127.0.0.1')
app.use(function (req,res) {
if(req.url === '/favicon.ico')return
res.send({
ip:req.ip,
ips:req.ips
})
console.log(app.get('trust proxy'));
})
app.listen('9090','0.0.0.0')
同時nginx設定代理:
server {
listen 8062;
server_name localhost;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://127.0.0.1:9090;
}
}
此時只有一臺代理服務,轉發到express的x-forwarded-for為"192.168.1.105",而代理伺服器nginx的ip為127.0.0.1,經過forwarded方法處理之後是待過濾的ip陣列是 ["127.0.0.1","192.168.1.105"]。
- 在另一臺電腦(內網ip192.168.1.105)訪問192.168.1.102(內網本機IP):8062 ,返回:
{"ip":"192.168.1.105","ips":["192.168.1.105"]}
解釋:因為trust proxy和nginx的ip一致,所以過濾掉了nginx的ip,剩下的ip中的第一個被認為是客戶端ip;因為ips本身是就是客戶端ip和代理ip的集合,這裡代理nginx被過濾掉了,最後和客戶端一樣。
- 去掉
app.set('trust proxy','127.0.0.1')
的結果:{"ip":"127.0.0.1","ips":[]}
解釋:沒有設定trust proxy,也就是認為沒有代理,獲取的ip為代理伺服器的ip。在此情況下ips為為空陣列。
- 換成
app.set('trust proxy','127.0.0.100/28')
的結果:{"ip":"127.0.0.1","ips":[]}
解釋:上面的28為網段的CIDR表示法,CIDR是什麼呢?有點像正則匹配一樣,舉個例子:127.0.0.1沒有落在127.0.0.100/28所便是的網段,所以express在使用過濾器的時候沒有匹配到,最後返回了第一個ip。沒有匹配到代表ips也為空陣列;
- 換成
app.set('trust proxy','127.0.0.100/18')
的結果:{"ip":"192.168.1.105","ips":["192.168.1.105"]}
解釋:127.0.0.1 落在了127.0.0.100/18表示的網段,和上面的相反,過濾掉127.0.0.1。
總結
程式的邏輯如下:
- 如果沒有設定
trust proxy
或者設定trust proxy
為false,獲取的是ip就是req.connection.remoteAddress
; - 如果設定了
trust proxy
,express會生成對應的過濾函式,過濾[代理ip,x-forwarded-for中的ip],返回結果陣列的第一個ip; - ips的處理邏輯類似;