Skip to content

Commit 5a4aead

Browse files
[Rust Server] Get server example to compile (#17876)
1 parent 2960180 commit 5a4aead

8 files changed

Lines changed: 288 additions & 41 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ public RustServerCodegen() {
233233
supportingFiles.add(new SupportingFile("server-server_auth.mustache", "src/server", "server_auth.rs"));
234234
supportingFiles.add(new SupportingFile("client-mod.mustache", "src/client", "mod.rs"));
235235
supportingFiles.add(new SupportingFile("example-server-main.mustache", "examples/server", "main.rs"));
236+
supportingFiles.add(new SupportingFile("example-server-tokio-io.rs", "examples/server", "tokio_io.rs"));
236237
supportingFiles.add(new SupportingFile("example-server-server.mustache", "examples/server", "server.rs"));
237238
supportingFiles.add(new SupportingFile("example-server-auth.mustache", "examples/server", "server_auth.rs"));
238239
supportingFiles.add(new SupportingFile("example-client-main.mustache", "examples/client", "main.rs"));

modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,12 @@ frunk-enum-core = { version = "0.3.0", optional = true }
156156
jsonwebtoken = { version = "9.3.1", optional = false }
157157

158158
[dev-dependencies]
159+
always_send = "0.1.1"
159160
clap = "4.5"
160161
env_logger = "0.11"
161162
tokio = { version = "1.45", features = ["full"] }
162163
native-tls = "0.2"
164+
pin-project = "1.1.10"
163165

164166
[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies]
165167
tokio-openssl = "0.6"

modules/openapi-generator/src/main/resources/rust-server/context.mustache

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ where
3030
}
3131
}
3232

33+
impl<T, A> Clone for MakeAddContext<T, A>
34+
where
35+
T: Clone,
36+
{
37+
fn clone(&self) -> Self {
38+
Self {
39+
inner: self.inner.clone(),
40+
marker: PhantomData,
41+
}
42+
}
43+
}
44+
3345
// Make a service that adds context.
3446
impl<Target, T, A, B, C, D> Service<Target> for
3547
MakeAddContext<T, A>

