├── .gitignore ├── .travis.yml ├── src ├── tojson.rs ├── response.rs ├── lib.rs └── request.rs ├── README.md ├── .gitlab-ci.yml ├── Cargo.toml ├── LICENSE-MIT └── tests └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | os: 7 | - linux 8 | - osx 9 | -------------------------------------------------------------------------------- /src/tojson.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "with_json")] 2 | use json; 3 | 4 | use response; 5 | 6 | pub trait ToJson { 7 | type ResultType; 8 | fn json(&self) -> Self::ResultType; 9 | } 10 | 11 | #[cfg(feature = "with_json")] 12 | impl ToJson for response::Response { 13 | type ResultType = json::Result; 14 | fn json(&self) -> ::ResultType { 15 | let text = self.text().unwrap(); 16 | json::parse(text) 17 | // self.text().map(|t| json::parse(t)).unwrap() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # requests-rs 2 | Rust HTTP client library styled after awesome Python requests 3 | 4 | Build status 5 | 6 | branch | status 7 | :-------|:------: 8 | master| [![Build Status](https://travis-ci.org/imp/requests-rs.svg?branch=master)](https://travis-ci.org/imp/requests-rs) 9 | develop| [![Build Status](https://travis-ci.org/imp/requests-rs.svg?branch=develop)](https://travis-ci.org/imp/requests-rs) 10 | 11 | [![Travis](https://img.shields.io/travis/imp/requests-rs.svg)](http:://travis-ci.org/imp/requests-rs) 12 | 13 | Building on OSX 10.11+ 14 | ``` 15 | export OPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include 16 | cargo clean 17 | cargo build 18 | ``` 19 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - test 3 | - deploy 4 | 5 | stable: 6 | image: rustdocker/rust:stable 7 | stage: test 8 | script: 9 | - cargo test --verbose --jobs 1 --release 10 | 11 | beta: 12 | image: rustdocker/rust:beta 13 | stage: test 14 | script: 15 | - cargo test --verbose --jobs 1 --release 16 | 17 | nightly: 18 | image: rustdocker/rust:nightly 19 | stage: test 20 | script: 21 | - cargo test --verbose --jobs 1 --release 22 | 23 | .pages: 24 | image: rustdocker/rust:stable 25 | stage: deploy 26 | only: 27 | - master 28 | script: 29 | - cargo doc 30 | - rm -rf public 31 | - mkdir public 32 | - cp -R target/doc/* public 33 | artifacts: 34 | paths: 35 | - public 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Cyril Plisko "] 3 | categories = ["web-programming::http-client"] 4 | description = "Rust HTTP client API styled after awesome Python requests - [WORK IN PROGRESS]" 5 | documentation = "https://docs.rs/requests" 6 | homepage = "https://gitlab.com/imp/requests-rs" 7 | keywords = ["http", "rest", "client", "python", "requests"] 8 | license = "MIT" 9 | name = "requests" 10 | readme = "README.md" 11 | repository = "https://gitlab.com/imp/requests-rs.git" 12 | version = "0.0.30" 13 | 14 | [badges] 15 | travis-ci = { repository = "imp/requests-rs" } 16 | gitlab = { repository = "imp/requests-rs" } 17 | 18 | [dependencies] 19 | hyper = "0.10" 20 | 21 | [dependencies.hyper-native-tls] 22 | optional = true 23 | version = "0.2" 24 | 25 | [dependencies.json] 26 | optional = true 27 | version = "0.11" 28 | 29 | [features] 30 | ssl = ["hyper-native-tls"] 31 | with_json = ["json"] 32 | default = ["ssl", "with_json"] 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cyril Plisko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::convert::From; 3 | use std::str; 4 | use hyper; 5 | use hyper::header::{Headers, ContentLength, ContentType}; 6 | 7 | pub use hyper::status::StatusCode; 8 | pub type Codes = StatusCode; 9 | pub type HyperResponse = hyper::client::Response; 10 | 11 | #[derive(Debug)] 12 | pub struct Response { 13 | content: Vec, 14 | inner: HyperResponse, 15 | } 16 | 17 | impl From for Response { 18 | fn from(mut raw: HyperResponse) -> Self { 19 | let mut content = match raw.headers.get::() { 20 | Some(&ContentLength(length)) => Vec::with_capacity(length as usize), 21 | None => Vec::new(), 22 | }; 23 | 24 | if raw.read_to_end(&mut content).is_err() { 25 | content = Vec::new() 26 | } 27 | 28 | Response { 29 | content: content, 30 | inner: raw, 31 | } 32 | } 33 | } 34 | 35 | impl<'a> Response { 36 | pub fn url(&self) -> &str { 37 | self.inner.url.as_str() 38 | } 39 | 40 | pub fn status_code(&self) -> Codes { 41 | self.inner.status 42 | } 43 | 44 | pub fn reason(&self) -> &str { 45 | self.inner.status.canonical_reason().unwrap_or("UNAVAILABLE") 46 | } 47 | 48 | pub fn ok(&self) -> bool { 49 | self.inner.status == StatusCode::Ok 50 | } 51 | 52 | pub fn text(&'a self) -> Option<&'a str> { 53 | str::from_utf8(&self.content).ok() 54 | } 55 | 56 | pub fn content(&'a self) -> &'a Vec { 57 | &self.content 58 | } 59 | 60 | pub fn is_json(&self) -> bool { 61 | self.inner.headers.get::() == Some(&ContentType::json()) 62 | } 63 | 64 | pub fn headers(&self) -> &Headers { 65 | &self.inner.headers 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! requests - HTTP client library with simple API.\ 2 | //! If you have used Python requests module you will find the API familiar. 3 | //! 4 | //! # Quick Start 5 | //! 6 | //! ```rust 7 | //! extern crate requests; 8 | //! use requests::ToJson; 9 | //! 10 | //! fn main() { 11 | //! let response = requests::get("http://httpbin.org/get").unwrap(); 12 | //! assert_eq!(response.url(), "http://httpbin.org/get"); 13 | //! assert_eq!(response.reason(), "OK"); 14 | //! assert_eq!(response.status_code(), requests::StatusCode::Ok); 15 | //! 16 | //! let data = response.json().unwrap(); 17 | //! assert_eq!(data["url"], "http://httpbin.org/get"); 18 | //! assert_eq!(data["headers"]["Host"], "httpbin.org"); 19 | //! assert_eq!(data["headers"]["User-Agent"], 20 | //! concat!("requests-rs/", env!("CARGO_PKG_VERSION"))); 21 | //! } 22 | //! ``` 23 | 24 | extern crate hyper; 25 | #[cfg(feature = "ssl")] 26 | extern crate hyper_native_tls; 27 | #[cfg(feature = "with_json")] 28 | extern crate json; 29 | 30 | mod request; 31 | mod response; 32 | mod tojson; 33 | 34 | pub use request::Request; 35 | pub use response::Response; 36 | pub use response::{Codes, StatusCode}; 37 | pub use tojson::ToJson; 38 | 39 | pub type Result = hyper::Result; 40 | pub type Error = hyper::error::Error; 41 | 42 | pub fn get>(url: T) -> Result { 43 | Request::default().get(url.as_ref()) 44 | } 45 | 46 | pub fn post>(url: T) -> Result { 47 | Request::default().post(url.as_ref()) 48 | } 49 | 50 | pub fn put>(url: T) -> Result { 51 | Request::default().put(url.as_ref()) 52 | } 53 | 54 | pub fn head>(url: T) -> Result { 55 | Request::default().head(url.as_ref()) 56 | } 57 | 58 | pub fn delete>(url: T) -> Result { 59 | Request::default().delete(url.as_ref()) 60 | } 61 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | use hyper::client::{Client, IntoUrl}; 2 | use hyper::header::{Headers, Accept, UserAgent}; 3 | use hyper::net::HttpsConnector; 4 | use hyper::Url; 5 | #[cfg(feature = "ssl")] 6 | use hyper_native_tls::NativeTlsClient; 7 | 8 | use super::Response; 9 | use super::Result; 10 | 11 | const DEFAULT_USER_AGENT: &'static str = concat!("requests-rs/", env!("CARGO_PKG_VERSION")); 12 | 13 | 14 | fn get_hyper_client(url: &Url) -> Client { 15 | if url.scheme() == "https" { 16 | let ssl = NativeTlsClient::new().unwrap(); 17 | let connector = HttpsConnector::new(ssl); 18 | Client::with_connector(connector) 19 | } else { 20 | Client::new() 21 | } 22 | 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct Request { 27 | headers: Headers, 28 | } 29 | 30 | impl Default for Request { 31 | fn default() -> Self { 32 | let mut request = Request::new(); 33 | request.user_agent(DEFAULT_USER_AGENT); 34 | request 35 | } 36 | } 37 | 38 | impl Request { 39 | pub fn new() -> Self { 40 | Request { headers: Headers::new() } 41 | } 42 | 43 | pub fn json() -> Self { 44 | let mut request = Request::new(); 45 | request.user_agent(DEFAULT_USER_AGENT); 46 | request.headers.set(Accept::json()); 47 | request 48 | } 49 | 50 | pub fn user_agent(&mut self, ua: &str) { 51 | self.headers.set(UserAgent(ua.to_owned())) 52 | } 53 | 54 | pub fn get(&self, url: U) -> Result { 55 | let url = url.into_url()?; 56 | get_hyper_client(&url) 57 | .get(url) 58 | .headers(self.headers.clone()) 59 | .send() 60 | .map(Response::from) 61 | } 62 | 63 | pub fn post(&self, url: U) -> Result { 64 | let url = url.into_url()?; 65 | get_hyper_client(&url) 66 | .post(url) 67 | .headers(self.headers.clone()) 68 | .send() 69 | .map(Response::from) 70 | } 71 | 72 | pub fn put(&self, url: U) -> Result { 73 | let url = url.into_url()?; 74 | get_hyper_client(&url) 75 | .put(url) 76 | .headers(self.headers.clone()) 77 | .send() 78 | .map(Response::from) 79 | } 80 | 81 | pub fn head(&self, url: U) -> Result { 82 | let url = url.into_url()?; 83 | get_hyper_client(&url) 84 | .head(url) 85 | .headers(self.headers.clone()) 86 | .send() 87 | .map(Response::from) 88 | } 89 | 90 | pub fn delete(&self, url: U) -> Result { 91 | let url = url.into_url()?; 92 | get_hyper_client(&url) 93 | .delete(url) 94 | .headers(self.headers.clone()) 95 | .send() 96 | .map(Response::from) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate hyper; 2 | extern crate requests; 3 | #[cfg(feature = "with_json")] 4 | extern crate json; 5 | 6 | // use requests::Codes; 7 | use requests::{delete, get, head, post, put}; 8 | use requests::{Codes, Request, Response, StatusCode}; 9 | use requests::ToJson; 10 | 11 | fn assert_response_is_ok(response: &Response, url: &str) { 12 | assert_eq!(response.url(), url); 13 | assert_eq!(response.status_code(), StatusCode::Ok); 14 | assert_eq!(response.reason(), "OK"); 15 | } 16 | 17 | #[test] 18 | fn simple_get() { 19 | const URL: &'static str = "http://httpbin.org/get"; 20 | let res = get(URL).unwrap(); 21 | assert_response_is_ok(&res, URL); 22 | let data = res.json().unwrap(); 23 | println!("{:?}", data); 24 | assert_eq!(data["url"], URL); 25 | assert_eq!(data["headers"]["Host"], "httpbin.org"); 26 | assert_eq!(data["headers"]["User-Agent"], 27 | concat!("requests-rs/", env!("CARGO_PKG_VERSION"))); 28 | } 29 | 30 | #[test] 31 | fn simple_get_string_url_https() { 32 | const URL: &'static str = "https://httpbin.org/get"; 33 | let res = get(String::from(URL)).unwrap(); 34 | assert_response_is_ok(&res, URL); 35 | let res = get(&String::from(URL)).unwrap(); 36 | assert_response_is_ok(&res, URL); 37 | } 38 | 39 | #[test] 40 | fn simple_get_string_url() { 41 | const URL: &'static str = "http://httpbin.org/get"; 42 | let res = get(String::from(URL)).unwrap(); 43 | assert_response_is_ok(&res, URL); 44 | let res = get(&String::from(URL)).unwrap(); 45 | assert_response_is_ok(&res, URL); 46 | } 47 | 48 | #[test] 49 | fn simple_post() { 50 | const URL: &'static str = "http://httpbin.org/post"; 51 | let res = post(URL).unwrap(); 52 | assert_response_is_ok(&res, URL); 53 | } 54 | 55 | #[test] 56 | fn simple_put() { 57 | const URL: &'static str = "http://httpbin.org/put"; 58 | let res = put(URL).unwrap(); 59 | assert_response_is_ok(&res, URL); 60 | } 61 | 62 | #[test] 63 | fn simple_head() { 64 | const URL: &'static str = "http://httpbin.org/get"; 65 | let res = head(URL).unwrap(); 66 | assert_response_is_ok(&res, URL); 67 | } 68 | 69 | #[test] 70 | fn simple_delete() { 71 | const URL: &'static str = "http://httpbin.org/delete"; 72 | let res = delete(URL).unwrap(); 73 | assert_response_is_ok(&res, URL); 74 | } 75 | 76 | #[test] 77 | fn user_agent() { 78 | let useragent = concat!("{\n \"user-agent\": \"requests-rs/", 79 | env!("CARGO_PKG_VERSION"), 80 | "\"\n}\n"); 81 | const URL: &'static str = "http://httpbin.org/user-agent"; 82 | let res = get(URL).unwrap(); 83 | assert_response_is_ok(&res, URL); 84 | assert_eq!(res.text(), Some(useragent)); 85 | } 86 | 87 | #[test] 88 | fn custom_user_agent() { 89 | const UA: &'static str = concat!("requests-rs-tests/", env!("CARGO_PKG_VERSION")); 90 | const URL: &'static str = "http://httpbin.org/user-agent"; 91 | let mut request = Request::new(); 92 | request.user_agent(UA); 93 | let res = request.get(URL).unwrap(); 94 | assert_response_is_ok(&res, URL); 95 | assert!(res.is_json()); 96 | 97 | let ua = res.json().unwrap(); 98 | assert_eq!(ua["user-agent"], UA); 99 | } 100 | 101 | #[test] 102 | fn user_agent_json() { 103 | 104 | const URL: &'static str = "http://httpbin.org/user-agent"; 105 | let res = get(URL).unwrap(); 106 | assert_response_is_ok(&res, URL); 107 | assert!(res.is_json()); 108 | 109 | let ua = res.json().unwrap(); 110 | assert_eq!(ua["user-agent"], 111 | concat!("requests-rs/", env!("CARGO_PKG_VERSION"))); 112 | } 113 | 114 | #[test] 115 | fn content() { 116 | const URL: &'static str = "http://httpbin.org/headers"; 117 | let content = concat!("{\n", 118 | " \"headers\": {\n", 119 | " \"Connection\": \"close\", \n", 120 | " \"Host\": \"httpbin.org\", \n", 121 | " \"User-Agent\": \"requests-rs/", env!("CARGO_PKG_VERSION"), "\"\n", 122 | " }\n", 123 | "}\n"); 124 | 125 | let res = get(URL).unwrap(); 126 | assert_response_is_ok(&res, URL); 127 | println!("{}", res.text().unwrap()); 128 | println!("{}", content); 129 | assert_eq!(res.content(), &content.as_bytes()); 130 | } 131 | 132 | #[test] 133 | fn headers() { 134 | use hyper::header::UserAgent; 135 | 136 | const URL: &'static str = "http://httpbin.org/response-headers?User-Agent=requests-rs-test"; 137 | let res = get(URL).unwrap(); 138 | assert_response_is_ok(&res, URL); 139 | println!("{:?}", res.text()); 140 | println!("{:?}", res.headers()); 141 | println!("{:?}", res.headers().get::()); 142 | assert_eq!(res.headers().get::().unwrap(), 143 | &UserAgent("requests-rs-test".to_owned())); 144 | } 145 | 146 | #[cfg(feature = "with_json")] 147 | #[test] 148 | fn accept_json() { 149 | const URL: &'static str = "http://httpbin.org/headers"; 150 | let res = Request::json().get(URL).unwrap(); 151 | assert_response_is_ok(&res, URL); 152 | assert!(res.is_json()); 153 | let data = res.json().unwrap(); 154 | println!("{:?}", data); 155 | assert_eq!(data["headers"]["Accept"], "application/json"); 156 | } 157 | 158 | macro_rules! status_code_test { 159 | ($($name:ident: $numeric:expr,)+) => { 160 | $(#[test] 161 | fn $name() { 162 | let res = get(&format!("http://httpbin.org/status/{}", $numeric)).unwrap(); 163 | println!("{}", res.text().unwrap()); 164 | assert_eq!(res.status_code(), Codes::from_u16($numeric)); 165 | })+ 166 | } 167 | } 168 | 169 | status_code_test! { 170 | status_code_100: 100, 171 | status_code_101: 101, 172 | status_code_102: 102, 173 | status_code_200: 200, 174 | status_code_201: 201, 175 | status_code_202: 202, 176 | status_code_203: 203, 177 | status_code_204: 204, 178 | status_code_205: 205, 179 | status_code_206: 206, 180 | status_code_207: 207, 181 | status_code_208: 208, 182 | status_code_226: 226, 183 | status_code_300: 300, 184 | // status_code_301: 301, 185 | // status_code_302: 302, 186 | // status_code_303: 303, 187 | status_code_304: 304, 188 | // status_code_305: 305, 189 | // status_code_307: 307, 190 | status_code_308: 308, 191 | status_code_400: 400, 192 | status_code_401: 401, 193 | status_code_402: 402, 194 | status_code_403: 403, 195 | status_code_404: 404, 196 | status_code_405: 405, 197 | status_code_406: 406, 198 | status_code_407: 407, 199 | status_code_408: 408, 200 | status_code_409: 409, 201 | status_code_410: 410, 202 | status_code_411: 411, 203 | status_code_412: 412, 204 | status_code_413: 413, 205 | status_code_414: 414, 206 | status_code_415: 415, 207 | status_code_416: 416, 208 | status_code_417: 417, 209 | status_code_418: 418, 210 | status_code_419: 419, 211 | status_code_420: 420, 212 | status_code_421: 421, 213 | status_code_422: 422, 214 | status_code_423: 423, 215 | status_code_424: 424, 216 | status_code_426: 426, 217 | status_code_428: 428, 218 | status_code_429: 429, 219 | status_code_431: 431, 220 | status_code_451: 451, 221 | status_code_500: 500, 222 | status_code_501: 501, 223 | status_code_502: 502, 224 | status_code_503: 503, 225 | status_code_504: 504, 226 | status_code_505: 505, 227 | status_code_506: 506, 228 | status_code_507: 507, 229 | status_code_508: 508, 230 | status_code_510: 510, 231 | status_code_511: 511, 232 | } 233 | --------------------------------------------------------------------------------