Actix::From(Rocket)

Bradley Noyes published on
10 min, 1952 words

Actix::From(Rocket)🔗

I was inspired by Nick’s post to migrate my code from Rocket to Actix. I have also been nagged by recent struggles with the nightly compiler and its compatibility with all the other crates that I use and other the platforms that I use; my code runs on 32/64 bit arm and 32/64 bit x86. When I started using Rocket around a year ago, the nightly compiler was just as stable as the stable compiler, but things have changed as rust is a growing eco system.

The above post stated how far actix has come regarding ergonomics and that migrating from Rocket to Actix was painless. I had a little bit of a struggle, and I think is worthwhile sharing for anyone else wishing to make this move.

For me, once the differences below were addressed, porting the code was relatively straightforward. The vast majority of my code remained the same.

Ergonomics🔗

Rocket is the clear winner in ergonomics, as it is using a lot of nightly features. While Actix does not win in ergonomics, it does compile with stable. Give the fact that rust is a relatively new language its great to have multiple choices of frameworks. By the time the nightly features used by rocket become stabilized, Actix will likely become more ergonomic.

It’s also nice to see Gotham will continue as a third option.

State🔗

State in Rocket🔗

In Rocket you can have state for your endpoints using their Managed State feature. In Actix you can do the same. However, in one way they differ is that in Rocket you can use multiple States by added to the function signature.

    #![feature(plugin)]
    #![plugin(rocket_codegen)]
    
    extern crate rocket;
    
    use rocket::State;
    
    #[derive(Debug)]
    struct StateOne {
        one: u32
    }
    
    #[derive(Debug)]
    struct StateTwo {
        two: u32
    }
    
    #[get("/states")]
    fn states( state_data_one: State<StateOne>, state_data_two: State<StateTwo> )
    {
        let _state_one : &StateOne = state_data_one.inner();
        let _state_two : &StateTwo = state_data_two.inner();
        println!("state_date_one: {:?}", _state_one);
        println!("state_date_two: {:?}", _state_two);
    }
    
    fn main() {
        rocket::ignite()
            .manage(StateOne { one: 1 })
            .manage(StateTwo { two: 2 }) 
            .mount("/", routes![states]).launch();
    }

Possible runtime error In Rockets State🔗

Side note: In Rocket you can specify multiple states, but if you omit to manage one of those states in the ignite call, you’ll get a runtime error. It would be awesome to get a compile error rather than a runtime error (not a panic).

    🚀  Rocket has launched from http://localhost:8000
    GET /states:
        => Matched: GET /states
        => Error: Attempted to retrieve unmanaged state!
        => Outcome: Failure
        => Warning: Responding with 500 Internal Server Error catcher.
        => Response succeeded.

State in Actix🔗

Actix has the ability to hold state too, although actix can only handle one state per endpoint. Bummer.

    extern crate actix_web;
    use actix_web::{server, http, App, HttpRequest, HttpResponse};

    #[derive(Debug)]
    struct StateOne {
        one: u32
    }
    
    #[derive(Debug)]
    struct StateTwo {
        two: u32
    }
    
    fn states(req: HttpRequest<StateOne>) -> HttpResponse {
        let state_one = req.state();
        println!("state_one: {:?}", state_one);
        HttpResponse::Ok().into()
    }
    
    fn main() {
        server::new( move || {
            vec![
                App::with_state( StateOne { one : 1 } )
                        .prefix("/")
                        .resource("/states", |r| r.method(http::Method::GET).with(states))
                        .boxed(),
            ]
        }).bind("127.0.0.1:8088")
            .unwrap()
            .run();
    }

But wait! there’s is a workaround, your managed state can be a tuple composed of multiple structs for states. So we’ll create a tuple composed of StateOne and StateTwo, then pass that into with_state.

Our new App lunch line looks like:

            App::with_state( (StateOne { one : 1 }, StateTwo {two:2}) )
                    .prefix("/")
                    .resource("/states", |r| r.method(http::Method::GET).with(states))
                    .boxed(),

And the new function looks like:

    fn states(req: HttpRequest<(StateOne, StateTwo)>) -> HttpResponse {
        let (state_one, state_two) = req.state();
        println!("state_one: {:?}", state_one);
        println!("state_two: {:?}", state_two);
        HttpResponse::Ok().into()
    }

And it all works great! As a plus, with actix, if you omit a state in your tuple that you pass to with_state you’ll get a compile error, unlike with Rocket.

What about URL Dispatch?🔗

URL Dispatch in rocket🔗

