Basic rust wrapper over the curl_easy* interface. Also a very very tiny http client
To build it type rustc rust_curl.rc -O
.
If you want to build it with a main function instead of as a library, use
rustc rust_curl.rc -O --bin
to see the example output provided
by the program below.
Here is example usage of the laughable "HTTP client" included:
use std::hashmap::HashMap;
use request::Request;
use std::str::from_bytes;
let client = http_client::HttpClient::new();
let url = "http://api.4chan.org/pol/threads.json";
let mut headers = HashMap::new();
headers.insert(headers::request::ACCEPT.to_owned(),~"application/json");
let req = Request::new(url.to_owned(),headers,~[]);
let resp = client.exec(&req).get();
for resp.headers.each |&k, &v| {
println(fmt!("%s: %s",k,v));
}
println(from_bytes(resp.body));
The client is very basic, ignores any cookies that are received (although technically you can still send cookies, by adding the "Cookie" header to the request HashMap) and so is only suitable for the most basic of use cases. I do plan to improve it in the future though.
If you have more complicated use patterns, you will probably want to directly use the underlying curl wrapper. You will essentially be using the curl API and so I suggest visiting their docs at: http://curl.haxx.se/libcurl/c/libcurl-easy.html
The wrapper I provide around curl is pretty thin, so you should be able to
do just about anything you could using the C curl easy_* interface. Below is
the code for the HttpClient::get(&self, req: &Request)
method
pub fn exec(&self, req: &Request) -> Result<Response,~str> {
let url = req.url.to_str();
self.curl.easy_setopt_str(opt::URL, url);
self.curl.easy_setopt_write_fn(write_fn);
let body = ~[];
self.curl.easy_setopt_buf(opt::WRITEDATA, &body);
self.curl.easy_setopt_header_fn(header_fn);
let headers: HashMap<~str,~str> = HashMap::new();
self.curl.easy_setopt_map(opt::HEADERDATA, &headers);
let err = match req.headers.is_empty() {
true => { self.curl.easy_perform() }
false => {
unsafe {
let mut list = 0 as *curl_slist;
for req.headers.each |&k, &v| {
let h = fmt!("%s: %s",k,v);
do h.as_c_str |s| {
list = curl_slist_append(list,s);
}
}
self.curl.easy_setopt_slist(opt::HTTPHEADER, list):q;
let rc = self.curl.easy_perform();
curl_slist_free_all(list);
rc
}
}
};
if err != code::CURLE_OK {
return Err(Curl::easy_strerror(err));
}
let resp = Response::new(headers,body);
Ok(resp)
}
Two main things to take notice of. The Curl::easy_setopt(&self, val: T)
is generic, as it forwards it's arguments to curl_easy_setopt
which is a C function that can take either a pointer to a function,
user supplied data for a Curl callback, a 32bit int, or a 64bit int.
It is up to you to take care of the lifetimes and types of any objects
you give this function. You will see the lifetime management I have to
do with the curl_slist
of headers that I pass in, for example.
Make sure any functions that you give as an argument have been declared
as extern C
. Here are the two such functions I used above:
extern "C" fn write_fn (data: *u8, size: size_t, nmemb: size_t, user_data: *()) -> size_t {
use std::vec::raw::from_buf_raw;
let body: &mut ~[u8] = unsafe { transmute(user_data) };
unsafe { body.push_all_move(from_buf_raw(data,(size * nmemb) as uint)); }
size * nmemb
}
extern "C" fn header_fn (data: *c_char, size: size_t, nmemb: size_t, user_data: *()) -> size_t {
use std::str::raw::from_c_str_len;
use std::str::*;
let head = unsafe { from_c_str_len(data,(size * nmemb) as uint) };
let colon_res = head.find(':');
if colon_res.is_none() { return size * nmemb; }
let colon = colon_res.get();
let (name, value) = (head.substr(0,colon), head.substr(colon + 2 ,head.len() - colon - 3) );
if name == "Set-Cookie" { return size * nmemb; }
let h: &mut HashMap<~str,~str> = unsafe { transmute(user_data) };
h.insert(name.to_owned(),value.to_owned());
size * nmemb
}