Compare commits
10 Commits
dc7aa8e0f2
...
imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90af7b9da9 | ||
|
|
f486fc3adc | ||
|
|
7af4adf829 | ||
|
|
4e6d835094 | ||
|
|
88b0789254 | ||
|
|
b0f54d80f2 | ||
|
|
756384f4cd | ||
|
|
fd4040d90d | ||
|
|
e4cf88c1a1 | ||
|
|
f6aa3be671 |
2
.github/workflows/CI.yml
vendored
2
.github/workflows/CI.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- 1.49 # never go past Hyper's own MSRV
|
||||
- 1.56 # never go past Hyper's own MSRV
|
||||
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 0.3.14 (August 16, 2022)
|
||||
|
||||
* Add `Error::is_reset` function.
|
||||
* Bump MSRV to Rust 1.56.
|
||||
* Return `RST_STREAM(NO_ERROR)` when the server early responds.
|
||||
|
||||
# 0.3.13 (March 31, 2022)
|
||||
|
||||
* Update private internal `tokio-util` dependency.
|
||||
|
||||
@@ -5,7 +5,7 @@ name = "h2"
|
||||
# - html_root_url.
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create git tag
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
license = "MIT"
|
||||
authors = [
|
||||
"Carl Lerche <me@carllerche.com>",
|
||||
|
||||
@@ -526,7 +526,7 @@ where
|
||||
///
|
||||
/// This setting is configured by the server peer by sending the
|
||||
/// [`SETTINGS_ENABLE_CONNECT_PROTOCOL` parameter][2] in a `SETTINGS` frame.
|
||||
/// This method returns the currently acknowledged value recieved from the
|
||||
/// This method returns the currently acknowledged value received from the
|
||||
/// remote.
|
||||
///
|
||||
/// [1]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
|
||||
@@ -1022,6 +1022,39 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the header table size.
|
||||
///
|
||||
/// This setting informs the peer of the maximum size of the header compression
|
||||
/// table used to encode header blocks, in octets. The encoder may select any value
|
||||
/// equal to or less than the header table size specified by the sender.
|
||||
///
|
||||
/// The default value is 4,096.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio::io::{AsyncRead, AsyncWrite};
|
||||
/// # use h2::client::*;
|
||||
/// # use bytes::Bytes;
|
||||
/// #
|
||||
/// # async fn doc<T: AsyncRead + AsyncWrite + Unpin>(my_io: T)
|
||||
/// # -> Result<((SendRequest<Bytes>, Connection<T, Bytes>)), h2::Error>
|
||||
/// # {
|
||||
/// // `client_fut` is a future representing the completion of the HTTP/2
|
||||
/// // handshake.
|
||||
/// let client_fut = Builder::new()
|
||||
/// .header_table_size(1_000_000)
|
||||
/// .handshake(my_io);
|
||||
/// # client_fut.await
|
||||
/// # }
|
||||
/// #
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn header_table_size(&mut self, size: u32) -> &mut Self {
|
||||
self.settings.set_header_table_size(Some(size));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the first stream ID to something other than 1.
|
||||
#[cfg(feature = "unstable")]
|
||||
pub fn initial_stream_id(&mut self, stream_id: u32) -> &mut Self {
|
||||
@@ -1280,7 +1313,7 @@ where
|
||||
///
|
||||
/// This limit is configured by the server peer by sending the
|
||||
/// [`SETTINGS_MAX_CONCURRENT_STREAMS` parameter][1] in a `SETTINGS` frame.
|
||||
/// This method returns the currently acknowledged value recieved from the
|
||||
/// This method returns the currently acknowledged value received from the
|
||||
/// remote.
|
||||
///
|
||||
/// [1]: https://tools.ietf.org/html/rfc7540#section-5.1.2
|
||||
|
||||
@@ -88,6 +88,12 @@ impl<T> FramedRead<T> {
|
||||
pub fn set_max_header_list_size(&mut self, val: usize) {
|
||||
self.max_header_list_size = val;
|
||||
}
|
||||
|
||||
/// Update the header table size setting.
|
||||
#[inline]
|
||||
pub fn set_header_table_size(&mut self, val: usize) {
|
||||
self.hpack.queue_size_update(val);
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a frame.
|
||||
|
||||
@@ -95,6 +95,11 @@ impl<T, B> Codec<T, B> {
|
||||
self.framed_write().set_header_table_size(val)
|
||||
}
|
||||
|
||||
/// Set the decoder header table size size.
|
||||
pub fn set_recv_header_table_size(&mut self, val: usize) {
|
||||
self.inner.set_header_table_size(val)
|
||||
}
|
||||
|
||||
/// Set the max header list size that can be received.
|
||||
pub fn set_max_recv_header_list_size(&mut self, val: usize) {
|
||||
self.inner.set_max_header_list_size(val);
|
||||
|
||||
10
src/error.rs
10
src/error.rs
@@ -59,10 +59,7 @@ impl Error {
|
||||
|
||||
/// Returns true if the error is an io::Error
|
||||
pub fn is_io(&self) -> bool {
|
||||
match self.kind {
|
||||
Kind::Io(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.kind, Kind::Io(..))
|
||||
}
|
||||
|
||||
/// Returns the error if the error is an io::Error
|
||||
@@ -92,6 +89,11 @@ impl Error {
|
||||
matches!(self.kind, Kind::GoAway(..))
|
||||
}
|
||||
|
||||
/// Returns true if the error is from a `RST_STREAM`.
|
||||
pub fn is_reset(&self) -> bool {
|
||||
matches!(self.kind, Kind::Reset(..))
|
||||
}
|
||||
|
||||
/// Returns true if the error was received in a frame from the remote.
|
||||
///
|
||||
/// Such as from a received `RST_STREAM` or `GOAWAY` frame.
|
||||
|
||||
@@ -686,13 +686,13 @@ impl Iterator for Iter {
|
||||
return Some(Method(method));
|
||||
}
|
||||
|
||||
if let Some(scheme) = pseudo.scheme.take() {
|
||||
return Some(Scheme(scheme));
|
||||
}
|
||||
|
||||
if let Some(authority) = pseudo.authority.take() {
|
||||
return Some(Authority(authority));
|
||||
}
|
||||
|
||||
if let Some(scheme) = pseudo.scheme.take() {
|
||||
return Some(Scheme(scheme));
|
||||
}
|
||||
|
||||
if let Some(path) = pseudo.path.take() {
|
||||
return Some(Path(path));
|
||||
|
||||
@@ -121,11 +121,9 @@ impl Settings {
|
||||
self.header_table_size
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn set_header_table_size(&mut self, size: Option<u32>) {
|
||||
self.header_table_size = size;
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn load(head: Head, payload: &[u8]) -> Result<Settings, Error> {
|
||||
use self::Setting::*;
|
||||
|
||||
43
src/lib.rs
43
src/lib.rs
@@ -78,7 +78,7 @@
|
||||
//! [`server::handshake`]: server/fn.handshake.html
|
||||
//! [`client::handshake`]: client/fn.handshake.html
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/h2/0.3.13")]
|
||||
#![doc(html_root_url = "https://docs.rs/h2/0.3.14")]
|
||||
#![deny(missing_debug_implementations, missing_docs)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
|
||||
@@ -133,44 +133,3 @@ pub use crate::share::{FlowControl, Ping, PingPong, Pong, RecvStream, SendStream
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
pub use codec::{Codec, SendError, UserError};
|
||||
|
||||
use std::task::Poll;
|
||||
|
||||
// TODO: Get rid of this trait once https://github.com/rust-lang/rust/pull/63512
|
||||
// is stabilized.
|
||||
trait PollExt<T, E> {
|
||||
/// Changes the success value of this `Poll` with the closure provided.
|
||||
fn map_ok_<U, F>(self, f: F) -> Poll<Option<Result<U, E>>>
|
||||
where
|
||||
F: FnOnce(T) -> U;
|
||||
/// Changes the error value of this `Poll` with the closure provided.
|
||||
fn map_err_<U, F>(self, f: F) -> Poll<Option<Result<T, U>>>
|
||||
where
|
||||
F: FnOnce(E) -> U;
|
||||
}
|
||||
|
||||
impl<T, E> PollExt<T, E> for Poll<Option<Result<T, E>>> {
|
||||
fn map_ok_<U, F>(self, f: F) -> Poll<Option<Result<U, E>>>
|
||||
where
|
||||
F: FnOnce(T) -> U,
|
||||
{
|
||||
match self {
|
||||
Poll::Ready(Some(Ok(t))) => Poll::Ready(Some(Ok(f(t)))),
|
||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_err_<U, F>(self, f: F) -> Poll<Option<Result<T, U>>>
|
||||
where
|
||||
F: FnOnce(E) -> U,
|
||||
{
|
||||
match self {
|
||||
Poll::Ready(Some(Ok(t))) => Poll::Ready(Some(Ok(t))),
|
||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(f(e)))),
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ impl Settings {
|
||||
codec.set_max_recv_header_list_size(max as usize);
|
||||
}
|
||||
|
||||
if let Some(val) = local.header_table_size() {
|
||||
codec.set_recv_header_table_size(val as usize);
|
||||
}
|
||||
|
||||
streams.apply_local_settings(local)?;
|
||||
self.local = Local::Synced;
|
||||
Ok(())
|
||||
|
||||
@@ -12,7 +12,6 @@ use http::{HeaderMap, Request, Response};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use tokio::io::AsyncWrite;
|
||||
|
||||
use crate::PollExt;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{fmt, io};
|
||||
|
||||
@@ -1282,7 +1281,7 @@ impl OpaqueStreamRef {
|
||||
me.actions
|
||||
.recv
|
||||
.poll_pushed(cx, &mut stream)
|
||||
.map_ok_(|(h, key)| {
|
||||
.map_ok(|(h, key)| {
|
||||
me.refs += 1;
|
||||
let opaque_ref =
|
||||
OpaqueStreamRef::new(self.inner.clone(), &mut me.store.resolve(key));
|
||||
@@ -1462,9 +1461,21 @@ fn drop_stream_ref(inner: &Mutex<Inner>, key: store::Key) {
|
||||
|
||||
fn maybe_cancel(stream: &mut store::Ptr, actions: &mut Actions, counts: &mut Counts) {
|
||||
if stream.is_canceled_interest() {
|
||||
// Server is allowed to early respond without fully consuming the client input stream
|
||||
// But per the RFC, must send a RST_STREAM(NO_ERROR) in such cases. https://www.rfc-editor.org/rfc/rfc7540#section-8.1
|
||||
// Some other http2 implementation may interpret other error code as fatal if not respected (i.e: nginx https://trac.nginx.org/nginx/ticket/2376)
|
||||
let reason = if counts.peer().is_server()
|
||||
&& stream.state.is_send_closed()
|
||||
&& stream.state.is_recv_streaming()
|
||||
{
|
||||
Reason::NO_ERROR
|
||||
} else {
|
||||
Reason::CANCEL
|
||||
};
|
||||
|
||||
actions
|
||||
.send
|
||||
.schedule_implicit_reset(stream, Reason::CANCEL, counts, &mut actions.task);
|
||||
.schedule_implicit_reset(stream, reason, counts, &mut actions.task);
|
||||
actions.recv.enqueue_reset_expiration(stream, counts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ where
|
||||
///
|
||||
/// This limit is configured by the client peer by sending the
|
||||
/// [`SETTINGS_MAX_CONCURRENT_STREAMS` parameter][1] in a `SETTINGS` frame.
|
||||
/// This method returns the currently acknowledged value recieved from the
|
||||
/// This method returns the currently acknowledged value received from the
|
||||
/// remote.
|
||||
///
|
||||
/// [1]: https://tools.ietf.org/html/rfc7540#section-5.1.2
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::proto::{self, WindowSize};
|
||||
use bytes::{Buf, Bytes};
|
||||
use http::HeaderMap;
|
||||
|
||||
use crate::PollExt;
|
||||
use std::fmt;
|
||||
#[cfg(feature = "stream")]
|
||||
use std::pin::Pin;
|
||||
@@ -307,8 +306,8 @@ impl<B: Buf> SendStream<B> {
|
||||
pub fn poll_capacity(&mut self, cx: &mut Context) -> Poll<Option<Result<usize, crate::Error>>> {
|
||||
self.inner
|
||||
.poll_capacity(cx)
|
||||
.map_ok_(|w| w as usize)
|
||||
.map_err_(Into::into)
|
||||
.map_ok(|w| w as usize)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sends a single data frame to the remote peer.
|
||||
@@ -403,7 +402,7 @@ impl RecvStream {
|
||||
|
||||
/// Poll for the next data frame.
|
||||
pub fn poll_data(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, crate::Error>>> {
|
||||
self.inner.inner.poll_data(cx).map_err_(Into::into)
|
||||
self.inner.inner.poll_data(cx).map_err(Into::into)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
||||
@@ -372,6 +372,11 @@ impl Mock<frame::Settings> {
|
||||
self.0.set_enable_connect_protocol(Some(val));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header_table_size(mut self, val: u32) -> Self {
|
||||
self.0.set_header_table_size(Some(val));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mock<frame::Settings>> for frame::Settings {
|
||||
|
||||
@@ -1452,6 +1452,40 @@ async fn extended_connect_request() {
|
||||
join(srv, h2).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_builder_header_table_size() {
|
||||
h2_support::trace_init!();
|
||||
let (io, mut srv) = mock::new();
|
||||
let mut settings = frame::Settings::default();
|
||||
|
||||
settings.set_header_table_size(Some(10000));
|
||||
|
||||
let srv = async move {
|
||||
let recv_settings = srv.assert_client_handshake().await;
|
||||
assert_frame_eq(recv_settings, settings);
|
||||
|
||||
srv.recv_frame(
|
||||
frames::headers(1)
|
||||
.request("GET", "https://example.com/")
|
||||
.eos(),
|
||||
)
|
||||
.await;
|
||||
srv.send_frame(frames::headers(1).response(200).eos()).await;
|
||||
};
|
||||
|
||||
let mut builder = client::Builder::new();
|
||||
builder.header_table_size(10000);
|
||||
|
||||
let h2 = async move {
|
||||
let (mut client, mut h2) = builder.handshake::<_, Bytes>(io).await.unwrap();
|
||||
let request = Request::get("https://example.com/").body(()).unwrap();
|
||||
let (response, _) = client.send_request(request, true).unwrap();
|
||||
h2.drive(response).await.unwrap();
|
||||
};
|
||||
|
||||
join(srv, h2).await;
|
||||
}
|
||||
|
||||
const SETTINGS: &'static [u8] = &[0, 0, 0, 4, 0, 0, 0, 0, 0];
|
||||
const SETTINGS_ACK: &'static [u8] = &[0, 0, 0, 4, 1, 0, 0, 0, 0];
|
||||
|
||||
|
||||
@@ -566,7 +566,9 @@ async fn sends_reset_cancel_when_req_body_is_dropped() {
|
||||
client
|
||||
.recv_frame(frames::headers(1).response(200).eos())
|
||||
.await;
|
||||
client.recv_frame(frames::reset(1).cancel()).await;
|
||||
client
|
||||
.recv_frame(frames::reset(1).reason(Reason::NO_ERROR))
|
||||
.await;
|
||||
};
|
||||
|
||||
let srv = async move {
|
||||
|
||||
Reference in New Issue
Block a user