Url Dispatch in rocket is straightforward, take our previous function and just add a parameter to its function signature and edit the function annotation,

    #[get("/<param>/states")]
    fn states( state_data_one: State<StateOne>, state_data_two: State<StateTwo>, param: String  )
    {
        let _state_one : &StateOne = state_data_one.inner();
        let _state_two : &StateTwo = state_data_two.inner();
        println!("state_date_one: {:?}", _state_one);
        println!("state_date_two: {:?}", _state_two);
        println!("url dispatch param: {:?}", param);
    }

Now when you request curl http://localhost:8000/my_param_string/states you’ll get the my_param_string as the dispatch variable. Simple.

URL Dispatch in Actix🔗

Url Dispatch in Actix is also relatively simple, I recommend reading their documentation after you read this post. You need to call the Actix app with the URL Dispatch parameter encoded in the endpoint,

    App::with_state( (StateOne { one : 1 }, StateTwo {two:2}) )
                    .prefix("/")
                    .resource("/{param}/states", |r| r.method(http::Method::GET).with(states))
                    .boxed(),

The edited function to handle the endpoint is below. Of note is the info parameter which you can use to obtain the parameter in the URL, param in this case.

    use actix_web::Path; // don't forget that you need the Path module
    
    fn states(req: HttpRequest<(StateOne, StateTwo)>, info: Path<String> ) -> HttpResponse {
        let (state_one, state_two) = req.state();
        println!("state_one: {:?}", state_one);
        println!("state_two: {:?}", state_two);
        println!("param: {:?}", info.to_string());
        HttpResponse::Ok().into()
    }

URL Dispatch + State🔗

