brson/httptest

Let's make a web service and client in Rust

So I'm working on this project Rust regression testing

Let's make a web service and client in Rust

So I'm working on this project Crater for doing Rust regression testing. Looking into the feasibility of writing parts in Rust. I need an HTTP server that speaks JSON, along with a corresponding client. I don't see a lot of docs on how to do this so I'm recording my investigation.

I'm going to use Iron and Hyper, neither of which I have experience with.

Each commit in this repo corresponds with a chapter, so follow along if you want.

Edit: Some inaccuracies within! Thanks /r/rust!

1. Preparing to serve some JSON

I start by asking Cargo to give me a new executable project called 'httptest'. Passing --bin says to create a source file in src/main.rs that will be compiled to an application.

I'll use Iron for the server, so following the instructions in their docs add the following to my Cargo.toml file.

Then into main.rs I just copy their example.

And type cargo build.

Pure success so far. Damn, Rust is smooth. Let's try running the server with cargo run.

I sit here waiting a while expecting it to print "On 3000" but it never does. Cargo must be capturing output. Let's see if we're serving something.

Oh, that's super cool. We know how to build a web server now. Good starting point.

2. Serving a struct as JSON

Is rustc-serialize still the easiest way to convert to and from JSON? Maybe I should use serde, but then you really want serde_macros, but that only works on Rust nightlies. Should I just use nightlies? Nobody else is going to need to use this.

Let's just go with the tried-and-true rustc-serialize. Now my Cargo.toml 'dependencies' section looks like the following.

Based on the rustc-serialize docs I update main.rs to look like this:

And then run cargo build.

Lot's of new dependencies now. But an error. Response::with has this definition:

I don't know what a Modifier is. Some docs don't help. I don't know what to do here but I notice that the original example passed a tuple to Response::with whereas my update treated Response::with as taking two parameters. It seems a tuple is a Modifier.

Add the tuple to Ok(Response::with((status::Ok, payload))), execute cargo run, curl some JSON.

Blam! We're sending JSON. Time for another break.

3. Routes

Next I want to POST some JSON, but before I do that I need a proper URL to post to, so I guess I need to learn how to set up routes.

I look at the Iron docs and don't see anything obvious in the main text, but there's a crate called router that might be interesting.

The module docs are "Router provides fast and flexible routing for Iron", but not much else. How do I use a Router?! After I lot of sleuthing I discover an example. OK, let's try to adapt that to our evolving experiment.

I add router = "*" to my Cargo.toml [dependencies] section, and begin writing. The following is what I come up with before getting stuck reading the POST data.

This uses Router to control handler dispatch. It builds and still responds to curl http://localhost:3000, but the handler for the /set route is yet unimplemented.

Now to read the POST body into a string. The docs for Request say the field body is an iterator, so we just need to collect that iterator into a string.

I first try let payload = request.body.read_to_string(); because I know it used to work.

It does not work.

I throw my hands up in disgust. 'Why does this method no longer exist? The Rust team is always playing tricks on us!' Then I notice the compiler has - at some length - explained that the method does exist and that I should import std::io::Read.

I add the import and discover that read_to_string behaves differently than I thought.

Ok, yeah the signature of Read::read_to_string is now fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error>, so that the buffer is supplied and errors handled. Rewrite the set_greeting method.

Let's run this and give it a curl.

Oh, Rust. You're just too bad.

4. Mutation

Hey, I know all those .unwrap()s are wrong. I don't care. We're prototyping.

Before we continue on to writing a client, I want to modify this toy example to store some state on POST to /set and report it later. I'll make greeting a local, capture it in some closures, then see how the compiler complains.

Here's my new main function, before attempting to compile:

rustc is not happy. But I expected that. I'm throwing types at it just to get a response. Tell me what to do rustc.

Those error messages are confusing, but clearly the closure is the wrong type. The get and post methods of Router take a Handler, and from the doc page I see there's an impl defined as

That's a mouthful, but Handler is defined for Fn, not FnOnce or FnMut, and it has to be Send + Sync. Since it needs to be send, we're not going to be capturing any references, and since the environment isn't mutable we have to use interior mutability to mutate the greeting. So I'm going to use a sendable smart pointer, Arc, and to make it mutable, put a Mutex inside it. For that we need to import both from the standard library like this: use std::sync::{Mutex, Arc};. I'm also going to have to move the captures with move |r| ... to avoid capturing by reference.

