Skip to content

Commit 4296bc0

Browse files
committed
fix(server): add fix for keep-alive connection on http version 1.0
Change behaviour of connection or server response when the request is version 1.0 and the Connection: keep-alive header is not present. 1. If the response is also version 1.0, then connection is closed if the server keep-alive header is not present. 2. If the response is version 1.1, then the keep-alive header is added when downgrading to version 1.0. CLOSES: #1614
1 parent bdbaa85 commit 4296bc0

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

src/proto/h1/conn.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use std::marker::PhantomData;
44

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

1010
use ::Chunk;
1111
use proto::{BodyLength, DecodedLength, MessageHead};
12+
use headers::connection_keep_alive;
1213
use super::io::{Buffered};
1314
use super::{EncodedBuf, Encode, Encoder, /*Decode,*/ Decoder, Http1Transaction, ParseContext};
1415

@@ -438,12 +439,38 @@ where I: AsyncRead + AsyncWrite,
438439
}
439440
}
440441

442+
// Fix keep-alives when Connection: keep-alive header is not present
443+
fn fix_keep_alive(&mut self, head: &mut MessageHead<T::Outgoing>) {
444+
let outgoing_is_keep_alive = head
445+
.headers
446+
.get(CONNECTION)
447+
.and_then(|value| Some(connection_keep_alive(value)))
448+
.unwrap_or(false);
449+
450+
if !outgoing_is_keep_alive {
451+
match head.version {
452+
// If response is version 1.0 and keep-alive is not present in the response,
453+
// disable keep-alive so the server closes the connection
454+
Version::HTTP_10 => self.state.disable_keep_alive(),
455+
// If response is version 1.1 and keep-alive is wanted, add
456+
// Connection: keep-alive header when not present
457+
Version::HTTP_11 => if self.state.wants_keep_alive() {
458+
head.headers
459+
.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
460+
},
461+
_ => (),
462+
}
463+
}
464+
}
465+
441466
// If we know the remote speaks an older version, we try to fix up any messages
442467
// to work with our older peer.
443468
fn enforce_version(&mut self, head: &mut MessageHead<T::Outgoing>) {
444469

445470
match self.state.version {
446471
Version::HTTP_10 => {
472+
// Fixes response or connection when keep-alive header is not present
473+
self.fix_keep_alive(head);
447474
// If the remote only knows HTTP/1.0, we should force ourselves
448475
// to do only speak HTTP/1.0 as well.
449476
head.version = Version::HTTP_10;

tests/server.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use tokio::reactor::Handle;
3030
use tokio_io::{AsyncRead, AsyncWrite};
3131

3232

33-
use hyper::{Body, Request, Response, StatusCode};
33+
use hyper::{Body, Request, Response, StatusCode, Version};
3434
use hyper::client::Client;
3535
use hyper::server::conn::Http;
3636
use hyper::server::Server;
@@ -637,6 +637,7 @@ fn keep_alive() {
637637
fn http_10_keep_alive() {
638638
let foo_bar = b"foo bar baz";
639639
let server = serve();
640+
// Response version 1.1 with no keep-alive header will downgrade to 1.0 when served
640641
server.reply()
641642
.header("content-length", foo_bar.len().to_string())
642643
.body(foo_bar);
@@ -658,6 +659,10 @@ fn http_10_keep_alive() {
658659
}
659660
}
660661

662+
// Connection: keep-alive header should be added when downgrading to a 1.0 response
663+
let response = String::from_utf8(buf.to_vec()).unwrap();
664+
response.contains("Connection: keep-alive\r\n");
665+
661666
// try again!
662667

663668
let quux = b"zar quux";
@@ -682,6 +687,69 @@ fn http_10_keep_alive() {
682687
}
683688
}
684689

690+
#[test]
691+
fn http_10_close_on_no_ka() {
692+
let foo_bar = b"foo bar baz";
693+
let server = serve();
694+
695+
// A server response with version 1.0 but no keep-alive header
696+
server
697+
.reply()
698+
.version(Version::HTTP_10)
699+
.header("content-length", foo_bar.len().to_string())
700+
.body(foo_bar);
701+
let mut req = connect(server.addr());
702+
703+
// The client request with version 1.0 that may have the keep-alive header
704+
req.write_all(
705+
b"\
706+
GET / HTTP/1.0\r\n\
707+
Host: example.domain\r\n\
708+
Connection: keep-alive\r\n\
709+
\r\n\
710+
",
711+
).expect("writing 1");
712+
713+
let mut buf = [0; 1024 * 8];
714+
loop {
715+
let n = req.read(&mut buf[..]).expect("reading 1");
716+
if n < buf.len() {
717+
if &buf[n - foo_bar.len()..n] == foo_bar {
718+
break;
719+
} else {
720+
}
721+
}
722+
}
723+
724+
// try again!
725+
726+
let quux = b"zar quux";
727+
server
728+
.reply()
729+
.header("content-length", quux.len().to_string())
730+
.body(quux);
731+
732+
// the write can possibly succeed, since it fills the kernel buffer on the first write
733+
let _ = req.write_all(
734+
b"\
735+
GET /quux HTTP/1.1\r\n\
736+
Host: example.domain\r\n\
737+
Connection: close\r\n\
738+
\r\n\
739+
",
740+
);
741+
742+
let mut buf = [0; 1024 * 8];
743+
match req.read(&mut buf[..]) {
744+
// Ok(0) means EOF, so a proper shutdown
745+
// Err(_) could mean ConnReset or something, also fine
746+
Ok(0) | Err(_) => {}
747+
Ok(n) => {
748+
panic!("read {} bytes on a disabled keep-alive socket", n);
749+
}
750+
}
751+
}
752+
685753
#[test]
686754
fn disable_keep_alive() {
687755
let foo_bar = b"foo bar baz";

0 commit comments

Comments
 (0)