在 Rust 中使用 Hyper 和 Tokio 構建高性能 HTTP 代理服務器 -基於路由-

如果你正在尋求構建一個高性能、安全的 HTTP 代理服務器,Rust 的 hyper 和 tokio 庫提供了一個強大的組合,可以幫助你入門。Rust 強大的內存安全和併發特性使其成爲構建健壯的網絡應用程序的理想選擇,而 hyper 爲構建 HTTP 服務器和客戶端提供了靈活且易於使用的接口。tokio 是一個用於編寫可靠的、異步的、具有高 I/O 吞吐量的應用程序運行時。

在本文中,我們將介紹如何使用 hyper 和 tokio 在 Rust 中構建一個簡單的 HTTP 代理服務器。我們將介紹使用 hyper 構建 HTTP 服務器、使用 tokio 向目標 URL 發出請求以及將傳入請求轉發到目標 URL 的基礎知識。在本文結束時,你將擁有使用 Rust、hyper 和 tokio 構建高性能 HTTP 代理服務器的堅實基礎。

創建一個 Rust 項目:

cargo new http-proxy-server

在 Cargo.toml 文件中加入依賴項:

[dependencies]
hyper = { version = "0.14"features = ["full"] }
tokio = { version = "1"features = ["full"] }
http = "0.2"

接下來,我們將定義一個簡單的代理服務器,它在指定端口上偵聽並將傳入的請求轉發到目標 URL。我們的服務器將使用 hyper::service 模塊中的 make_service_fn 方法來創建一個新的服務來處理傳入的請求,並將它們傳遞給代理函數進行處理:

#![deny(warnings)]

use std::convert::Infallible;
use std::net::SocketAddr;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Client, Request, Response, Server};

type HttpClient = Client<hyper::client::HttpConnector>;

#[tokio::main]
async fn main() {
    let addr = SocketAddr::from(([127, 0, 0, 1], 8100));

    let client = Client::builder()
        .http1_title_case_headers(true)
        .http1_preserve_header_case(true)
        .build_http();

    let make_service = make_service_fn(move |_| {
        let client = client.clone();
        async move { Ok::<_, Infallible>(service_fn(move |req| proxy(client.clone(), req))) }
    });

    let server = Server::bind(&addr)
        .http1_preserve_header_case(true)
        .http1_title_case_headers(true)
        .serve(make_service);

    println!("Listening on http://{}", addr);

    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

main 函數設置一個新的 HTTP 服務器,監聽指定的套接字地址。我們使用 hyper 中的 Client 結構來創建一個 HTTP 客戶端,我們將使用該客戶端將請求轉發到目標 URL。

make_service_fn 函數創建一個處理傳入請求的新服務,並將它們傳遞給代理函數進行處理。我們使用 Rust 的 async/await 語法定義了一個異步函數,該函數返回一個 Result,其中包含一個有效的服務或一個錯誤。

Server::bind 方法創建一個新的 HTTP 服務器,該服務器偵聽指定的套接字地址,Server::serve 方法啓動服務器並使用指定的服務爲傳入的請求提供服務。

接下來,讓我們定義我們的 proxy 函數,它將處理傳入的請求並將它們轉發到目標 URL:

async fn proxy(_client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    let headers = req.headers().clone();
    println!("headers: {:?}", headers);

    let path = req.uri().path().to_string();
    if path.starts_with("/hello") {
        let target_url = "http://127.0.0.1:8000".to_owned();
        let resp = get_response(_client, req, &target_url, &path).await?;
        return Ok(resp);
    }

    let resp = Response::new(Body::from("sorry! no route found"));
    Ok(resp)
}

在 proxy 函數中,我們首先克隆傳入的請求頭並將它們打印到控制檯。接下來,我們提取請求路徑並檢查它是否以 “/hello” 開頭。如果是,我們使用 get_response 函數創建一個新的 HTTP 請求到目標 URL,並將響應返回給客戶端。如果請求路徑不是以 “/hello” 開頭,我們將向客戶端返回 “沒有找到路由” 的錯誤消息。

get_response 函數創建一個新的 HTTP 請求到目標 URL,使用 client.request 方法發送請求,然後等待響應並將其返回給調用者。下面是更新後的 get_response 函數:

async fn get_response(client: HttpClient, req: Request<Body>, target_url: &str, path: &str) -> Result<Response<Body>, hyper::Error> {
    let target_url = format!("{}{}", target_url, path);
    let headers = req.headers().clone();
    let mut request_builder = Request::builder()
        .method(req.method())
        .uri(target_url)
        .body(req.into_body())
        .unwrap();

    *request_builder.headers_mut() = headers;
    let response = client.request(request_builder).await?;
    let body = hyper::body::to_bytes(response.into_body()).await?;
    let body = String::from_utf8(body.to_vec()).unwrap();

    let mut resp = Response::new(Body::from(body));
    *resp.status_mut() = http::StatusCode::OK;
    Ok(resp)
}

get_response 函數首先通過連接目標 URL 和傳入請求路徑來構造完整的目標 URL。然後,我們克隆傳入的請求頭,並使用它們構造一個新的 HTTP 請求,該請求具有與傳入請求相同的方法、URI 和主體。

我們使用 client.request 方法將新的 HTTP 請求發送到目標 URL,等待響應,並將響應體轉換爲字符串。最後,創建一個與目標響應具有相同主體的新響應,並將其返回給調用者。

就是這樣!只需幾行代碼,我們就在 Rust 中創建了一個基本的 HTTP 代理服務器,可以將傳入的請求轉發到目標 URL。你可以擴展此代碼以添加其他特性,如緩存、身份驗證和負載均衡的。

執行 cargo run 運行,這將啓動 HTTP 代理服務器並偵聽指定端口上的傳入請求。你可以通過打開 web 瀏覽器並導航到 http://localhost:8100/hello 來測試服務器。服務器應該將請求轉發到目標 URL,並將響應返回給瀏覽器。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6ILSsaW-13ItUSigoKKY-g