1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-30 18:34:36 +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)
* Fix HEAD requests handling
* 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::str::FromStr;
use http::Version;
use http::{Version, Method, HttpTryFrom};
use http::header::{HeaderMap, HeaderValue,
ACCEPT_ENCODING, CONNECTION,
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
@ -378,10 +378,12 @@ impl PayloadEncoder {
ContentEncoding::Identity
};
let transfer = match body {
let mut transfer = match body {
Body::Empty => {
if req.method != Method::HEAD {
resp.headers_mut().remove(CONTENT_LENGTH);
TransferEncoding::eof(buf)
}
TransferEncoding::length(0, buf)
},
Body::Binary(ref mut bytes) => {
if encoding.is_compression() {
@ -404,7 +406,14 @@ impl PayloadEncoder {
*bytes = Binary::from(tmp.take());
encoding = ContentEncoding::Identity;
}
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)
}
Body::Streaming(_) | Body::Actor(_) => {
@ -425,7 +434,12 @@ impl PayloadEncoder {
}
}
};
//
if req.method == Method::HEAD {
transfer.kind = TransferEncodingKind::Length(0);
} else {
resp.replace_body(body);
}
PayloadEncoder(
match encoding {
@ -714,14 +728,18 @@ impl TransferEncoding {
Ok(*eof)
},
TransferEncodingKind::Length(ref mut remaining) => {
if *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)
} else {
Ok(true)
}
},
}
}

View File

@ -100,8 +100,8 @@ impl<T, H> Http1<T, H>
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
pub fn poll(&mut self) -> Poll<(), ()> {
// keep-alive timer
if self.keepalive_timer.is_some() {
match self.keepalive_timer.as_mut().unwrap().poll() {
if let Some(ref mut timer) = self.keepalive_timer {
match timer.poll() {
Ok(Async::Ready(_)) => {
trace!("Keep-alive timeout, close connection");
return Ok(Async::Ready(()))
@ -146,10 +146,8 @@ impl<T, H> Http1<T, H>
item.flags.insert(EntryFlags::FINISHED);
}
},
Ok(Async::NotReady) => {
// no more IO for this iteration
io = true;
},
Ok(Async::NotReady) => io = true,
Err(err) => {
// it is not possible to recover from error
// during pipe handling, so just drop connection
@ -227,38 +225,7 @@ impl<T, H> Http1<T, H>
self.tasks.push_back(
Entry {pipe: pipe.unwrap_or_else(|| Pipeline::error(HTTPNotFound)),
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) => {
// start keep-alive timer, this also is slow request timeout
if self.tasks.is_empty() {
@ -293,7 +260,38 @@ impl<T, H> Http1<T, H>
}
}
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 futures::{Async, Poll};
use tokio_io::AsyncWrite;
use http::Version;
use http::{Method, Version};
use http::header::{HeaderValue, CONNECTION, DATE};
use helpers;
@ -132,7 +132,11 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
match body {
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) =>
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()));
}
#[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]
fn test_body_length() {
let srv = test::TestServer::new(