1. 程式人生 > 實用技巧 >express中是如何處理IP的?

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。

總結

程式的邏輯如下:

  1. 如果沒有設定trust proxy或者設定trust proxy為false,獲取的是ip就是req.connection.remoteAddress
  2. 如果設定了trust proxy ,express會生成對應的過濾函式,過濾[代理ip,x-forwarded-for中的ip],返回結果陣列的第一個ip;
  3. ips的處理邏輯類似;