Скопируйте тело и заголовки из гипер-HTTP-запроса в новый запрос при проверке тела

Я хотел бы создать небольшой прокси-сервер Rust HTTP с использованием hyper, который принимает запросы, перенаправляет их и сбрасывает тело запроса + тело.

Основываясь на этом примере, прокси-часть работает нормально.

Однако я не могу просто скопировать и распечатать тело запроса. Моя основная проблема в том, что тело запроса нельзя просто скопировать во что-то вроде Vec<u8>. Я не могу deconstruct запрос прочитать тело, а затем создать его позже, поскольку деконструированные заголовки не могут быть добавлены в новый запрос.

Следующий код показывает мой минимальный пример HTTP-прокси:

extern crate futures;
extern crate hyper;
extern crate tokio_core;

use futures::{Future, Stream};

use hyper::{Body, Client, StatusCode};
use hyper::client::HttpConnector;
use hyper::header::{ContentLength, ContentType};
use hyper::server::{Http, Request, Response, Service};

use tokio_core::reactor::Core;

type HTTPClient = Client<HttpConnector, Body>;

struct Server {
    client: HTTPClient,
}

impl Server {
    pub fn new(client: HTTPClient) -> Server {
        Server { client: client }
    }
}

impl Service for Server {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, mut req: Request) -> Self::Future {
        let req_uri_str = {
            let uri = req.uri();
            format!(
                "http://localhost{}?{}",
                uri.path(),
                uri.query().unwrap_or_default()
            )
        };
        req.set_uri(req_uri_str.parse().unwrap());

        // Try to create a copy of the new request
        /*
        let (method, uri, version, headers, body) = req.deconstruct();
        let mut req_copy: Request<hyper::Body> = Request::new(method, uri);

        // Main problem: How can the request body be copied?
        // >>> let body_bytes: Vec<u8> = ...

        req_copy.set_body(body);
        req_copy.set_version(version);

        // Try to copy the headers
        for header in headers.iter() {
            req_copy.headers_mut().set(header.value().unwrap());
        }
        */

        // This works if the request is not deconstructed
        let work = self.client
            .request(req)
            .and_then(|res| futures::future::ok(res))
            .or_else(|err| {
                let body = format!("{}\n", err);
                futures::future::ok(
                    Response::new()
                        .with_status(StatusCode::BadRequest)
                        .with_header(ContentType::plaintext())
                        .with_header(ContentLength(body.len() as u64))
                        .with_body(body),
                )
            });

        Box::new(work)
    }
}

fn main() {
    // Create HTTP client core + handles
    let mut core = Core::new().unwrap();
    let handle = core.handle();
    let handle_clone = handle.clone();

    // Create HTTP server
    let server_addr = "127.0.0.1:9999".parse().unwrap();
    let server = Http::new()
        .serve_addr_handle(&server_addr, &handle, move || {
            Ok(Server::new(Client::new(&handle_clone)))
        })
        .unwrap();

    // Connect HTTP client with server
    let handle_clone2 = handle.clone();
    handle.spawn(
        server
            .for_each(move |conn| {
                handle_clone2.spawn(conn.map(|_| ()).map_err(|err| println!("Error: {:?}", err)));
                Ok(())
            })
            .map_err(|_| ()),
    );

    core.run(futures::future::empty::<(), ()>()).unwrap();
}

Это работает нормально, если у вас есть какая-либо HTTP-служба, работающая на порту 80, подключение с помощью браузера к порту 9999 будет идеально пересылать любые ответы и запросы.

Однако, если вы снова включите строки, касающиеся создания нового скопированного запроса, мой подход не удастся, поскольку я не понимаю, как копировать заголовки. (Более того, это мне не очень помогает, когда дело доходит до копирования тела запроса)

Мне известно, что здесь есть похожие вопросы, но ни один из них не соответствует моему требованию повторно использовать тело запроса после его просмотра (или вообще не иметь ответов).


