feat(http2): add HTTP/2 support for Client and Server
This commit is contained in:
@@ -129,7 +129,7 @@ where I: AsyncRead + AsyncWrite,
|
||||
let must_error = self.should_error_on_eof();
|
||||
self.state.close_read();
|
||||
self.io.consume_leading_lines();
|
||||
let was_mid_parse = !self.io.read_buf().is_empty();
|
||||
let was_mid_parse = e.is_parse() || !self.io.read_buf().is_empty();
|
||||
return if was_mid_parse || must_error {
|
||||
debug!("parse error ({}) with {} bytes", e, self.io.read_buf().len());
|
||||
self.on_parse_error(e)
|
||||
@@ -566,7 +566,7 @@ where I: AsyncRead + AsyncWrite,
|
||||
match self.io.io_mut().shutdown() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(())) => {
|
||||
trace!("shut down IO");
|
||||
trace!("shut down IO complete");
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -599,6 +599,12 @@ where I: AsyncRead + AsyncWrite,
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Used in h1::dispatch tests
|
||||
#[cfg(test)]
|
||||
pub(super) fn io_mut(&mut self) -> &mut I {
|
||||
self.io.io_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, B: AsRef<[u8]>, T> fmt::Debug for Conn<I, B, T> {
|
||||
|
||||
@@ -7,8 +7,8 @@ use tokio_service::Service;
|
||||
use body::{Body, Payload};
|
||||
use proto::{BodyLength, Conn, Http1Transaction, MessageHead, RequestHead, RequestLine, ResponseHead};
|
||||
|
||||
pub(crate) struct Dispatcher<D, Bs, I, B, T> {
|
||||
conn: Conn<I, B, T>,
|
||||
pub(crate) struct Dispatcher<D, Bs: Payload, I, T> {
|
||||
conn: Conn<I, Bs::Data, T>,
|
||||
dispatch: D,
|
||||
body_tx: Option<::body::Sender>,
|
||||
body_rx: Option<Bs>,
|
||||
@@ -31,23 +31,20 @@ pub struct Server<S: Service> {
|
||||
}
|
||||
|
||||
pub struct Client<B> {
|
||||
callback: Option<::client::dispatch::Callback<ClientMsg<B>, Response<Body>>>,
|
||||
callback: Option<::client::dispatch::Callback<Request<B>, Response<Body>>>,
|
||||
rx: ClientRx<B>,
|
||||
}
|
||||
|
||||
pub type ClientMsg<B> = Request<B>;
|
||||
type ClientRx<B> = ::client::dispatch::Receiver<Request<B>, Response<Body>>;
|
||||
|
||||
type ClientRx<B> = ::client::dispatch::Receiver<ClientMsg<B>, Response<Body>>;
|
||||
|
||||
impl<D, Bs, I, B, T> Dispatcher<D, Bs, I, B, T>
|
||||
impl<D, Bs, I, T> Dispatcher<D, Bs, I, T>
|
||||
where
|
||||
D: Dispatch<PollItem=MessageHead<T::Outgoing>, PollBody=Bs, RecvItem=MessageHead<T::Incoming>>,
|
||||
I: AsyncRead + AsyncWrite,
|
||||
B: AsRef<[u8]>,
|
||||
T: Http1Transaction,
|
||||
Bs: Payload<Data=B>,
|
||||
Bs: Payload,
|
||||
{
|
||||
pub fn new(dispatch: D, conn: Conn<I, B, T>) -> Self {
|
||||
pub fn new(dispatch: D, conn: Conn<I, Bs::Data, T>) -> Self {
|
||||
Dispatcher {
|
||||
conn: conn,
|
||||
dispatch: dispatch,
|
||||
@@ -286,13 +283,12 @@ where
|
||||
}
|
||||
|
||||
|
||||
impl<D, Bs, I, B, T> Future for Dispatcher<D, Bs, I, B, T>
|
||||
impl<D, Bs, I, T> Future for Dispatcher<D, Bs, I, T>
|
||||
where
|
||||
D: Dispatch<PollItem=MessageHead<T::Outgoing>, PollBody=Bs, RecvItem=MessageHead<T::Incoming>>,
|
||||
I: AsyncRead + AsyncWrite,
|
||||
B: AsRef<[u8]>,
|
||||
T: Http1Transaction,
|
||||
Bs: Payload<Data=B>,
|
||||
Bs: Payload,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ::Error;
|
||||
@@ -493,11 +489,18 @@ mod tests {
|
||||
fn client_read_bytes_before_writing_request() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
::futures::lazy(|| {
|
||||
let io = AsyncIo::new_buf(b"HTTP/1.1 200 OK\r\n\r\n".to_vec(), 100);
|
||||
// Block at 0 for now, but we will release this response before
|
||||
// the request is ready to write later...
|
||||
let io = AsyncIo::new_buf(b"HTTP/1.1 200 OK\r\n\r\n".to_vec(), 0);
|
||||
let (mut tx, rx) = ::client::dispatch::channel();
|
||||
let conn = Conn::<_, ::Chunk, ClientTransaction>::new(io);
|
||||
let mut dispatcher = Dispatcher::new(Client::new(rx), conn);
|
||||
|
||||
// First poll is needed to allow tx to send...
|
||||
assert!(dispatcher.poll().expect("nothing is ready").is_not_ready());
|
||||
// Unblock our IO, which has a response before we've sent request!
|
||||
dispatcher.conn.io_mut().block_in(100);
|
||||
|
||||
let res_rx = tx.try_send(::Request::new(::Body::empty())).unwrap();
|
||||
|
||||
let a1 = dispatcher.poll().expect("error should be sent on channel");
|
||||
@@ -506,13 +509,6 @@ mod tests {
|
||||
.expect("callback poll")
|
||||
.expect_err("callback response");
|
||||
|
||||
/*
|
||||
let err = match async {
|
||||
Async::Ready(result) => result.unwrap_err(),
|
||||
Async::Pending => panic!("callback should be ready"),
|
||||
};
|
||||
*/
|
||||
|
||||
match (err.0.kind(), err.1) {
|
||||
(&::error::Kind::Canceled, Some(_)) => (),
|
||||
other => panic!("expected Canceled, got {:?}", other),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
pub(crate) use self::conn::Conn;
|
||||
pub(crate) use self::dispatch::Dispatcher;
|
||||
pub use self::decode::Decoder;
|
||||
pub use self::encode::{EncodedBuf, Encoder};
|
||||
pub use self::io::Cursor; //TODO: move out of h1::io
|
||||
|
||||
mod conn;
|
||||
mod date;
|
||||
|
||||
144
src/proto/h2/client.rs
Normal file
144
src/proto/h2/client.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use bytes::IntoBuf;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use futures::future::{self, Either};
|
||||
use futures::sync::oneshot;
|
||||
use h2::client::{Builder, Handshake, SendRequest};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use body::Payload;
|
||||
use ::common::{Exec, Never};
|
||||
use super::{PipeToSendStream, SendBuf};
|
||||
use ::{Body, Request, Response};
|
||||
|
||||
type ClientRx<B> = ::client::dispatch::Receiver<Request<B>, Response<Body>>;
|
||||
|
||||
pub struct Client<T, B>
|
||||
where
|
||||
B: Payload,
|
||||
{
|
||||
executor: Exec,
|
||||
rx: ClientRx<B>,
|
||||
state: State<T, SendBuf<B::Data>>,
|
||||
}
|
||||
|
||||
enum State<T, B> where B: IntoBuf {
|
||||
Handshaking(Handshake<T, B>),
|
||||
Ready(SendRequest<B>, oneshot::Sender<Never>),
|
||||
}
|
||||
|
||||
impl<T, B> Client<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Send + 'static,
|
||||
B: Payload,
|
||||
{
|
||||
pub(crate) fn new(io: T, rx: ClientRx<B>, exec: Exec) -> Client<T, B> {
|
||||
let handshake = Builder::new()
|
||||
// we don't expose PUSH promises yet
|
||||
.enable_push(false)
|
||||
.handshake(io);
|
||||
|
||||
Client {
|
||||
executor: exec,
|
||||
rx: rx,
|
||||
state: State::Handshaking(handshake),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> Future for Client<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Send + 'static,
|
||||
B: Payload + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
let next = match self.state {
|
||||
State::Handshaking(ref mut h) => {
|
||||
let (request_tx, conn) = try_ready!(h.poll().map_err(::Error::new_h2));
|
||||
// A oneshot channel is used entirely to detect when the
|
||||
// 'Client' has been dropped. This is to get around a bug
|
||||
// in h2 where dropping all SendRequests won't notify a
|
||||
// parked Connection.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let fut = conn
|
||||
.map_err(|e| debug!("client h2 connection error: {}", e))
|
||||
.select2(rx)
|
||||
.then(|res| match res {
|
||||
Ok(Either::A(((), _))) |
|
||||
Err(Either::A(((), _))) => {
|
||||
// conn has finished either way
|
||||
Either::A(future::ok(()))
|
||||
},
|
||||
Err(Either::B((_, conn))) => {
|
||||
// oneshot has been dropped, hopefully polling
|
||||
// the connection some more should start shutdown
|
||||
// and then close
|
||||
trace!("send_request dropped, starting conn shutdown");
|
||||
Either::B(conn)
|
||||
}
|
||||
Ok(Either::B((never, _))) => match never {},
|
||||
});
|
||||
self.executor.execute(fut);
|
||||
State::Ready(request_tx, tx)
|
||||
},
|
||||
State::Ready(ref mut tx, _) => {
|
||||
try_ready!(tx.poll_ready().map_err(::Error::new_h2));
|
||||
match self.rx.poll() {
|
||||
Ok(Async::Ready(Some((req, mut cb)))) => {
|
||||
// check that future hasn't been canceled already
|
||||
if let Async::Ready(()) = cb.poll_cancel().expect("poll_cancel cannot error") {
|
||||
trace!("request canceled");
|
||||
continue;
|
||||
}
|
||||
let (head, body) = req.into_parts();
|
||||
let mut req = ::http::Request::from_parts(head, ());
|
||||
super::strip_connection_headers(req.headers_mut());
|
||||
let eos = body.is_end_stream();
|
||||
let (fut, body_tx) = match tx.send_request(req, eos) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
debug!("client send request error: {}", err);
|
||||
let _ = cb.send(Err((::Error::new_h2(err), None)));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if !eos {
|
||||
let pipe = PipeToSendStream::new(body, body_tx);
|
||||
self.executor.execute(pipe.map_err(|e| debug!("client request body error: {}", e)));
|
||||
}
|
||||
|
||||
let fut = fut
|
||||
.then(move |result| {
|
||||
match result {
|
||||
Ok(res) => {
|
||||
let res = res.map(::Body::h2);
|
||||
let _ = cb.send(Ok(res));
|
||||
},
|
||||
Err(err) => {
|
||||
debug!("client response error: {}", err);
|
||||
let _ = cb.send(Err((::Error::new_h2(err), None)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
self.executor.execute(fut);
|
||||
continue;
|
||||
},
|
||||
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
|
||||
Ok(Async::Ready(None)) |
|
||||
Err(_) => {
|
||||
trace!("client tx dropped");
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
self.state = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,121 @@
|
||||
use bytes::Buf;
|
||||
use futures::{Async, Future, Poll};
|
||||
use h2::{Reason, SendStream};
|
||||
use http::HeaderMap;
|
||||
use http::header::{CONNECTION, TRANSFER_ENCODING};
|
||||
|
||||
use ::body::Payload;
|
||||
use ::proto::h1::Cursor;
|
||||
|
||||
mod client;
|
||||
mod server;
|
||||
|
||||
pub(crate) use self::client::Client;
|
||||
pub(crate) use self::server::Server;
|
||||
|
||||
fn strip_connection_headers(headers: &mut HeaderMap) {
|
||||
if headers.remove(TRANSFER_ENCODING).is_some() {
|
||||
trace!("removed illegal Transfer-Encoding header");
|
||||
}
|
||||
if headers.contains_key(CONNECTION) {
|
||||
warn!("Connection header illegal in HTTP/2");
|
||||
//TODO: actually remove it, after checking the value
|
||||
//and removing all related headers
|
||||
}
|
||||
}
|
||||
|
||||
// body adapters used by both Client and Server
|
||||
|
||||
struct PipeToSendStream<S>
|
||||
where
|
||||
S: Payload,
|
||||
{
|
||||
body_tx: SendStream<SendBuf<S::Data>>,
|
||||
stream: S,
|
||||
}
|
||||
|
||||
impl<S> PipeToSendStream<S>
|
||||
where
|
||||
S: Payload,
|
||||
{
|
||||
fn new(stream: S, tx: SendStream<SendBuf<S::Data>>) -> PipeToSendStream<S> {
|
||||
PipeToSendStream {
|
||||
body_tx: tx,
|
||||
stream: stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Future for PipeToSendStream<S>
|
||||
where
|
||||
S: Payload,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
// TODO: make use of flow control on SendStream
|
||||
// If you're looking at this and thinking of trying to fix this TODO,
|
||||
// you may want to look at:
|
||||
// https://docs.rs/h2/0.1.*/h2/struct.SendStream.html
|
||||
//
|
||||
// With that doc open, we'd want to do these things:
|
||||
// - check self.body_tx.capacity() to see if we can send *any* data
|
||||
// - if > 0:
|
||||
// - poll self.stream
|
||||
// - reserve chunk.len() more capacity (because its about to be used)?
|
||||
// - send the chunk
|
||||
// - else:
|
||||
// - try reserve a smallish amount of capacity
|
||||
// - call self.body_tx.poll_capacity(), return if NotReady
|
||||
match self.stream.poll_data() {
|
||||
Ok(Async::Ready(Some(chunk))) => {
|
||||
trace!("send body chunk: {}B", chunk.as_ref().len());
|
||||
self.body_tx.send_data(SendBuf(Some(Cursor::new(chunk))), false)
|
||||
.map_err(::Error::new_h2)?;
|
||||
},
|
||||
Ok(Async::Ready(None)) => {
|
||||
trace!("send body eos");
|
||||
self.body_tx.send_data(SendBuf(None), true)
|
||||
.map_err(::Error::new_h2)?;
|
||||
return Ok(Async::Ready(()));
|
||||
},
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
let err = ::Error::new_user_body(err);
|
||||
trace!("send body user stream error: {}", err);
|
||||
self.body_tx.send_reset(Reason::INTERNAL_ERROR);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SendBuf<B>(Option<Cursor<B>>);
|
||||
|
||||
impl<B: AsRef<[u8]>> Buf for SendBuf<B> {
|
||||
#[inline]
|
||||
fn remaining(&self) -> usize {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|b| b.remaining())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bytes(&self) -> &[u8] {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|b| b.bytes())
|
||||
.unwrap_or(&[])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn advance(&mut self, cnt: usize) {
|
||||
self.0
|
||||
.as_mut()
|
||||
.map(|b| b.advance(cnt));
|
||||
}
|
||||
}
|
||||
|
||||
198
src/proto/h2/server.rs
Normal file
198
src/proto/h2/server.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use h2::Reason;
|
||||
use h2::server::{Builder, Connection, Handshake, SendResponse};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use ::body::Payload;
|
||||
use ::common::Exec;
|
||||
use ::server::Service;
|
||||
use super::{PipeToSendStream, SendBuf};
|
||||
|
||||
use ::{Body, Request, Response};
|
||||
|
||||
pub(crate) struct Server<T, S, B>
|
||||
where
|
||||
S: Service,
|
||||
B: Payload,
|
||||
{
|
||||
exec: Exec,
|
||||
service: S,
|
||||
state: State<T, B>,
|
||||
}
|
||||
|
||||
enum State<T, B>
|
||||
where
|
||||
B: Payload,
|
||||
{
|
||||
Handshaking(Handshake<T, SendBuf<B::Data>>),
|
||||
Serving(Serving<T, B>),
|
||||
}
|
||||
|
||||
struct Serving<T, B>
|
||||
where
|
||||
B: Payload,
|
||||
{
|
||||
conn: Connection<T, SendBuf<B::Data>>,
|
||||
}
|
||||
|
||||
|
||||
impl<T, S, B> Server<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request=Request<Body>, Response=Response<B>>,
|
||||
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||
S::Future: Send + 'static,
|
||||
B: Payload,
|
||||
{
|
||||
pub(crate) fn new(io: T, service: S, exec: Exec) -> Server<T, S, B> {
|
||||
let handshake = Builder::new()
|
||||
.handshake(io);
|
||||
Server {
|
||||
exec,
|
||||
state: State::Handshaking(handshake),
|
||||
service,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn graceful_shutdown(&mut self) {
|
||||
unimplemented!("h2 server graceful shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B> Future for Server<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request=Request<Body>, Response=Response<B>>,
|
||||
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||
S::Future: Send + 'static,
|
||||
B: Payload,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
let next = match self.state {
|
||||
State::Handshaking(ref mut h) => {
|
||||
let conn = try_ready!(h.poll().map_err(::Error::new_h2));
|
||||
State::Serving(Serving {
|
||||
conn: conn,
|
||||
})
|
||||
},
|
||||
State::Serving(ref mut srv) => {
|
||||
return srv.poll_server(&mut self.service, &self.exec);
|
||||
}
|
||||
};
|
||||
self.state = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> Serving<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Payload,
|
||||
{
|
||||
fn poll_server<S>(&mut self, service: &mut S, exec: &Exec) -> Poll<(), ::Error>
|
||||
where
|
||||
S: Service<
|
||||
Request=Request<Body>,
|
||||
Response=Response<B>,
|
||||
>,
|
||||
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||
S::Future: Send + 'static,
|
||||
{
|
||||
while let Some((req, respond)) = try_ready!(self.conn.poll().map_err(::Error::new_h2)) {
|
||||
trace!("incoming request");
|
||||
let req = req.map(::Body::h2);
|
||||
let fut = H2Stream::new(service.call(req), respond);
|
||||
exec.execute(fut);
|
||||
}
|
||||
|
||||
// no more incoming streams...
|
||||
trace!("incoming connection complete");
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
|
||||
struct H2Stream<F, B>
|
||||
where
|
||||
B: Payload,
|
||||
{
|
||||
reply: SendResponse<SendBuf<B::Data>>,
|
||||
state: H2StreamState<F, B>,
|
||||
}
|
||||
|
||||
enum H2StreamState<F, B>
|
||||
where
|
||||
B: Payload,
|
||||
{
|
||||
Service(F),
|
||||
Body(PipeToSendStream<B>),
|
||||
}
|
||||
|
||||
impl<F, B> H2Stream<F, B>
|
||||
where
|
||||
F: Future<Item=Response<B>>,
|
||||
F::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||
B: Payload,
|
||||
{
|
||||
fn new(fut: F, respond: SendResponse<SendBuf<B::Data>>) -> H2Stream<F, B> {
|
||||
H2Stream {
|
||||
reply: respond,
|
||||
state: H2StreamState::Service(fut),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll2(&mut self) -> Poll<(), ::Error> {
|
||||
loop {
|
||||
let next = match self.state {
|
||||
H2StreamState::Service(ref mut h) => {
|
||||
let res = try_ready!(h.poll().map_err(::Error::new_user_service));
|
||||
let (head, body) = res.into_parts();
|
||||
let mut res = ::http::Response::from_parts(head, ());
|
||||
super::strip_connection_headers(res.headers_mut());
|
||||
macro_rules! reply {
|
||||
($eos:expr) => ({
|
||||
match self.reply.send_response(res, $eos) {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
trace!("send response error: {}", e);
|
||||
self.reply.send_reset(Reason::INTERNAL_ERROR);
|
||||
return Err(::Error::new_h2(e));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if !body.is_end_stream() {
|
||||
let body_tx = reply!(false);
|
||||
H2StreamState::Body(PipeToSendStream::new(body, body_tx))
|
||||
} else {
|
||||
reply!(true);
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
},
|
||||
H2StreamState::Body(ref mut pipe) => {
|
||||
return pipe.poll();
|
||||
}
|
||||
};
|
||||
self.state = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, B> Future for H2Stream<F, B>
|
||||
where
|
||||
F: Future<Item=Response<B>>,
|
||||
F::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||
B: Payload,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.poll2()
|
||||
.map_err(|e| debug!("stream error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use headers;
|
||||
|
||||
pub(crate) use self::h1::{dispatch, Conn};
|
||||
|
||||
mod h1;
|
||||
//mod h2;
|
||||
pub(crate) mod h1;
|
||||
pub(crate) mod h2;
|
||||
|
||||
|
||||
/// An Incoming Message head. Includes request/status line, and headers.
|
||||
|
||||
Reference in New Issue
Block a user