What's New Since Rustful 0.1.0?

16 Jul 2015 — Permalink

Rustful has, at the time of writing this, recently reached version 0.3.0, so I thought that it was time for a highlight of what has changed since it reached version 0.1.0.

I would first like to say that seeing the interest, that followed the release and the feedback I have gotten makes me really happy. Thank you all for considering, trying or using Rustful in your projects!

With that said, let’s look at what have been changed and added since version 0.1.0.

Improved Response Writing

We’ll begin with what is probably the biggest change. I estimate that approximately 100% of all projects that upgrade to 0.3.0 will break because of this, but I do also believe that it’s for the better. The response handling API has gone through a few changes that has touched almost everything to make it both more ergonomic and, above all, more powerful.

I’ll have to make a couple of sub headers here. That’s how big this is!

Response Writers

The options when writing response bodies have been quite limited, so far. You could write an empty response, by doing nothing at all, or write a chunked response, by calling into_writer on your Response instance1. The new and improved system divides response writing further, into three “modes” or “phases”.

You begin with the usual Response instance, where you can set headers and status. The Response has now also gained a one-shot send method that is used to send all the data to the client at once, and then end the response. This results in a body with a fixed size, which is usually what you want.

The other two modes, called Chunked and Raw, are for when buffering and sending everything at once becomes unpractical or impossible. They will let you stream the response, by sending one piece at the time, but they will do it in completely different ways. Chunked and Raw are created by calling into_chunked or into_raw, respectively, on a Response instance.

Chunked, which is basically the good old ResponseWriter, doesn’t need to know how much data you want to send. Each call to send will create a chunk that is annotated with its own size and it’s up to the client to piece everything together. However, this causes an overhead, so it’s usually not recommended if there’s a faster way.

Raw is more like Response in the way that you have to decide how long the response should be before writing it, but it’s still written piece by piece. This results in a fixed size response, but you don’t have to buffer everything in advance. So, why would you not always use this? Well, the fact that it’s up to the programmer to make sure ends meet comes with the risk of ends not meeting. This makes into_raw unsafe to call and forces us to bypass any eventual response filters.

Ok, but, what is Raw good for if it’s both unsafe and comes without filters? Its ability to send big pieces of data without the overhead of chunking, together with its guarantee that the data will be unfiltered makes it very useful for sending raw data files, like images, binaries, CSS documents, and so on. You will always be sure that the file is intact and you don’t have to buffer gigabytes of data.

General API Polishing

The general response API has also been tweaked to give better access to the headers, the status code and the filter storage. There are now both mutable and immutable access, as well as the ability to handle raw headers.

An other thing that has changed, and this happened quite early, is that send will ignore any errors that occurs. Why? Because most of the errors that would occur are network errors and those are hard or impossible to recover from. The few errors that would be possible to recover from would mainly be caused by failing filters and those can still be collected using try_send.

It may sometimes be better to just let the handler do its thing in the bliss of ignorance if we can’t handle non-fatal errors in a meaningful way, except ignoring them. It shouldn’t be a problem, as I see it, as long as it doesn’t break things.

Do you have an other view? A better solution to the problem? Please, enlighten me!

More Info

You can read more about this and look at some silly and not so realistic examples in the documentation.

Globally Accessible Storage

This change isn’t big in the same way as the previous one, but rather in that it’s the most requested feature and also the most problem causing feature. The concept of a shared storage was previously introduced in Rustful as something for data caches, but was then removed because of coherence problems with generic functions. This is thereby the second attempt at this and hopefully the way to go (with reservation for possible API changes).

This introduces the Global type that is based on the AnyMap, which works like a hash map, but with types as keys. This lets you store anything that implements Any + Send + Sync, so you can simply make a type that contains what you want to store and put it in there.

The global storage is immutable once the server has started, to allow lock free sharing between threads, and content can always be selectively locked with some kind of mutex if mutability is necessary. It’s available to handlers in the Context, where it’s accessed through the global field, and also available to all filters.

More information about this can be found in the documentation for the context module and the Global type.

Hyperlinks are probably one of the most important pieces in the REST puzzle and they were maybe not impossible, but surely tedious to gather and send to the client, until recently. The router interface has gained support for providing hyperlinks that will be forwarded to the handler through a hypermedia field in Context. This means that there’s no need to hardcode anything and you can reuse the same code for encoding the hyperlinks everywhere.

Basic support for simple hyperlinks have been implemented in TreeRouter and it can be activated by setting the field find_hyperlinks to true. It’s deactivated by default because of a possible increase in search overhead, caused by the the need to search more paths.

I’m also planning to add support for more hypermedia, like descriptions, but I’m still not sure how to do it in a good way. I would love to hear your thought on this and what you would like to see support for. Anyway, this feature is still very young, so expect it to move around a bit in future 0.X versions.

You can read some more about hyperlinks in the sparse documentation for Hypermedia and Endpoint.

File Loader

It’s common to send plain files, like images, binaries or scripts, to the client and I thought it was common enough to put it in the library. This should spare you the time spent on tedious boiler plate code and boring content type mapping. This addition introduces a module, called file, where you will find the type Loader the function ext_to_mime (that converts a file extension to a matching Mime).

The Loader takes a path to a file and a Response, assigns a value to the content-type header and streams the file to the client. It can be as simple as this:

if let Some(file) = context.variables.get("file") {
    //Make a full path from the file name and send it
    let path = format!("resources/{}", file);
    let res = Loader::new().send_file(&path, response);

    //Handle eventual error in `res`...
}

