feat(server): add Http::max_buf_size() option
				
					
				
			The internal connection's read and write bufs will be restricted from growing bigger than the configured `max_buf_size`. Closes #1368
This commit is contained in:
		| @@ -58,6 +58,10 @@ where I: AsyncRead + AsyncWrite, | |||||||
|         self.io.set_flush_pipeline(enabled); |         self.io.set_flush_pipeline(enabled); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn set_max_buf_size(&mut self, max: usize) { | ||||||
|  |         self.io.set_max_buf_size(max); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[cfg(feature = "tokio-proto")] |     #[cfg(feature = "tokio-proto")] | ||||||
|     fn poll_incoming(&mut self) -> Poll<Option<Frame<super::MessageHead<T::Incoming>, super::Chunk, ::Error>>, io::Error> { |     fn poll_incoming(&mut self) -> Poll<Option<Frame<super::MessageHead<T::Incoming>, super::Chunk, ::Error>>, io::Error> { | ||||||
|         trace!("Conn::poll_incoming()"); |         trace!("Conn::poll_incoming()"); | ||||||
| @@ -1221,7 +1225,7 @@ mod tests { | |||||||
|         let _: Result<(), ()> = future::lazy(|| { |         let _: Result<(), ()> = future::lazy(|| { | ||||||
|             let io = AsyncIo::new_buf(vec![], 0); |             let io = AsyncIo::new_buf(vec![], 0); | ||||||
|             let mut conn = Conn::<_, proto::Chunk, ServerTransaction>::new(io, Default::default()); |             let mut conn = Conn::<_, proto::Chunk, ServerTransaction>::new(io, Default::default()); | ||||||
|             let max = ::proto::io::MAX_BUFFER_SIZE + 4096; |             let max = ::proto::io::DEFAULT_MAX_BUFFER_SIZE + 4096; | ||||||
|             conn.state.writing = Writing::Body(Encoder::length((max * 2) as u64), None); |             conn.state.writing = Writing::Body(Encoder::length((max * 2) as u64), None); | ||||||
|  |  | ||||||
|             assert!(conn.start_send(Frame::Body { chunk: Some(vec![b'a'; 1024 * 8].into()) }).unwrap().is_ready()); |             assert!(conn.start_send(Frame::Body { chunk: Some(vec![b'a'; 1024 * 8].into()) }).unwrap().is_ready()); | ||||||
|   | |||||||
| @@ -10,11 +10,12 @@ use super::{Http1Transaction, MessageHead}; | |||||||
| use bytes::{BytesMut, Bytes}; | use bytes::{BytesMut, Bytes}; | ||||||
|  |  | ||||||
| const INIT_BUFFER_SIZE: usize = 8192; | const INIT_BUFFER_SIZE: usize = 8192; | ||||||
| pub const MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100; | pub const DEFAULT_MAX_BUFFER_SIZE: usize = 8192 + 4096 * 100; | ||||||
|  |  | ||||||
| pub struct Buffered<T> { | pub struct Buffered<T> { | ||||||
|     flush_pipeline: bool, |     flush_pipeline: bool, | ||||||
|     io: T, |     io: T, | ||||||
|  |     max_buf_size: usize, | ||||||
|     read_blocked: bool, |     read_blocked: bool, | ||||||
|     read_buf: BytesMut, |     read_buf: BytesMut, | ||||||
|     write_buf: WriteBuf, |     write_buf: WriteBuf, | ||||||
| @@ -34,6 +35,7 @@ impl<T: AsyncRead + AsyncWrite> Buffered<T> { | |||||||
|         Buffered { |         Buffered { | ||||||
|             flush_pipeline: false, |             flush_pipeline: false, | ||||||
|             io: io, |             io: io, | ||||||
|  |             max_buf_size: DEFAULT_MAX_BUFFER_SIZE, | ||||||
|             read_buf: BytesMut::with_capacity(0), |             read_buf: BytesMut::with_capacity(0), | ||||||
|             write_buf: WriteBuf::new(), |             write_buf: WriteBuf::new(), | ||||||
|             read_blocked: false, |             read_blocked: false, | ||||||
| @@ -44,6 +46,11 @@ impl<T: AsyncRead + AsyncWrite> Buffered<T> { | |||||||
|         self.flush_pipeline = enabled; |         self.flush_pipeline = enabled; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn set_max_buf_size(&mut self, max: usize) { | ||||||
|  |         self.max_buf_size = max; | ||||||
|  |         self.write_buf.max_buf_size = max; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pub fn read_buf(&self) -> &[u8] { |     pub fn read_buf(&self) -> &[u8] { | ||||||
|         self.read_buf.as_ref() |         self.read_buf.as_ref() | ||||||
|     } |     } | ||||||
| @@ -51,7 +58,7 @@ impl<T: AsyncRead + AsyncWrite> Buffered<T> { | |||||||
|     pub fn write_buf_mut(&mut self) -> &mut Vec<u8> { |     pub fn write_buf_mut(&mut self) -> &mut Vec<u8> { | ||||||
|         self.write_buf.maybe_reset(); |         self.write_buf.maybe_reset(); | ||||||
|         self.write_buf.maybe_reserve(0); |         self.write_buf.maybe_reserve(0); | ||||||
|         &mut self.write_buf.0.bytes |         &mut self.write_buf.buf.bytes | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn consume_leading_lines(&mut self) { |     pub fn consume_leading_lines(&mut self) { | ||||||
| @@ -75,8 +82,8 @@ impl<T: AsyncRead + AsyncWrite> Buffered<T> { | |||||||
|                     return Ok(Async::Ready(head)) |                     return Ok(Async::Ready(head)) | ||||||
|                 }, |                 }, | ||||||
|                 None => { |                 None => { | ||||||
|                     if self.read_buf.capacity() >= MAX_BUFFER_SIZE { |                     if self.read_buf.capacity() >= self.max_buf_size { | ||||||
|                         debug!("MAX_BUFFER_SIZE reached, closing"); |                         debug!("max_buf_size ({}) reached, closing", self.max_buf_size); | ||||||
|                         return Err(::Error::TooLarge); |                         return Err(::Error::TooLarge); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
| @@ -259,22 +266,28 @@ impl<T: Write> AtomicWrite for T { | |||||||
|  |  | ||||||
| // an internal buffer to collect writes before flushes | // an internal buffer to collect writes before flushes | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| struct WriteBuf(Cursor<Vec<u8>>); | struct WriteBuf{ | ||||||
|  |     buf: Cursor<Vec<u8>>, | ||||||
|  |     max_buf_size: usize, | ||||||
|  | } | ||||||
|  |  | ||||||
| impl WriteBuf { | impl WriteBuf { | ||||||
|     fn new() -> WriteBuf { |     fn new() -> WriteBuf { | ||||||
|         WriteBuf(Cursor::new(Vec::new())) |         WriteBuf { | ||||||
|  |             buf: Cursor::new(Vec::new()), | ||||||
|  |             max_buf_size: DEFAULT_MAX_BUFFER_SIZE, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn write_into<W: Write>(&mut self, w: &mut W) -> io::Result<usize> { |     fn write_into<W: Write>(&mut self, w: &mut W) -> io::Result<usize> { | ||||||
|         self.0.write_to(w) |         self.buf.write_to(w) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn buffer(&mut self, data: &[u8]) -> usize { |     fn buffer(&mut self, data: &[u8]) -> usize { | ||||||
|         trace!("WriteBuf::buffer() len = {:?}", data.len()); |         trace!("WriteBuf::buffer() len = {:?}", data.len()); | ||||||
|         self.maybe_reset(); |         self.maybe_reset(); | ||||||
|         self.maybe_reserve(data.len()); |         self.maybe_reserve(data.len()); | ||||||
|         let vec = &mut self.0.bytes; |         let vec = &mut self.buf.bytes; | ||||||
|         let len = cmp::min(vec.capacity() - vec.len(), data.len()); |         let len = cmp::min(vec.capacity() - vec.len(), data.len()); | ||||||
|         assert!(vec.capacity() - vec.len() >= len); |         assert!(vec.capacity() - vec.len() >= len); | ||||||
|         unsafe { |         unsafe { | ||||||
| @@ -291,28 +304,28 @@ impl WriteBuf { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn remaining(&self) -> usize { |     fn remaining(&self) -> usize { | ||||||
|         self.0.remaining() |         self.buf.remaining() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn maybe_reserve(&mut self, needed: usize) { |     fn maybe_reserve(&mut self, needed: usize) { | ||||||
|         let vec = &mut self.0.bytes; |         let vec = &mut self.buf.bytes; | ||||||
|         let cap = vec.capacity(); |         let cap = vec.capacity(); | ||||||
|         if cap == 0 { |         if cap == 0 { | ||||||
|             let init = cmp::min(MAX_BUFFER_SIZE, cmp::max(INIT_BUFFER_SIZE, needed)); |             let init = cmp::min(self.max_buf_size, cmp::max(INIT_BUFFER_SIZE, needed)); | ||||||
|             trace!("WriteBuf reserving initial {}", init); |             trace!("WriteBuf reserving initial {}", init); | ||||||
|             vec.reserve(init); |             vec.reserve(init); | ||||||
|         } else if cap < MAX_BUFFER_SIZE { |         } else if cap < self.max_buf_size { | ||||||
|             vec.reserve(cmp::min(needed, MAX_BUFFER_SIZE - cap)); |             vec.reserve(cmp::min(needed, self.max_buf_size - cap)); | ||||||
|             trace!("WriteBuf reserved {}", vec.capacity() - cap); |             trace!("WriteBuf reserved {}", vec.capacity() - cap); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn maybe_reset(&mut self) { |     fn maybe_reset(&mut self) { | ||||||
|         if self.0.pos != 0 && self.0.remaining() == 0 { |         if self.buf.pos != 0 && self.buf.remaining() == 0 { | ||||||
|             self.0.pos = 0; |             self.buf.pos = 0; | ||||||
|             unsafe { |             unsafe { | ||||||
|                 self.0.bytes.set_len(0); |                 self.buf.bytes.set_len(0); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ pub use self::service::{const_service, service_fn}; | |||||||
| /// which handle a connection to an HTTP server. Each instance of `Http` can be | /// which handle a connection to an HTTP server. Each instance of `Http` can be | ||||||
| /// configured with various protocol-level options such as keepalive. | /// configured with various protocol-level options such as keepalive. | ||||||
| pub struct Http<B = ::Chunk> { | pub struct Http<B = ::Chunk> { | ||||||
|  |     max_buf_size: Option<usize>, | ||||||
|     keep_alive: bool, |     keep_alive: bool, | ||||||
|     pipeline: bool, |     pipeline: bool, | ||||||
|     _marker: PhantomData<B>, |     _marker: PhantomData<B>, | ||||||
| @@ -129,6 +130,7 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | |||||||
|     pub fn new() -> Http<B> { |     pub fn new() -> Http<B> { | ||||||
|         Http { |         Http { | ||||||
|             keep_alive: true, |             keep_alive: true, | ||||||
|  |             max_buf_size: None, | ||||||
|             pipeline: false, |             pipeline: false, | ||||||
|             _marker: PhantomData, |             _marker: PhantomData, | ||||||
|         } |         } | ||||||
| @@ -142,6 +144,12 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Set the maximum buffer size for the connection. | ||||||
|  |     pub fn max_buf_size(&mut self, max: usize) -> &mut Self { | ||||||
|  |         self.max_buf_size = Some(max); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Aggregates flushes to better support pipelined responses. |     /// Aggregates flushes to better support pipelined responses. | ||||||
|     /// |     /// | ||||||
|     /// Experimental, may be have bugs. |     /// Experimental, may be have bugs. | ||||||
| @@ -226,6 +234,7 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | |||||||
|             new_service: new_service, |             new_service: new_service, | ||||||
|             protocol: Http { |             protocol: Http { | ||||||
|                 keep_alive: self.keep_alive, |                 keep_alive: self.keep_alive, | ||||||
|  |                 max_buf_size: self.max_buf_size, | ||||||
|                 pipeline: self.pipeline, |                 pipeline: self.pipeline, | ||||||
|                 _marker: PhantomData, |                 _marker: PhantomData, | ||||||
|             }, |             }, | ||||||
| @@ -250,6 +259,9 @@ impl<B: AsRef<[u8]> + 'static> Http<B> { | |||||||
|         }; |         }; | ||||||
|         let mut conn = proto::Conn::new(io, ka); |         let mut conn = proto::Conn::new(io, ka); | ||||||
|         conn.set_flush_pipeline(self.pipeline); |         conn.set_flush_pipeline(self.pipeline); | ||||||
|  |         if let Some(max) = self.max_buf_size { | ||||||
|  |             conn.set_max_buf_size(max); | ||||||
|  |         } | ||||||
|         Connection { |         Connection { | ||||||
|             conn: proto::dispatch::Dispatcher::new(proto::dispatch::Server::new(service), conn), |             conn: proto::dispatch::Dispatcher::new(proto::dispatch::Server::new(service), conn), | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -113,6 +113,9 @@ impl<T, B> ServerProto<T> for Http<B> | |||||||
|         }; |         }; | ||||||
|         let mut conn = proto::Conn::new(io, ka); |         let mut conn = proto::Conn::new(io, ka); | ||||||
|         conn.set_flush_pipeline(self.pipeline); |         conn.set_flush_pipeline(self.pipeline); | ||||||
|  |         if let Some(max) = self.max_buf_size { | ||||||
|  |             conn.set_max_buf_size(max); | ||||||
|  |         } | ||||||
|         __ProtoBindTransport { |         __ProtoBindTransport { | ||||||
|             inner: future::ok(conn), |             inner: future::ok(conn), | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -958,6 +958,41 @@ fn illegal_request_length_returns_400_response() { | |||||||
|     core.run(fut).unwrap_err(); |     core.run(fut).unwrap_err(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn max_buf_size() { | ||||||
|  |     let _ = pretty_env_logger::try_init(); | ||||||
|  |     let mut core = Core::new().unwrap(); | ||||||
|  |     let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap(), &core.handle()).unwrap(); | ||||||
|  |     let addr = listener.local_addr().unwrap(); | ||||||
|  |  | ||||||
|  |     const MAX: usize = 16_000; | ||||||
|  |  | ||||||
|  |     thread::spawn(move || { | ||||||
|  |         let mut tcp = connect(&addr); | ||||||
|  |         tcp.write_all(b"POST /").expect("write 1"); | ||||||
|  |         tcp.write_all(&vec![b'a'; MAX]).expect("write 2"); | ||||||
|  |         tcp.write_all(b" HTTP/1.1\r\n\r\n").expect("write 3"); | ||||||
|  |         let mut buf = [0; 256]; | ||||||
|  |         tcp.read(&mut buf).expect("read 1"); | ||||||
|  |  | ||||||
|  |         let expected = "HTTP/1.1 400 "; | ||||||
|  |         assert_eq!(s(&buf[..expected.len()]), expected); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     let fut = listener.incoming() | ||||||
|  |         .into_future() | ||||||
|  |         .map_err(|_| unreachable!()) | ||||||
|  |         .and_then(|(item, _incoming)| { | ||||||
|  |             let (socket, _) = item.unwrap(); | ||||||
|  |             Http::<hyper::Chunk>::new() | ||||||
|  |                 .max_buf_size(MAX) | ||||||
|  |                 .serve_connection(socket, HelloWorld) | ||||||
|  |                 .map(|_| ()) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |     core.run(fut).unwrap_err(); | ||||||
|  | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn remote_addr() { | fn remote_addr() { | ||||||
|     let server = serve(); |     let server = serve(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user