• 如何使用hyper和tokio在Rust中构建一个简单的HTTP代理服务器
  • 发布于 2个月前
  • 1617 热度
    0 评论
  • Vinda
  • 4 粉丝 55 篇博客
  •   
如果你正在寻求构建一个高性能、安全的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();
     // 堆代码 duidaima.com
    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,并将响应返回给浏览器。
用户评论