Skip to content

Commit 26417fc

Browse files
committed
perf(h1): improve parsing and encoding of http1 messages
1 parent c3c35e8 commit 26417fc

File tree

13 files changed

+1014
-450
lines changed

13 files changed

+1014
-450
lines changed

src/client/conn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ impl<T, B, R> Future for HandshakeInner<T, B, R>
531531
where
532532
T: AsyncRead + AsyncWrite + Send + 'static,
533533
B: Payload,
534-
R: proto::Http1Transaction<
534+
R: proto::h1::Http1Transaction<
535535
Incoming=StatusCode,
536536
Outgoing=proto::RequestLine,
537537
>,

src/client/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ where C: Connect + Sync + 'static,
193193
Version::HTTP_11 => (),
194194
other => {
195195
error!("Request has unsupported version \"{:?}\"", other);
196-
//TODO: replace this with a proper variant
197196
return ResponseFuture::new(Box::new(future::err(::Error::new_user_unsupported_version())));
198197
}
199198
}

src/error.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ pub(crate) enum Kind {
2626
Parse(Parse),
2727
/// A message reached EOF, but is not complete.
2828
Incomplete,
29-
/// A protocol upgrade was encountered, but not yet supported in hyper.
30-
Upgrade,
3129
/// A client connection received a response when not waiting for one.
3230
MismatchedResponse,
3331
/// A pending item was dropped before ever being processed.
@@ -74,6 +72,9 @@ pub(crate) enum Parse {
7472
Header,
7573
TooLarge,
7674
Status,
75+
76+
/// A protocol upgrade was encountered, but not yet supported in hyper.
77+
UpgradeNotSupported,
7778
}
7879

7980
/*
@@ -141,10 +142,6 @@ impl Error {
141142
Error::new(Kind::Canceled, cause.map(Into::into))
142143
}
143144

144-
pub(crate) fn new_upgrade() -> Error {
145-
Error::new(Kind::Upgrade, None)
146-
}
147-
148145
pub(crate) fn new_incomplete() -> Error {
149146
Error::new(Kind::Incomplete, None)
150147
}
@@ -161,10 +158,6 @@ impl Error {
161158
Error::new(Kind::Parse(Parse::Status), None)
162159
}
163160

164-
pub(crate) fn new_version() -> Error {
165-
Error::new(Kind::Parse(Parse::Version), None)
166-
}
167-
168161
pub(crate) fn new_version_h2() -> Error {
169162
Error::new(Kind::Parse(Parse::VersionH2), None)
170163
}
@@ -260,8 +253,8 @@ impl StdError for Error {
260253
Kind::Parse(Parse::Header) => "invalid Header provided",
261254
Kind::Parse(Parse::TooLarge) => "message head is too large",
262255
Kind::Parse(Parse::Status) => "invalid Status provided",
256+
Kind::Parse(Parse::UpgradeNotSupported) => "unsupported protocol upgrade",
263257
Kind::Incomplete => "message is incomplete",
264-
Kind::Upgrade => "unsupported protocol upgrade",
265258
Kind::MismatchedResponse => "response received without matching request",
266259
Kind::Closed => "connection closed",
267260
Kind::Connect => "an error occurred trying to connect",
@@ -325,8 +318,8 @@ impl From<http::status::InvalidStatusCode> for Parse {
325318
}
326319
}
327320

328-
impl From<http::uri::InvalidUriBytes> for Parse {
329-
fn from(_: http::uri::InvalidUriBytes) -> Parse {
321+
impl From<http::uri::InvalidUri> for Parse {
322+
fn from(_: http::uri::InvalidUri) -> Parse {
330323
Parse::Uri
331324
}
332325
}

src/headers.rs

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,43 @@ use std::fmt::Write;
22

33
use bytes::BytesMut;
44
use http::HeaderMap;
5-
use http::header::{CONNECTION, CONTENT_LENGTH, EXPECT, TRANSFER_ENCODING};
5+
use http::header::{CONTENT_LENGTH, TRANSFER_ENCODING};
66
use http::header::{HeaderValue, OccupiedEntry, ValueIter};
77

88
/// Maximum number of bytes needed to serialize a u64 into ASCII decimal.
99
const MAX_DECIMAL_U64_BYTES: usize = 20;
1010

11-
pub fn connection_keep_alive(headers: &HeaderMap) -> bool {
12-
for line in headers.get_all(CONNECTION) {
13-
if let Ok(s) = line.to_str() {
14-
for val in s.split(',') {
15-
if eq_ascii(val.trim(), "keep-alive") {
16-
return true;
17-
}
18-
}
19-
}
20-
}
11+
pub fn connection_keep_alive(value: &HeaderValue) -> bool {
12+
connection_has(value, "keep-alive")
13+
}
2114

22-
false
15+
pub fn connection_close(value: &HeaderValue) -> bool {
16+
connection_has(value, "close")
2317
}
2418

25-
pub fn connection_close(headers: &HeaderMap) -> bool {
26-
for line in headers.get_all(CONNECTION) {
27-
if let Ok(s) = line.to_str() {
28-
for val in s.split(',') {
29-
if eq_ascii(val.trim(), "close") {
30-
return true;
31-
}
19+
fn connection_has(value: &HeaderValue, needle: &str) -> bool {
20+
if let Ok(s) = value.to_str() {
21+
for val in s.split(',') {
22+
if eq_ascii(val.trim(), needle) {
23+
return true;
3224
}
3325
}
3426
}
35-
3627
false
3728
}
3829

39-
pub fn content_length_parse(headers: &HeaderMap) -> Option<u64> {
40-
content_length_parse_all(headers.get_all(CONTENT_LENGTH).into_iter())
30+
pub fn content_length_parse(value: &HeaderValue) -> Option<u64> {
31+
value
32+
.to_str()
33+
.ok()
34+
.and_then(|s| s.parse().ok())
35+
}
36+
37+
pub fn content_length_parse_all(headers: &HeaderMap) -> Option<u64> {
38+
content_length_parse_all_values(headers.get_all(CONTENT_LENGTH).into_iter())
4139
}
4240

43-
pub fn content_length_parse_all(values: ValueIter<HeaderValue>) -> Option<u64> {
41+
pub fn content_length_parse_all_values(values: ValueIter<HeaderValue>) -> Option<u64> {
4442
// If multiple Content-Length headers were sent, everything can still
4543
// be alright if they all contain the same value, and all parse
4644
// correctly. If not, then it's an error.
@@ -70,10 +68,6 @@ pub fn content_length_parse_all(values: ValueIter<HeaderValue>) -> Option<u64> {
7068
}
7169
}
7270

73-
pub fn content_length_zero(headers: &mut HeaderMap) {
74-
headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
75-
}
76-
7771
pub fn content_length_value(len: u64) -> HeaderValue {
7872
let mut len_buf = BytesMut::with_capacity(MAX_DECIMAL_U64_BYTES);
7973
write!(len_buf, "{}", len)
@@ -84,21 +78,24 @@ pub fn content_length_value(len: u64) -> HeaderValue {
8478
}
8579
}
8680

87-
pub fn expect_continue(headers: &HeaderMap) -> bool {
88-
Some(&b"100-continue"[..]) == headers.get(EXPECT).map(|v| v.as_bytes())
89-
}
90-
9181
pub fn transfer_encoding_is_chunked(headers: &HeaderMap) -> bool {
9282
is_chunked(headers.get_all(TRANSFER_ENCODING).into_iter())
9383
}
9484

9585
pub fn is_chunked(mut encodings: ValueIter<HeaderValue>) -> bool {
9686
// chunked must always be the last encoding, according to spec
9787
if let Some(line) = encodings.next_back() {
98-
if let Ok(s) = line.to_str() {
99-
if let Some(encoding) = s.rsplit(',').next() {
100-
return eq_ascii(encoding.trim(), "chunked");
101-
}
88+
return is_chunked_(line);
89+
}
90+
91+
false
92+
}
93+
94+
pub fn is_chunked_(value: &HeaderValue) -> bool {
95+
// chunked must always be the last encoding, according to spec
96+
if let Ok(s) = value.to_str() {
97+
if let Some(encoding) = s.rsplit(',').next() {
98+
return eq_ascii(encoding.trim(), "chunked");
10299
}
103100
}
104101

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![doc(html_root_url = "https://docs.rs/hyper/0.11.22")]
22
#![deny(missing_docs)]
3-
#![deny(warnings)]
3+
//#![deny(warnings)]
44
#![deny(missing_debug_implementations)]
55
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
66

src/proto/h1/conn.rs

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ use std::marker::PhantomData;
44

55
use bytes::{Buf, Bytes};
66
use futures::{Async, Poll};
7-
use http::{Method, Version};
7+
use http::{HeaderMap, Method, Version};
88
use tokio_io::{AsyncRead, AsyncWrite};
99

1010
use ::Chunk;
11-
use proto::{BodyLength, Decode, Http1Transaction, MessageHead};
11+
use proto::{BodyLength, MessageHead};
1212
use super::io::{Buffered};
13-
use super::{EncodedBuf, Encoder, Decoder};
13+
use super::{EncodedBuf, Encode, Encoder, Decode, Decoder, Http1Transaction, ParseContext};
1414

1515
const H2_PREFACE: &'static [u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
1616

@@ -36,6 +36,7 @@ where I: AsyncRead + AsyncWrite,
3636
Conn {
3737
io: Buffered::new(io),
3838
state: State {
39+
cached_headers: None,
3940
error: None,
4041
keep_alive: KA::Busy,
4142
method: None,
@@ -118,8 +119,11 @@ where I: AsyncRead + AsyncWrite,
118119
trace!("Conn::read_head");
119120

120121
loop {
121-
let (version, head) = match self.io.parse::<T>() {
122-
Ok(Async::Ready(head)) => (head.version, head),
122+
let msg = match self.io.parse::<T>(ParseContext {
123+
cached_headers: &mut self.state.cached_headers,
124+
req_method: &mut self.state.method,
125+
}) {
126+
Ok(Async::Ready(msg)) => msg,
123127
Ok(Async::NotReady) => return Ok(Async::NotReady),
124128
Err(e) => {
125129
// If we are currently waiting on a message, then an empty
@@ -141,48 +145,32 @@ where I: AsyncRead + AsyncWrite,
141145
}
142146
};
143147

144-
match version {
145-
Version::HTTP_10 |
146-
Version::HTTP_11 => {},
147-
_ => {
148-
error!("unimplemented HTTP Version = {:?}", version);
149-
self.state.close_read();
150-
//TODO: replace this with a more descriptive error
151-
return Err(::Error::new_version());
152-
}
153-
};
154-
self.state.version = version;
155-
156-
let decoder = match T::decoder(&head, &mut self.state.method) {
157-
Ok(Decode::Normal(d)) => {
148+
self.state.version = msg.head.version;
149+
let head = msg.head;
150+
let decoder = match msg.decode {
151+
Decode::Normal(d) => {
158152
d
159153
},
160-
Ok(Decode::Final(d)) => {
154+
Decode::Final(d) => {
161155
trace!("final decoder, HTTP ending");
162156
debug_assert!(d.is_eof());
163157
self.state.close_read();
164158
d
165159
},
166-
Ok(Decode::Ignore) => {
160+
Decode::Ignore => {
167161
// likely a 1xx message that we can ignore
168162
continue;
169163
}
170-
Err(e) => {
171-
debug!("decoder error = {:?}", e);
172-
self.state.close_read();
173-
return self.on_parse_error(e)
174-
.map(|()| Async::NotReady);
175-
}
176164
};
177165

178166
debug!("incoming body is {}", decoder);
179167

180168
self.state.busy();
181-
if head.expecting_continue() {
182-
let msg = b"HTTP/1.1 100 Continue\r\n\r\n";
183-
self.io.write_buf_mut().extend_from_slice(msg);
169+
if msg.expect_continue {
170+
let cont = b"HTTP/1.1 100 Continue\r\n\r\n";
171+
self.io.write_buf_mut().extend_from_slice(cont);
184172
}
185-
let wants_keep_alive = head.should_keep_alive();
173+
let wants_keep_alive = msg.keep_alive;
186174
self.state.keep_alive &= wants_keep_alive;
187175
let (body, reading) = if decoder.is_eof() {
188176
(false, Reading::KeepAlive)
@@ -410,8 +398,17 @@ where I: AsyncRead + AsyncWrite,
410398
self.enforce_version(&mut head);
411399

412400
let buf = self.io.write_buf_mut();
413-
self.state.writing = match T::encode(head, body, &mut self.state.method, self.state.title_case_headers, buf) {
401+
self.state.writing = match T::encode(Encode {
402+
head: &mut head,
403+
body,
404+
keep_alive: self.state.wants_keep_alive(),
405+
req_method: &mut self.state.method,
406+
title_case_headers: self.state.title_case_headers,
407+
}, buf) {
414408
Ok(encoder) => {
409+
debug_assert!(self.state.cached_headers.is_none());
410+
debug_assert!(head.headers.is_empty());
411+
self.state.cached_headers = Some(head.headers);
415412
if !encoder.is_eof() {
416413
Writing::Body(encoder)
417414
} else if encoder.is_last() {
@@ -430,24 +427,12 @@ where I: AsyncRead + AsyncWrite,
430427
// If we know the remote speaks an older version, we try to fix up any messages
431428
// to work with our older peer.
432429
fn enforce_version(&mut self, head: &mut MessageHead<T::Outgoing>) {
433-
//use header::Connection;
434-
435-
let wants_keep_alive = if self.state.wants_keep_alive() {
436-
let ka = head.should_keep_alive();
437-
self.state.keep_alive &= ka;
438-
ka
439-
} else {
440-
false
441-
};
442430

443431
match self.state.version {
444432
Version::HTTP_10 => {
445433
// If the remote only knows HTTP/1.0, we should force ourselves
446434
// to do only speak HTTP/1.0 as well.
447435
head.version = Version::HTTP_10;
448-
if wants_keep_alive {
449-
//TODO: head.headers.set(Connection::keep_alive());
450-
}
451436
},
452437
_ => {
453438
// If the remote speaks HTTP/1.1, then it *should* be fine with
@@ -617,13 +602,27 @@ impl<I, B: Buf, T> fmt::Debug for Conn<I, B, T> {
617602
}
618603

619604
struct State {
605+
/// Re-usable HeaderMap to reduce allocating new ones.
606+
cached_headers: Option<HeaderMap>,
607+
/// If an error occurs when there wasn't a direct way to return it
608+
/// back to the user, this is set.
620609
error: Option<::Error>,
610+
/// Current keep-alive status.
621611
keep_alive: KA,
612+
/// If mid-message, the HTTP Method that started it.
613+
///
614+
/// This is used to know things such as if the message can include
615+
/// a body or not.
622616
method: Option<Method>,
623617
title_case_headers: bool,
618+
/// Set to true when the Dispatcher should poll read operations
619+
/// again. See the `maybe_notify` method for more.
624620
notify_read: bool,
621+
/// State of allowed reads
625622
reading: Reading,
623+
/// State of allowed writes
626624
writing: Writing,
625+
/// Either HTTP/1.0 or 1.1 connection
627626
version: Version,
628627
}
629628

src/proto/h1/dispatch.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use http::{Request, Response, StatusCode};
44
use tokio_io::{AsyncRead, AsyncWrite};
55

66
use body::{Body, Payload};
7-
use proto::{BodyLength, Conn, Http1Transaction, MessageHead, RequestHead, RequestLine, ResponseHead};
7+
use proto::{BodyLength, Conn, MessageHead, RequestHead, RequestLine, ResponseHead};
8+
use super::Http1Transaction;
89
use service::Service;
910

1011
pub(crate) struct Dispatcher<D, Bs: Payload, I, T> {

src/proto/h1/encode.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use iovec::IoVec;
77
use common::StaticBuf;
88

99
/// Encoders to handle different Transfer-Encodings.
10-
#[derive(Debug, Clone)]
10+
#[derive(Debug, Clone, PartialEq)]
1111
pub struct Encoder {
1212
kind: Kind,
1313
is_last: bool,
@@ -70,8 +70,9 @@ impl Encoder {
7070
}
7171
}
7272

73-
pub fn set_last(&mut self) {
74-
self.is_last = true;
73+
pub fn set_last(mut self, is_last: bool) -> Self {
74+
self.is_last = is_last;
75+
self
7576
}
7677

7778
pub fn is_last(&self) -> bool {

0 commit comments

Comments
 (0)