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

Client sends TransferEncoding: chunked for empty Body #1373

Closed
brandlk opened this issue Nov 12, 2017 · 8 comments
Closed

Client sends TransferEncoding: chunked for empty Body #1373

brandlk opened this issue Nov 12, 2017 · 8 comments
Labels
A-client Area: client. C-bug Category: bug. Something is wrong. This is bad! E-easy Effort: easy. A task that would be a great starting point for a new contributor.

Comments

@brandlk
Copy link

brandlk commented Nov 12, 2017

When receiving a HTTP request without a body, a Request with an empty Body is constructed instead of a Request with None as Body. Since GET requests neither have the ContentLength nor the TransferEncoding header set (this is the correct behavious as far as I understood), the hyper implementation automatically sets TransferEncoding: chunked in case I forward this request unchanged (as for example a proxy would do). This results in some servers responding with "Bad request" (for example "example.org"), which again seems to be the correct behaviour. Some servers are apparently more tolerant and reply nevertheless.

This means by literally doing nothing (e.g. just forwarding an incoming request), hyper turns a valid request into an invalid one, which seems strange.
Of course I could inspect the body and send a request without a body in case it is empty or build a request without a body in case neither ContentLength nor TransferEnconding is set, but this feels a bit like a workaround. And additionally, a Request does not offer any method to remove a body from the request. The method body() which takes the body out of the request, just replaces the original one with an empty one and thus does not help in this case. This means I have to build a new request and copy the headers from the original one before passing it on.

Is this behaviour intended? Or is there a chance to fix it? By skimming through the implementation I figured that Body is just a wrapper around the tokio-proto Body. This struct internally distinguishes between "Empty", "Stream" and "Once" but doesn't expose this functionality. If it would, (e.g, offering a "is_empty()" method), then it would be easy to change the hyper behaviour when encoding a request. But I'm not sure if it is desired to expose this, since conceptually there is no difference between an "officially" empty body and a stream without items. So maybe this is a stupid approach.

@seanmonstar
Copy link
Member

No, this is not expected behavior. In fact, in tests/client.rs, the very first test makes sure that no body doesn't send additional headers.

Indeed, it's as you've described: the reason is because request::from_wire will create a Request with a Some(Body::empty()), which then does imply there is a body.

@seanmonstar seanmonstar added A-client Area: client. E-easy Effort: easy. A task that would be a great starting point for a new contributor. C-bug Category: bug. Something is wrong. This is bad! labels Nov 14, 2017
@brandlk
Copy link
Author

brandlk commented Nov 14, 2017

Thanks a lot for this fast fix!

@walfie
Copy link

walfie commented Feb 16, 2018

Was this bug reintroduced at some point? I'm upgrading an app from hyper 0.11.1 and I'm having a similar issue on hyper 0.11.18. The Transfer-Encoding header is being added to requests that have an empty body.

Code

Cargo.toml

[package]
authors = ["Example"]
name = "hyper-chunked-get-error"
version = "0.1.0"

[dependencies]
futures = "0.1.18"
hyper = "=0.11.18"
tokio-core = "0.1.12"

src/main.rs

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

use hyper::{Client, Method, Request};
use tokio_core::reactor::Core;
use futures::{Future, Stream};

fn main() {
    let mut core = Core::new().unwrap();

    let client = Client::new(&core.handle());

    let uri = "http://localhost:9000".parse().unwrap();
    let mut request = Request::new(Method::Get, uri);
    let body: &[u8] = &[]; // Set empty body
    request.set_body(body);

    let response = core.run({
        client
            .request(request)
            .and_then(|resp| resp.body().concat2())
    }).unwrap();
    println!("{:?}", std::str::from_utf8(&response).unwrap());
}

Running

When I run it, e.g.,

# Listen to port 9000
while true; do nc -l 9000; done

# In a different shell
cargo run

I get as output (from nc), for any version of hyper >=0.11.2:

GET / HTTP/1.1
Host: localhost:9000
Transfer-Encoding: chunked

0

If I downgrade to hyper 0.11.1, I get:

GET / HTTP/1.1
Host: localhost:9000

(no Transfer-Encoding header, as I would expect)

or maybe I'm doing something wrong? I could open a separate issue if it's actually a different problem.

@aep
Copy link

aep commented Jun 19, 2018

running into this too. blocks rusoto/rusoto#1074

@seanmonstar
Copy link
Member

@aep this is using v0.12?

How is the body constructed?

@aep
Copy link

aep commented Jun 19, 2018

@seanmonstar wrap_stream which immediately returns Ok(Async::Ready(None))
using Body::empty() works fine for now

@seanmonstar
Copy link
Member

Ah, ok. With wrap_stream, there's no way to know how big the body could actually be, so it will default to transfer-encoding then.

@aep
Copy link

aep commented Jun 19, 2018

yep, no idea how to do this correctly.

I would prefer if it didn't chunk at all, since there are cases where you might want to take the stream and just send whatever you want yourself (websocket for example)

but for rusoto, this hack works fine: rusoto/rusoto@ed67c83#diff-77037ac1da2b41727b3e95f3c007f331R370

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-client Area: client. C-bug Category: bug. Something is wrong. This is bad! E-easy Effort: easy. A task that would be a great starting point for a new contributor.
Projects
None yet
Development

No branches or pull requests

4 participants