modules/openapi-generator/src/main/resources/rust-server/example-server-auth.mustache

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use swagger::{
22
ApiError,
3-
auth::{Basic, Bearer},
43
Has,
54
XSpanIdString};
65
use {{{externCrateName}}}::{AuthenticationApi, Claims};
@@ -95,10 +94,10 @@ fn get_jwt_error_string(error: JwtError::Error) -> String {
9594
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
9695
9796
/// Implementation of the method to map a Bearer-token to an Authorization
98-
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
99-
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
97+
fn bearer_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
98+
debug!("\tAuthorizationApi: Received Bearer-token, {token:#?}");
10099
101-
match extract_token_data(&bearer.token, b"secret") {
100+
match extract_token_data(token, b"secret") {
102101
Ok(auth_data) => {
103102
debug!("\tUnpack auth_data as: {auth_data:#?}");
104103
let authorization = build_authorization(auth_data.claims);
@@ -124,8 +123,8 @@ impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Syn
124123
}
125124

126125
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
127-
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
128-
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
126+
fn basic_authorization(&self, username: &str, _password: &str) -> Result<Authorization, ApiError> {
127+
debug!("\tAuthorizationApi: Received Basic-token, {username}");
129128
130129
// TODO: insert the logic to map received apikey to the set of claims
131130
let claims = full_permission_claim();
@@ -135,4 +134,3 @@ impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Syn
135134
}
136135

137136
}
138-

modules/openapi-generator/src/main/resources/rust-server/example-server-common.mustache

Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use async_trait::async_trait;
66
use futures::{future, Stream, StreamExt, TryFutureExt, TryStreamExt};
7-
use hyper::server::conn::Http;
8-
use hyper::service::Service;
7+
use hyper::server::conn::http1;
8+
use hyper::service::{service_fn, Service};
99
use log::info;
1010
use std::future::Future;
1111
use std::marker::PhantomData;
@@ -17,26 +17,34 @@ use swagger::auth::MakeAllowAllAuthenticator;
1717
use swagger::EmptyContext;
1818
use tokio::net::TcpListener;
1919

20+
use crate::tokio_io::TokioIo;
21+
2022
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
2123
use openssl::ssl::{Ssl, SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
2224

2325
use {{{externCrateName}}}::models;
2426

27+
/// Needed because `hyper`'s `service_fn` is sent to a `tokio::task::spawn`,
28+
/// which requires the future to be `'static`.
29+
///
30+
/// Because `MakeAllowAllAuthenticator` is not `Clone`, this is a shorthand way
31+
/// of creating the `service`.
32+
///
33+
/// This is not a `fn` because the generics are extremely deeply nested.
34+
macro_rules! create_service {
35+
() => {{
36+
let server = Server::new();
37+
let service = MakeService::new(server);
38+
let service = MakeAllowAllAuthenticator::new(service, "cosmo");
39+
wayfinder_workflow_web::server::context::MakeAddContext::<_, EmptyContext>::new(
40+
service
41+
)
42+
}};
43+
}
44+
2545
/// Builds an SSL implementation for Simple HTTPS from some hard-coded file names
2646
pub async fn create(addr: &str, https: bool) {
27-
let addr = addr.parse().expect("Failed to parse bind address");
28-
29-
let server = Server::new();
30-
31-
let service = MakeService::new(server);
32-
33-
let service = MakeAllowAllAuthenticator::new(service, "cosmo");
34-
35-
#[allow(unused_mut)]
36-
let mut service =
37-
{{{externCrateName}}}::server::context::MakeAddContext::<_, EmptyContext>::new(
38-
service
39-
);
47+
let addr: SocketAddr = addr.parse().expect("Failed to parse bind address");
4048
4149
if https {
4250
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
@@ -61,14 +69,14 @@ pub async fn create(addr: &str, https: bool) {
6169
if let Ok((tcp, _)) = tcp_listener.accept().await {
6270
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
6371
let addr = tcp.peer_addr().expect("Unable to get remote address");
64-
let service = service.call(addr);
72+
let service = create_service!().call(addr);
6573
6674
tokio::spawn(async move {
6775
let tls = tokio_openssl::SslStream::new(ssl, tcp).map_err(|_| ())?;
6876
let service = service.await.map_err(|_| ())?;
6977
70-
Http::new()
71-
.serve_connection(tls, service)
78+
http1::Builder::new()
79+
.serve_connection(TokioIo::new(tcp_stream), service)
7280
.await
7381
.map_err(|_| ())
7482
});
@@ -78,11 +86,63 @@ pub async fn create(addr: &str, https: bool) {
7886
} else {
7987
info!("Starting a server (over http, so no TLS)");
8088
// Using HTTP
81-
hyper::server::Server::bind(&addr).serve(service).await.unwrap()
89+
let listener = TcpListener::bind(&addr).await.unwrap();
90+
println!("Listening on http://{}", addr);
91+
92+
loop {
93+
// When an incoming TCP connection is received grab a TCP stream for
94+
// client<->server communication.
95+
//
96+
// Note, this is a .await point, this loop will loop forever but is not a busy loop. The
97+
// .await point allows the Tokio runtime to pull the task off of the thread until the task
98+
// has work to do. In this case, a connection arrives on the port we are listening on and
99+
// the task is woken up, at which point the task is then put back on a thread, and is
100+
// driven forward by the runtime, eventually yielding a TCP stream.
101+
let (tcp_stream, _addr) = listener.accept().await.expect("Failed to accept connection");
102+
103+
let service = create_service!();
104+
let my_service_fn = service_fn(move |req| {
105+
let add_context = service.call(());
106+
107+
async move {
108+
let add_context = add_context.await?;
109+
add_context.call(req).await
110+
}
111+
});
112+
113+
// Spin up a new task in Tokio so we can continue to listen for new TCP connection on the
114+
// current task without waiting for the processing of the HTTP1 connection we just received
115+
// to finish
116+
tokio::task::spawn(async move {
117+
// Handle the connection from the client using HTTP1 and pass any
118+
// HTTP requests received on that connection to the `hello` function
119+
let result = hyper::server::conn::http1::Builder::new()
120+
.serve_connection(TokioIo::new(tcp_stream), my_service_fn)
121+
// `always_send` is here, because we run into:
122+
//
123+
// ```md
124+
// implementation of `From` is not general enough
125+
//
126+
// `Box<(dyn StdError + std::marker::Send + Sync + 'static)>` must implement `From<Box<(dyn StdError + std::marker::Send + Sync + '0)>>`, for any lifetime `'0`...
127+
// ...but it actually implements `From<Box<(dyn StdError + std::marker::Send + Sync + 'static)>>`
128+
// ```
129+
//
130+
// This is caused by this rust bug:
131+
//
132+
// <https://users.rust-lang.org/t/implementation-of-from-is-not-general-enough-with-hyper/105799>
133+
// <https://github.com/rust-lang/rust/issues/102211>
134+
.always_send()
135+
.await;
136+
if let Err(err) = result
137+
{
138+
println!("Error serving connection: {:?}", err);
139+
}
140+
});
141+
}
82142
}
83143
}
84144

85-
#[derive(Copy, Clone)]
145+
#[derive(Copy)]
86146
pub struct Server<C> {
87147
marker: PhantomData<C>,
88148
}
@@ -92,3 +152,11 @@ impl<C> Server<C> {
92152
Server{marker: PhantomData}
93153
}
94154
}
155+
156+
impl<C> Clone for Server<C> {
157+
fn clone(&self) -> Self {
158+
Self {
159+
marker: PhantomData,
160+
}
161+
}
162+
}

modules/openapi-generator/src/main/resources/rust-server/example-server-main.mustache

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@
33

44
#![allow(missing_docs)]
55

6-
7-
use clap::{App, Arg};
6+
use clap::{Arg, Command};
87

98
mod server;
109
mod server_auth;
11-
10+
mod tokio_io;
1211

1312
/// Create custom server, wire it to the autogenerated router,
1413
/// and pass it to the web server.
1514
#[tokio::main]
1615
async fn main() {
1716
env_logger::init();
1817
19-
let matches = App::new("server")
20-
.arg(Arg::with_name("https")
21-
.long("https")
22-
.help("Whether to use HTTPS or not"))
18+
let matches = Command::new("server")
19+
.arg(
20+
Arg::new("https")
21+
.long("https")
22+
.help("Whether to use HTTPS or not"),
23+
)
2324
.get_matches();
2425
25-
let addr = "127.0.0.1:{{{serverPort}}}";
26+
let addr = "127.0.0.1:8080";
2627
27-
server::create(addr, matches.is_present("https")).await;
28+
server::create(addr, matches.contains_id("https")).await;
2829
}

0 commit comments

Comments
 (0)