Skip to content

Commit 44c34ce

Browse files
committed
fix(server): error if Response code is 1xx
Returning a Response from a Service with a 1xx StatusCode is not currently supported in hyper. It has always resulted in broken semantics. This patch simply errors better. - A Response with 1xx status is converted into a 500 response with no body. - An error is returned from the `server::Connection` to alert about the bad response.
1 parent 2277422 commit 44c34ce

File tree

5 files changed

+80
-13
lines changed

5 files changed

+80
-13
lines changed

src/proto/conn.rs

+23-5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ where I: AsyncRead + AsyncWrite,
4040
Conn {
4141
io: Buffered::new(io),
4242
state: State {
43+
error: None,
4344
keep_alive: keep_alive,
4445
method: None,
4546
read_task: None,
@@ -437,11 +438,18 @@ where I: AsyncRead + AsyncWrite,
437438
buf.extend_from_slice(pending.buf());
438439
}
439440
}
440-
let encoder = T::encode(head, body, &mut self.state.method, buf);
441-
self.state.writing = if !encoder.is_eof() {
442-
Writing::Body(encoder, None)
443-
} else {
444-
Writing::KeepAlive
441+
self.state.writing = match T::encode(head, body, &mut self.state.method, buf) {
442+
Ok(encoder) => {
443+
if !encoder.is_eof() {
444+
Writing::Body(encoder, None)
445+
} else {
446+
Writing::KeepAlive
447+
}
448+
},
449+
Err(err) => {
450+
self.state.error = Some(err);
451+
Writing::Closed
452+
}
445453
};
446454
}
447455

@@ -626,6 +634,14 @@ where I: AsyncRead + AsyncWrite,
626634
self.state.disable_keep_alive();
627635
}
628636
}
637+
638+
pub fn take_error(&mut self) -> ::Result<()> {
639+
if let Some(err) = self.state.error.take() {
640+
Err(err)
641+
} else {
642+
Ok(())
643+
}
644+
}
629645
}
630646

631647
// ==== tokio_proto impl ====
@@ -736,6 +752,7 @@ impl<I, B: AsRef<[u8]>, T, K: KeepAlive> fmt::Debug for Conn<I, B, T, K> {
736752
}
737753

