Hyper: Can hyper work with async-std instead of Tokio?

Created on 15 Jan 2020  路  13Comments  路  Source: hyperium/hyper

I think this may be answered elsewhere, but for some reason it's rather hard to get search results using the term "async-std" :-]

Most helpful comment

How to run Hyper on async-std

Instructions copied from https://github.com/async-rs/async-std-hyper

Full Cargo.toml: https://github.com/async-rs/async-std-hyper/blob/master/Cargo.toml
Full code: https://github.com/async-rs/async-std-hyper/blob/master/src/main.rs

Step 1: Dependencies

Add async-std, hyper, and tokio as dependencies to your crate:

[dependencies]
async-std = "1"
hyper = { version = "0.13", default-features = false }
tokio = { version = "0.2", default-features = false }

Step 2: Compatibility layer

Copy this compat module into your crate:

pub mod compat {
    use std::pin::Pin;
    use std::task::{Context, Poll};

    use async_std::io;
    use async_std::net::{TcpListener, TcpStream};
    use async_std::prelude::*;
    use async_std::task;

    #[derive(Clone)]
    pub struct HyperExecutor;

    impl<F> hyper::rt::Executor<F> for HyperExecutor
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        fn execute(&self, fut: F) {
            task::spawn(fut);
        }
    }

    pub struct HyperListener(pub TcpListener);

    impl hyper::server::accept::Accept for HyperListener {
        type Conn = HyperStream;
        type Error = io::Error;

        fn poll_accept(
            mut self: Pin<&mut Self>,
            cx: &mut Context,
        ) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
            let stream = task::ready!(Pin::new(&mut self.0.incoming()).poll_next(cx)).unwrap()?;
            Poll::Ready(Some(Ok(HyperStream(stream))))
        }
    }

    pub struct HyperStream(pub TcpStream);

    impl tokio::io::AsyncRead for HyperStream {
        fn poll_read(
            mut self: Pin<&mut Self>,
            cx: &mut Context,
            buf: &mut [u8],
        ) -> Poll<io::Result<usize>> {
            Pin::new(&mut self.0).poll_read(cx, buf)
        }
    }

    impl tokio::io::AsyncWrite for HyperStream {
        fn poll_write(
            mut self: Pin<&mut Self>,
            cx: &mut Context,
            buf: &[u8],
        ) -> Poll<io::Result<usize>> {
            Pin::new(&mut self.0).poll_write(cx, buf)
        }

        fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
            Pin::new(&mut self.0).poll_flush(cx)
        }

        fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
            Pin::new(&mut self.0).poll_close(cx)
        }
    }
}

Step 3: Configure Hyper

Configure the hyper builder with:

let server = Server::builder(compat::HyperListener(listener))
    .executor(compat::Executor);

Full example:

use std::convert::Infallible;

use async_std::net::TcpListener;
use async_std::task;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};

use compat; // This is the module from Step 2.

async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from("Hello World!")))
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    task::block_on(async {
        let addr = "127.0.0.1:3000";
        let listener = TcpListener::bind(addr).await?;

        let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(hello)) });
        let server = Server::builder(compat::HyperListener(listener))
            .executor(compat::HyperExecutor)
            .serve(make_svc);

        println!("Listening on http://{}", addr);
        server.await?;
        Ok(())
    })
}

All 13 comments

Unless you specifically need hyper, you may want to consider tide and surf. They have been written for async-std and are very easy to use.

@jedisct1 Thanks! I like that Hyper is battle-tested, much more so than tide.

hyper has optional features tcp and runtime which provide easy support for Tokio.

It's possible to disable these optional features, and "provide your own runtime", like has been done in tide and Fuchsia, for example.

@seanmonstar Okay, to paraphrase what you're saying: would you say that there isn't a simple way to use hyper with async-std's runtime?

I like that Hyper is battle-tested, much more so than tide

Tide uses Hyper as a backend.

@jedisct1 so tide basically is an example of how to use hyper with another executor?

tide is modular and provides a modern abstraction layer to multiple backends. The default backend is hyper.

