1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

Fix HEAD requests handling

This commit is contained in:
Nikolay Kim 2018-01-20 16:12:38 -08:00
parent a7c24aace1
commit 91c44a1cf1
5 changed files with 135 additions and 53 deletions

View File

@ -2,6 +2,8 @@
## 0.3.2 (2018-01-xx) ## 0.3.2 (2018-01-xx)
* Fix HEAD requests handling
* Can't have multiple Applications on a single server with different state #49 * Can't have multiple Applications on a single server with different state #49

View File

@ -3,7 +3,7 @@ use std::io::{Read, Write};
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::str::FromStr; use std::str::FromStr;
use http::Version; use http::{Version, Method, HttpTryFrom};
use http::header::{HeaderMap, HeaderValue, use http::header::{HeaderMap, HeaderValue,
ACCEPT_ENCODING, CONNECTION, ACCEPT_ENCODING, CONNECTION,
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
@ -378,10 +378,12 @@ impl PayloadEncoder {
ContentEncoding::Identity ContentEncoding::Identity
}; };
let transfer = match body { let mut transfer = match body {
Body::Empty => { Body::Empty => {
resp.headers_mut().remove(CONTENT_LENGTH); if req.method != Method::HEAD {
TransferEncoding::eof(buf) resp.headers_mut().remove(CONTENT_LENGTH);
}
TransferEncoding::length(0, buf)
}, },
Body::Binary(ref mut bytes) => { Body::Binary(ref mut bytes) => {
if encoding.is_compression() { if encoding.is_compression() {
@ -404,7 +406,14 @@ impl PayloadEncoder {
*bytes = Binary::from(tmp.take()); *bytes = Binary::from(tmp.take());
encoding = ContentEncoding::Identity; encoding = ContentEncoding::Identity;
} }
resp.headers_mut().remove(CONTENT_LENGTH); if req.method == Method::HEAD {
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
resp.headers_mut().insert(
CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
} else {
resp.headers_mut().remove(CONTENT_LENGTH);
}
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} }
Body::Streaming(_) | Body::Actor(_) => { Body::Streaming(_) | Body::Actor(_) => {
@ -425,7 +434,12 @@ impl PayloadEncoder {
} }
} }
}; };
resp.replace_body(body); //
if req.method == Method::HEAD {
transfer.kind = TransferEncodingKind::Length(0);
} else {
resp.replace_body(body);
}
PayloadEncoder( PayloadEncoder(
match encoding { match encoding {
@ -714,14 +728,18 @@ impl TransferEncoding {
Ok(*eof) Ok(*eof)
}, },
TransferEncodingKind::Length(ref mut remaining) => { TransferEncodingKind::Length(ref mut remaining) => {
if msg.is_empty() { if *remaining > 0 {
return Ok(*remaining == 0) if msg.is_empty() {
} return Ok(*remaining == 0)
let max = cmp::min(*remaining, msg.len() as u64); }
self.buffer.extend(msg.take().split_to(max as usize).into()); let len = cmp::min(*remaining, msg.len() as u64);
self.buffer.extend(msg.take().split_to(len as usize).into());
*remaining -= max as u64; *remaining -= len as u64;
Ok(*remaining == 0) Ok(*remaining == 0)
} else {
Ok(true)
}
}, },
} }
} }

View File

@ -100,8 +100,8 @@ impl<T, H> Http1<T, H>
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
pub fn poll(&mut self) -> Poll<(), ()> { pub fn poll(&mut self) -> Poll<(), ()> {
// keep-alive timer // keep-alive timer
if self.keepalive_timer.is_some() { if let Some(ref mut timer) = self.keepalive_timer {
match self.keepalive_timer.as_mut().unwrap().poll() { match timer.poll() {
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
trace!("Keep-alive timeout, close connection"); trace!("Keep-alive timeout, close connection");
return Ok(Async::Ready(())) return Ok(Async::Ready(()))
@ -146,10 +146,8 @@ impl<T, H> Http1<T, H>
item.flags.insert(EntryFlags::FINISHED); item.flags.insert(EntryFlags::FINISHED);
} }
}, },
Ok(Async::NotReady) => { // no more IO for this iteration
// no more IO for this iteration Ok(Async::NotReady) => io = true,
io = true;
},
Err(err) => { Err(err) => {
// it is not possible to recover from error // it is not possible to recover from error
// during pipe handling, so just drop connection // during pipe handling, so just drop connection
@ -227,38 +225,7 @@ impl<T, H> Http1<T, H>
self.tasks.push_back( self.tasks.push_back(
Entry {pipe: pipe.unwrap_or_else(|| Pipeline::error(HTTPNotFound)), Entry {pipe: pipe.unwrap_or_else(|| Pipeline::error(HTTPNotFound)),
flags: EntryFlags::empty()}); flags: EntryFlags::empty()});
}
Err(ReaderError::Disconnect) => {
not_ready = false;
self.flags.insert(Flags::ERROR);
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
}
}, },
Err(err) => {
// notify all tasks
not_ready = false;
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
}
// kill keepalive
self.flags.remove(Flags::KEEPALIVE);
self.keepalive_timer.take();
// on parse error, stop reading stream but tasks need to be completed
self.flags.insert(Flags::ERROR);
if self.tasks.is_empty() {
if let ReaderError::Error(err) = err {
self.tasks.push_back(
Entry {pipe: Pipeline::error(err.error_response()),
flags: EntryFlags::empty()});
}
}
}
Ok(Async::NotReady) => { Ok(Async::NotReady) => {
// start keep-alive timer, this also is slow request timeout // start keep-alive timer, this also is slow request timeout
if self.tasks.is_empty() { if self.tasks.is_empty() {
@ -293,7 +260,38 @@ impl<T, H> Http1<T, H>
} }
} }
break break
} },
Err(ReaderError::Disconnect) => {
not_ready = false;
self.flags.insert(Flags::ERROR);
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
}
},
Err(err) => {
// notify all tasks
not_ready = false;
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
}
// kill keepalive
self.flags.remove(Flags::KEEPALIVE);
self.keepalive_timer.take();
// on parse error, stop reading stream but tasks need to be completed
self.flags.insert(Flags::ERROR);
if self.tasks.is_empty() {
if let ReaderError::Error(err) = err {
self.tasks.push_back(
Entry {pipe: Pipeline::error(err.error_response()),
flags: EntryFlags::empty()});
}
}
},
} }
} }

