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 | ||||
|     } | ||||
|  | ||||
|     /// 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. | ||||
|     pub fn enable_push(&mut self, enabled: bool) -> &mut Self { | ||||
|         self.settings.set_enable_push(enabled); | ||||
|   | ||||
| @@ -74,7 +74,6 @@ impl Settings { | ||||
|         self.max_concurrent_streams | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "unstable")] | ||||
|     pub fn set_max_concurrent_streams(&mut self, max: Option<u32>) { | ||||
|         self.max_concurrent_streams = max; | ||||
|     } | ||||
|   | ||||
| @@ -76,7 +76,9 @@ where | ||||
|             local_next_stream_id: next_stream_id, | ||||
|             local_push_enabled: settings.is_push_enabled(), | ||||
|             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 { | ||||
|             state: State::Open, | ||||
|   | ||||
| @@ -194,6 +194,18 @@ impl Builder { | ||||
|         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. | ||||
|     /// | ||||
|     /// 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"); | ||||
| } | ||||
|  | ||||
| #[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] | ||||
| fn request_over_max_concurrent_streams_errors() { | ||||
|     let _ = ::env_logger::init(); | ||||
|   | ||||
| @@ -22,6 +22,56 @@ fn read_preface_in_multiple_frames() { | ||||
|     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] | ||||
| fn serve_request() { | ||||
|     let _ = ::env_logger::init(); | ||||
|   | ||||
| @@ -249,6 +249,11 @@ impl Mock<frame::Reset> { | ||||
|         let id = self.0.stream_id(); | ||||
|         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 { | ||||
|   | ||||
| @@ -384,19 +384,32 @@ impl AsyncWrite for Pipe { | ||||
| } | ||||
|  | ||||
| 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 | ||||
|         Self: Sized + 'static, | ||||
|         Self: Future<Item = (frame::Settings, Handle)>, | ||||
|         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(); | ||||
|  | ||||
|         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 { | ||||
|             inner: boxed, | ||||
|             frame: frame::Settings::default().into(), | ||||
|             frame: settings.into(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user