From 477bf0d8ae0b22a4ac2d1f52d0cdcd91599546bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 23 Dec 2018 10:19:12 -0800 Subject: [PATCH] Send HTTP/1.1 100 Continue if request contains expect: continue header #634 --- CHANGES.md | 4 +++- Cargo.toml | 6 +++--- src/server/h1.rs | 46 ++++++++++++++++++++++++++++++----------- src/server/h1decoder.rs | 31 ++++++++++++++++++++------- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 232d85b8d..bdc50fc51 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,13 @@ # Changes -## [0.7.17] - 2018-xx-xx +## [0.7.17] - 2018-12-23 ### Added * Support for custom content types in `JsonConfig`. #637 +* Send `HTTP/1.1 100 Continue` if request contains `expect: continue` header #634 + ### Fixed * HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631 diff --git a/Cargo.toml b/Cargo.toml index cd13e341c..32ca147c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.16" +version = "0.7.17" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -62,7 +62,7 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.9" -actix-net = "0.2.2" +actix-net = "0.2.6" askama_escape = "0.1.0" base64 = "0.10" @@ -105,7 +105,7 @@ slab = "0.4" tokio = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" -tokio-timer = "0.2" +tokio-timer = "0.2.8" tokio-reactor = "0.1" tokio-current-thread = "0.1" diff --git a/src/server/h1.rs b/src/server/h1.rs index f0edefae2..fa7d2fda5 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -7,6 +7,7 @@ use futures::{Async, Future, Poll}; use tokio_current_thread::spawn; use tokio_timer::Delay; +use body::Binary; use error::{Error, PayloadError}; use http::{StatusCode, Version}; use payload::{Payload, PayloadStatus, PayloadWriter}; @@ -50,32 +51,40 @@ pub struct Http1Dispatcher { } enum Entry { - Task(H::Task), + Task(H::Task, Option<()>), Error(Box), } impl Entry { fn into_task(self) -> H::Task { match self { - Entry::Task(task) => task, + Entry::Task(task, _) => task, Entry::Error(_) => panic!(), } } fn disconnected(&mut self) { match *self { - Entry::Task(ref mut task) => task.disconnected(), + Entry::Task(ref mut task, _) => task.disconnected(), Entry::Error(ref mut task) => task.disconnected(), } } fn poll_io(&mut self, io: &mut Writer) -> Poll { match *self { - Entry::Task(ref mut task) => task.poll_io(io), + Entry::Task(ref mut task, ref mut except) => { + match except { + Some(_) => { + let _ = io.write(&Binary::from("HTTP/1.1 100 Continue\r\n\r\n")); + } + _ => (), + }; + task.poll_io(io) + } Entry::Error(ref mut task) => task.poll_io(io), } } fn poll_completed(&mut self) -> Poll<(), Error> { match *self { - Entry::Task(ref mut task) => task.poll_completed(), + Entry::Task(ref mut task, _) => task.poll_completed(), Entry::Error(ref mut task) => task.poll_completed(), } } @@ -463,7 +472,11 @@ where 'outer: loop { match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { mut msg, payload })) => { + Ok(Some(Message::Message { + mut msg, + mut expect, + payload, + })) => { updated = true; self.flags.insert(Flags::STARTED); @@ -484,6 +497,12 @@ where match self.settings.handler().handle(msg) { Ok(mut task) => { if self.tasks.is_empty() { + if expect { + expect = false; + let _ = self.stream.write(&Binary::from( + "HTTP/1.1 100 Continue\r\n\r\n", + )); + } match task.poll_io(&mut self.stream) { Ok(Async::Ready(ready)) => { // override keep-alive state @@ -510,7 +529,10 @@ where } } } - self.tasks.push_back(Entry::Task(task)); + self.tasks.push_back(Entry::Task( + task, + if expect { Some(()) } else { None }, + )); continue 'outer; } Err(_) => { @@ -608,13 +630,13 @@ mod tests { impl Message { fn message(self) -> Request { match self { - Message::Message { msg, payload: _ } => msg, + Message::Message { msg, .. } => msg, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - Message::Message { msg: _, payload } => payload, + Message::Message { payload, .. } => payload, _ => panic!("error"), } } @@ -874,13 +896,13 @@ mod tests { let settings = wrk_settings(); let mut reader = H1Decoder::new(); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } buf.extend(b"t"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } buf.extend(b"es"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); match reader.decode(&mut buf, &settings) { diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 80b49983b..ece6b3cce 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -20,7 +20,11 @@ pub(crate) struct H1Decoder { #[derive(Debug)] pub(crate) enum Message { - Message { msg: Request, payload: bool }, + Message { + msg: Request, + payload: bool, + expect: bool, + }, Chunk(Bytes), Eof, } @@ -63,10 +67,11 @@ impl H1Decoder { .parse_message(src, settings) .map_err(DecoderError::Error)? { - Async::Ready((msg, decoder)) => { + Async::Ready((msg, expect, decoder)) => { self.decoder = decoder; Ok(Some(Message::Message { msg, + expect, payload: self.decoder.is_some(), })) } @@ -85,11 +90,12 @@ impl H1Decoder { &self, buf: &mut BytesMut, settings: &ServiceConfig, - ) -> Poll<(Request, Option), ParseError> { + ) -> Poll<(Request, bool, Option), ParseError> { // Parse http message let mut has_upgrade = false; let mut chunked = false; let mut content_length = None; + let mut expect_continue = false; let msg = { // Unsafe: we read only this data only after httparse parses headers into. @@ -165,11 +171,17 @@ impl H1Decoder { } // connection keep-alive state header::CONNECTION => { - let ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { - if version == Version::HTTP_10 && conn.eq_ignore_ascii_case("keep-alive") { + let ka = if let Ok(conn) = + value.to_str().map(|conn| conn.trim()) + { + if version == Version::HTTP_10 + && conn.eq_ignore_ascii_case("keep-alive") + { true } else { - version == Version::HTTP_11 && !(conn.eq_ignore_ascii_case("close") || conn.eq_ignore_ascii_case("upgrade")) + version == Version::HTTP_11 + && !(conn.eq_ignore_ascii_case("close") + || conn.eq_ignore_ascii_case("upgrade")) } } else { false @@ -186,6 +198,11 @@ impl H1Decoder { } } } + header::EXPECT => { + if value == "100-continue" { + expect_continue = true + } + } _ => (), } @@ -216,7 +233,7 @@ impl H1Decoder { None }; - Ok(Async::Ready((msg, decoder))) + Ok(Async::Ready((msg, expect_continue, decoder))) } }