Repository: hyperium/h2 (rust)

Issue:
Support configuration of max_concurrent_streams
`server::Builder` should support `set_max_concurrent_streams(&mut self, u32)` so that a server can constrain the number of streams per client.

Similarly, `client::Builder` should have this, though I _think_ a client can only configure the number of concurrent server-initiated streams (i.e. Push Promises).  While we're here, `client::Builder` should probalby also have a `set_push_promise_enabled(&mut self, bool)`


Discussion:
>  While we're here, `client::Builder` should probably also have a `set_push_promise_enabled(&mut self, bool)`

There's `builder.enable_push(bool)`: https://github.com/carllerche/h2/blob/6ec7f38cd7378a6d99ab4300a07763ab1722dcb4/src/client.rs#L181
Looks good to me.

Patch:
diff --git a/src/client.rs b/src/client.rs
--- a/src/client.rs
+++ b/src/client.rs
@@ -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);
diff --git a/src/frame/settings.rs b/src/frame/settings.rs
--- a/src/frame/settings.rs
+++ b/src/frame/settings.rs
@@ -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;
     }
diff --git a/src/proto/connection.rs b/src/proto/connection.rs
--- a/src/proto/connection.rs
+++ b/src/proto/connection.rs
@@ -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,
diff --git a/src/server.rs b/src/server.rs
--- a/src/server.rs
+++ b/src/server.rs
@@ -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


Test patch:
diff --git a/tests/client_request.rs b/tests/client_request.rs
--- a/tests/client_request.rs
+++ b/tests/client_request.rs
@@ -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();
diff --git a/tests/server.rs b/tests/server.rs
--- a/tests/server.rs
+++ b/tests/server.rs
@@ -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();
diff --git a/tests/support/frames.rs b/tests/support/frames.rs
--- a/tests/support/frames.rs
+++ b/tests/support/frames.rs
@@ -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 {
diff --git a/tests/support/mock.rs b/tests/support/mock.rs
--- a/tests/support/mock.rs
+++ b/tests/support/mock.rs
@@ -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(),
         }
     }