738754
struct State<B, K> {
755+
error: Option<::Error>,
739756
keep_alive: K,
740757
method: Option<Method>,
741758
read_task: Option<Task>,
@@ -767,6 +784,7 @@ impl<B: AsRef<[u8]>, K: KeepAlive> fmt::Debug for State<B, K> {
767784
.field("reading", &self.reading)
768785
.field("writing", &self.writing)
769786
.field("keep_alive", &self.keep_alive.status())
787+
.field("error", &self.error)
770788
//.field("method", &self.method)
771789
.field("read_task", &self.read_task)
772790
.finish()

src/proto/dispatch.rs

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ where
7373

7474
if self.is_done() {
7575
try_ready!(self.conn.shutdown());
76+
self.conn.take_error()?;
7677
trace!("Dispatch::poll done");
7778
Ok(Async::Ready(()))
7879
} else {

src/proto/h1/parse.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,23 @@ impl Http1Transaction for ServerTransaction {
111111
}
112112

113113

114-
fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> Encoder {
114+
fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> ::Result<Encoder> {
115115
trace!("ServerTransaction::encode has_body={}, method={:?}", has_body, method);
116116

117-
let body = ServerTransaction::set_length(&mut head, has_body, method.as_ref());
117+
// hyper currently doesn't support returning 1xx status codes as a Response
118+
// This is because Service only allows returning a single Response, and
119+
// so if you try to reply with a e.g. 100 Continue, you have no way of
120+
// replying with the latter status code response.
121+
let ret = if head.subject.is_informational() {
122+
error!("response with 1xx status code not supported");
123+
head = MessageHead::default();
124+
head.subject = ::StatusCode::InternalServerError;
125+
head.headers.set(ContentLength(0));
126+
Err(::Error::Status)
127+
} else {
128+
Ok(ServerTransaction::set_length(&mut head, has_body, method.as_ref()))
129+
};
130+
118131

119132
let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE;
120133
dst.reserve(init_cap);
@@ -133,7 +146,8 @@ impl Http1Transaction for ServerTransaction {
133146
extend(dst, b"\r\n");
134147
}
135148
extend(dst, b"\r\n");
136-
body
149+
150+
ret
137151
}
138152

139153
fn should_error_on_parse_eof() -> bool {
@@ -289,7 +303,7 @@ impl Http1Transaction for ClientTransaction {
289303
}
290304
}
291305

292-
fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> Encoder {
306+
fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> ::Result<Encoder> {
293307
trace!("ClientTransaction::encode has_body={}, method={:?}", has_body, method);
294308

295309
*method = Some(head.subject.0.clone());
@@ -300,7 +314,7 @@ impl Http1Transaction for ClientTransaction {
300314
dst.reserve(init_cap);
301315
let _ = write!(FastWrite(dst), "{} {}\r\n{}\r\n", head.subject, head.version, head.headers);
302316

303-
body
317+
Ok(body)
304318
}
305319

306320
fn should_error_on_parse_eof() -> bool {
@@ -645,7 +659,7 @@ mod tests {
645659

646660
b.iter(|| {
647661
let mut vec = Vec::new();
648-
ServerTransaction::encode(head.clone(), true, &mut None, &mut vec);
662+
ServerTransaction::encode(head.clone(), true, &mut None, &mut vec).unwrap();
649663
assert_eq!(vec.len(), len);
650664
::test::black_box(vec);
651665
})

src/proto/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ pub trait Http1Transaction {
148148
type Outgoing: Default;
149149
fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>;
150150
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<::Method>) -> ::Result<Option<h1::Decoder>>;
151-
fn encode(head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> h1::Encoder;
151+
fn encode(head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> ::Result<h1::Encoder>;
152152

153153
fn should_error_on_parse_eof() -> bool;
154154
fn should_read_first() -> bool;

tests/server.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ use std::sync::{Arc, Mutex};
2222
use std::thread;
2323
use std::time::Duration;
2424

25-
use hyper::server::{Http, Request, Response, Service, NewService};
25+
use hyper::StatusCode;
26+
use hyper::server::{Http, Request, Response, Service, NewService, service_fn};
2627

2728

2829
#[test]
@@ -867,6 +868,38 @@ fn nonempty_parse_eof_returns_error() {
867868
core.run(fut).unwrap_err();
868869
}
869870

871+
#[test]
872+
fn returning_1xx_response_is_error() {
873+
let mut core = Core::new().unwrap();
874+
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap(), &core.handle()).unwrap();
875+
let addr = listener.local_addr().unwrap();
876+
877+
thread::spawn(move || {
878+
let mut tcp = connect(&addr);
879+
tcp.write_all(b"GET / HTTP/1.1\r\n\r\n").unwrap();
880+
let mut buf = [0; 256];
881+
tcp.read(&mut buf).unwrap();
882+
883+
let expected = "HTTP/1.1 500 ";
884+
assert_eq!(s(&buf[..expected.len()]), expected);
885+
});
886+
887+
let fut = listener.incoming()
888+
.into_future()
889+
.map_err(|_| unreachable!())
890+
.and_then(|(item, _incoming)| {
891+
let (socket, _) = item.unwrap();
892+
Http::<hyper::Chunk>::new()
893+
.serve_connection(socket, service_fn(|_| {
894+
Ok(Response::<hyper::Body>::new()
895+
.with_status(StatusCode::Continue))
896+
}))
897+
.map(|_| ())
898+
});
899+
900+
core.run(fut).unwrap_err();
901+
}
902+
870903
#[test]
871904
fn remote_addr() {
872905
let server = serve();
@@ -1191,3 +1224,4 @@ impl Drop for Dropped {
11911224
self.0.store(true, Ordering::SeqCst);
11921225
}
11931226
}
1227+

0 commit comments

Comments
 (0)