#protobuf #middleware #friends #tower #serialization #json #tower-web

tower-web-protobuf

Middleware and friends for tower-web that help with Protobufs

1 unstable release

0.1.0 Jun 30, 2020

#13 in #friends

MIT license

33KB
568 lines

Tower Web Protobuf

Protobuf middleware and friends for tower-web.

Build Status Docs Coverage Status License: MIT

A Quick Example

extern crate prost;
#[macro_use]
extern crate tower_web;
extern crate tower_web_protobuf;

use prost::Message;
use tower_web::ServiceBuilder;
use tower_web_protobuf::{Proto, ProtobufMiddleware};

// Messages:
#[derive(Clone, PartialEq, Message, Serialize, Deserialize)]
pub struct Hello {
    #[prost(string, tag = "1")]
    pub name: String,
}

#[derive(Clone, Debug)]
struct HelloWorld;

type In<M> = Proto<M>;
type Out<M> = Result<Proto<M>, ()>;

impl_web! {
    impl HelloWorld {
        // You can provide this endpoint with either a Protobuf or JSON
        // encoded body. The endpoint will respond with a Protobuf or JSON
        // encoded `Hello` message depending on the Accept header.
        #[get("/hello/")]
        fn hello(&self, hello: In<Hello>) -> Out<Hello> {
            Ok(hello)
        }
    }
}

pub fn main() {
    let addr = "127.0.0.1:8080".parse().expect("Invalid address");

    ServiceBuilder::new()
        .resource(HelloWorld)
        // (true, true) permits JSON decoding (requests) and encoding
        // (responses)
        .middleware(ProtobufMiddleware::new(true, true))
        .run(&addr)
        .unwrap();
}

How does it work?

tower-web-protobuf provides a generic Proto type that can wrap any Protobuf message struct (really anything that implements prost's Message type - unfortunately this does mean that you're forced to use prost with this crate).

Proto types have a custom Extract and Response implementation that handle serializing and deserializing protobuf messages. By default these implementations will try to parse all incoming messages as protobufs and will encode all responses as protobufs, regardless of the content-type and accept headers that were sent with the corresponding request.

In order to actually abide by the headers, we need to introduce some middleware. Currently, the middleware allows you to enable/disable JSON support for encoding/decoding (shown above).

To be clear, it's possible to just use the Proto type and to ignore the middlware entirely. If your use case only involves sending and receiving protobufs (no JSON), this may even be ideal.

There's also a plaintext serialization/deserialization option based on serde-plain.

Usage

First, add tower-web-protobuf to your Cargo.toml:

[dependencies]
tower-web-protobuf = { git = "https://github.com/rrbutani/tower-web-protobuf" }

In order to use the Proto type, your message structs must implement the traits in MessagePlus. This means implementing prost's Message and serde's DeserializeOwned and Serialize. For most use cases, this means using prost and adding #[derive] attributes for serde's traits.

With prost, there are two main ways to do this: add a build.rs file (like this one) that uses prost_build or add #[derive]s on your existing structs to turn them into protobuf messages.

If you go the first route, make sure you add something like the following to derive serde's traits for your messages:

    prost_build::Config::new()
        .type_attribute(".", "#[derive(Serialize, Deserialize)]")
        .type_attribute(".", "#[serde(rename_all = \"snake_case\")]")
        .type_attribute(".", "#[serde(deny_unknown_fields)]")
        .compile_protos(
            proto_files_in_dir(MESSAGE_DIR).as_slice(),
            &[MESSAGE_DIR.into()],
        )
        .unwrap();

build.rs has the above code in context and the endpoints example has a sample usage.

An example of the other way is shown in example above which is identical to the identity example. The key bit is the #[derive] on the message.

Finally, wrap your message types with Proto (or use the In and Out type aliases shown above) and add the ProtobufMiddleware if you so desire. It might just work.

Error handling isn't great and logging for serialization/deserialization errors isn't quite there yet either. As is probably obvious, if you're planning to use this for important things expect trouble.

<todo: replace docs with docs.rs link, add license from crates.io, add crates.io link)> <todo: link all structs to the official docs.rs doc pages>

Dependencies

~22–30MB
~498K SLoC