{"id":16189958,"url":"https://github.com/brson/httptest","last_synced_at":"2025-07-07T08:39:08.084Z","repository":{"id":30732243,"uuid":"34288541","full_name":"brson/httptest","owner":"brson","description":"An example of a Rust web service with Iron and Hyper","archived":false,"fork":false,"pushed_at":"2021-01-31T16:54:55.000Z","size":24,"stargazers_count":328,"open_issues_count":4,"forks_count":23,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-31T13:19:13.502Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-04-20T21:51:54.000Z","updated_at":"2025-01-07T01:55:22.000Z","dependencies_parsed_at":"2022-09-08T18:01:35.390Z","dependency_job_id":null,"html_url":"https://github.com/brson/httptest","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brson%2Fhttptest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brson%2Fhttptest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brson%2Fhttptest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brson%2Fhttptest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brson","download_url":"https://codeload.github.com/brson/httptest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247675607,"owners_count":20977378,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T07:37:44.814Z","updated_at":"2025-04-07T15:10:45.076Z","avatar_url":"https://github.com/brson.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Let's make a web service and client in Rust\n\nSo 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.\n\nI'm going to use [Iron] and [Hyper], neither of which I have experience with.\n\nEach commit in this repo corresponds with a chapter, so follow along if you want.\n\n**Edit: Some inaccuracies within! [Thanks /r/rust!](http://www.reddit.com/r/rust/comments/33k1yn/lets_make_a_web_service_and_client/)**\n\n# 1. Preparing to serve some JSON\n\nI start by asking Cargo to give me a new executable project called 'httptest'. Passing `--bin` says\nto create a source file in `src/main.rs` that will be compiled to an application.\n\n```text\n$ cargo new httptest --bin\n```\n\nI'll use [Iron] for the server, so following the instructions in their docs add the following\nto my `Cargo.toml` file.\n\n```toml\n[dependencies]\niron = \"*\"\n```\n\nThen into `main.rs` I just copy their example.\n\n```rust\nextern crate iron;\n\nuse iron::prelude::*;\nuse iron::status;\n\nfn main() {\n    fn hello_world(_: \u0026mut Request) -\u003e IronResult\u003cResponse\u003e {\n        Ok(Response::with((status::Ok, \"Hello World!\")))\n    }\n\n    Iron::new(hello_world).http(\"localhost:3000\").unwrap();\n    println!(\"On 3000\");\n}\n```\n\nAnd type `cargo build`.\n\n```text\n$ cargo build\nCompiling hyper v0.3.13\nCompiling iron v0.1.16\nCompiling httptest v0.2.0 (file:///opt/dev/httptest)\n```\n\nPure success so far. Damn, Rust is smooth. Let's try running the server with `cargo run`.\n\n```text\n$ cargo run\nRunning `target/debug/httptest`\n```\n\nI sit here waiting a while expecting it to print \"On 3000\" but it never does. Cargo\nmust be capturing output. Let's see if we're serving something.\n\n```text\n$ curl http://localhost:3000\nHello World!\n```\n\nOh, that's super cool. We know how to build a web server now. Good starting point.\n\n# 2. Serving a struct as JSON\n\nIs [rustc-serialize] still the easiest way to convert to and from\nJSON? Maybe I should use [serde], but then you really want\n[serde_macros], but that only works on Rust nightlies.  Should I just\nuse nightlies? Nobody else is going to need to use this.\n\nLet's just go with the tried-and-true rustc-serialize. Now my Cargo.toml\n'dependencies' section looks like the following.\n\n```toml\n[dependencies]\niron = \"*\"\nrustc-serialize = \"*\"\n```\n\nBased on the [rustc-serialize docs](https://doc.rust-lang.org/rustc-serialize/rustc_serialize/json/index.html) I update `main.rs`\nto look like this:\n\n```rust\nextern crate iron;\nextern crate rustc_serialize;\n\nuse iron::prelude::*;\nuse iron::status;\nuse rustc_serialize::json;\n\n#[derive(RustcEncodable)]\nstruct Greeting {\n    msg: String\n}\n\nfn main() {\n    fn hello_world(_: \u0026mut Request) -\u003e IronResult\u003cResponse\u003e {\n        let greeting = Greeting { msg: \"Hello, World\".to_string() };\n        let payload = json::encode(\u0026greeting).unwrap();\n        Ok(Response::with((status::Ok, payload)))\n    }\n\n    Iron::new(hello_world).http(\"localhost:3000\").unwrap();\n    println!(\"On 3000\");\n}\n```\n\nAnd then run `cargo build`.\n\n```text\n$ cargo build\n   Updating registry `https://github.com/rust-lang/crates.io-index`\n Downloading unicase v1.1.1\n \u003c... more downloading here ...\u003e\n   Compiling lazy_static v0.1.15\n \u003c... more compiling here ...\u003e\n   Compiling httptest v0.2.0 (file:///opt/dev/httptest)\nsrc/main.rs:17:12: 17:47 error: this function takes 1 parameter but 2 parameters were supplied [E0061]\nsrc/main.rs:17         Ok(Response::with(status::Ok, payload))\n                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsrc/main.rs:17:12: 17:47 help: run `rustc --explain E0061` to see a detailed explanation\nerror: aborting due to previous error\nCould not compile `httptest`.\n\nTo learn more, run the command again with --verbose.\n```\n\nLot's of new dependencies now. But an error. [`Response::with`](http://ironframework.io/doc/iron/response/struct.Response.html#method.with) has this definition:\n\n```rust\nfn with\u003cM: Modifier\u003cResponse\u003e\u003e(m: M) -\u003e Response\n```\n\nI don't know what a `Modifier` is. [Some\ndocs](http://ironframework.io/doc/iron/modifiers/index.html) don't\nhelp. I don't know what to do here but I notice that the original\nexample passed a tuple to `Response::with` whereas my update treated\n`Response::with` as taking two parameters. It seems a tuple is a\n`Modifier`.\n\nAdd the tuple to `Ok(Response::with((status::Ok, payload)))`, execute `cargo run`, curl some JSON.\n\n```\n$ curl http://localhost:3000\n{\"msg\":\"Hello, World\"}\n```\n\nBlam! We're sending JSON. Time for another break.\n\n# 3. Routes\n\nNext I want to POST some JSON, but before I do that I need a proper\nURL to post to, so I guess I need to learn how to set up routes.\n\nI look at the Iron [docs](http://ironframework.io/doc/iron/) and don't\nsee anything obvious in the main text, but there's a crate called\n[router](http://ironframework.io/doc/router/index.html) that might be\ninteresting.\n\nThe module docs are \"`Router` provides fast and flexible routing for\nIron\", but not much else. How do I use a `Router`?! After I lot of\nsleuthing I discover [an\nexample](https://github.com/iron/router/blob/master/examples/simple.rs).\nOK, let's try to adapt that to our evolving experiment.\n\nI add `router = \"*\"` to my `Cargo.toml` `[dependencies]` section,\nand begin writing. The following is what I come up with\nbefore getting stuck reading the POST data.\n\n```rust\nextern crate iron;\nextern crate router;\nextern crate rustc_serialize;\n\nuse iron::prelude::*;\nuse iron::status;\nuse router::Router;\nuse rustc_serialize::json;\n\n#[derive(RustcEncodable, RustcDecodable)]\nstruct Greeting {\n    msg: String\n}\n\nfn main() {\n    let mut router = Router::new();\n\n    router.get(\"/\", hello_world);\n    router.post(\"/set\", set_greeting);\n\n    fn hello_world(_: \u0026mut Request) -\u003e IronResult\u003cResponse\u003e {\n        let greeting = Greeting { msg: \"Hello, World\".to_string() };\n        let payload = json::encode(\u0026greeting).unwrap();\n        Ok(Response::with((status::Ok, payload)))\n    }\n\n    // Receive a message by POST and play it back.\n    fn set_greeting(request: \u0026mut Request) -\u003e IronResult\u003cResponse\u003e {\n        let payload = request.body.read_to_string();\n        let request: Greeting = json::decode(payload).unwrap();\n        let greeting = Greeting { msg: request.msg };\n        let payload = json::encode(\u0026greeting).unwrap();\n        Ok(Response::with((status::Ok, payload)))\n    }\n\n    Iron::new(router).http(\"localhost:3000\").unwrap();\n}\n```\n\nThis uses `Router` to control handler dispatch. It builds and still\nresponds to `curl http://localhost:3000`, but the handler for the\n`/set` route is yet unimplemented.\n\nNow to read the POST body into a string. The [docs for\n`Request`](http://ironframework.io/doc/iron/request/struct.Request.html)\nsay the field `body` is an iterator, so we just need to collect\nthat iterator into a string.\n\nI first try `let payload = request.body.read_to_string();` because I\nknow it used to work.\n\nIt does not work.\n\n```text\n$ cargo build\nCompiling httptest v0.2.0 (file:///opt/dev/httptest)\nsrc/main.rs:29:36: 29:52 error: no method named `read_to_string` found for type `iron::request::Body\u003c'_, '_\u003e` in the current scope\nsrc/main.rs:29         let payload = request.body.read_to_string();\n                                                  ^~~~~~~~~~~~~~~~\nsrc/main.rs:29:36: 29:52 help: items from traits can only be used if the trait is in scope; the following trait is implemented but not in scope, perhaps add a `use` for it:\nsrc/main.rs:29:36: 29:52 help: candidate #1: use `std::io::Read`\nerror: aborting due to previous error\nCould not compile `httptest`.\n\nTo learn more, run the command again with --verbose.\n```\n\nI throw my hands up in disgust. 'Why does this method no longer exist?\nThe Rust team is always playing tricks on us!' Then I notice the\ncompiler has - at some length - explained that the method does exist\nand that I should import `std::io::Read`.\n\nI add the import and discover that `read_to_string` behaves\ndifferently than I thought.\n\n```text\n101 $ cargo build\nCompiling httptest v0.2.0 (file:///opt/dev/httptest)\nsrc/main.rs:30:36: 30:52 error: this function takes 1 parameter but 0 parameters were supplied [E0061]\nsrc/main.rs:30         let payload = request.body.read_to_string();\n                                                  ^~~~~~~~~~~~~~~~\n```\n\nOk, yeah the signature of `Read::read_to_string` is now `fn\nread_to_string(\u0026mut self, buf: \u0026mut String) -\u003e Result\u003cusize, Error\u003e`,\nso that the buffer is supplied and errors handled. Rewrite the\n`set_greeting` method.\n\n```rust\n    // Receive a message by POST and play it back.\n    fn set_greeting(request: \u0026mut Request) -\u003e IronResult\u003cResponse\u003e {\n        let mut payload = String::new();\n        request.body.read_to_string(\u0026mut payload).unwrap();\n        let request: Greeting = json::decode(\u0026payload).unwrap();\n        let greeting = Greeting { msg: request.msg };\n        let payload = json::encode(\u0026greeting).unwrap();\n        Ok(Response::with((status::Ok, payload)))\n    }\n```\n\nLet's run this and give it a curl.\n\n```text\n$ curl -X POST -d '{\"msg\":\"Just trust the Rust\"}' http://localhost:3000/set\n{\"msg\":\"Just trust the Rust\"}\n```\n\nOh, Rust. You're just too bad.\n\n# 4. Mutation\n\nHey, I know all those `.unwrap()`s are wrong. I don't care. We're prototyping.\n\nBefore we continue on to writing a client, I want to modify this toy example\nto store some state on POST to `/set` and report it later. I'll make `greeting`\na local, capture it in some closures, then see how the compiler complains.\n\nHere's my new `main` function, before attempting to compile:\n\n```rust\nfn main() {\n    let mut greeting = Greeting { msg: \"Hello, World\".to_string() };\n\n    let mut router = Router::new();\n\n    router.get(\"/\", |r| hello_world(r, \u0026greeting));\n    router.post(\"/set\", |r| set_greeting(r, \u0026mut greeting));\n\n    fn hello_world(_: \u0026mut Request, greeting: \u0026Greeting) -\u003e IronResult\u003cResponse\u003e {\n        let payload = json::encode(\u0026greeting).unwrap();\n        Ok(Response::with((status::Ok, payload)))\n    }\n\n    // Receive a message by POST and play it back.\n    fn set_greeting(request: \u0026mut Request, greeting: \u0026mut Greeting) -\u003e IronResult\u003cResponse\u003e {\n        let mut payload = String::new();\n        request.body.read_to_string(\u0026mut payload).unwrap();\n        *greeting = json::decode(\u0026payload).unwrap();\n        Ok(Response::with(status::Ok))\n    }\n\n    Iron::new(router).http(\"localhost:3000\").unwrap();\n}\n```\n\n```text\nsrc/main.rs:21:12: 21:51 error: type mismatch resolving `for\u003c'r, 'r, 'r\u003e \u003c[closure@src/main.rs:21:21: 21:50 greeting:_] as core::ops::FnOnce\u003c(\u0026'r mut iron::request::Request\u003c'r, 'r\u003e,)\u003e\u003e::Output == core::result::Result\u003ciron::response::Response, iron::error::IronError\u003e`:\n expected bound lifetime parameter ,\n    found concrete lifetime [E0271]\nsrc/main.rs:21     router.get(\"/\", |r| hello_world(r, \u0026greeting));\n                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsrc/main.rs:21:12: 21:51 help: run `rustc --explain E0271` to see a detailed explanation\nsrc/main.rs:22:12: 22:60 error: type mismatch resolving `for\u003c'r, 'r, 'r\u003e \u003c[closure@src/main.rs:22:25: 22:59 greeting:_] as core::ops::FnOnce\u003c(\u0026'r mut iron::request::Request\u003c'r, 'r\u003e,)\u003e\u003e::Output == core::result::Result\u003ciron::response::Response, iron::error::IronError\u003e`:\n expected bound lifetime parameter ,\n    found concrete lifetime [E0271]\nsrc/main.rs:22     router.post(\"/set\", |r| set_greeting(r, \u0026mut greeting));\n                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsrc/main.rs:22:12: 22:60 help: run `rustc --explain E0271` to see a detailed explanation\nsrc/main.rs:21:12: 21:51 error: type mismatch: the type `[closure@src/main.rs:21:21: 21:50 greeting:\u0026Greeting]` implements the trait `core::ops::Fn\u003c(\u0026mut iron::request::Request\u003c'_, '_\u003e,)\u003e`, but the trait `for\u003c'r, 'r, 'r\u003e core::ops::Fn\u003c(\u0026'r mut iron::request::Request\u003c'r, 'r\u003e,)\u003e` is required (expected concrete lifetime, found bound lifetime parameter ) [E0281]\nsrc/main.rs:21     router.get(\"/\", |r| hello_world(r, \u0026greeting));\n                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsrc/main.rs:21:12: 21:51 help: run `rustc --explain E0281` to see a detailed explanation\nsrc/main.rs:22:12: 22:60 error: the trait `for\u003c'r, 'r, 'r\u003e core::ops::Fn\u003c(\u0026'r mut iron::request::Request\u003c'r, 'r\u003e,)\u003e` is not implemented for the type `[closure@src/main.rs:22:25: 22:59 greeting:\u0026mut Greeting]` [E0277]\nsrc/main.rs:22     router.post(\"/set\", |r| set_greeting(r, \u0026mut greeting));\n                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsrc/main.rs:22:12: 22:60 help: run `rustc --explain E0277` to see a detailed explanation\nerror: aborting due to 4 previous errors\nCould not compile `httptest`.\n```\n\nrustc is not happy. But I expected that. I'm throwing types at it just to get a response. Tell me what to do rustc.\n\nThose error messages are confusing, but clearly the closure is the wrong type. The `get` and `post` methods of `Router` take a [`Handler`](http://ironframework.io/doc/iron/middleware/trait.Handler.html), and from the doc page I see there's an impl defined as\n\n```rust\npub trait Handler: Send + Sync + Any {\n    fn handle(\u0026self, \u0026mut Request) -\u003e IronResult\u003cResponse\u003e;\n}\n```\n\nThat's a mouthful, but `Handler` is defined for `Fn`, not `FnOnce` or\n`FnMut`, and it has to be `Send + Sync`. Since it needs to be send,\nwe're not going to be capturing any references, and since the\nenvironment isn't mutable we have to use interior mutability to mutate\nthe greeting. So I'm going to use a sendable smart pointer, `Arc`, and\nto make it mutable, put a `Mutex` inside it. For that we need to import\nboth from the standard library like this: `use std::sync::{Mutex, Arc};`.\nI'm also going to have to move the captures with `move |r| ...` to avoid\ncapturing by reference.\n\nUpdating my code like so yields the same error messages.\n\n```rust\n    let greeting = Arc::new(Mutex::new(Greeting { msg: \"Hello, World\".to_string() }));\n    let greeting_clone = greeting.clone();\n\n    let mut router = Router::new();\n\n    router.get(\"/\", move |r| hello_world(r, \u0026greeting.lock().unwrap()));\n    router.post(\"/set\", move |r| set_greeting(r, \u0026mut greeting_clone.lock().unwrap()));\n```\n\nrustc doesn't like the lifetimes of my closure. Why? I don't know. I ask reem\nin #rust if he knows what to do.\n\nSeveral hours later reem says\n\n```text\n16:02 \u003c reem\u003e brson: Partially hint the type of the request art, rustc has trouble inferring HRTBs\n```\n\nHRTB means 'higher-ranked trait bounds', which means roughly 'complicated lifetimes'.\n\nI change those same lines to hint the type of `r: \u0026mut Request` and everything works...\n\n```rust\n    let greeting = Arc::new(Mutex::new(Greeting { msg: \"Hello, World\".to_string() }));\n    let greeting_clone = greeting.clone();\n\n    let mut router = Router::new();\n\n    router.get(\"/\", move |r: \u0026mut Request| hello_world(r, \u0026greeting.lock().unwrap()));\n    router.post(\"/set\", move |r: \u0026mut Request| set_greeting(r, \u0026mut greeting_clone.lock().unwrap()));\n```\n\nIt was seemingly a bug in Rust's inferencer. That's lame.\n\nNow it builds again, so we can test with curl.\n\n```text\n$ curl http://localhost:3000\n{\"msg\":\"Hello, World\"}\n$ curl -X POST -d '{\"msg\":\"Just trust the Rust\"}' http://localhost:3000/set\n$ curl http://localhost:3000\n{\"msg\":\"Just trust the Rust\"}\n```\n\nNow we're playing with power.\n\n# 5. The client\n\nWe've got a little JSON server going. Now let's write the client. This\ntime we're going to use [Hyper] directly.\n\nI add it to my `Cargo.toml`: `hyper = \"*\"`, then create `src/bin/client.rs`:\n\n```rust\nextern crate hyper;\n\nfn main() { }\n```\n\nSource files in `src/bin/` are automatically built as executables by cargo. Run `cargo build`\nand the `target/debug/client` program appears. Good, universe is sane. Now figure out Hyper.\n\nCribbing off the [Hyper client example](http://hyperium.github.io/hyper/hyper/client/index.html) I come\nup with this snippet that just makes a request for \"/\" and prints the body:\n\n```rust\nextern crate hyper;\n\nuse hyper::*;\nuse std::io::Read;\n\nfn main() {\n    let client = Client::new();\n    let mut res = client.get(\"http://localhost:3000/\").send().unwrap();\n    assert_eq!(res.status, hyper::Ok);\n    let mut s = String::new();\n    res.read_to_string(\u0026mut s).unwrap();\n    println!(\"{}\", s);\n}\n```\n\nBut now `cargo run` no longer works.\n\n```text\n$ cargo run\n`cargo run` requires that a project only have one executable; use the `--bin` option to specify which one to run\n```\n\nI must type `cargo run --bin httptest` to start the server. I do so, then `cargo run --bin client` and see\n\n```text\n$ cargo run --bin client\nRunning `target/debug/client`\n{\"msg\":\"Hello, World\"}\n```\n\nOh, man, I'm a Rust wizard. One last thing I want to do, make the POST\nrequest to set the message. Obvious thing to do is change `client.get`\nto\n[`client.post`](http://hyperium.github.io/hyper/hyper/client/struct.Client.html#method.post).\nThis returns a\n[`RequestBuilder`](http://hyperium.github.io/hyper/hyper/client/struct.RequestBuilder.html),\nso I'm looking for a builder method that sets the payload. How about [`body`](http://hyperium.github.io/hyper/hyper/client/struct.RequestBuilder.html#method.body)?\n\nMy new creation:\n\n```rust\nextern crate hyper;\n\nuse hyper::*;\nuse std::io::Read;\n\nfn main() {\n    let client = Client::new();\n    let res = client.post(\"http://localhost:3000/set\").body(\"Just trust the Rust\").send().unwrap();\n    assert_eq!(res.status, hyper::Ok);\n    let mut res = client.get(\"http://localhost:3000/\").send().unwrap();\n    assert_eq!(res.status, hyper::Ok);\n    let mut s = String::new();\n    res.read_to_string(\u0026mut s).unwrap();\n    println!(\"{}\", s);\n}\n```\n\nBut running it is disappointing.\n\n```text\n101 $ cargo run --bin client\n   Compiling httptest v0.2.0 (file:///Users/danieleesposti/workspace/httptest)\n     Running `target/debug/client`\nthread '\u003cmain\u003e' panicked at 'assertion failed: `(left == right)` (left: `InternalServerError`, right: `Ok`)', src/bin/client.rs:9\n```\n\nAnd simultaneously I see that the server has also errored:\n\n```text\n$ cargo run --bin httptest\n   Running `target/debug/httptest`\nthread '\u003cunnamed\u003e' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError(SyntaxError(\"invalid syntax\", 1, 1))', src/libcore/result.rs:741\n```\n\nIt'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:\n\n```text\n$ cargo run --bin client\n   Compiling httptest v0.2.0 (file:///opt/dev/httptest)\n      Running `target/debug/client`\n{\"msg\":\"Just trust the Rust\"}\n```\n\nAnd 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].\n\n\n\n[Crater]: https://github.com/brson/taskcluster-crater\n[Rust]: https://github.com/brson/taskcluster-crater\n[Iron]: http://ironframework.io/\n[Hyper]: http://hyperium.github.io/hyper/hyper/index.html\n[rustc-serialize]: https://crates.io/crates/rustc-serialize\n[serde]: https://crates.io/crates/serde\n[serde_macros]: https://crates.io/crates/serde_macros\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrson%2Fhttptest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrson%2Fhttptest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrson%2Fhttptest/lists"}