在開發Web服務的時候,我們可能會需要透過客戶端的IP位址,來協助判斷其所在的地區。一般來說,透過HTTP協定獲取客戶端IP的來源有三個地方,一個是Socket連線的IP位址,一個是HTTP請求中的X-Real-IP標頭欄位,一個是HTTP請求中的X-Forwarded-For標頭欄位。



為什麼不能直接用Socket連線的IP位址?因為HTTP連線並不是單純地一點對一點,客戶端有可能會透過幾個正向代理伺服器(Proxy Server),經過一些我們架設的反向代理伺服器(Reverse Proxy Server),才連結到我們的Web伺服器。如果Web伺服器中的Web應用程式是直接取用Socket連線的IP位址的話,就只會得到我們自己架設的反向代理伺服器的IP位址。

代理伺服器在轉發HTTP請求時,可以替HTTP請求加上X-Real-IP標頭欄位,來保留客戶端的真實IP位址資訊,如此一來Web應用程式就可以藉由讀取HTTP請求中的X-Real-IP標頭欄位來獲取客戶端的真實IP位址。有些代理伺服器,還會將自己的IP添加到X-Forwarded-For標頭欄位中,形成一個IP鏈,如下:

X-Forwarded-For: client_ip, proxy_1_ip, proxy_2_ip, proxy_3_ip

在實作Rocket的請求守衛時,request::Request結構實體雖然有提供一些方法可以取得Socket連線的IP位址和X-Real-IP標頭欄位的IP位址,但由於過於低階,很難直接拿來做實務上的應用。

Client's IP Address Request Guard for Rocket Framework

「Client's IP Address Request Guard for Rocket Framework」是筆者開發出來的套件,提供兩個請求守衛,可以正確取得客戶端的IP位址,或是取得客戶端的真實IP位址。

Crates.io

https://crates.io/crates/rocket-client-addr

Cargo.toml

rocket-client-addr = "*"

使用方法

rocket_client_addr這個crate提供的ClientAddr結構體可以當作請求守衛,能正確取得客戶端的IP位址。如果有代理伺服器,就會取得最後的代理伺服器的IP位址,但會略過本地端的代理伺服器。

以下是一個使用範例:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

extern crate rocket_client_addr;

use rocket_client_addr::ClientAddr;

#[get("/ipv4")]
fn ipv4(client_addr: &ClientAddr) -> String {
    client_addr.get_ipv4_string().unwrap()
}

#[get("/ipv6")]
fn ipv6(client_addr: &ClientAddr) -> String {
    client_addr.get_ipv6_string()
}

fn main() {
    rocket::ignite()
        .mount("/", routes![ipv4])
        .mount("/", routes![ipv6])
        .launch();
}

rocket_client_addr這個crate提供的ClientRealAddr結構體可以當作請求守衛,能正確取得客戶端的真實IP位址。

以下是一個使用範例:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

extern crate rocket_client_addr;

use rocket_client_addr::ClientRealAddr;

#[get("/ipv4")]
fn ipv4(client_addr: &ClientRealAddr) -> String {
    client_addr.get_ipv4_string().unwrap()
}

#[get("/ipv6")]
fn ipv6(client_addr: &ClientRealAddr) -> String {
    client_addr.get_ipv6_string()
}

fn main() {
    rocket::ignite()
        .mount("/", routes![ipv4])
        .mount("/", routes![ipv6])
        .launch();
}