Crates.io | axum-connect |
lib.rs | axum-connect |
version | 0.3.2 |
source | src |
created_at | 2023-03-03 04:54:03.577723 |
updated_at | 2024-01-11 20:41:28.952729 |
description | Connect-Web RPC for Axum |
homepage | |
repository | https://github.com/AThilenius/axum-connect |
max_upload_size | |
id | 799438 |
size | 49,799 |
Brings the protobuf-based Connect-Web RPC framework to Rust via idiomatic Axum.
axum-connect:0.3
works with axum:0.7
axum-connect:0.2
works with axum:0.6
parts
that impl RpcFromRequestParts
just like
with Axum.RpcIntoResponse
just like Axum.RpcIntoResponse
and RpcFromRequestParts
just like
Axum.
connect-web
RPC over HTTP.connect-web
error handling in idiomatic Axum/Rust.*.proto
files in a separate crate.We use axum-connect
in production, but I don't kow that anyone with more sense
does. It's written in Rust which obviously offers some amazing compiler
guarantees, but it's not well tested or battle-proven yet. Do what you will with
that information.
Please let me know if you're using axum-connect
! And open issues if you find a
bug.
Prior knowledge with Protobuf (both the IDL and it's use in RPC frameworks) and Axum are assumed.
You'll need 2 axum-connect
crates, one for code-gen and one for runtime use.
Because of how prost works, you'll also need to add it to your own project.
You'll obviously also need axum
and tokio
.
# Note: axum-connect-build will fetch `protoc` for you.
cargo add --build axum-connect-build
cargo add axum-connect prost axum
cargo add tokio --features full
Start by creating the obligatory 'hello world' proto service definition.
proto/hello.proto
syntax = "proto3";
package hello;
message HelloRequest { string name = 1; }
message HelloResponse { string message = 1; }
service HelloWorldService {
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
Use the axum_connect_codegen
crate to generate Rust code from the proto IDL.
Currently all codegen is done by having the proto files locally on-disk, and using a
build.rs
file. Someday I hope to support more of Buf's idioms like Remote Code Gen.
build.rs
use axum_connect_build::{axum_connect_codegen, AxumConnectGenSettings};
fn main() {
// This helper will use `proto` as the import path, and globs all .proto
// files in the `proto` directory.
//
// Note that you might need to re-save the `build.rs` file after updating
// a proto file to get rust-analyzer to pickup the change. I haven't put
// time into looking for a fix to that yet.
let settings = AxumConnectGenSettings::from_directory_recursive("proto")
.expect("failed to glob proto files");
axum_connect_codegen(settings).unwrap();
}
With the boring stuff out of the way, let's implement our service using Axum!
use async_stream::stream;
use axum::{extract::Host, Router};
use axum_connect::{futures::Stream, prelude::*};
use proto::hello::*;
mod proto {
pub mod hello {
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
}
}
#[tokio::main]
async fn main() {
// Build our application with a route. Note the `rpc` method which was added by `axum-connect`.
// It expect a service method handler, wrapped in it's respective type. The handler (below) is
// just a normal Rust function. Just like Axum, it also supports extractors!
let app = Router::new()
.rpc(HelloWorldService::say_hello(say_hello_success))
.rpc(HelloWorldService::say_hello_stream(say_hello_stream));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3030")
.await
.unwrap();
println!("listening on http://{:?}", listener.local_addr());
axum::serve(listener, app).await.unwrap();
}
async fn say_hello_success(
Host(host): Host,
request: HelloRequest
) -> HelloResponse {
HelloResponse {
message: format!(
"Hello {}! You're addressing the hostname: {}.",
request.name, host
),
}
}
To test it out, try hitting the endpoint manually.
curl --location 'http://localhost:3030/hello.HelloWorldService/SayHello' \
--header 'Content-Type: application/json' \
--data '{ "name": "Alec" }'
From here you can stand up a connect-web
TypeScript/Go project to call your
API with end-to-end typed RPCs.
Both the request and response types are derived in axum-connect
. This might
seem redundant at first.
Let's go over the easier one first, RpcIntoResponse
. Connect RPCs are not
arbitrary HTML requests, they cannot return arbitrary HTML responses. For
example, streaming responses MUST return an HTTP 200 regardless of error state.
Keeping with Axum's (fantastic) paradigm, that is enforced by the type system.
RPC handlers may not return arbitrary HTML, but instead must return something
that axum-connect
knows how to turn into a valid Connect response.
Somewhat less intuitively, axum-connect
derives RpcFromRequestParts
, which
is almost identical to Axum's FromRequestParts
. Importantly though,
FromRequestParts
can return back an error of arbitrary HTML responses, which
is a problem for the same reason.
Axum also allows a FromRequest
to occupy the last parameter in a handler which
consumed the remainder of the HTTP request (including the body). axum-connect
needs to handle the request input itself, so there is no equivalent for RPCs
handlers.
RpcFromRequestParts
RpcIntoError
, but I haven't fully
thought this problem through. I just know that having to specific both a
FromRequestParts
and an RpcFromRequestParts
on custom types is a PITA.connect-web
and...buf.build
to support remote codegen and streamlined proto handlingconnect-web
picking up support for the sameaxum-connect
highly focused. Good at what it does and
nothing else.The installed version of protoc
can be configured in the
AxumConnectGenSettings
if you need/wish to do so. Setting the value to None
will disable the download entirely.
Prost stopped shipping protoc
binaries (a decision I disagree with) so
axum-connect-build
internally uses
protoc-fetcher to download and
resolve a copy of protoc
. This is far more turnkey than forcing every build
environment (often Heroku and/or Docker) to have a recent protoc
binary
pre-installed. This behavior can be disabled if you disagree, or if you need to
comply with corporate policy, or your build environment is offline.
I would someday like to replace all of it with a new 'lean and mean' protoc library for the Rust community. One with a built-in parser, that supports only the latest proto3 syntax as well as the canonical JSON serialization format and explicitly doesn't support many of the rarely used features. But that day is not today.
Axum-Connect is dual licensed (at your option)