The content type is guessed, based on the file extension, and will default to application/octet-stream if it’s unknown. The mapping is done using data from Apache that is parsed and transformed into a static hash map at compile time. A static hash map is used to make searching fast and to avoid the overhead of a dynamic hash map.

You can find a couple of more details in the documentation.

Updated content_type! Macro

The content_type! macro was originally written to simplify a much more complex syntax, but the reasons for using it instead of what it expands to became fewer after the switch to Hyper and when it was rewritten as a macro rule. This has now changed and content_type! has become both “smarter” and has regained its original syntax.

The syntax is meant to mimic the content type header, which is main_type/sub_type; param1=value1; .... The reason for this is both that the syntax is quite well established and that it’s a relatively nice syntax (if we ignore that it’s not so Rust-like). It may look something like this, when in use:

#[macro_use]
let server_result = Server {
    handlers: router,
    host: 8080.into(),
    content_type: content_type!(Application / Json; Charset = Utf8),
    ..Server::default()
}.run();

An other change, that you may notice if you have used this before, is that it now can take identifiers. The old string parsing is still there, but you can now opt for the builtin enum variants to skip the parsing step (even if it’s small). This makes it possible to mix and match:

content_type!(Application / "octet-stream"; "type" = "image/gif")

You are also not required to import the enums or their variants to use this macro, since they are instead implicitly imported inside it. This is considered a bit of a test to see if it’s too much magic or not, and I am willing to change it if it would prove to be more harmful than good. However, I think it will be just fine.

Want to see some more? Take a look at the documentation.

A More Convenient insert_routes! Macro

The insert_routes! macro has also received some changes, but not as dramatic as the previously mentioned ones. The first change is that handlers at local root paths (to /) no longer requires a path fragment when they are assigned. This changes the following:

insert_routes! {
    &mut my_router => {
        "/" => Get: Api(Some(list_all)),
        "/" => Post: Api(Some(store_item)),
        "/" => Delete: Api(Some(clear_all)),
        "/" => Options: Api(None),
        ":id" => {
            "/" => Get: Api(Some(get_item)),
            "/" => Patch: Api(Some(edit_item)),
            "/" => Delete: Api(Some(delete_item)),
            "/" => Options: Api(None)
        }
    }
}

to this:

insert_routes! {
    &mut my_router => {
        Get: Api(Some(list_all)),
        Post: Api(Some(store_item)),
        Delete: Api(Some(clear_all)),
        Options: Api(None),
        ":id" => {
            Get: Api(Some(get_item)),
            Patch: Api(Some(edit_item)),
            Delete: Api(Some(delete_item)),
            Options: Api(None)
        }
    }
}

The former example is still valid and may be preferred by some, but it’s now also possible to skip those "/" => (or "" =>) parts and avoid even more typos than before.

The second change is that the HTTP methods are implicitly included from within the macro, just like with content_type!. It’s just for the sake of convenience.

The last change is that a trailing , is no longer a syntax error, so doing this is totally fine:

insert_routes! {
    &mut my_router => {
        Get: Api(Some(list_all)),
        Post: Api(Some(store_item)),
        Delete: Api(Some(clear_all)),
        Options: Api(None),
        ":id" => {
            Get: Api(Some(get_item)),
            Patch: Api(Some(edit_item)),
            Delete: Api(Some(delete_item)),
            Options: Api(None), // <-- See here...
        }, // <-- ...and here
    }
}

More info about this macro can be found in the documentation.

Other Changes

There have, of course, also been some other changes, like more elaborate documentation, optional SSL and JSON body decoding, that are not explained here and you can always take a look at the documentation, merged pull requests or the commit history if you are interested in them. However, I do have two honorable mentions that I would like to add. These are not really code changes, but they may still be interesting.

The first is that Rustful is now tested for Windows compatibility, using AppVeyor. This means that Windows users should be able to use it just fine, after some tinkering with (or after disabling) OpenSSL. Please get in touch if you happen do find some hidden Windows issue. It’s very much appreciated!

The second is that Rustful uses Homu for merging and testing pull requests. What this means is that a pull request will be merged in a separate branch and tested again before the actual merge with the master branch. This is to prevent accidental breaks from changes made after a pull request has passed its usual tests, when multiple pull requests are about to be merged. This does unfortunately not use AppVeyor, but there’s still a test after the merge for when things goes wrong.

What’s Next?

The next big thing, aside from the usual fixing, polishing and documentation work, will probably be to implement support for multipart/form-data. My hope is that this will be seamlessly available through the body field in Context as yet an other parsing method, but how this should be done is yet to be decided. It’s still on the idea table and I don’t yet know if it will fit in version 0.3, but more about this will be available through issue #49 and any related, upcoming pull requests.

I would also like to extend the metadata support, as mentioned before, but how and with what is still not decided. Though, I’m quite sure that I will add support for descriptions and/or titles in some way.

Some Final Words

That’s all for this time. These change reports will probably appear sporadically in the future, whenever I feel like it’s time. Possibly once every second month, if the pace continues like this.

You can find more information about Rustful on crates.io and in the repository. I have also opened a Gitter chat for various discussions and questions. I’m dropping by from time to time, but your messages are persistent, so don’t worry if I’m away or if you get there first and it’s all empty.

Thank you for reading, and have fun with these new features!

  1. You could also maybe convince the response to write a stream with a fixed size, but that required knowledge of the internals of both Rustful and Hyper.