Crates.io | twurst-server |
lib.rs | twurst-server |
version | 0.0.2 |
source | src |
created_at | 2024-11-27 13:03:48.235609 |
updated_at | 2024-11-28 14:16:40.878233 |
description | Twirp server related code |
homepage | |
repository | https://github.com/helsing-ai/twurst |
max_upload_size | |
id | 1463002 |
size | 29,898 |
Crate implementing the needed runtime for the code generated by twurst-build
in order to run Twirp servers.
To start you need first to have a gRPC .proto
file (e.g. service.proto
).
Then build your proto files by creating a build.rs
file with:
fn main() -> std::io::Result<()> {
twurst_build::TwirpBuilder::new()
.with_server()
.compile_protos(&["proto/service.proto"], &["proto"])
}
and add to your Cargo.toml
:
[dependencies]
axum = ""
prost = ""
prost-types = ""
prost-reflect = ""
twurst-server = ""
[build-dependencies]
twurst-build = ""
Note that protoc
must be available, see prost-build
documentation on this topic.
If you are using Nix, nix-shell -p protobuf
is enough to provide protoc
.
Then you can implement a Twirp server with:
use proto::*;
use twurst_server::twirp_fallback;
mod proto {
include!(concat!(env!("OUT_DIR"), "/example.rs")); // example is the name of your proto package
}
/// The service implementation
struct ExampleServiceServicer {}
impl ExampleService for ExampleServiceServicer {
async fn test(
&self,
request: TestRequest
) -> Result<TestResponse, TwirpError> {
unimplemented!()
}
}
async fn main() {
axum::serve(
tokio::net::TcpListener::bind("localhost:8080").await?,
axum::Router::new()
.nest("/twirp", ExampleServiceServicer {}.into_router().fallback(twirp_fallback))
).await
}
Note that you can make use of tower
or tower-http
layers to customize the server:
use twurst_server::twirp_fallback;
async fn main() {
axum::serve(
tokio::net::TcpListener::bind("localhost:8080").await?,
axum::Router::new()
.nest("/twirp", ExampleServiceServicer {}.into_router().fallback(twirp_fallback))
.layer(tower_http::cors::CorsLayer::new())
).await
}
It is also possible to use axum
extractors in the generated code.
For example, to get access to the request headers you can tweak your build.rs
TwirpBuilder::new()
call:
twurst_build::TwirpBuilder::new()
.with_server()
.with_axum_request_extractor("headers", "::axum::http::HeaderMap")
.compile_protos(&["proto/service.proto"], &["proto"])
then your trait implementation will change to:
impl self::proto::ExampleService for ExampleServiceServicer {
async fn test(
&self,
request: TestRequest,
headers: ::axum::http::HeaderMap
) -> Result<TestResponse, TwirpError> {
unimplemented!()
}
}
Any type implementing FromRequestParts
work.
Note that you can use Router::merge
to serve multiple Twirp services:
use twurst_server::twirp_fallback;
ExampleServiceServicer {}
.into_router()
.merge(OtherExampleServiceServicer {}.into_router())
.fallback(twirp_fallback)
Note the single fallback
call.
To make testing easier you can use the generated client code to test your server:
use twurst_client::TwirpHttpClient;
let client = ExampleServiceClient::new(ExampleServiceServicer {}.into_router());
note that you need to add to your build.rs
.with_client()
alongside .with_server()
.
twurst-server
has also basic gRPC support to serve easily both Twirp and gRPC.
For that enable the grpc
feature of the twurst-server
crate, then you can serve gRPC nearly like Twirp:
use twurst_server::grpc_fallback;
async fn main() {
axum::serve(
tokio::net::TcpListener::bind("localhost:8080").await?,
ExampleServiceServicer {}.into_grpc_router().fallback(grpc_fallback)
).await
}
Router::merge
still works if you want to serve multiple services.
Note that no limit is set on requests size, use RequestBodyLimit
layer if you want to set one.
grpc
that provides gRPC support behind tonic
Copyright 2024 Helsing GmbH
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.