View File

@ -2,7 +2,7 @@ use std::io;
use bytes::BufMut; use bytes::BufMut;
use futures::{Async, Poll}; use futures::{Async, Poll};
use tokio_io::AsyncWrite; use tokio_io::AsyncWrite;
use http::Version; use http::{Method, Version};
use http::header::{HeaderValue, CONNECTION, DATE}; use http::header::{HeaderValue, CONNECTION, DATE};
use helpers; use helpers;
@ -132,7 +132,11 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
match body { match body {
Body::Empty => Body::Empty =>
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"), if req.method != Method::HEAD {
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n");
} else {
buffer.extend_from_slice(b"\r\n");
},
Body::Binary(ref bytes) => Body::Binary(ref bytes) =>
helpers::write_content_length(bytes.len(), &mut buffer), helpers::write_content_length(bytes.len(), &mut buffer),
_ => _ =>

View File

@ -152,6 +152,66 @@ fn test_body_br_streaming() {
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_head_empty() {
let srv = test::TestServer::new(
|app| app.handler(|_| {
httpcodes::HTTPOk.build()
.content_length(STR.len() as u64).finish()}));
let client = reqwest::Client::new();
let mut res = client.head(&srv.url("/")).send().unwrap();
assert!(res.status().is_success());
let mut bytes = BytesMut::with_capacity(2048).writer();
let len = res.headers()
.get::<reqwest::header::ContentLength>().map(|ct_len| **ct_len).unwrap();
assert_eq!(len, STR.len() as u64);
let _ = res.copy_to(&mut bytes);
let bytes = bytes.into_inner();
assert!(bytes.is_empty());
}
#[test]
fn test_head_binary() {
let srv = test::TestServer::new(
|app| app.handler(|_| {
httpcodes::HTTPOk.build()
.content_encoding(headers::ContentEncoding::Identity)
.content_length(100).body(STR)}));
let client = reqwest::Client::new();
let mut res = client.head(&srv.url("/")).send().unwrap();
assert!(res.status().is_success());
let mut bytes = BytesMut::with_capacity(2048).writer();
let len = res.headers()
.get::<reqwest::header::ContentLength>().map(|ct_len| **ct_len).unwrap();
assert_eq!(len, STR.len() as u64);
let _ = res.copy_to(&mut bytes);
let bytes = bytes.into_inner();
assert!(bytes.is_empty());
}
#[test]
fn test_head_binary2() {
let srv = test::TestServer::new(
|app| app.handler(|_| {
httpcodes::HTTPOk.build()
.content_encoding(headers::ContentEncoding::Identity)
.body(STR)
}));
let client = reqwest::Client::new();
let mut res = client.head(&srv.url("/")).send().unwrap();
assert!(res.status().is_success());
let mut bytes = BytesMut::with_capacity(2048).writer();
let len = res.headers()
.get::<reqwest::header::ContentLength>().map(|ct_len| **ct_len).unwrap();
assert_eq!(len, STR.len() as u64);
let _ = res.copy_to(&mut bytes);
let bytes = bytes.into_inner();
assert!(bytes.is_empty());
}
#[test] #[test]
fn test_body_length() { fn test_body_length() {
let srv = test::TestServer::new( let srv = test::TestServer::new(