2 releases

new 0.0.2 Nov 28, 2024
0.0.1 Nov 27, 2024

#1387 in Network programming

Download history 58/week @ 2024-11-21

62 downloads per month

Apache-2.0

30KB
566 lines

Crate implementing the needed runtime for the code generated by twurst-build in order to run Twirp servers.

Getting started

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().

gRPC support

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.

Cargo features

  • grpc that provides gRPC support behind tonic

License

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.

Dependencies

~5–11MB
~136K SLoC