feat(client): implement HttpMessage for HTTP/1.1
This commit is contained in:
		
							
								
								
									
										243
									
								
								src/http11.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								src/http11.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,243 @@ | ||||
| //! Adapts the HTTP/1.1 implementation into the `HttpMessage` API. | ||||
| use std::io::{self, Write, BufWriter, Read}; | ||||
| use std::net::Shutdown; | ||||
|  | ||||
| use method::{Method}; | ||||
| use header::{ContentLength, TransferEncoding}; | ||||
| use header::Encoding::Chunked; | ||||
| use http::{HttpWriter, LINE_ENDING}; | ||||
| use http::HttpReader::{SizedReader, ChunkedReader, EofReader}; | ||||
| use http::HttpWriter::{ChunkedWriter, SizedWriter, EmptyWriter}; | ||||
| use buffer::BufReader; | ||||
| use http::{self, HttpReader}; | ||||
|  | ||||
| use message::{ | ||||
|     HttpMessage, | ||||
|     RequestHead, | ||||
|     ResponseHead, | ||||
| }; | ||||
| use net::NetworkStream; | ||||
| use header; | ||||
| use version; | ||||
|  | ||||
| /// An implementation of the `HttpMessage` trait for HTTP/1.1. | ||||
| #[derive(Debug)] | ||||
| pub struct Http11Message { | ||||
|     stream: Option<Box<NetworkStream + Send>>, | ||||
|     writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>, | ||||
|     reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>, | ||||
| } | ||||
|  | ||||
| impl Write for Http11Message { | ||||
|     #[inline] | ||||
|     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
|         match self.writer { | ||||
|             None => Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                           "Not in a writable state")), | ||||
|             Some(ref mut writer) => writer.write(buf), | ||||
|         } | ||||
|     } | ||||
|     #[inline] | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         match self.writer { | ||||
|             None => Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                           "Not in a writable state")), | ||||
|             Some(ref mut writer) => writer.flush(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Read for Http11Message { | ||||
|     #[inline] | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         match self.reader { | ||||
|             None => Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                           "Not in a readable state")), | ||||
|             Some(ref mut reader) => reader.read(buf), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl HttpMessage for Http11Message { | ||||
|     fn set_outgoing(&mut self, mut head: RequestHead) -> ::Result<RequestHead> { | ||||
|         if self.stream.is_none() { | ||||
|             return Err(From::from(io::Error::new( | ||||
|                         io::ErrorKind::Other, | ||||
|                         "Message not idle, cannot start new outgoing"))); | ||||
|         } | ||||
|         let mut stream = BufWriter::new(self.stream.take().unwrap()); | ||||
|  | ||||
|         let mut uri = head.url.serialize_path().unwrap(); | ||||
|         if let Some(ref q) = head.url.query { | ||||
|             uri.push('?'); | ||||
|             uri.push_str(&q[..]); | ||||
|         } | ||||
|  | ||||
|         let version = version::HttpVersion::Http11; | ||||
|         debug!("request line: {:?} {:?} {:?}", head.method, uri, version); | ||||
|         try!(write!(&mut stream, "{} {} {}{}", | ||||
|                     head.method, uri, version, LINE_ENDING)); | ||||
|  | ||||
|         let stream = match head.method { | ||||
|             Method::Get | Method::Head => { | ||||
|                 debug!("headers={:?}", head.headers); | ||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); | ||||
|                 EmptyWriter(stream) | ||||
|             }, | ||||
|             _ => { | ||||
|                 let mut chunked = true; | ||||
|                 let mut len = 0; | ||||
|  | ||||
|                 match head.headers.get::<header::ContentLength>() { | ||||
|                     Some(cl) => { | ||||
|                         chunked = false; | ||||
|                         len = **cl; | ||||
|                     }, | ||||
|                     None => () | ||||
|                 }; | ||||
|  | ||||
|                 // can't do in match above, thanks borrowck | ||||
|                 if chunked { | ||||
|                     let encodings = match head.headers.get_mut::<header::TransferEncoding>() { | ||||
|                         Some(&mut header::TransferEncoding(ref mut encodings)) => { | ||||
|                             //TODO: check if chunked is already in encodings. use HashSet? | ||||
|                             encodings.push(header::Encoding::Chunked); | ||||
|                             false | ||||
|                         }, | ||||
|                         None => true | ||||
|                     }; | ||||
|  | ||||
|                     if encodings { | ||||
|                         head.headers.set::<header::TransferEncoding>( | ||||
|                             header::TransferEncoding(vec![header::Encoding::Chunked])) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 debug!("headers={:?}", head.headers); | ||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); | ||||
|  | ||||
|                 if chunked { | ||||
|                     ChunkedWriter(stream) | ||||
|                 } else { | ||||
|                     SizedWriter(stream, len) | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         self.writer = Some(stream); | ||||
|  | ||||
|         Ok(head) | ||||
|     } | ||||
|  | ||||
|     fn get_incoming(&mut self) -> ::Result<ResponseHead> { | ||||
|         try!(self.flush_outgoing()); | ||||
|         if self.stream.is_none() { | ||||
|             // The message was already in the reading state... | ||||
|             // TODO Decide what happens in case we try to get a new incoming at that point | ||||
|             return Err(From::from( | ||||
|                     io::Error::new(io::ErrorKind::Other, | ||||
|                     "Read already in progress"))); | ||||
|         } | ||||
|  | ||||
|         let stream = self.stream.take().unwrap(); | ||||
|         let mut stream = BufReader::new(stream); | ||||
|  | ||||
|         let head = try!(http::parse_response(&mut stream)); | ||||
|         let raw_status = head.subject; | ||||
|         let headers = head.headers; | ||||
|  | ||||
|         let body = if headers.has::<TransferEncoding>() { | ||||
|             match headers.get::<TransferEncoding>() { | ||||
|                 Some(&TransferEncoding(ref codings)) => { | ||||
|                     if codings.len() > 1 { | ||||
|                         trace!("TODO: #2 handle other codings: {:?}", codings); | ||||
|                     }; | ||||
|  | ||||
|                     if codings.contains(&Chunked) { | ||||
|                         ChunkedReader(stream, None) | ||||
|                     } else { | ||||
|                         trace!("not chuncked. read till eof"); | ||||
|                         EofReader(stream) | ||||
|                     } | ||||
|                 } | ||||
|                 None => unreachable!() | ||||
|             } | ||||
|         } else if headers.has::<ContentLength>() { | ||||
|             match headers.get::<ContentLength>() { | ||||
|                 Some(&ContentLength(len)) => SizedReader(stream, len), | ||||
|                 None => unreachable!() | ||||
|             } | ||||
|         } else { | ||||
|             trace!("neither Transfer-Encoding nor Content-Length"); | ||||
|             EofReader(stream) | ||||
|         }; | ||||
|  | ||||
|         self.reader = Some(body); | ||||
|  | ||||
|         Ok(ResponseHead { | ||||
|             headers: headers, | ||||
|             raw_status: raw_status, | ||||
|             version: head.version, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn close_connection(&mut self) -> ::Result<()> { | ||||
|         try!(self.get_mut().close(Shutdown::Both)); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Http11Message { | ||||
|     /// Consumes the `Http11Message` and returns the underlying `NetworkStream`. | ||||
|     pub fn into_inner(mut self) -> Box<NetworkStream + Send> { | ||||
|         if self.stream.is_some() { | ||||
|             self.stream.take().unwrap() | ||||
|         } else if self.writer.is_some() { | ||||
|             self.writer.take().unwrap().into_inner().into_inner().unwrap() | ||||
|         } else if self.reader.is_some() { | ||||
|             self.reader.take().unwrap().into_inner().into_inner() | ||||
|         } else { | ||||
|             panic!("Http11Message lost its underlying stream somehow"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Gets a mutable reference to the underlying `NetworkStream`, regardless of the state of the | ||||
|     /// `Http11Message`. | ||||
|     pub fn get_mut(&mut self) -> &mut Box<NetworkStream + Send> { | ||||
|         if self.stream.is_some() { | ||||
|             self.stream.as_mut().unwrap() | ||||
|         } else if self.writer.is_some() { | ||||
|             self.writer.as_mut().unwrap().get_mut().get_mut() | ||||
|         } else if self.reader.is_some() { | ||||
|             self.reader.as_mut().unwrap().get_mut().get_mut() | ||||
|         } else { | ||||
|             panic!("Http11Message lost its underlying stream somehow"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Creates a new `Http11Message` that will use the given `NetworkStream` for communicating to | ||||
|     /// the peer. | ||||
|     pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message { | ||||
|         Http11Message { | ||||
|             stream: Some(stream), | ||||
|             writer: None, | ||||
|             reader: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Flushes the current outgoing content and moves the stream into the `stream` property. | ||||
|     /// | ||||
|     /// TODO It might be sensible to lift this up to the `HttpMessage` trait itself... | ||||
|     pub fn flush_outgoing(&mut self) -> ::Result<()> { | ||||
|         match self.writer { | ||||
|             None => return Ok(()), | ||||
|             Some(_) => {}, | ||||
|         }; | ||||
|  | ||||
|         let writer = self.writer.take().unwrap(); | ||||
|         let raw = try!(writer.end()).into_inner().unwrap(); // end() already flushes | ||||
|         self.stream = Some(raw); | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| @@ -186,6 +186,7 @@ pub mod status; | ||||
| pub mod uri; | ||||
| pub mod version; | ||||
| pub mod message; | ||||
| pub mod http11; | ||||
|  | ||||
|  | ||||
| /// Re-exporting the mime crate, for convenience. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user