Skip to content

Commit a9dcc59

Browse files
committed
feat(server): dropping a Response will write out to the underlying stream
1 parent 7f5e038 commit a9dcc59

File tree

3 files changed

+195
-45
lines changed

3 files changed

+195
-45
lines changed

examples/server.rs

-4
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,21 @@ fn echo(mut req: Request, mut res: Response) {
2727
res.headers_mut().set(ContentLength(out.len() as u64));
2828
let mut res = try_return!(res.start());
2929
try_return!(res.write_all(out));
30-
try_return!(res.end());
3130
return;
3231
},
3332
(&Post, "/echo") => (), // fall through, fighting mutable borrows
3433
_ => {
3534
*res.status_mut() = hyper::NotFound;
36-
try_return!(res.start().and_then(|res| res.end()));
3735
return;
3836
}
3937
},
4038
_ => {
41-
try_return!(res.start().and_then(|res| res.end()));
4239
return;
4340
}
4441
};
4542

4643
let mut res = try_return!(res.start());
4744
try_return!(copy(&mut req, &mut res));
48-
try_return!(res.end());
4945
}
5046

5147
fn main() {

src/server/mod.rs

-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
//! (hyper::Get, _) => StatusCode::NotFound,
1616
//! _ => StatusCode::MethodNotAllowed
1717
//! };
18-
//!
19-
//! res.start().unwrap().end().unwrap();
2018
//! }).listen("0.0.0.0:8080").unwrap();
2119
use std::fmt;
2220
use std::io::{ErrorKind, BufWriter, Write};

src/server/response.rs

+195-39
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
//!
33
//! These are responses sent by a `hyper::Server` to clients, after
44
//! receiving a request.
5+
use std::any::{Any, TypeId};
56
use std::marker::PhantomData;
7+
use std::mem;
68
use std::io::{self, Write};
9+
use std::ptr;
710

811
use time::now_utc;
912

@@ -14,9 +17,10 @@ use status;
1417
use net::{Fresh, Streaming};
1518
use version;
1619

20+
1721
/// The outgoing half for a Tcp connection, created by a `Server` and given to a `Handler`.
1822
#[derive(Debug)]
19-
pub struct Response<'a, W = Fresh> {
23+
pub struct Response<'a, W: Any = Fresh> {
2024
/// The HTTP version of this response.
2125
pub version: version::HttpVersion,
2226
// Stream the Response is writing to, not accessible through UnwrittenResponse
@@ -26,10 +30,10 @@ pub struct Response<'a, W = Fresh> {
2630
// The outgoing headers on this response.
2731
headers: header::Headers,
2832

29-
_marker: PhantomData<W>
33+
_writing: PhantomData<W>
3034
}
3135

32-
impl<'a, W> Response<'a, W> {
36+
impl<'a, W: Any> Response<'a, W> {
3337
/// The status of this response.
3438
#[inline]
3539
pub fn status(&self) -> status::StatusCode { self.status }
@@ -47,31 +51,26 @@ impl<'a, W> Response<'a, W> {
4751
version: version,
4852
body: body,
4953
headers: headers,
50-
_marker: PhantomData,
54+
_writing: PhantomData,
5155
}
5256
}
5357

5458
/// Deconstruct this Response into its constituent parts.
5559
pub fn deconstruct(self) -> (version::HttpVersion, HttpWriter<&'a mut (Write + 'a)>,
5660
status::StatusCode, header::Headers) {
57-
(self.version, self.body, self.status, self.headers)
58-
}
59-
}
60-
61-
impl<'a> Response<'a, Fresh> {
62-
/// Creates a new Response that can be used to write to a network stream.
63-
pub fn new(stream: &'a mut (Write + 'a)) -> Response<'a, Fresh> {
64-
Response {
65-
status: status::StatusCode::Ok,
66-
version: version::HttpVersion::Http11,
67-
headers: header::Headers::new(),
68-
body: ThroughWriter(stream),
69-
_marker: PhantomData,
61+
unsafe {
62+
let parts = (
63+
self.version,
64+
ptr::read(&self.body),
65+
self.status,
66+
ptr::read(&self.headers)
67+
);
68+
mem::forget(self);
69+
parts
7070
}
7171
}
7272

