diff options
| author | ihc童鞋@提不起劲 <[email protected]> | 2021-12-13 17:28:24 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-12-13 17:28:24 +0800 |
| commit | ad56175d27ba40f195aa3fe5a1a2ff31936ae42c (patch) | |
| tree | 23721b1e50838a60341e16102b362fe57041806f /docs | |
| parent | ee8e7c2b2aa6c345367044d9bd83f360b5664ba0 (diff) | |
example: add hyper client example and related docs (#22)
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/en/http.md | 96 | ||||
| -rw-r--r-- | docs/zh/http.md | 96 |
2 files changed, 192 insertions, 0 deletions
diff --git a/docs/en/http.md b/docs/en/http.md new file mode 100644 index 0000000..33a884e --- /dev/null +++ b/docs/en/http.md @@ -0,0 +1,96 @@ +--- +title: HTTP Server & HTTP Client +date: 2021-12-13 16:00:00 +author: ihciah +--- + +This article briefly explains how to use HTTP in monoio. Related examples can be consulted: +1. [hyper_client.rs](/examples/hyper_client.rs) +2. [hyper_server.rs](/examples/hyper_server.rs) + +## hyper + +In Rust, http is often done using the `hyper` library. Maybe you will use libraries such as `reqwest` or `wrap` to implement HTTP requirements, but their underlying implementation is based on `hyper`. + +Part of the code inside hyper is bound to tokio, but it also provides the freedom to replace Runtime to a certain extent. Tokio will not be introduced when the `runtime` feature is turned off. + +In addition to the things that can be described by common traits, Runtime itself generally directly provides methods such as `spawn` and `sleep`. To decouple the runtime, the specific library must define its own traits and implement them for the known Runtime(or leave it to runtime developers). `hyper` uses the `Executor` trait to abstract the `spawn` method. + +We can implement `Executor` like this: +```rust +#[derive(Clone)] +struct HyperExecutor; + +impl<F> hyper::rt::Executor<F> for HyperExecutor +where + F: Future +'static, + F::Output:'static, +{ + fn execute(&self, fut: F) { + monoio::spawn(fut); + } +} +``` + +## server + +The general logic of the server is to cyclically receive the connection, and then hand the connection to the corresponding service for processing; in order not to block subsequent connections, the processing will often be spawned out to complete. + +We can use the `hyper::server::conn::Http::with_executor` method to specify the `Executor` used by hyper. + +```rust +pub(crate) async fn serve_http<S, F, R, A>(addr: A, service: S) -> std::io::Result<()> +where + S: FnMut(Request<Body>) -> F +'static + Copy, + F: Future<Output = Result<Response<Body>, R>> +'static, + R: std::error::Error +'static + Send + Sync, + A: Into<SocketAddr>, +{ + let listener = TcpListener::bind(addr.into())?; + loop { + let (stream, _) = listener.accept().await?; + monoio::spawn( + Http::new() + .with_executor(HyperExecutor) + .serve_connection(TcpStreamCompat::from(stream), service_fn(service)), + ); + } +} +``` + +Then provide the handler. + +```rust +async fn hyper_handler(req: Request<Body>) -> Result<Response<Body>, std::convert::Infallible> { + match (req.method(), req.uri().path()) { + (&Method::GET, "/") => Ok(Response::new(Body::from("Hello World!"))), + (&Method::GET, "/monoio") => Ok(Response::new(Body::from("Hello Monoio!"))), + _ => Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("404 not found")) + .unwrap()), + } +} +``` + +## client + +The client's implementation involves how to create a connection. This is also strongly coupled by Runtime, so hyper provides the `Connector` abstraction, which is defined as a Tower Service, which receives `hyper::Uri` and returns the implementation `tokio::io::AsyncRead + The future of tokio::io::AsyncWrite + hyper::client::connect::Connection` (that is, connection in a broad sense). + +We implement `HyperConnection` based on `monoio_compat::TcpStreamCompat`; and provide `HyperConnector` to implement this `Service` (for specific implementation refer to [`examples/hyper_client.rs`](/examples/hyper_client.rs)). + +Finally, we specify `Executor` and `Connector` to create a client and do a request. + +```rust +let client = hyper::Client::builder() + .executor(HyperExecutor) + .build::<HyperConnector, hyper::Body>(connector); +let res = client + .get("http://127.0.0.1:23300/monoio".parse().unwrap()) + .await + .expect("failed to fetch"); +``` + +An additional note here is that hyper does not support thread-per-core Runtime well. You can refer to the [issue](https://github.com/hyperium/hyper/issues/2341) mentioned by the author of Glommio. + +The Response of the Service that created the connection just mentioned is constrained to be `Send`; but obviously there is no way to do `Send` in monoio (or other Runtimes of the same model). But in fact it is indeed running in this thread, so in order to not change the hyper, we forcibly mark it as `Send`. This approach is not elegant at all and easily causes unsoundness, requiring users to strictly pay attention to their own code, and is not recommended for use in production. But it works.
\ No newline at end of file diff --git a/docs/zh/http.md b/docs/zh/http.md new file mode 100644 index 0000000..9d8a46c --- /dev/null +++ b/docs/zh/http.md @@ -0,0 +1,96 @@ +--- +title: HTTP 服务端 & HTTP 客户端 +date: 2021-12-13 16:00:00 +author: ihciah +--- + +本文简要说明如何在 monoio 中使用 HTTP。相关 examples 可以查阅: +1. [hyper_client.rs](/examples/hyper_client.rs) +2. [hyper_server.rs](/examples/hyper_server.rs) + +## hyper + +在 Rust 中 http 往往使用 `hyper` 库完成。也许你会使用 `reqwest` 或 `wrap` 等库来实现 HTTP 需求,但他们底层都是基于 `hyper` 实现的。 + +hyper 内部部分代码绑定了 tokio,但一定程度上也提供了替换 Runtime 的自由度,关闭 `runtime` feature 的情况下是不会引入 tokio 的。 + +除了可以使用通用 trait 描述的东西,Runtime 本身一般会直接提供 `spawn`、`sleep` 等方法,要解耦 runtime 必须由具体库自己定义 trait 并为已知 Runtime 实现(或者交给 Runtime 作者实现)。在 `hyper` 内部通过 `Executor` trait 来抽象 `spawn` 方法。 + +我们可以这样实现 Executor: +```rust +#[derive(Clone)] +struct HyperExecutor; + +impl<F> hyper::rt::Executor<F> for HyperExecutor +where + F: Future + 'static, + F::Output: 'static, +{ + fn execute(&self, fut: F) { + monoio::spawn(fut); + } +} +``` + +## server + +服务端的一般逻辑是循环接收连接,然后把连接交给对应的服务处理;为了不阻塞后续连接,往往处理会 spawn 出去完成。 + +我们可以通过 `hyper::server::conn::Http::with_executor` 方法来指定 hyper 使用的 Executor。 + +```rust +pub(crate) async fn serve_http<S, F, R, A>(addr: A, service: S) -> std::io::Result<()> +where + S: FnMut(Request<Body>) -> F + 'static + Copy, + F: Future<Output = Result<Response<Body>, R>> + 'static, + R: std::error::Error + 'static + Send + Sync, + A: Into<SocketAddr>, +{ + let listener = TcpListener::bind(addr.into())?; + loop { + let (stream, _) = listener.accept().await?; + monoio::spawn( + Http::new() + .with_executor(HyperExecutor) + .serve_connection(TcpStreamCompat::from(stream), service_fn(service)), + ); + } +} +``` + +之后提供 handler 即可。 + +```rust +async fn hyper_handler(req: Request<Body>) -> Result<Response<Body>, std::convert::Infallible> { + match (req.method(), req.uri().path()) { + (&Method::GET, "/") => Ok(Response::new(Body::from("Hello World!"))), + (&Method::GET, "/monoio") => Ok(Response::new(Body::from("Hello Monoio!"))), + _ => Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("404 not found")) + .unwrap()), + } +} +``` + +## client + +客户端的实现涉及如何创建连接,这个也是 Runtime 强耦合的,所以 hyper 提供了 `Connector` 抽象,它被定义为一个 Tower Service,接收 `hyper::Uri` 返回实现 `tokio::io::AsyncRead + tokio::io::AsyncWrite + hyper::client::connect::Connection`(也就是广义上的连接)的 future。 + +我们基于 `monoio_compat::TcpStreamCompat` 定义 `HyperConnection` 来实现上述约束;并提供 `HyperConnector` 实现这个 Service(具体实现参考 [`examples/hyper_client.rs`](/examples/hyper_client.rs))。 + +最后我们指定 Executor 和 Connector 创建 client,并发起请求。 + +```rust +let client = hyper::Client::builder() + .executor(HyperExecutor) + .build::<HyperConnector, hyper::Body>(connector); +let res = client + .get("http://127.0.0.1:23300/monoio".parse().unwrap()) + .await + .expect("failed to fetch"); +``` + +这里要额外说明的是,hyper 并没有很好地支持 thread-per-core 的 Runtime。可以参考 Glommio 的作者提的 [issue](https://github.com/hyperium/hyper/issues/2341)。 + +刚刚提到的创建连接的 Service 它的 Response 被约束为是 `Send` 的;但明显在 monoio(或其他同样模型的 Runtime)中没办法做到 `Send`。但是事实上它又是确实在本线程运行的,所以为了在不改动 hyper 的情况下,我们强行标记其是 `Send` 的。这种做法一点也不优雅,容易造成 unsoundness,需要使用者严格注意自己的代码,不推荐在生产中使用。
\ No newline at end of file |
