diff --git a/curl-sys/lib.rs b/curl-sys/lib.rs index 7cae355acc..207eba4714 100644 --- a/curl-sys/lib.rs +++ b/curl-sys/lib.rs @@ -8,8 +8,11 @@ use libc::{c_void, c_int, c_char, c_uint, c_long}; pub type CURLINFO = c_int; pub type CURL = c_void; +pub type CURLM = c_void; pub type curl_slist = c_void; pub type CURLoption = c_int; +pub type CURLMoption = c_int; +pub type C = c_int; #[repr(C)] #[derive(Clone, Copy)] @@ -465,13 +468,103 @@ ALIAS!(CURLOPT_WRITEDATA, CURLOPT_FILE); ALIAS!(CURLOPT_HEADERDATA, CURLOPT_WRITEHEADER); ALIAS!(CURLOPT_XFERINFODATA, CURLOPT_PROGRESSDATA); +macro_rules! DEFOPTM { + ($name:ident, $ty:ident, $num:expr) => ( + #[allow(dead_code)] + pub const $name: CURLMoption = $ty + $num; + ) +} + +macro_rules! ALIASM { + ($name:ident, $to:ident) => ( + #[allow(dead_code)] + pub const $name: CURLMoption = $to; + ) +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub enum CURLMcode { + CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or + curl_multi_socket*() soon */ + CURLM_OK, + CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ + CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ + CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ + CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ + CURLM_ADDED_ALREADY, /* an easy handle already added to a multi handle was + attempted to get added - again */ + CURLM_LAST +} + +/* This is the socket callback function pointer */ +DEFOPTM!(CURLMOPT_SOCKETFUNCTION, FUNCTIONPOINT, 1); +/* This is the argument passed to the socket callback */ +DEFOPTM!(CURLMOPT_SOCKETDATA, OBJECTPOINT, 2); + +/* set to 1 to enable pipelining for this multi handle */ +DEFOPTM!(PIPELINING, LONG, 3); + +/* This is the timer callback function pointer */ +DEFOPTM!(TIMERFUNCTION, FUNCTIONPOINT, 4); + +/* This is the argument passed to the timer callback */ +DEFOPTM!(TIMERDATA, OBJECTPOINT, 5); + +/* maximum number of entries in the connection cache */ +DEFOPTM!(MAXCONNECTS, LONG, 6); + +/* maximum number of (pipelining) connections to one host */ +DEFOPTM!(MAX_HOST_CONNECTIONS, LONG, 7); + +/* maximum number of requests in a pipeline */ +DEFOPTM!(MAX_PIPELINE_LENGTH, LONG, 8); + +/* a connection with a content-length longer than this + will not be considered for pipelining */ +DEFOPTM!(CONTENT_LENGTH_PENALTY_SIZE, OFF_T, 9); + +/* a connection with a chunk length longer than this + will not be considered for pipelining */ +DEFOPTM!(CHUNK_LENGTH_PENALTY_SIZE, OFF_T, 10); + +/* a list of site names(+port) that are blacklisted from + pipelining */ +DEFOPTM!(PIPELINING_SITE_BL, OBJECTPOINT, 11); + +/* a list of server types that are blacklisted from + pipelining */ +DEFOPTM!(PIPELINING_SERVER_BL, OBJECTPOINT, 12); + +/* maximum number of open connections in total */ +DEFOPTM!(MAX_TOTAL_CONNECTIONS, LONG, 13); + +//CURLMOPT_LASTENTRY /* the last unused */ + +ALIASM!(LONG, CURLOPTTYPE_LONG); +ALIASM!(OBJECTPOINT, CURLOPTTYPE_OBJECTPOINT); +ALIASM!(FUNCTIONPOINT, CURLOPTTYPE_FUNCTIONPOINT); +ALIASM!(OFF_T, CURLOPTTYPE_OFF_T); + extern { + + // Easy curl mode (one thread per connection) pub fn curl_easy_strerror(code: CURLcode) -> *const c_char; pub fn curl_easy_init() -> *mut CURL; pub fn curl_easy_setopt(curl: *mut CURL, option: CURLoption, ...) -> CURLcode; pub fn curl_easy_perform(curl: *mut CURL) -> CURLcode; pub fn curl_easy_cleanup(curl: *mut CURL); pub fn curl_easy_getinfo(curl: *const CURL, info: CURLINFO, ...) -> CURLcode; + + // Multi curl mode + pub fn curl_multi_cleanup(curl: *mut CURLM); + pub fn curl_multi_add_handle(curl: *mut CURLM, easy: *mut CURL) ->CURLMcode; + pub fn curl_multi_init() -> *mut CURLM; + pub fn curl_multi_setopt(curl: *mut CURLM, option: CURLMoption, ...) -> CURLMcode; + pub fn curl_multi_strerror(code: CURLMcode) -> *const c_char; + pub fn curl_global_cleanup(); pub fn curl_slist_append(list: *mut curl_slist, diff --git a/curl-sys/multi.rs b/curl-sys/multi.rs new file mode 100644 index 0000000000..3a55c95475 --- /dev/null +++ b/curl-sys/multi.rs @@ -0,0 +1,110 @@ +#![allow(non_camel_case_types, raw_pointer_derive)] + +extern crate libc; +#[cfg(not(target_env = "msvc"))] +extern crate libz_sys; +#[cfg(unix)] +extern crate openssl_sys; + +use libc::{c_void, c_int, c_char, c_uint, c_long}; + +pub type CURL = c_void; +pub type CURLM = c_void; +pub type CURLMoption = c_int; +pub type C = c_int; + + +pub const CURLOPTTYPE_LONG: c_int = 0; +pub const CURLOPTTYPE_OBJECTPOINT: c_int = 10_000; +pub const CURLOPTTYPE_FUNCTIONPOINT: c_int = 20_000; +pub const CURLOPTTYPE_OFF_T: c_int = 30_000; + + +macro_rules! DEFOPTM { + ($name:ident, $ty:ident, $num:expr) => ( + #[allow(dead_code)] + pub const $name: CURLMoption = $ty + $num; + ) +} + +macro_rules! ALIASM { + ($name:ident, $to:ident) => ( + #[allow(dead_code)] + pub const $name: CURLMoption = $to; + ) +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub enum CURLMcode { + CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or + curl_multi_socket*() soon */ + CURLM_OK, + CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ + CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ + CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ + CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ + CURLM_ADDED_ALREADY, /* an easy handle already added to a multi handle was + attempted to get added - again */ + CURLM_LAST +} + +/* This is the socket callback function pointer */ +DEFOPTM!(CURLMOPT_SOCKETFUNCTION, FUNCTIONPOINT, 1); +/* This is the argument passed to the socket callback */ +DEFOPTM!(CURLMOPT_SOCKETDATA, OBJECTPOINT, 2); + +/* set to 1 to enable pipelining for this multi handle */ +DEFOPTM!(PIPELINING, LONG, 3); + +/* This is the timer callback function pointer */ +DEFOPTM!(TIMERFUNCTION, FUNCTIONPOINT, 4); + +/* This is the argument passed to the timer callback */ +DEFOPTM!(TIMERDATA, OBJECTPOINT, 5); + +/* maximum number of entries in the connection cache */ +DEFOPTM!(MAXCONNECTS, LONG, 6); + +/* maximum number of (pipelining) connections to one host */ +DEFOPTM!(MAX_HOST_CONNECTIONS, LONG, 7); + +/* maximum number of requests in a pipeline */ +DEFOPTM!(MAX_PIPELINE_LENGTH, LONG, 8); + +/* a connection with a content-length longer than this + will not be considered for pipelining */ +DEFOPTM!(CONTENT_LENGTH_PENALTY_SIZE, OFF_T, 9); + +/* a connection with a chunk length longer than this + will not be considered for pipelining */ +DEFOPTM!(CHUNK_LENGTH_PENALTY_SIZE, OFF_T, 10); + +/* a list of site names(+port) that are blacklisted from + pipelining */ +DEFOPTM!(PIPELINING_SITE_BL, OBJECTPOINT, 11); + +/* a list of server types that are blacklisted from + pipelining */ +DEFOPTM!(PIPELINING_SERVER_BL, OBJECTPOINT, 12); + +/* maximum number of open connections in total */ +DEFOPTM!(MAX_TOTAL_CONNECTIONS, LONG, 13); + +//CURLMOPT_LASTENTRY /* the last unused */ + +ALIASM!(LONG, CURLOPTTYPE_LONG); +ALIASM!(OBJECTPOINT, CURLOPTTYPE_OBJECTPOINT); +ALIASM!(FUNCTIONPOINT, CURLOPTTYPE_FUNCTIONPOINT); +ALIASM!(OFF_T, CURLOPTTYPE_OFF_T); + +extern { + // Multi curl mode + pub fn curl_multi_cleanup(curl: *mut CURLM); + pub fn curl_multi_add_handle(curl: *mut CURLM, easy: *mut CURL) ->CURLMcode; + pub fn curl_multi_init() -> *mut CURLM; + pub fn curl_multi_setopt(curl: *mut CURLM, option: CURLMoption, ...) -> CURLMcode; + pub fn curl_multi_strerror(code: CURLMcode) -> *const c_char; +} diff --git a/src/ffi/easy.rs b/src/ffi/easy.rs index 5a9a3609b0..38ed2be183 100644 --- a/src/ffi/easy.rs +++ b/src/ffi/easy.rs @@ -15,7 +15,7 @@ use curl_ffi as ffi; pub type ProgressCb<'a> = FnMut(usize, usize, usize, usize) + 'a; pub struct Easy { - curl: *mut ffi::CURL + pub curl: *mut ffi::CURL } impl Easy { diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index cb01dc2a0b..84854dfcc3 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -6,5 +6,8 @@ pub mod easy; pub mod err; pub mod info; pub mod list; +pub mod multi; +pub mod multi_err; +pub mod multi_opt; pub mod opt; pub mod version; diff --git a/src/ffi/multi.rs b/src/ffi/multi.rs new file mode 100644 index 0000000000..69dae0cfcf --- /dev/null +++ b/src/ffi/multi.rs @@ -0,0 +1,190 @@ +#![allow(dead_code)] + +use std::sync::{Once, ONCE_INIT}; +use std::mem; +use std::collections::HashMap; +use std::slice; +use libc::{self, c_int, c_long, c_double, size_t}; +use super::{consts, err, info, opt}; +use super::multi_err::*; +use http::body::Body; +use http::{header, Response}; +use super::easy::Easy; + +use curl_ffi as ffi; + +pub type ProgressCb<'a> = FnMut(usize, usize, usize, usize) + 'a; + +pub struct Multi { + curl: *mut ffi::CURLM +} + +impl Multi { + pub fn new() -> Multi { + // Ensure that curl is globally initialized + global_init(); + + let handle = unsafe { + let p = ffi::curl_multi_init(); + /* setup the generic multi interface options we want */ +// ffi::curl_multi_setopt(p, p, opt::SOCKETDATA, &p); +// ffi::curl_multi_setopt(p, CURLMOPT_TIMERFUNCTION, curl_progress_fn); +// ffi::curl_multi_setopt(p, CURLMOPT_TIMERDATA, &p); + +// ffi::curl_multi_setopt(p, opt::NOPROGRESS, 0); + p + }; + + Multi { curl: handle } + } + + pub fn add_connection(&mut self, easy: Easy) -> Result<(), ErrCodeM> { + let mut res = ErrCodeM(OK); + + unsafe { res = ErrCodeM(ffi::curl_multi_add_handle(self.curl, easy.curl)); } + + if res.is_success() { Ok(()) } else { Err(res) } + } + + #[inline] + pub fn setopt(&mut self, option: opt::Opt, val: T) -> Result<(), ErrCodeM> { + // TODO: Prevent setting callback related options + let mut res = ErrCodeM(OK); + + unsafe { + val.with_c_repr(|repr| { + res = ErrCodeM(ffi::curl_multi_setopt(self.curl, option, repr)); + }) + } + + if res.is_success() { Ok(()) } else { Err(res) } + } +} + +#[inline] +fn global_init() { + // Schedule curl to be cleaned up after we're done with this whole process + static INIT: Once = ONCE_INIT; + INIT.call_once(|| unsafe { + assert_eq!(libc::atexit(cleanup), 0); + }); + + extern fn cleanup() { + // TODO perhaps this and easy could check the cURL code. + unsafe { ffi::curl_global_cleanup() } + } +} + +impl Drop for Multi { + fn drop(&mut self) { + unsafe { ffi::curl_multi_cleanup(self.curl) } + } +} + +/* + * + * TODO: Move this into handle + * + */ + +struct ResponseBuilderM { + code: u32, + hdrs: HashMap>, + body: Vec +} + +impl ResponseBuilderM { + fn new() -> ResponseBuilderM { + ResponseBuilderM { + code: 0, + hdrs: HashMap::new(), + body: Vec::new() + } + } + + fn add_header(&mut self, name: &str, val: &str) { + // TODO: Reduce allocations + use std::ascii::AsciiExt; + let name = name.to_ascii_lowercase(); + + let inserted = match self.hdrs.get_mut(&name) { + Some(vals) => { + vals.push(val.to_string()); + true + } + None => false + }; + + if !inserted { + self.hdrs.insert(name, vec!(val.to_string())); + } + } + + fn build(self) -> Response { + let ResponseBuilderM { code, hdrs, body } = self; + Response::new(code, hdrs, body) + } +} + +/* + * + * ===== Callbacks ===== + */ + +extern fn curl_read_fn(p: *mut u8, size: size_t, nmemb: size_t, + body: *mut Body) -> size_t { + if body.is_null() { + return 0; + } + + let dst = unsafe { slice::from_raw_parts_mut(p, (size * nmemb) as usize) }; + let body = unsafe { &mut *body }; + + match body.read(dst) { + Ok(len) => len as size_t, + Err(_) => consts::CURL_READFUNC_ABORT as size_t, + } +} + +extern fn curl_write_fn(p: *mut u8, size: size_t, nmemb: size_t, + resp: *mut ResponseBuilderM) -> size_t { + if !resp.is_null() { + let builder: &mut ResponseBuilderM = unsafe { mem::transmute(resp) }; + let chunk = unsafe { slice::from_raw_parts(p as *const u8, + (size * nmemb) as usize) }; + builder.body.extend(chunk.iter().map(|x| *x)); + } + + size * nmemb +} + +extern fn curl_header_fn(p: *mut u8, size: size_t, nmemb: size_t, + resp: &mut ResponseBuilderM) -> size_t { + // TODO: Skip the first call (it seems to be the status line) + + let vec = unsafe { slice::from_raw_parts(p as *const u8, + (size * nmemb) as usize) }; + + match header::parse(&vec) { + Some((name, val)) => { + resp.add_header(name, val); + } + None => {} + } + + vec.len() as size_t +} + +pub extern "C" fn curl_progress_fn(cb: *mut Box, dltotal: c_double, dlnow: c_double, ultotal: c_double, ulnow: c_double) -> c_int { + #[inline] + fn to_usize(v: c_double) -> usize { + if v > 0.0 { v as usize } else { 0 } + } + + if !cb.is_null() { + let cb: &mut ProgressCb = unsafe { &mut **cb }; + (*cb)(to_usize(dltotal), to_usize(dlnow), to_usize(ultotal), to_usize(ulnow)); + } + + 0 +} diff --git a/src/ffi/multi_err.rs b/src/ffi/multi_err.rs new file mode 100644 index 0000000000..b9574c95a6 --- /dev/null +++ b/src/ffi/multi_err.rs @@ -0,0 +1,65 @@ +use std::ffi::CStr; +use std::error; +use std::fmt; +use std::str; + +use curl_ffi as ffi; +/* please call curl_multi_perform() or curl_multi_socket*() soon */ + +pub use curl_ffi::CURLMcode::CURLM_CALL_MULTI_PERFORM as OK; +/* the passed-in handle is not a valid CURLM handle */ + +pub use curl_ffi::CURLMcode::CURLM_BAD_HANDLE as BAD_HANDLE; +/* an easy handle was not good/valid */ +pub use curl_ffi::CURLMcode::CURLM_BAD_EASY_HANDLE as EASY_HANDLE; +/* if you ever get this, you're in deep sh*t */ +pub use curl_ffi::CURLMcode::CURLM_OUT_OF_MEMORY as OUT_OF_MEMORY; +/* this is a libcurl bug */ +pub use curl_ffi::CURLMcode::CURLM_INTERNAL_ERROR as INTERNAL_ERROR; +/* the passed in socket argument did not match */ +pub use curl_ffi::CURLMcode::CURLM_BAD_SOCKET as BAD_SOCKET; +/* curl_multi_setopt() with unsupported option */ +pub use curl_ffi::CURLMcode::CURLM_UNKNOWN_OPTION as UNKNOWN_OPTION; +/* an easy handle already added to a multi handle was + attempted to get added - again */ +pub use curl_ffi::CURLMcode::CURLM_ADDED_ALREADY as ADDED_ALREADY; + +#[derive(Copy, Clone)] +pub struct ErrCodeM(pub ffi::CURLMcode); + +impl ErrCodeM { + pub fn is_success(self) -> bool { + self.code() as i32 == OK as i32 + } + + pub fn code(self) -> ffi::CURLMcode { let ErrCodeM(c) = self; c } +} + +impl fmt::Debug for ErrCodeM { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for ErrCodeM { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let s = unsafe { + CStr::from_ptr(ffi::curl_multi_strerror(self.code())).to_bytes() + }; + + match str::from_utf8(s) { + Ok(s) => write!(fmt, "{}", s), + Err(err) => write!(fmt, "{}", err) + } + } +} + +impl error::Error for ErrCodeM { + fn description(&self) -> &str { + let code = self.code(); + let s = unsafe { + CStr::from_ptr(ffi::curl_multi_strerror(code) as *const _).to_bytes() + }; + str::from_utf8(s).unwrap() + } +} diff --git a/src/ffi/multi_opt.rs b/src/ffi/multi_opt.rs new file mode 100644 index 0000000000..6d7280fa9a --- /dev/null +++ b/src/ffi/multi_opt.rs @@ -0,0 +1,64 @@ +#![allow(dead_code)] + +use std::ffi::CString; +use std::path::Path; +use libc::{c_void}; + +use curl_ffi as ffi; + +// multi +pub use curl_ffi::CURLMOPT_SOCKETFUNCTION as SOCKETFUNCTION; +pub use curl_ffi::CURLMOPT_SOCKETDATA as SOCKETDATA; + +pub type Opt = ffi::CURLMoption; + +pub trait OptVal { + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void); +} + +impl OptVal for isize { + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void) { + f(self as *const c_void) + } +} + +impl OptVal for i32 { + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void) { + (self as isize).with_c_repr(f) + } +} + +impl OptVal for usize { + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void) { + f(self as *const c_void) + } +} + +impl OptVal for bool { + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void) { + f(self as usize as *const c_void) + } +} + +impl<'a> OptVal for &'a str { + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void) { + let s = CString::new(self).unwrap(); + f(s.as_ptr() as *const c_void) + } +} + +impl<'a> OptVal for &'a Path { + #[cfg(unix)] + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void) { + use std::ffi::OsStr; + use std::os::unix::prelude::*; + let s: &OsStr = self.as_ref(); + let s = CString::new(s.as_bytes()).unwrap(); + f(s.as_ptr() as *const c_void) + } + #[cfg(windows)] + fn with_c_repr(self, f: F) where F: FnOnce(*const c_void) { + let s = CString::new(self.to_str().unwrap()).unwrap(); + f(s.as_ptr() as *const c_void) + } +} diff --git a/src/http/handle.rs b/src/http/handle.rs index f611890901..bf97391d17 100644 --- a/src/http/handle.rs +++ b/src/http/handle.rs @@ -16,7 +16,7 @@ use self::BodyType::{Fixed, Chunked}; const DEFAULT_TIMEOUT_MS: usize = 30_000; pub struct Handle { - easy: Easy, + pub easy: Easy, } impl Handle { @@ -182,7 +182,7 @@ pub struct Request<'a, 'b> { follow: bool, } -enum BodyType { +pub enum BodyType { Fixed(usize), Chunked, } diff --git a/src/http/mod.rs b/src/http/mod.rs index 10f4d60f8f..41b356226f 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,12 +1,22 @@ -pub use self::handle::{Handle,Request}; +pub use self::handle::{Handle,Request,Method}; + +pub use self::multi_handle::MultiHandle; pub use self::response::{Headers,Response}; pub mod body; pub mod handle; +pub mod multi_handle; pub mod header; mod response; + + #[inline] pub fn handle() -> Handle { Handle::new() } + +#[inline] +pub fn multi_handle() -> MultiHandle { + MultiHandle::new() +} diff --git a/src/http/multi_handle.rs b/src/http/multi_handle.rs new file mode 100644 index 0000000000..a9034e882b --- /dev/null +++ b/src/http/multi_handle.rs @@ -0,0 +1,287 @@ +use std::collections::hash_map::{HashMap, Entry}; +use std::path::Path; + +use url::Url; + +use ffi; +use ffi::multi_opt; +use ffi::multi::Multi; +use http::Response; +use ProgressCb; +use ErrCodeM; +use http::body::{Body,ToBody}; +use http::handle::Method; +use http::handle::Method::{Get, Head, Post, Put, Patch, Delete}; +use http::handle::BodyType; +use http::handle::BodyType::{Fixed, Chunked}; +use http::handle::Handle; + +const DEFAULT_TIMEOUT_MS: usize = 30_000; + +pub struct MultiHandle { + multi: Multi, +} + +impl MultiHandle { + pub fn new() -> MultiHandle { + return configure(MultiHandle { multi: Multi::new() } + .timeout(DEFAULT_TIMEOUT_MS) + .connect_timeout(DEFAULT_TIMEOUT_MS)); + + #[cfg(unix)] + fn configure(handle: MultiHandle) -> MultiHandle { + let probe = ::openssl::probe::probe(); + let handle = match probe.cert_file { + Some(ref path) => handle.ssl_ca_info(path), + None => handle, + }; + match probe.cert_dir { + Some(ref path) => handle.ssl_ca_path(path), + None => handle, + } + } + + #[cfg(not(unix))] + fn configure(handle: Handle) -> MultiHandle { handle } + } + pub fn add_connection(mut self, handle: Handle) { + self.multi.add_connection(handle.easy); + } + + pub fn timeout(mut self, ms: usize) -> MultiHandle { +// self.multi.setopt(opt::TIMEOUT_MS, ms).unwrap(); + self + } + + pub fn connect_timeout(mut self, ms: usize) -> MultiHandle { +// self.multi.setopt(opt::CONNECTTIMEOUT_MS, ms).unwrap(); + self + } + + /// Set the time in seconds that the transfer speed should be below + /// the `low_speed_limit` rate of bytes per second for the library to + /// consider it too slow and abort. + /// + /// The default for this option is 0 which means that this option is + /// disabled. + pub fn low_speed_timeout(mut self, seconds: usize) -> MultiHandle { +// self.multi.setopt(opt::LOW_SPEED_TIME, seconds).unwrap(); + self + } + + /// Set the average transfer speed in bytes per second that the + /// transfer should be below during `low_speed_timeout` seconds for + /// libcurl to consider it to be too slow and abort. + /// + /// The default for this option is 0 which means that this option is + /// disabled. + pub fn low_speed_limit(mut self, bytes_per_second: usize) -> MultiHandle { +// self.multi.setopt(opt::LOW_SPEED_LIMIT, bytes_per_second).unwrap(); + self + } + + pub fn verbose(mut self) -> MultiHandle { +// self.multi.setopt(opt::VERBOSE, 1).unwrap(); + self + } + + pub fn proxy(mut self, proxy: U) -> MultiHandle { + proxy.with_url_str(|s| { +// self.multi.setopt(opt::PROXY, s).unwrap(); + }); + + self + } + + pub fn ssl_ca_path(mut self, path: &Path) -> MultiHandle { +// self.multi.setopt(opt::CAPATH, path).unwrap(); + self + } + + pub fn ssl_ca_info(mut self, path: &Path) -> MultiHandle { +// self.multi.setopt(opt::CAINFO, path).unwrap(); + self + } + + pub fn cookie_jar(mut self, path: &Path) -> MultiHandle { +// self.multi.setopt(opt::COOKIEJAR, path).unwrap(); + self + } + + pub fn cookie_file(mut self, path: &Path) -> MultiHandle { +// self.multi.setopt(opt::COOKIEFILE, path).unwrap(); + self + } + + pub fn cookies(self, path: &Path) -> MultiHandle { + self.cookie_jar(path).cookie_file(path) + } + + pub fn get<'a, 'b, U: ToUrl>(&'a mut self, uri: U) -> Request<'a, 'b> { + Request::new(self, Get).uri(uri) + } + + pub fn head<'a, 'b, U: ToUrl>(&'a mut self, uri: U) -> Request<'a, 'b> { + Request::new(self, Head).uri(uri) + } + + pub fn post<'a, 'b, U: ToUrl, B: ToBody<'b>>(&'a mut self, uri: U, body: B) -> Request<'a, 'b> { + Request::new(self, Post).uri(uri).body(body) + } + + pub fn put<'a, 'b, U: ToUrl, B: ToBody<'b>>(&'a mut self, uri: U, body: B) -> Request<'a, 'b> { + Request::new(self, Put).uri(uri).body(body) + } + + pub fn patch<'a, 'b, U: ToUrl, B: ToBody<'b>>(&'a mut self, uri: U, body: B) -> Request<'a, 'b> { + Request::new(self, Patch).uri(uri).body(body) + } + + pub fn delete<'a, 'b, U: ToUrl>(&'a mut self, uri: U) -> Request<'a, 'b> { + Request::new(self, Delete).uri(uri) + } +} + + +pub struct Request<'a, 'b> { + err: Option, + handle: &'a mut MultiHandle, + method: Method, + headers: HashMap>, + body: Option>, + body_type: Option, + content_type: bool, // whether or not the content type was set + expect_continue: bool, // whether to expect a 100 continue from the server + progress: Option>>, + follow: bool, +} + +impl<'a, 'b> Request<'a, 'b> { + pub fn new(handle: &'a mut MultiHandle, method: Method) -> Request<'a, 'b> { + Request { + err: None, + handle: handle, + method: method, + headers: HashMap::new(), + body: None, + body_type: None, + content_type: false, + expect_continue: false, + progress: None, + follow: false, + } + } + + pub fn uri(mut self, uri: U) -> Request<'a, 'b> { + uri.with_url_str(|s| { + }); + + self + } + + pub fn body>(mut self, body: B) -> Request<'a, 'b> { + self.body = Some(body.to_body()); + self + } + + pub fn content_type(mut self, ty: &str) -> Request<'a, 'b> { + if !self.content_type { + self.content_type = true; + append_header(&mut self.headers, "Content-Type", ty); + } + + self + } + + pub fn content_length(mut self, len: usize) -> Request<'a, 'b> { + self.body_type = Some(Fixed(len)); + self + } + + pub fn chunked(mut self) -> Request<'a, 'b> { + self.body_type = Some(Chunked); + self + } + + pub fn expect_continue(mut self) -> Request<'a, 'b> { + self.expect_continue = true; + self + } + + pub fn header(mut self, name: &str, val: &str) -> Request<'a, 'b> { + append_header(&mut self.headers, name, val); + self + } + + pub fn get_header(&self, name: &str) -> Option<&[String]> { + self.headers.get(name).map(|a| &a[..]) + } + + pub fn headers<'c, 'd, I: Iterator>(mut self, hdrs: I) -> Request<'a, 'b> { + for (name, val) in hdrs { + append_header(&mut self.headers, name, val); + } + + self + } + + pub fn progress(mut self, cb: F) -> Request<'a, 'b> + where F: FnMut(usize, usize, usize, usize) + 'b + { + self.progress = Some(Box::new(cb) as Box>); + self + } + + pub fn follow_redirects(mut self, follow: bool) -> Request<'a, 'b> { + self.follow = follow; + self + } + +} + + +fn append_header(map: &mut HashMap>, key: &str, val: &str) { + match map.entry(key.to_string()) { + Entry::Vacant(entry) => { + let mut values = Vec::new(); + values.push(val.to_string()); + entry.insert(values) + }, + Entry::Occupied(entry) => entry.into_mut() + }; +} + +pub trait ToUrl{ + fn with_url_str(self, f: F) where F: FnOnce(&str); +} + +impl<'a> ToUrl for &'a str { + fn with_url_str(self, f: F) where F: FnOnce(&str) { + f(self); + } +} + +impl<'a> ToUrl for &'a Url { + fn with_url_str(self, f: F) where F: FnOnce(&str) { + self.to_string().with_url_str(f); + } +} + +impl ToUrl for String { + fn with_url_str(self, f: F) where F: FnOnce(&str) { + self[..].with_url_str(f); + } +} + +#[cfg(test)] +mod tests { + use super::MultiHandle; + + #[test] + fn get_header() { + let mut h = MultiHandle::new(); + + let r = h.get("/foo").header("foo", "bar"); + assert_eq!(r.get_header("foo"), Some(&["bar".to_string()][..])); + } +} diff --git a/src/lib.rs b/src/lib.rs index d01ae143f3..14431378c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,10 @@ extern crate curl_sys as curl_ffi; #[cfg(unix)] extern crate openssl_sys as openssl; +pub use ffi::multi::Multi; pub use ffi::easy::ProgressCb; pub use ffi::err::ErrCode; +pub use ffi::multi_err::ErrCodeM; // Version accessors pub use ffi::version::{