73-
/// Consume this Response<Fresh>, writing the Headers and Status and creating a Response<Streaming>
74-
pub fn start(mut self) -> io::Result<Response<'a, Streaming>> {
73+
fn write_head(&mut self) -> io::Result<Body> {
7574
debug!("writing head: {:?} {:?}", self.version, self.status);
7675
try!(write!(&mut self.body, "{} {}{}{}", self.version, self.status, CR as char, LF as char));
7776

@@ -80,19 +79,14 @@ impl<'a> Response<'a, Fresh> {
8079
}
8180

8281

83-
let mut chunked = true;
84-
let mut len = 0;
82+
let mut body_type = Body::Chunked;
8583

86-
match self.headers.get::<header::ContentLength>() {
87-
Some(cl) => {
88-
chunked = false;
89-
len = **cl;
90-
},
91-
None => ()
84+
if let Some(cl) = self.headers.get::<header::ContentLength>() {
85+
body_type = Body::Sized(**cl);
9286
};
9387

9488
// can't do in match above, thanks borrowck
95-
if chunked {
89+
if body_type == Body::Chunked {
9690
let encodings = match self.headers.get_mut::<header::TransferEncoding>() {
9791
Some(&mut header::TransferEncoding(ref mut encodings)) => {
9892
//TODO: check if chunked is already in encodings. use HashSet?
@@ -113,46 +107,208 @@ impl<'a> Response<'a, Fresh> {
113107
try!(write!(&mut self.body, "{}", self.headers));
114108
try!(write!(&mut self.body, "{}", LINE_ENDING));
115109

116-
let stream = if chunked {
117-
ChunkedWriter(self.body.into_inner())
118-
} else {
119-
SizedWriter(self.body.into_inner(), len)
110+
Ok(body_type)
111+
}
112+
113+
114+
}
115+
116+
impl<'a> Response<'a, Fresh> {
117+
/// Creates a new Response that can be used to write to a network stream.
118+
#[inline]
119+
pub fn new(stream: &'a mut (Write + 'a)) -> Response<'a, Fresh> {
120+
Response {
121+
status: status::StatusCode::Ok,
122+
version: version::HttpVersion::Http11,
123+
headers: header::Headers::new(),
124+
body: ThroughWriter(stream),
125+
_writing: PhantomData,
126+
}
127+
}
128+
129+
/// Consume this Response<Fresh>, writing the Headers and Status and creating a Response<Streaming>
130+
pub fn start(mut self) -> io::Result<Response<'a, Streaming>> {
131+
let body_type = try!(self.write_head());
132+
let (version, body, status, headers) = self.deconstruct();
133+
let stream = match body_type {
134+
Body::Chunked => ChunkedWriter(body.into_inner()),
135+
Body::Sized(len) => SizedWriter(body.into_inner(), len)
120136
};
121137

122138
// "copy" to change the phantom type
123139
Ok(Response {
124-
version: self.version,
140+
version: version,
125141
body: stream,
126-
status: self.status,
127-
headers: self.headers,
128-
_marker: PhantomData,
142+
status: status,
143+
headers: headers,
144+
_writing: PhantomData,
129145
})
130146
}
131-
132147
/// Get a mutable reference to the status.
133148
#[inline]
134149
pub fn status_mut(&mut self) -> &mut status::StatusCode { &mut self.status }
135150

136151
/// Get a mutable reference to the Headers.
152+
#[inline]
137153
pub fn headers_mut(&mut self) -> &mut header::Headers { &mut self.headers }
138154
}
139155

156+
140157
impl<'a> Response<'a, Streaming> {
141158
/// Flushes all writing of a response to the client.
159+
#[inline]
142160
pub fn end(self) -> io::Result<()> {
143-
debug!("ending");
144-
try!(self.body.end());
161+
trace!("ending");
162+
let (_, body, _, _) = self.deconstruct();
163+
try!(body.end());
145164
Ok(())
146165
}
147166
}
148167

149168
impl<'a> Write for Response<'a, Streaming> {
169+
#[inline]
150170
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
151171
debug!("write {:?} bytes", msg.len());
152172
self.body.write(msg)
153173
}
154174

175+
#[inline]
155176
fn flush(&mut self) -> io::Result<()> {
156177
self.body.flush()
157178
}
158179
}
180+
181+
#[derive(PartialEq)]
182+
enum Body {
183+
Chunked,
184+
Sized(u64),
185+
}
186+
187+
impl<'a, T: Any> Drop for Response<'a, T> {
188+
fn drop(&mut self) {
189+
if TypeId::of::<T>() == TypeId::of::<Fresh>() {
190+
let mut body = match self.write_head() {
191+
Ok(Body::Chunked) => ChunkedWriter(self.body.get_mut()),
192+
Ok(Body::Sized(len)) => SizedWriter(self.body.get_mut(), len),
193+
Err(e) => {
194+
debug!("error dropping request: {:?}", e);
195+
return;
196+
}
197+
};
198+
end(&mut body);
199+
} else {
200+
end(&mut self.body);
201+
};
202+
203+
204+
#[inline]
205+
fn end<W: Write>(w: &mut W) {
206+
match w.write(&[]) {
207+
Ok(_) => match w.flush() {
208+
Ok(_) => debug!("drop successful"),
209+
Err(e) => debug!("error dropping request: {:?}", e)
210+
},
211+
Err(e) => debug!("error dropping request: {:?}", e)
212+
}
213+
}
214+
}
215+
}
216+
217+
#[cfg(test)]
218+
mod tests {
219+
use mock::MockStream;
220+
use super::Response;
221+
222+
macro_rules! lines {
223+
($s:ident = $($line:pat),+) => ({
224+
let s = String::from_utf8($s.write).unwrap();
225+
let mut lines = s.split_terminator("\r\n");
226+
227+
$(
228+
match lines.next() {
229+
Some($line) => (),
230+
other => panic!("line mismatch: {:?} != {:?}", other, stringify!($line))
231+
}
232+
)+
233+
234+
assert_eq!(lines.next(), None);
235+
})
236+
}
237+
238+
#[test]
239+
fn test_fresh_start() {
240+
let mut stream = MockStream::new();
241+
{
242+
let res = Response::new(&mut stream);
243+
res.start().unwrap().deconstruct();
244+
}
245+
246+
lines! { stream =
247+
"HTTP/1.1 200 OK",
248+
_date,
249+
_transfer_encoding,
250+
""
251+
}
252+
}
253+
254+
#[test]
255+
fn test_streaming_end() {
256+
let mut stream = MockStream::new();
257+
{
258+
let res = Response::new(&mut stream);
259+
res.start().unwrap().end().unwrap();
260+
}
261+
262+
lines! { stream =
263+
"HTTP/1.1 200 OK",
264+
_date,
265+
_transfer_encoding,
266+
"",
267+
"0",
268+
"" // empty zero body
269+
}
270+
}
271+
272+
#[test]
273+
fn test_fresh_drop() {
274+
use status::StatusCode;
275+
let mut stream = MockStream::new();
276+
{
277+
let mut res = Response::new(&mut stream);
278+
*res.status_mut() = StatusCode::NotFound;
279+
}
280+
281+
lines! { stream =
282+
"HTTP/1.1 404 Not Found",
283+
_date,
284+
_transfer_encoding,
285+
"",
286+
"0",
287+
"" // empty zero body
288+
}
289+
}
290+
291+
#[test]
292+
fn test_streaming_drop() {
293+
use std::io::Write;
294+
use status::StatusCode;
295+
let mut stream = MockStream::new();
296+
{
297+
let mut res = Response::new(&mut stream);
298+
*res.status_mut() = StatusCode::NotFound;
299+
let mut stream = res.start().unwrap();
300+
stream.write_all(b"foo").unwrap();
301+
}
302+
303+
lines! { stream =
304+
"HTTP/1.1 404 Not Found",
305+
_date,
306+
_transfer_encoding,
307+
"",
308+
"3",
309+
"foo",
310+
"0",
311+
"" // empty zero body
312+
}
313+
}
314+
}

0 commit comments

Comments
 (0)