Skip to content

Commit cc07c81

Browse files
azriel-healthpointdsteeley
authored andcommitted
[Rust Server] Get server example to compile (OpenAPITools#17876)
1 parent ccf8589 commit cc07c81

File tree

8 files changed

+290
-41
lines changed

8 files changed

+290
-41
lines changed

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: 90 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,36 @@ 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+
{
37+
let server = Server::new();
38+
let service = MakeService::new(server);
39+
let service = MakeAllowAllAuthenticator::new(service, "cosmo");
40+
{{{externCrateName}}}::server::context::MakeAddContext::<_, EmptyContext>::new(
41+
service
42+
)
43+
}
44+
};
45+
}
46+
2547
/// Builds an SSL implementation for Simple HTTPS from some hard-coded file names
2648
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-
);
49+
let addr: SocketAddr = addr.parse().expect("Failed to parse bind address");
4050
4151
if https {
4252
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
@@ -61,14 +71,14 @@ pub async fn create(addr: &str, https: bool) {
6171
if let Ok((tcp, _)) = tcp_listener.accept().await {
6272
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
6373
let addr = tcp.peer_addr().expect("Unable to get remote address");
64-
let service = service.call(addr);
74+
let service = create_service!().call(addr);
6575
6676
tokio::spawn(async move {
6777
let tls = tokio_openssl::SslStream::new(ssl, tcp).map_err(|_| ())?;
6878
let service = service.await.map_err(|_| ())?;
6979
70-
Http::new()
71-
.serve_connection(tls, service)
80+
http1::Builder::new()
81+
.serve_connection(TokioIo::new(tcp_stream), service)
7282
.await
7383
.map_err(|_| ())
7484
});
@@ -78,11 +88,63 @@ pub async fn create(addr: &str, https: bool) {
7888
} else {
7989
info!("Starting a server (over http, so no TLS)");
8090
// Using HTTP
81-
hyper::server::Server::bind(&addr).serve(service).await.unwrap()
91+
let listener = TcpListener::bind(&addr).await.unwrap();
92+
println!("Listening on http://{}", addr);
93+
94+
loop {
95+
// When an incoming TCP connection is received grab a TCP stream for
96+
// client<->server communication.
97+
//
98+
// Note, this is a .await point, this loop will loop forever but is not a busy loop. The
99+
// .await point allows the Tokio runtime to pull the task off of the thread until the task
100+
// has work to do. In this case, a connection arrives on the port we are listening on and
101+
// the task is woken up, at which point the task is then put back on a thread, and is
102+
// driven forward by the runtime, eventually yielding a TCP stream.
103+
let (tcp_stream, _addr) = listener.accept().await.expect("Failed to accept connection");
104+
105+
let service = create_service!();
106+
let my_service_fn = service_fn(move |req| {
107+
let add_context = service.call(());
108+
109+
async move {
110+
let add_context = add_context.await?;
111+
add_context.call(req).await
112+
}
113+
});
114+
115+
// Spin up a new task in Tokio so we can continue to listen for new TCP connection on the
116+
// current task without waiting for the processing of the HTTP1 connection we just received
117+
// to finish
118+
tokio::task::spawn(async move {
119+
// Handle the connection from the client using HTTP1 and pass any
120+
// HTTP requests received on that connection to the `hello` function
121+
let result = hyper::server::conn::http1::Builder::new()
122+
.serve_connection(TokioIo::new(tcp_stream), my_service_fn)
123+
// `always_send` is here, because we run into:
124+
//
125+
// ```md
126+
// implementation of `From` is not general enough
127+
//
128+
// `Box<(dyn StdError + std::marker::Send + Sync + 'static)>` must implement `From<Box<(dyn StdError + std::marker::Send + Sync + '0)>>`, for any lifetime `'0`...
129+
// ...but it actually implements `From<Box<(dyn StdError + std::marker::Send + Sync + 'static)>>`
130+
// ```
131+
//
132+
// This is caused by this rust bug:
133+
//
134+
// <https://users.rust-lang.org/t/implementation-of-from-is-not-general-enough-with-hyper/105799>
135+
// <https://github.com/rust-lang/rust/issues/102211>
136+
.always_send()
137+
.await;
138+
if let Err(err) = result
139+
{
140+
println!("Error serving connection: {:?}", err);
141+
}
142+
});
143+
}
82144
}
83145
}
84146

85-
#[derive(Copy, Clone)]
147+
#[derive(Copy)]
86148
pub struct Server<C> {
87149
marker: PhantomData<C>,
88150
}
@@ -92,3 +154,11 @@ impl<C> Server<C> {
92154
Server{marker: PhantomData}
93155
}
94156
}
157+
158+
impl<C> Clone for Server<C> {
159+
fn clone(&self) -> Self {
160+
Self {
161+
marker: PhantomData,
162+
}
163+
}
164+
}

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)