JSON Requests with rust-http

I've been playing around with Rust a lot the past couple weeks. It's a neat little language with some cool ideas (I particularly like its memory-safety features, though they take some getting used to), but sadly the ecosystem has some catching up to do.

In particular, making HTTP requests in Rust is a bit of a mess right now. The closest Rust has to a working requests library at the moment is rust-http, which is not being maintained while its creator works on its forthcoming replacement, Teepee. Unfortunately, rust-http's API is not the clearest to use and its documentation is rather sparse.

The fun part, though, is that this means I've had to figure out a lot of its quirks myself.

I've been working on writing a client for a JSON API and I needed to use rust-http to make lots of JSON requests and then parse their responses as JSON, with as convenient an interface as possible. Here's what I came up with.

(Disclaimer: I have no idea whether what follows is idiomatic Rust -- I've been using Rust for less than a month -- but it works for me.)

Individual requests are encapsulated in a Request struct (method is a Method and headers is an instance of HeaderCollection):

struct Request {
  method: method::Method,
  path: String,
  headers: request::HeaderCollection,
  body: Json
}

The json_request method takes a Request struct and tries to make a JSON request with it, returning Ok(Some(response: Json)) if the response is valid JSON, Ok(None) if the response is not valid JSON, or Err(error) if the request failed in some way or did not receive a 200 OK response:

// Makes a JSON request with headers and body and expects a JSON response
// Returns one of:
//    Ok(Some(response)) : successfully received JSON response
//    Ok(None)           : response is not valid JSON
//    Err(error)         : did not receive a 200 OK response
fn json_request(request_params: Request) -> Result<Option<Json>, String> {
  let application_json = MediaType::new("application".to_string(), "json".to_string(), vec![]);

  let url = Url::parse(request_params.path.as_slice()).ok().expect("path is not a valid URL");

  let body_str = request_params.body.to_string();
  let data: &[u8] = body_str.as_bytes();

  let mut request: RequestWriter = RequestWriter::new(request_params.method, url).unwrap();
  request.headers.content_length = Some(data.len());
  request.headers.content_type = Some(application_json);
  request.headers.accept = Some("*/*".to_string());
  request.headers.user_agent = Some("Rust".to_string()); // (some APIs require a User-Agent)
  for header in request_params.headers.iter() {
    request.headers.insert(header);
  }
  request.write(data);

  let mut response = request.read_response().ok().expect("Failed to send request");
  let response_text: String = response.read_to_string().ok().expect("Failed to read response");
  // println!("{}: {}", response.status, response_text);

  match response.status {
    status::Ok => Ok(json::from_str(response_text.as_slice()).ok()),
    _          => Err(response_text)
  }
}

Here's an example of how this method can be used:

// see http://doc.rust-lang.org/serialize/json/ to learn how to serialize datatypes into Json
// or pass your body as a raw Json string this way:
let empty_body = from_str::<Json>("{}").unwrap();

// to learn how to add custom headers to a HeaderCollection, see:
// http://www.rust-ci.org/chris-morgan/rust-http/doc/http/headers/request/struct.HeaderCollection.html
let empty_headers = request::HeaderCollection::new();

// available methods: http://www.rust-ci.org/chris-morgan/rust-http/doc/http/method/enum.Method.html
let get = method::Get;

let request = Request {
  method: get,
  path: "https://api.github.com/users/AlexNisnevich".to_string(),
  headers: empty_headers,
  body: empty_body
};

match json_request(request) {
  Ok(Some(response)) => println!("{}", response.to_pretty_str()),
  Ok(None)           => println!("JSON parsing error"),
  Err(error)         => println!("Request failed: {}", error)
}

And yeah, that's about it. It's still nowhere near as nice as, say, Python's requests library, but it certainly beats working directly with rust-http's low-level interface.

The whole thing is on GitHub for your perusal.

Comments

blog comments powered by Disqus
Fork me on GitHub