From e224a6d1268750a2abeacdd4d7e93771c9f7d7ed Mon Sep 17 00:00:00 2001 From: Deluvi Date: Tue, 23 Jun 2020 20:42:10 +0200 Subject: [PATCH] Set chunked Transfer-Encoding when using `send` Previously, using `.send()` on `Request` would require to set either the Transfer-Encoding or the Content-Length header. In an effort to provide better ergonomics for this library and to avoid making users fall in a not-so obvious pitfall, the library should build a valid Request without asking the user to mess around with the headers. This commit attempts to fix this issue: if the user use `.send()` to provide an unknown sized reader, the chunked Transfer-Encoding will be used. Of course, there are prior checks to ensure we do not override the user wish, like if the user already set a Content-Length or a Transfer-Encoding. This commit fix an edge case where the Content-Length would not be automatically set if the Transfer-Encoding is set but not chunked. --- src/body.rs | 2 +- src/lib.rs | 2 +- src/request.rs | 7 ++++--- src/unit.rs | 19 +++++++++++++++---- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/body.rs b/src/body.rs index b6b188f..5ec8526 100644 --- a/src/body.rs +++ b/src/body.rs @@ -66,7 +66,7 @@ impl SizedReader { impl Payload { pub fn into_read(self) -> SizedReader { match self { - Payload::Empty => SizedReader::new(None, Box::new(empty())), + Payload::Empty => SizedReader::new(Some(0), Box::new(empty())), Payload::Text(text, _charset) => { #[cfg(feature = "charset")] let bytes = { diff --git a/src/lib.rs b/src/lib.rs index de2129f..829657e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ //! which follows a build pattern. The builders are finished using: //! //! * [`.call()`](struct.Request.html#method.call) without a request body. -//! * [`.send()`](struct.Request.html#method.send) with a request body as `Read` (chunked encoding). +//! * [`.send()`](struct.Request.html#method.send) with a request body as `Read` (chunked encoding support for non-known sized readers). //! * [`.send_string()`](struct.Request.html#method.send_string) body as string. //! * [`.send_bytes()`](struct.Request.html#method.send_bytes) body as bytes. //! * [`.send_form()`](struct.Request.html#method.send_form) key-value pairs as application/x-www-form-urlencoded. diff --git a/src/request.rs b/src/request.rs index 4f1e6aa..12848bd 100644 --- a/src/request.rs +++ b/src/request.rs @@ -210,8 +210,10 @@ impl Request { /// Send data from a reader. /// - /// This uses [chunked transfer encoding](https://tools.ietf.org/html/rfc7230#section-4.1). - /// The caller is responsible for setting the Transfer-Encoding: chunked header. + /// If no Content-Length and Transfer-Encoding header has been set, it uses the [chunked transfer encoding](https://tools.ietf.org/html/rfc7230#section-4.1). + /// + /// The caller may set the Content-Length header to the expected byte size of the reader if is + /// known. /// /// The input from the reader is buffered into chunks of size 16,384, the max size of a TLS fragment. /// @@ -222,7 +224,6 @@ impl Request { /// /// let resp = ureq::post("http://localhost/example-upload") /// .set("Content-Type", "text/plain") - /// .set("Transfer-Encoding", "chunked") /// .send(read); /// ``` pub fn send(&mut self, reader: impl Read + 'static) -> Response { diff --git a/src/unit.rs b/src/unit.rs index f3822f2..1f5d9fc 100644 --- a/src/unit.rs +++ b/src/unit.rs @@ -35,12 +35,12 @@ impl Unit { pub(crate) fn new(req: &Request, url: &Url, mix_queries: bool, body: &SizedReader) -> Self { // - let is_chunked = req + let (is_transfer_encoding_set, mut is_chunked) = req .header("transfer-encoding") // if the user has set an encoding header, obey that. - .map(|enc| !enc.is_empty()) + .map(|enc| (!enc.is_empty(), enc == "chunked")) // otherwise, no chunking. - .unwrap_or(false); + .unwrap_or((false, false)); let query_string = combine_query(&url, &req.query, mix_queries); @@ -52,8 +52,19 @@ impl Unit { // chunking and Content-Length headers are mutually exclusive // also don't write this if the user has set it themselves if !is_chunked && !req.has("content-length") { + // if the payload is of known size (everything beside an unsized reader), set + // Content-Length, + // otherwise, use the chunked Transfer-Encoding (only if no other Transfer-Encoding + // has been set if let Some(size) = body.size { - extra.push(Header::new("Content-Length", &format!("{}", size))); + if size != 0 { + extra.push(Header::new("Content-Length", &format!("{}", size))); + } + } else { + if !is_transfer_encoding_set { + extra.push(Header::new("Transfer-Encoding", "chunked")); + is_chunked = true; + } } }