person Philipp Ludwig    schedule 20.03.2018    source источник
comment
@Shepmaster Да, я прочитал все руководства и просмотрел примеры, представленные в репозитории; ни один из них не касается этой проблемы.   -  person Philipp Ludwig    schedule 20.03.2018
comment
@Shepmaster Я включил в свой вопрос полный, работоспособный пример. Я попытался сделать его минимальным, насколько это возможно, но для запуска HTTP-сервера и клиента просто требуется некоторый шаблонный код. Не стесняйтесь сокращать его везде, где видите какой-либо потенциал.   -  person Philipp Ludwig    schedule 20.03.2018


Ответы (1)


тело запроса нельзя просто скопировать во что-то вроде Vec<u8>

Конечно, может. В стандартной библиотеке Rust стоит запомнить возможности трейта Iterator. Имея дело с фьючерсами, вам также следует запомнить возможности Future < / a> и Stream.

Например, Body Hyper реализует Stream. Это означает, что вы можете использовать _7 _ метод:

Объедините все результаты потока в один расширяемый пункт назначения, возвращая будущее, представляющее конечный результат.

Это создает один большой Chunk, который можно преобразовать в Vec:

extern crate hyper;
extern crate futures;

use futures::{Future, Stream};

fn example(req: hyper::Request) {
    req.body().concat2().map(|chunk| {
        let body = chunk.to_vec(); 
        println!("{:?}", body);
        ()
    });
    // Use this future somehow!
}

Точно так же Vec<u8> можно преобразовать обратно в Body.

поскольку деконструированные заголовки не могут быть добавлены в новый запрос.

req_copy.headers_mut().extend(headers.iter());

Все вместе:

fn create_localhost_request(req: Request) -> (Request, Body) {
    let (method, uri, version, headers, body) = req.deconstruct();

    let req_uri_str = {
        format!(
            "http://localhost{}?{}",
            uri.path(),
            uri.query().unwrap_or_default()
        )
    };
    let uri = req_uri_str.parse().unwrap();

    let mut req_copy = Request::new(method, uri);
    req_copy.set_version(version);
    req_copy.headers_mut().extend(headers.iter());

    (req_copy, body)
}

fn perform_proxy_request(
    client: HttpClient,
    req: Request,
) -> Box<Future<Item = Response, Error = hyper::Error>> {
    Box::new(client.request(req).or_else(|err| {
        let body = format!("{}\n", err);
        Ok(Response::new()
            .with_status(StatusCode::BadRequest)
            .with_header(ContentType::plaintext())
            .with_header(ContentLength(body.len() as u64))
            .with_body(body))
    }))
}

impl Service for Server {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, req: Request) -> Self::Future {
        let (mut req, body) = create_localhost_request(req);
        let client = self.client.clone();

        let work = body
            .concat2()
            .map(|chunk| chunk.to_vec())
            // Do whatever we need with the body here, but be careful
            // about doing any synchronous work.
            .map(move |body| {
                req.set_body(body);
                req
            })
            .and_then(|req| perform_proxy_request(client, req));

        Box::new(work)
    }
}
person Shepmaster    schedule 20.03.2018
comment
Должен признаться, мне следовало подумать о простой работе с телом запроса, используя возможности Iterator (я видел concat2 в примерах). Спасибо большое, это как раз то, что я хотел! - person Philipp Ludwig; 20.03.2018
comment
@PhilippLudwig concat2 не предоставлен Iterator; это предоставляется Stream. Выше есть ссылка на соответствующие документы. - person Shepmaster; 20.03.2018
comment
Понятно. Благодаря вашему подробному объяснению мне также удалось легко проверить ответ. Тем не менее, мне нужно будет прочитать о Streams и Futures. - person Philipp Ludwig; 20.03.2018
comment
Есть ли причина, по которой этот бит кода никогда не попадал в черту clone объекта запроса? - person adv; 13.08.2020
comment
@adv Я не знаю, видели ли когда-либо это гипер-сопровождающие и отправлял ли кто-нибудь запрос на добавление. Возможно, у вас получится! - person Shepmaster; 17.08.2020
comment
Я могу это сделать после того, как мы закончим этот проект. Отсутствие clone в объекте запроса сделало этот проект в 10 раз сложнее, чем нужно. Я обязательно посмотрю PR методом копирования. Спасибо. - person adv; 18.08.2020