The hard part, for me, was to combine Url dispatch with state. if you just try to change the endpoint function (and include the actix_web::Path module), you’ll get a compile error.

    error[E0593]: function is expected to take 1 argument, but it takes 2 arguments

      --> src/main.rs:31:82
       |
    17 | fn states(req: HttpRequest<(StateOne, StateTwo)>, info: Path<String> ) -> HttpResponse {
       | -------------------------------------------------------------------------------------- takes 2 arguments
    ...
    31 |                     .resource("/{param}/states", |r| r.method(http::Method::GET).with(states))
       |                                                                                  ^^^^ expected function that takes 1 argument
    
    error: aborting due to previous error
    
    For more information about this error, try `rustc --explain E0593`.
    error: Could not compile `actix_main`.
    
    To learn more, run the command again with --verbose.

Darn.

The problem is that the with function doesn’t expect a second parameter. The proper way is to use the with2 function. So our new App function call is below:

     App::with_state( (StateOne { one : 1 }, StateTwo {two:2}) )
                        .prefix("/")
                        .resource("/{param}/states", |r| r.method(http::Method::GET).with2(states))
                        .boxed(),

It took a little work to find this function, I eventually saw it in the github issue #70 which is a great example of actix contributors working together.

Note: with2 and with3 are planned for removal for better ergonomics.

Query strings🔗

Query String in rocket🔗

I also had struggled with the Actix query strings. Here’s how rocket handles it. First you need to add #![feature(custom_derive)] at the top of your source file. Then you need to declare your struct to hold the query parameter,

    #[derive(Debug, FromForm)]
    struct QueryParam {
        q: Option<String>
    }

Next You need to edit the function annotation to tell rocket to expect a query parameter and edit the function signature. That’s it.

    #[get("/<param>/states?<query>")]
    fn states( state_data_one: State<StateOne>, state_data_two: State<StateTwo>, param: String, query: QueryParam  )
    {
        let _state_one : &StateOne = state_data_one.inner();
        let _state_two : &StateTwo = state_data_two.inner();
        println!("state_date_one: {:?}", _state_one);
        println!("state_date_two: {:?}", _state_two);
        println!("url dispatch param: {:?}", param);
        println!("url query param: {:?}", query);
    }

The downside, as far as I can tell, is that now when you request a url you must include the ? in the query, i.e. curl http://localhost:8000/my_param_string/states\?q\=query_param_string

If you omit the ? then you’ll get a 404 error, i.e. curl http://localhost:8000/my_param_string/states. You can work around this by creating a function that specifically handles just the URL without the ?, so its annotation would #[get("/<param>/states")].

Query String in Actix🔗

In actix it’s a little harder. You can easily get a string of the query param, by calling query_string on the HttpRequest struct but we don’t want a string, we want a struct to make our code type-safe. To get the struct form the query string, we need to import serde_urlencoded and serve.

At the top of the source file, add the following.

    #[macro_use] extern crate serde_derive;
    extern crate serde_urlencoded;

The struct that will represent the query parameters will need to derive deserialize,

    #[derive(Debug, Deserialize)]
    struct QueryParam {
        q: Option<String>
    }

And then, in the function definition, use serde_urlencoded::from_str to deserialize the url parameters.

    fn states(req: HttpRequest<(StateOne, StateTwo)>, info: Path<String> ) -> HttpResponse {
        let (state_one, state_two) = req.state();
        if let Ok(query_param) = serde_urlencoded::from_str::< QueryParam >(req.query_string())
        {
           println!("state_one: {:?}", state_one);
           println!("state_two: {:?}", state_two);
           println!("param: {:?}", info.to_string());
           println!("query param: {:?}", query_param);
        }
        HttpResponse::Ok().into()
    }

Or use extractors🔗

Alternatively you can you the Query extractor, which is actually cleaner then the above code. There’s some nice documentation about extractors in Actix. This method doesn’t require serde_urlencoded and keeps the type-safety.

    fn states2(req: HttpRequest<(StateOne, StateTwo)>, info: Query<QueryParam> ) -> HttpResponse {
        let (state_one, state_two) = req.state();
        println!("state_one: {:?}", state_one);
        println!("state_two: {:?}", state_two);
        println!("query param: {:?}", info.q);
        HttpResponse::Ok().into()
    }

POSTing🔗

Again, both Rocket and Actix capture data from an HTTP POST request.

POST in Rocket🔗

Capture post data in rocket is very simple. You need to add serde as a dependency so you can deserialize the POST data into a struct. You also need to add rocket_contrib as a dependency. To describe the POST data you want to accept, you just create a struct which derives Deserialize.

    #[derive(Debug, Serialize, Deserialize)]
    struct PostData {
        q: String
    }

Then you need to tell Rocket to accept POST for a given endpoint using an annotation (e.g. #post[...]) for the function that handles the endpoint.

    #[post("/<param>/states", format = "application/json", data = "<post_data>")]
    fn post_states( state_data_one: State<StateOne>, state_data_two: State<StateTwo>, param: String, post_data: Json<PostData> )
    {
        let state_one : &StateOne = state_data_one.inner();
        let state_two : &StateTwo = state_data_two.inner();
        println!("state_date_one: {:?}", state_one);
        println!("state_date_two: {:?}", state_two);
        println!("url dispatch param: {:?}", param);
        println!("url post data: {:?}", post_data);
    }

Lastly, tell rocket to use this function, and you’re done!

    rocket::ignite()
        .manage(StateOne { one: 1 })
        .manage(StateTwo { two: 2 })
        .mount("/", routes![states, post_states]).launch();

Actix POST🔗

Receiving POST data in actix is, again, just a little more work, but still very simple. Just like in the Rocket example, you need a striation which will hold the POST data, and that struct will need to derive the Deserialize trait from serde.

    #[derive(Debug, Serialize, Deserialize)]
    struct PostData {
        q: String
    }

To capture the post data with your endpoint function, the first argument needs to be a tuple comprised of the POST data, and HTTP request.

    fn post_states((post_data,  req): (Json<PostData>, HttpRequest<(StateOne, StateTwo)>), info: Path<String> ) -> HttpResponse {
        let (state_one, state_two) = req.state();
        println!("state_one: {:?}", state_one);
        println!("state_two: {:?}", state_two);
        println!("param: {:?}", info.to_string());
        println!("POST data: {:?}", post_data);
        HttpResponse::Ok().into()
    }

Lastly, you add that endpoint as a resource to the Actix app. If your endpoint is going to take in URL dispatch, you need to use the with2 function, if not you need to use the with function.

    server::new( move || {
        vec![
            App::with_state( (StateOne { one : 1 }, StateTwo {two:2}) )
                    .prefix("/")
                    .resource("/{param}/states", |r| r.method(http::Method::GET).with2(states))
                    .resource("/{param}/states", |r| r.method(http::Method::POST).with2(post_states))
                    .boxed(),
        ]
    }).bind("127.0.0.1:8088")
        .unwrap()
        .run();

All done.

Example code🔗

My example code can be found in my GitLab account:

Rocket🔗

https://gitlab.com/noyez_examples/actix_example

Actix🔗

https://gitlab.com/noyez_examples/rocket_example

Error Handling🔗

As a side node, another pain point I had when porting from Rocket to Actix is error handling. I was using error_chain, which would give me compiler errors that Error was not Sync, this appears to be known issue with error_chain. I had to move over to Failure.