@Extrawurst I think using hyper implies using tokio. Tide can use hyper, and if it does, it's likely using tokio.

No, that is not correct.

Tide doesn't use tokio, only async-std.

@rw Hyper out of the box supports tokio since, the people that work on hyper also work on tokio. That said, internally hyper uses traits for everything meaning with a little bit of work you can swap things out. Therefore, hyper can be used on any executor/runtime if you swap out the correct types. Nothing ties it directly to tokio besides its defaults.

TL;DR: Hyper _can_ support async-std or any other runtime via using traits like Executor and swapping out connectors. Hyper does provide defaults like https connectors etc that require tokio but these are addons.

See https://github.com/leo-lb/hyper-async-std for a project that is demoing how to use hyper on async-std (and is likely a good example of how to use hyper on any other runtime as well).

How to run Hyper on async-std

Instructions copied from https://github.com/async-rs/async-std-hyper

Full Cargo.toml: https://github.com/async-rs/async-std-hyper/blob/master/Cargo.toml
Full code: https://github.com/async-rs/async-std-hyper/blob/master/src/main.rs

Step 1: Dependencies

Add async-std, hyper, and tokio as dependencies to your crate:

[dependencies]
async-std = "1"
hyper = { version = "0.13", default-features = false }
tokio = { version = "0.2", default-features = false }

Step 2: Compatibility layer

Copy this compat module into your crate:

pub mod compat {
    use std::pin::Pin;
    use std::task::{Context, Poll};

    use async_std::io;
    use async_std::net::{TcpListener, TcpStream};
    use async_std::prelude::*;
    use async_std::task;

    #[derive(Clone)]
    pub struct HyperExecutor;

    impl<F> hyper::rt::Executor<F> for HyperExecutor
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        fn execute(&self, fut: F) {
            task::spawn(fut);
        }
    }

    pub struct HyperListener(pub TcpListener);

    impl hyper::server::accept::Accept for HyperListener {
        type Conn = HyperStream;
        type Error = io::Error;

        fn poll_accept(
            mut self: Pin<&mut Self>,
            cx: &mut Context,
        ) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
            let stream = task::ready!(Pin::new(&mut self.0.incoming()).poll_next(cx)).unwrap()?;
            Poll::Ready(Some(Ok(HyperStream(stream))))
        }
    }

    pub struct HyperStream(pub TcpStream);

    impl tokio::io::AsyncRead for HyperStream {
        fn poll_read(
            mut self: Pin<&mut Self>,
            cx: &mut Context,
            buf: &mut [u8],
        ) -> Poll<io::Result<usize>> {
            Pin::new(&mut self.0).poll_read(cx, buf)
        }
    }

    impl tokio::io::AsyncWrite for HyperStream {
        fn poll_write(
            mut self: Pin<&mut Self>,
            cx: &mut Context,
            buf: &[u8],
        ) -> Poll<io::Result<usize>> {
            Pin::new(&mut self.0).poll_write(cx, buf)
        }

        fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
            Pin::new(&mut self.0).poll_flush(cx)
        }

        fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
            Pin::new(&mut self.0).poll_close(cx)
        }
    }
}

Step 3: Configure Hyper

Configure the hyper builder with:

let server = Server::builder(compat::HyperListener(listener))
    .executor(compat::Executor);

Full example:

use std::convert::Infallible;

use async_std::net::TcpListener;
use async_std::task;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};

use compat; // This is the module from Step 2.

async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from("Hello World!")))
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    task::block_on(async {
        let addr = "127.0.0.1:3000";
        let listener = TcpListener::bind(addr).await?;

        let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(hello)) });
        let server = Server::builder(compat::HyperListener(listener))
            .executor(compat::HyperExecutor)
            .serve(make_svc);

        println!("Listening on http://{}", addr);
        server.await?;
        Ok(())
    })
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

janus picture janus  路  21Comments

seanmonstar picture seanmonstar  路  23Comments

scottlamb picture scottlamb  路  31Comments

frewsxcv picture frewsxcv  路  25Comments

habnabit picture habnabit  路  20Comments