My first PR for a Rust project
I’ve been doing a lot of Rust recently together with my colleague and friend Preethi Kumar. If you’ve been following us over at Adventures in Rust, you’ll know we’re building a toy HTTP server as our learning project.
While getting help from the #rust-beginners IRC channel, which is excellent for newbies btw, I came across people talking about this Rust HTTP client project, reqwest.
Looked at the issues posted at the repo and immediately felt motivated to solve atleast one of those issues. I tried to find the easiest one based on the issue title and this one seemed doable.
Checked with the maintainer Sean McArthur if I can work on this and he gave me the green signal.
The issue was about filtering certain sensitive headers before redirecting to a location on a different domain than the original domain (eg. redirecting from google.com/mail to some-shady-website.com/login. You don’t want your cookies to be available to that shady website now, do you?).
I then forked and cloned the repo and started going through the code. The codebase was split mainly into the files client.rs
, response.rs
, body.rs
and redirect.rs
.
The client.rs
file had the meat of the project and found the place where I might need to make the additions needed for this feature to work.
Ran the tests using cargo test
before I proceeded any further and they all reported green.
The first step here is to figure out if the redirect was happening between two different domains (or hosts). At this place in the source we have access to both the previous and current URLs.
Was initially dreading that I might have to manually parse the host portion of the URLs but then figured out that the variables I had access to were of type hyper::Url
which has this method fn host(&self) -> Option<Host<&str>>
that gives me what I needed.
What type are you?
Figuring out the type of a value you have should be one of the easiest things in Rust. But since I’m rather new to Rust and the reqwest project, I found it a little difficult to figure out what type the url
value was. They had used some generics as well which did not help me comprehend quickly.
So I let the compiler tell me what I needed. How? Simply call a random function on the value, url.foo()
for instance! And the compiler will gladly tell you the type you’re dealing with.
This is a handy trick that we can use when you’re in the middle of a large project trying to figure out types! :)
The Meat
I had the current and previous hosts now. All I had to do then was compare them and if they’re different, remove some of the headers before following the redirect. Here’s how I did it —
A nice benefit of having types
I’ve always thought having a static type system would not be fun at all. But Rust is slowly changing my opinion.
The hyper
crate has taken an approach of creating types for almost all HTTP headers. This has let them specify exactly the values each header can take and their format, and the way they would be serialised in the HTTP packet.
This provides a lot of safety and reduces a whole class of problems all together.
They also have a way to create use headers that do not have a type using the set_raw()
and other such functions.
The turbofish
This weird syntax you see, ::<Cookie>
, you see up there used on the headers.remove()
function is called turbofish in Rust.
Like I described before, the hyper
crate has typed headers. The crate has a struct Headers
which is a wrapper around a HashMap
— basically a collection of Header
values.
This struct has a function remove()
, but when you call headers.remove()
, the compiler has no way of knowing which type of Header
you’re trying to remove from the map.
From the source, we see that this — pub fn remove<H: Header + HeaderFormat>(&mut self) -> bool
is the signature of the function. We can see that they’ve used a generic type H
in there. But it does not take any arguments that clarify what type H
takes.
In such cases, there is an ambiguity and we need to help the compiler along. And that’s where the turbofish comes in. So, when you do something like — headers.remove::<Cookie>()
, you’re basically specialising the generic type H
as Cookie
.
Had a tough time understanding this and huge thanks to user kmc
on #rust-beginners for the help! ❤
Tests & Wrap up
The project has a lot of integration tests and unit tests. For integration tests, they use a custom mock server which was pretty interesting and intuitive to use. But in our case we needed to test a situation where the redirect was happening between two different hosts. It looked like I had to make some changes to the server for this. I checked with the maintainer and he was okay with writing unit tests for the moment.
So that’s what I did. And btw, these were my very first tests in Rust. Yay! :D
That’s the story of how I got my very first open source contribution done in Rust. Looking forward to contributing more.
Preethi Kumar and I have been looking at crates.io lately and hoping to get involved in that effort soon!
I’m sure you’re curious about what’s going on with our rust-httpd project. It’s been a busy few weeks for us here as our office, Spritle, moved to a new space **BIG SMILES**. We managed to put in some time to polish our CGI server implementation a bit. Hoping to spend more time next week!
Do follow our work at Adventures in Rust.