Updating my code like so yields the same error messages.

rustc doesn't like the lifetimes of my closure. Why? I don't know. I ask reem in #rust if he knows what to do.

Several hours later reem says

HRTB means 'higher-ranked trait bounds', which means roughly 'complicated lifetimes'.

I change those same lines to hint the type of r: &mut Request and everything works...

It was seemingly a bug in Rust's inferencer. That's lame.

Now it builds again, so we can test with curl.

Now we're playing with power.

5. The client

We've got a little JSON server going. Now let's write the client. This time we're going to use Hyper directly.

I add it to my Cargo.toml: hyper = "*", then create src/bin/client.rs:

Source files in src/bin/ are automatically built as executables by cargo. Run cargo build and the target/debug/client program appears. Good, universe is sane. Now figure out Hyper.

Cribbing off the Hyper client example I come up with this snippet that just makes a request for "/" and prints the body:

But now cargo run no longer works.

I must type cargo run --bin httptest to start the server. I do so, then cargo run --bin client and see

Oh, man, I'm a Rust wizard. One last thing I want to do, make the POST request to set the message. Obvious thing to do is change client.get to client.post. This returns a RequestBuilder, so I'm looking for a builder method that sets the payload. How about body?

My new creation:

But running it is disappointing.

And simultaneously I see that the server has also errored:

It's because of my bad error handling! I didn't pass valid JSON to the /set route. Fixing the body to be .body(r#"{ "msg": "Just trust the Rust" }"#) lets the client succeed:

And just like that we've created a web service and client in Rust. Looks like the future is near. Go build something with Iron and Hyper.

Issues

Collection of the latest Issues

Qqwy

Qqwy

Comment Icon0

I was linked to this tutorial from this YouTube video. It was great to follow along!

One thing that went wrong, however, are the Routing commands. It seems that newer versions of the router crate require router.get, router.post etc. to be used with three parameters, the third one being a symbolic name to refer to the route to later (such as in when using router::url_for).

caulagi

caulagi

Comment Icon1

cargo test fails for me with this error -

Am I missing something? I am running rustup with stable channel -

Information - Updated Apr 23, 2022

Stars: 323
Forks: 24
Issues: 4

Repositories & Extras

Wasm template for Rust hosting without npm-deploy on github pages using Travis script

It automatically hosts your wasm projects on gh-pages using a travis script on the latest commit

Wasm template for Rust hosting without npm-deploy on github pages using Travis script

If you like Rusty Engine, please sponsor me on GitHub or on Patreon, or take...

If you like Rusty Engine, please sponsor me on Patreon, or Bevy, which I encourage you to use directly for more serious game engine needs

If you like Rusty Engine, please sponsor me on GitHub or on Patreon, or take...

Roctogen: a rust client library for the GitHub v3 API

This client API is generated from the Isahc HTTP client

Roctogen: a rust client library for the GitHub v3 API

A rust github template for ease of use

Install the rust toolchain in order to have cargo installed by following

A rust github template for ease of use

Rust-generated WebAssembly GitHub action template

A template to bootstrap the creation of a Rust-generated WebAssembly GitHub action

Rust-generated WebAssembly GitHub action template

Template for Rust lib/bin module with built-in GitHub Action to build and test

You will want to change the lib name and bin name in Cargo

Template for Rust lib/bin module with built-in GitHub Action to build and test

cargo_auto_github_lib

Library for cargo-auto automation tasks written in rust language with functions for github

cargo_auto_github_lib

A Rust library to connect to AxonServer

See the GitHub project Command / Query Responsibility Segregation (CQRS)

A Rust library to connect to AxonServer

Rust client implementation for Brasil API

Rust client implementation for BrasilAPI GitHub Project

Rust client implementation for Brasil API

a Rust interface for GitHub

GitHub instances define methods for accessing api services that map closely to

a Rust interface for GitHub
Facebook Instagram Twitter GitHub Dribbble
Privacy