Add methods to {client, server}::Builder to set max concurrent streams (#150)
				
					
				
			This PR adds `max_concurrent_streams()` methods to the client and server `Builder`s to set the `max_concurrent_streams` setting. I've added unit tests to ensure the correct SETTINGS frame is sent. Closes #106
This commit is contained in:
		| @@ -187,6 +187,18 @@ impl Builder { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Set the maximum number of concurrent streams. | ||||||
|  |     /// | ||||||
|  |     /// Clients can only limit the maximum number of streams that that the | ||||||
|  |     /// server can initiate. See [Section 5.1.2] in the HTTP/2 spec for more | ||||||
|  |     /// details. | ||||||
|  |     /// | ||||||
|  |     /// [Section 5.1.2]: https://http2.github.io/http2-spec/#rfc.section.5.1.2 | ||||||
|  |     pub fn max_concurrent_streams(&mut self, max: u32) -> &mut Self { | ||||||
|  |         self.settings.set_max_concurrent_streams(Some(max)); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Enable or disable the server to send push promises. |     /// Enable or disable the server to send push promises. | ||||||
|     pub fn enable_push(&mut self, enabled: bool) -> &mut Self { |     pub fn enable_push(&mut self, enabled: bool) -> &mut Self { | ||||||
|         self.settings.set_enable_push(enabled); |         self.settings.set_enable_push(enabled); | ||||||
|   | |||||||
| @@ -74,7 +74,6 @@ impl Settings { | |||||||
|         self.max_concurrent_streams |         self.max_concurrent_streams | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(feature = "unstable")] |  | ||||||
|     pub fn set_max_concurrent_streams(&mut self, max: Option<u32>) { |     pub fn set_max_concurrent_streams(&mut self, max: Option<u32>) { | ||||||
|         self.max_concurrent_streams = max; |         self.max_concurrent_streams = max; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -76,7 +76,9 @@ where | |||||||
|             local_next_stream_id: next_stream_id, |             local_next_stream_id: next_stream_id, | ||||||
|             local_push_enabled: settings.is_push_enabled(), |             local_push_enabled: settings.is_push_enabled(), | ||||||
|             remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE, |             remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE, | ||||||
|             remote_max_initiated: None, |             remote_max_initiated: settings | ||||||
|  |                 .max_concurrent_streams() | ||||||
|  |                 .map(|max| max as usize), | ||||||
|         }); |         }); | ||||||
|         Connection { |         Connection { | ||||||
|             state: State::Open, |             state: State::Open, | ||||||
|   | |||||||
| @@ -194,6 +194,18 @@ impl Builder { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Set the maximum number of concurrent streams. | ||||||
|  |     /// | ||||||
|  |     /// Servers can only limit the maximum number of streams that that the | ||||||
|  |     /// client can initiate. See [Section 5.1.2] in the HTTP/2 spec for more | ||||||
|  |     /// details. | ||||||
|  |     /// | ||||||
|  |     /// [Section 5.1.2]: https://http2.github.io/http2-spec/#rfc.section.5.1.2 | ||||||
|  |     pub fn max_concurrent_streams(&mut self, max: u32) -> &mut Self { | ||||||
|  |         self.settings.set_max_concurrent_streams(Some(max)); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Bind an H2 server connection. |     /// Bind an H2 server connection. | ||||||
|     /// |     /// | ||||||
|     /// Returns a future which resolves to the connection value once the H2 |     /// Returns a future which resolves to the connection value once the H2 | ||||||
|   | |||||||
| @@ -148,6 +148,45 @@ fn request_stream_id_overflows() { | |||||||
|     h2.join(srv).wait().expect("wait"); |     h2.join(srv).wait().expect("wait"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn client_builder_max_concurrent_streams() { | ||||||
|  |     let _ = ::env_logger::init(); | ||||||
|  |     let (io, srv) = mock::new(); | ||||||
|  |  | ||||||
|  |     let mut settings = frame::Settings::default(); | ||||||
|  |     settings.set_max_concurrent_streams(Some(1)); | ||||||
|  |  | ||||||
|  |     let srv = srv | ||||||
|  |         .assert_client_handshake() | ||||||
|  |         .unwrap() | ||||||
|  |         .recv_custom_settings(settings) | ||||||
|  |         .recv_frame( | ||||||
|  |             frames::headers(1) | ||||||
|  |                 .request("GET", "https://example.com/") | ||||||
|  |                 .eos() | ||||||
|  |         ) | ||||||
|  |         .send_frame(frames::headers(1).response(200).eos()) | ||||||
|  |         .close(); | ||||||
|  |  | ||||||
|  |     let mut builder = Client::builder(); | ||||||
|  |     builder.max_concurrent_streams(1); | ||||||
|  |  | ||||||
|  |     let h2 = builder | ||||||
|  |         .handshake::<_, Bytes>(io) | ||||||
|  |         .expect("handshake") | ||||||
|  |         .and_then(|(mut client, h2)| { | ||||||
|  |             let request = Request::builder() | ||||||
|  |                 .method(Method::GET) | ||||||
|  |                 .uri("https://example.com/") | ||||||
|  |                 .body(()) | ||||||
|  |                 .unwrap(); | ||||||
|  |             let req = client.send_request(request, true).unwrap().unwrap(); | ||||||
|  |             h2.drive(req).map(move |(h2, _)| (client, h2)) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |     h2.join(srv).wait().expect("wait"); | ||||||
|  | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn request_over_max_concurrent_streams_errors() { | fn request_over_max_concurrent_streams_errors() { | ||||||
|     let _ = ::env_logger::init(); |     let _ = ::env_logger::init(); | ||||||
|   | |||||||
| @@ -22,6 +22,56 @@ fn read_preface_in_multiple_frames() { | |||||||
|     assert!(Stream::wait(h2).next().is_none()); |     assert!(Stream::wait(h2).next().is_none()); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn server_builder_set_max_concurrent_streams() { | ||||||
|  |     let _ = ::env_logger::init(); | ||||||
|  |     let (io, client) = mock::new(); | ||||||
|  |  | ||||||
|  |     let mut settings = frame::Settings::default(); | ||||||
|  |     settings.set_max_concurrent_streams(Some(1)); | ||||||
|  |  | ||||||
|  |     let client = client | ||||||
|  |         .assert_server_handshake() | ||||||
|  |         .unwrap() | ||||||
|  |         .recv_custom_settings(settings) | ||||||
|  |         .send_frame( | ||||||
|  |             frames::headers(1) | ||||||
|  |                 .request("GET", "https://example.com/"), | ||||||
|  |         ) | ||||||
|  |         .send_frame( | ||||||
|  |             frames::headers(3) | ||||||
|  |                 .request("GET", "https://example.com/"), | ||||||
|  |         ) | ||||||
|  |         .send_frame(frames::data(1, &b"hello"[..]).eos(),) | ||||||
|  |         .recv_frame(frames::reset(3).refused()) | ||||||
|  |         .recv_frame(frames::headers(1).response(200).eos()) | ||||||
|  |         .close(); | ||||||
|  |  | ||||||
|  |     let mut builder = Server::builder(); | ||||||
|  |     builder.max_concurrent_streams(1); | ||||||
|  |  | ||||||
|  |     let h2 = builder | ||||||
|  |         .handshake::<_, Bytes>(io) | ||||||
|  |         .expect("handshake") | ||||||
|  |         .and_then(|srv| { | ||||||
|  |             srv.into_future().unwrap().and_then(|(reqstream, srv)| { | ||||||
|  |                 let (req, mut stream) = reqstream.unwrap(); | ||||||
|  |  | ||||||
|  |                 assert_eq!(req.method(), &http::Method::GET); | ||||||
|  |  | ||||||
|  |                 let rsp = | ||||||
|  |                     http::Response::builder() | ||||||
|  |                         .status(200).body(()) | ||||||
|  |                         .unwrap(); | ||||||
|  |                 stream.send_response(rsp, true).unwrap(); | ||||||
|  |  | ||||||
|  |                 srv.into_future().unwrap() | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |     h2.join(client).wait().expect("wait"); | ||||||
|  | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn serve_request() { | fn serve_request() { | ||||||
|     let _ = ::env_logger::init(); |     let _ = ::env_logger::init(); | ||||||
|   | |||||||
| @@ -249,6 +249,11 @@ impl Mock<frame::Reset> { | |||||||
|         let id = self.0.stream_id(); |         let id = self.0.stream_id(); | ||||||
|         Mock(frame::Reset::new(id, frame::Reason::FLOW_CONTROL_ERROR)) |         Mock(frame::Reset::new(id, frame::Reason::FLOW_CONTROL_ERROR)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn refused(self) -> Self { | ||||||
|  |         let id = self.0.stream_id(); | ||||||
|  |         Mock(frame::Reset::new(id, frame::Reason::REFUSED_STREAM)) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<Mock<frame::Reset>> for SendFrame { | impl From<Mock<frame::Reset>> for SendFrame { | ||||||
|   | |||||||
| @@ -384,19 +384,32 @@ impl AsyncWrite for Pipe { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub trait HandleFutureExt { | pub trait HandleFutureExt { | ||||||
|     fn recv_settings(self) -> RecvFrame<Box<Future<Item = (Option<Frame>, Handle), Error = ()>>> |     fn recv_settings(self) | ||||||
|  |         -> RecvFrame<Box<Future<Item = (Option<Frame>, Handle), Error = ()>>> | ||||||
|     where |     where | ||||||
|         Self: Sized + 'static, |         Self: Sized + 'static, | ||||||
|         Self: Future<Item = (frame::Settings, Handle)>, |         Self: Future<Item = (frame::Settings, Handle)>, | ||||||
|         Self::Error: fmt::Debug, |         Self::Error: fmt::Debug, | ||||||
|     { |     { | ||||||
|         let map = self.map(|(settings, handle)| (Some(settings.into()), handle)) |         self.recv_custom_settings(frame::Settings::default()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn recv_custom_settings(self, settings: frame::Settings) | ||||||
|  |         -> RecvFrame<Box<Future<Item = (Option<Frame>, Handle), Error = ()>>> | ||||||
|  |     where | ||||||
|  |         Self: Sized + 'static, | ||||||
|  |         Self: Future<Item = (frame::Settings, Handle)>, | ||||||
|  |         Self::Error: fmt::Debug, | ||||||
|  |     { | ||||||
|  |         let map = self | ||||||
|  |             .map(|(settings, handle)| (Some(settings.into()), handle)) | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|  |  | ||||||
|         let boxed: Box<Future<Item = (Option<Frame>, Handle), Error = ()>> = Box::new(map); |         let boxed: Box<Future<Item = (Option<Frame>, Handle), Error = ()>> = | ||||||
|  |             Box::new(map); | ||||||
|         RecvFrame { |         RecvFrame { | ||||||
|             inner: boxed, |             inner: boxed, | ||||||
|             frame: frame::Settings::default().into(), |             frame: settings.into(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user