Skip to content

Commit 367cef8

Browse files
committed
Add !Send server example
1 parent 16daef6 commit 367cef8

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,7 @@ required-features = ["client-legacy", "http1", "tokio"]
7979
[[example]]
8080
name = "server"
8181
required-features = ["server", "http1", "tokio"]
82+
83+
[[example]]
84+
name = "single_threaded_server"
85+
required-features = ["server", "http1", "http2", "tokio"]

examples/single_threaded_server.rs

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! This example runs a server that responds to any request with "Hello, world!".
2+
//! Unlike it's server.rs counter part, it demonstrates using a !Send executor (i.e. gloomio,
3+
//! monoio).
4+
5+
use std::{convert::Infallible, error::Error};
6+
7+
use std::marker::PhantomData;
8+
use std::pin::Pin;
9+
use std::task::{Context, Poll};
10+
use std::thread;
11+
12+
use bytes::Bytes;
13+
use http::{header::CONTENT_TYPE, Request, Response};
14+
use http_body_util::{combinators::BoxBody, BodyExt, Full};
15+
use hyper::{body::Incoming, service::service_fn};
16+
use hyper_util::{
17+
rt::{TokioExecutor, TokioIo},
18+
server::conn::auto::Builder,
19+
};
20+
use tokio::{net::TcpListener, net::TcpStream, task::JoinSet};
21+
22+
/// Function from an incoming request to an outgoing response
23+
///
24+
/// This function gets turned into a [`hyper::service::Service`] later via
25+
/// [`service_fn`]. Instead of doing this, you could also write a type that
26+
/// implements [`hyper::service::Service`] directly and pass that in place of
27+
/// writing a function like this and calling [`service_fn`].
28+
async fn handle_request(_request: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
29+
let response = Response::builder()
30+
.header(CONTENT_TYPE, "text/plain")
31+
.body(Full::new(Bytes::from("Hello, world!\n")))
32+
.expect("values provided to the builder should be valid");
33+
34+
Ok(response)
35+
}
36+
37+
async fn upgradable_server() -> Result<(), Box<dyn Error + 'static>> {
38+
let listen_addr = "127.0.0.1:8000";
39+
let tcp_listener = TcpListener::bind(listen_addr).await?;
40+
println!("listening on http://{listen_addr}");
41+
42+
loop {
43+
let (stream, addr) = match tcp_listener.accept().await {
44+
Ok(x) => x,
45+
Err(e) => {
46+
eprintln!("failed to accept connection: {e}");
47+
continue;
48+
}
49+
};
50+
51+
let serve_connection = async move {
52+
println!("handling a request from {addr}");
53+
54+
let result = Builder::new(LocalExec)
55+
.serve_connection(TokioIo::new(stream), service_fn(handle_request))
56+
.await;
57+
58+
if let Err(e) = result {
59+
eprintln!("error serving {addr}: {e}");
60+
}
61+
62+
println!("handled a request from {addr}");
63+
};
64+
65+
tokio::task::spawn_local(serve_connection);
66+
}
67+
}
68+
69+
#[tokio::main(flavor = "current_thread")]
70+
async fn main() {
71+
let server_http2 = thread::spawn(move || {
72+
// Configure a runtime for the server that runs everything on the current thread
73+
let rt = tokio::runtime::Builder::new_current_thread()
74+
.enable_all()
75+
.build()
76+
.expect("build runtime");
77+
78+
// Combine it with a `LocalSet, which means it can spawn !Send futures...
79+
let local = tokio::task::LocalSet::new();
80+
local.block_on(&rt, upgradable_server()).unwrap();
81+
});
82+
83+
server_http2.join().unwrap()
84+
}
85+
86+
// NOTE: This part is only needed for HTTP/2. HTTP/1 doesn't need an executor.
87+
//
88+
// Since the Server needs to spawn some background tasks, we needed
89+
// to configure an Executor that can spawn !Send futures...
90+
#[derive(Clone, Copy, Debug)]
91+
struct LocalExec;
92+
93+
impl<F> hyper::rt::Executor<F> for LocalExec
94+
where
95+
F: std::future::Future + 'static, // not requiring `Send`
96+
{
97+
fn execute(&self, fut: F) {
98+
// This will spawn into the currently running `LocalSet`.
99+
tokio::task::spawn_local(fut);
100+
}
101+
}
102+
103+
struct IOTypeNotSend {
104+
_marker: PhantomData<*const ()>,
105+
stream: TokioIo<TcpStream>,
106+
}
107+
108+
impl IOTypeNotSend {
109+
fn new(stream: TokioIo<TcpStream>) -> Self {
110+
Self {
111+
_marker: PhantomData,
112+
stream,
113+
}
114+
}
115+
}
116+
117+
impl hyper::rt::Write for IOTypeNotSend {
118+
fn poll_write(
119+
mut self: Pin<&mut Self>,
120+
cx: &mut Context<'_>,
121+
buf: &[u8],
122+
) -> Poll<Result<usize, std::io::Error>> {
123+
Pin::new(&mut self.stream).poll_write(cx, buf)
124+
}
125+
126+
fn poll_flush(
127+
mut self: Pin<&mut Self>,
128+
cx: &mut Context<'_>,
129+
) -> Poll<Result<(), std::io::Error>> {
130+
Pin::new(&mut self.stream).poll_flush(cx)
131+
}
132+
133+
fn poll_shutdown(
134+
mut self: Pin<&mut Self>,
135+
cx: &mut Context<'_>,
136+
) -> Poll<Result<(), std::io::Error>> {
137+
Pin::new(&mut self.stream).poll_shutdown(cx)
138+
}
139+
}
140+
141+
impl hyper::rt::Read for IOTypeNotSend {
142+
fn poll_read(
143+
mut self: Pin<&mut Self>,
144+
cx: &mut Context<'_>,
145+
buf: hyper::rt::ReadBufCursor<'_>,
146+
) -> Poll<std::io::Result<()>> {
147+
Pin::new(&mut self.stream).poll_read(cx, buf)
148+
}
149+
}

0 commit comments

Comments
 (0)