Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help on proxy doing client request inside server request #1636

Closed
JeanMertz opened this issue Aug 15, 2018 · 4 comments
Closed

Help on proxy doing client request inside server request #1636

JeanMertz opened this issue Aug 15, 2018 · 4 comments

Comments

@JeanMertz
Copy link

I've been at this for a couple of hours, but haven't had any luck so far.

I'm trying to realise the following flow:

  • request is received by server
  • JSON body of request is deserialised by serde
  • A client is created which uses a URL based on the JSON body
  • A request object is created, being exactly the same as the received request, except for the destination URL
  • the response from the client is returned as the response from the server
  • without using wait()

I'm especially struggling with reading the body in a future. I (believe I) have to wait for the body to be ready before I can deserialise it, so I have an example using tokio's core::run, and while it compiled, it panicked, which brought me to this PR:

tokio-rs/tokio-core#319 (comment)

@seanmonstar even mentions there that this is a common pitfall for people using something like I'm describing above, but because I need to look inside the payload of the request, I fail to see a solution that doesn't involve either wait or core::run at this point.

Any help is greatly appreciated.

@seanmonstar
Copy link
Member

I (believe I) have to wait for the body to be ready before I can deserialise it

Can you instead just change your Future that passes the response for the server back? You shouldn't need to return a response immediately, that's the whole point of being able to return a Future. The server echo guide has examples of cases where you wouldn't be able to respond immediately, returning a new future.

@JeanMertz
Copy link
Author

JeanMertz commented Aug 17, 2018

Thank you for the pointer @seanmonstar. That helped me move forward.

edit here's the full source code of the second (non-working) example: https://gist.github.com/JeanMertz/4278c2cfaebae9c8e57a4c69e5c75435

edit 2 Turns out it was as simple as turning req.body_mut() into req.into_body(), still have to figure out why that is (it "Consumes the request", but I don't quite understand yet how that relates to the borrow checker), but it works 😃


I'm close, but having difficulty wrapping this up:

fn main() {
    let addr = ([127, 0, 0, 1], 3000).into();
    let service = || service_fn(proxy);

    let server = Server::bind(&addr)
        .serve(service)
        .map_err(|e| eprintln!("server error: {}", e));

    hyper::rt::run(server);
}

fn proxy(req: Request<Body>) -> impl Future<Item = Response<Body>, Error = Error> {
    let client = Client::new();

    req.body_mut()
        .concat2()
        .map(|chunk| chunk.to_vec())
        .map(|vec| {
            let uri = uri_from_request_body(&vec);
            let mut new_request = Request::builder();

            new_request.version(req.version()).method(req.method());

            for (key, value) in req.headers() {
                new_request.header(key, value);
            }

            new_request
                .uri(&uri)
                .header(http::header::HOST, uri.host().unwrap())
                .body(Body::from(vec))
                .unwrap()
        }).and_then(|req| client.request(req))
}

There's a lot of unwrapping going on (trying to get the happy path to work first), but this example doesn't compile yet, and I'm not sure yet how to tackle this:

https://gist.github.com/JeanMertz/a38abfa49ad12f6faa60aca48f8b0fba

Any thoughts on the proposed solution and how to handle the cannot be shared between threads safely error?

I had another solution that made the Future simpler, but caused problems with the borrow checker:

req.body_mut()
    .concat2()
    .map(|chunk| chunk.to_vec())
    .map(move |vec| {
        let uri = uri_from_request_body(&vec);

        new_request
            .uri(&uri)
            .header(http::header::HOST, uri.host().unwrap())
            .body(Body::from(vec))
            .unwrap()
    }).and_then(move |new_req| client.request(new_req))
error[E0597]: `req` does not live long enough
   --> src/main.rs:148:5
    |
148 |     req.body_mut()
    |     ^^^ borrowed value does not live long enough
...
160 | }
    | - borrowed value only lives until here
    |
    = note: borrowed value must be valid for the static lifetime...

@kschiess
Copy link

I've found that decomposing 'req' into '.parts()', I can more easily clone/reuse parts of the request for a client connection. Here's a bit of code:

let (mut parts, body) = req.into_parts();
// Use and clone at will
let req = Request::from_parts(parts, body);

Maybe that helps?

@seanmonstar
Copy link
Member

To consume the body, you can use req.into_body(). The req.body_mut() only gives a mutable borrow, and so the future won't last longer than the end of the function (but you wish to return the future, so borrow checker is angry).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants