Перейти к содержанию

Создание высокопроизводительного HTTP-прокси-сервера (на основе маршрута) в Rust с помощью Hyper & Tokio

Posted on:6 марта 2023 г. at 08:56

Example Dynamic OG Image link Если вы хотите создать высокопроизводительный и безопасный HTTP-прокси-сервер, Rust с крейтами hyper и tokio - это мощная комбинация для начала работы. Мощные функции безопасности памяти и параллелизма Rust делают его идеальным для создания надежных сетевых приложений, в то время как hyper предоставляет гибкий и простой в использовании интерфейс для создания HTTP-серверов и клиентов. tokio - это среда выполнения для написания надежных, асинхронных и компактных приложений с высокой пропускной способностью ввода-вывода.

В этой статье мы рассмотрим, как создать простой HTTP прокси-сервер в Rust с помощью hyper и tokio. Мы рассмотрим основы построения HTTP-сервера с помощью hyper, отправки запросов на URL с помощью tokio и переадресации входящих запросов на URL. К концу этого руководства у вас будет прочная основа для создания высокопроизводительных HTTP-прокси-серверов с помощью Rust, hyper и tokio.

cargo new http-proxy-server
cd http-proxy-server

echo 'hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http = "0.2"' >> Cargo.toml

cargo update

Далее мы определим простой прокси-сервер, который прослушивает указанный порт и перенаправляет входящие запросы на нужные URL. Наш сервер будет использовать метод make_service_fn из модуля hyper::service для создания новой службы, которая обрабатывает входящие запросы и передает их прокси-функции для обработчики:

#![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!("Прослушивание http://{}", addr);

    if let Err(e) = server.await {
        eprintln!("ошибка сервера: {}", e);
    }
}

Наша основная функция настраивает новый HTTP-сервер, который прослушивает указанный адрес сокета. Мы используем структуру Client из hyper crate для создания HTTP-клиента, который мы будем использовать для пересылки запросов на URL.

Функция make_service_fn создает новую службу, которая обрабатывает входящие запросы и передает их прокси-функции для обработки. Мы используем синтаксис async/await от Rust для определения асинхронной функции, которая возвращает результат, содержащий либо допустимую службу, либо ошибку.

Метод Server::bind создает новый HTTP-сервер, который прослушивает указанный адрес сокета, а метод Server::serve запускает сервер и обслуживает входящие запросы с использованием указанной службы.

Далее давайте определим нашу прокси-функцию, которая будет обрабатывать входящие запросы и перенаправлять их на роутер URL:

async fn proxy(_client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    let headers = req.headers().clone();
    println!("заголовки: {:?}", headers);

    let path = req.uri().path().to_string();
    if path.starts_with("/hello") {
        let target_url = "http://127.0.0.1:8000".to_owned();

В функции прокси мы сначала клонируем заголовки входящих запросов и выводим их на консоль. Далее мы извлекаем путь к запросу и проверяем, начинается ли он с /hello. Если это произойдет, мы создадим новый HTTP-запрос к роутеру URL с помощью функции get_response и вернем ответ клиенту.

Если путь запроса не начинается с /hello, мы возвращаем клиенту сообщение об ошибке “маршрут не найден”. Вот обновленная функция прокси-сервера:

async fn proxy(_client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    let headers = req.headers().clone();
    println!("заголовки: {:?}", 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("извините! маршрут не найден"));
    Ok(resp)
}

Функция 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, ожидания ответа и преобразования тела ответа в строку. Наконец, мы создаем новый ответ с тем же телом, что и сам ответ, и возвращаем его вызывающему.

Вот и все! С помощью всего нескольких строк кода мы создали базовый HTTP-прокси-сервер в Rust, который может перенаправлять входящие запросы на URL. Вы можете расширить этот код, чтобы добавить дополнительные функции, такие как кэширование, аутентификация и балансировка нагрузки.

Чтобы запустить этот код, просто сохраните его в файл с именем main.rs в новом проекте Rust и запустите следующую команду:

cargo run

Это запустит HTTP-прокси-сервер и будет прослушивать входящие запросы на указанный порт. Вы можете протестировать сервер, открыв веб-браузер и перейдя к http://localhost:8100/hello . Сервер должен перенаправить запрос на URL и вернуть ответ браузеру.

Вывод

В этой статье мы рассмотрели, как создать высокопроизводительный HTTP-прокси-сервер в Rust с помощью крейтов hyper и tokio. Мы рассмотрели основы построения HTTP-сервера с помощью hyper, выполнения запросов к роутеру URL с помощью tokio и переадресации входящих запросов на нужный URL.