This commit is contained in:
Martin Algesten
2018-06-16 10:50:23 +02:00
parent d1cf9fde3b
commit 5b237066e5
7 changed files with 171 additions and 64 deletions

View File

@@ -10,6 +10,34 @@ include!("response.rs");
include!("conn.rs"); include!("conn.rs");
include!("stream.rs"); include!("stream.rs");
/// Agents keep state between requests.
///
/// By default, no state, such as cookies, is kept between requests.
/// But by creating an agent as entry point for the request, we
/// can keep a state.
///
/// ```
/// let agent = ureq::agent().build();
///
/// let auth = agent
/// .post("/login")
/// .auth("martin", "rubbermashgum")
/// .call(); // blocks. puts auth cookies in agent.
///
/// if !auth.ok() {
/// println!("Noes!");
/// }
///
/// let secret = agent
/// .get("/my-protected-page")
/// .call(); // blocks and waits for request.
///
/// if !secret.ok() {
/// println!("Wot?!");
/// }
///
/// println!("Secret is: {}", secret.into_string().unwrap());
/// ```
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Agent { pub struct Agent {
headers: Vec<Header>, headers: Vec<Header>,
@@ -32,6 +60,16 @@ impl AgentState {
} }
impl Agent { impl Agent {
/// Creates a new agent. Typically you'd use [`ureq::agent()`](fn.agent.html) to
/// do this.
///
/// ```
/// let agent = ureq::Agent::new()
/// .set("X-My-Header", "Foo") // present on all requests from this agent
/// .build();
///
/// agent.get("/foo");
/// ```
pub fn new() -> Agent { pub fn new() -> Agent {
Default::default() Default::default()
} }
@@ -213,54 +251,71 @@ impl Agent {
} }
} }
/// Make a GET request from this agent.
pub fn get<S>(&self, path: S) -> Request pub fn get<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("GET", path) self.request("GET", path)
} }
/// Make a HEAD request from this agent.
pub fn head<S>(&self, path: S) -> Request pub fn head<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("HEAD", path) self.request("HEAD", path)
} }
/// Make a POST request from this agent.
pub fn post<S>(&self, path: S) -> Request pub fn post<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("POST", path) self.request("POST", path)
} }
/// Make a PUT request from this agent.
pub fn put<S>(&self, path: S) -> Request pub fn put<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("PUT", path) self.request("PUT", path)
} }
/// Make a DELETE request from this agent.
pub fn delete<S>(&self, path: S) -> Request pub fn delete<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("DELETE", path) self.request("DELETE", path)
} }
/// Make a TRACE request from this agent.
pub fn trace<S>(&self, path: S) -> Request pub fn trace<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("TRACE", path) self.request("TRACE", path)
} }
/// Make a OPTIONS request from this agent.
pub fn options<S>(&self, path: S) -> Request pub fn options<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("OPTIONS", path) self.request("OPTIONS", path)
} }
/// Make a CONNECT request from this agent.
pub fn connect<S>(&self, path: S) -> Request pub fn connect<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
self.request("CONNECT", path) self.request("CONNECT", path)
} }
/// Make a PATCH request from this agent.
pub fn patch<S>(&self, path: S) -> Request pub fn patch<S>(&self, path: S) -> Request
where where
S: Into<String>, S: Into<String>,

View File

@@ -13,7 +13,9 @@ impl Header {
/// The header name. /// The header name.
/// ///
/// ``` /// ```
/// let header = "X-Forwarded-For: 127.0.0.1".parse::<ureq::Header>().unwrap(); /// let header = "X-Forwarded-For: 127.0.0.1"
/// .parse::<ureq::Header>()
/// .unwrap();
/// assert_eq!("X-Forwarded-For", header.name()); /// assert_eq!("X-Forwarded-For", header.name());
/// ``` /// ```
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
@@ -23,7 +25,9 @@ impl Header {
/// The header value. /// The header value.
/// ///
/// ``` /// ```
/// let header = "X-Forwarded-For: 127.0.0.1".parse::<ureq::Header>().unwrap(); /// let header = "X-Forwarded-For: 127.0.0.1"
/// .parse::<ureq::Header>()
/// .unwrap();
/// assert_eq!("127.0.0.1", header.value()); /// assert_eq!("127.0.0.1", header.value());
/// ``` /// ```
pub fn value(&self) -> &str { pub fn value(&self) -> &str {
@@ -33,7 +37,9 @@ impl Header {
/// Compares the given str to the header name ignoring case. /// Compares the given str to the header name ignoring case.
/// ///
/// ``` /// ```
/// let header = "X-Forwarded-For: 127.0.0.1".parse::<ureq::Header>().unwrap(); /// let header = "X-Forwarded-For: 127.0.0.1"
/// .parse::<ureq::Header>()
/// .unwrap();
/// assert!(header.is_name("x-forwarded-for")); /// assert!(header.is_name("x-forwarded-for"));
/// ``` /// ```
pub fn is_name(&self, other: &str) -> bool { pub fn is_name(&self, other: &str) -> bool {

View File

@@ -1,3 +1,28 @@
//! ureq is a minimal request library.
//!
//! The goals of this library are:
//!
//! * Minimal dependency tree
//! * Obvious API
//!
//! # Plain requests
//!
//! Most standard methods (GET, POST, PUT etc), are supported as functions from the
//! top of the library ([`ureq::get`](fn.get.html), [`ureq::post`](fn.post.html),
//! [`ureq::put`](fn.out.html), etc).
//!
//! These top level http method functions create a [Request](struct.Request.html) instance
//! which follows a build pattern. The builders are finished using
//! [`.call()`](struct.Request.html#method.call),
//! [`.send_str()`](struct.Request.html#method.send_str) or
//! [`.send_json()`](struct.Request.html#method.send_json).
//!
//! # Agents
//!
//! To maintain a state, cookies, between requests, you use an [agent](struct.Agent.html).
//! Agents also follow the build pattern. Agents are created with `ureq::agent().build()`.
//!
extern crate ascii; extern crate ascii;
extern crate base64; extern crate base64;
extern crate chunked_transfer; extern crate chunked_transfer;
@@ -25,41 +50,19 @@ pub use agent::{Agent, Request, Response};
pub use header::Header; pub use header::Header;
// re-export // re-export
pub use serde_json::{to_value, Map, Value}; pub use serde_json::{to_value as serde_to_value, Map as SerdeMap, Value as SerdeValue};
pub use cookie::Cookie; pub use cookie::Cookie;
/// Agents keep state between requests. /// Agents are used to keep state between requests.
///
/// By default, no state, such as cookies, is kept between requests.
/// But by creating an agent as entry point for the request, we
/// can keep state.
///
/// ```
/// let agent = ureq::agent();
///
/// let auth = agent
/// .post("/login")
/// .auth("martin", "rubbermashgum")
/// .call(); // blocks. puts auth cookies in agent.
///
/// if !auth.ok() {
/// println!("Noes!");
/// }
///
/// let secret = agent
/// .get("/my-protected-page")
/// .call(); // blocks and waits for request.
///
/// if !secret.ok() {
/// println!("Wot?!");
/// }
///
/// println!("Secret is: {}", secret.into_string().unwrap());
/// ```
pub fn agent() -> Agent { pub fn agent() -> Agent {
Agent::new() Agent::new()
} }
/// Make a request setting the HTTP method via a string.
///
/// ```
/// ureq::request("GET", "https://www.google.com").call();
/// ```
pub fn request<M, S>(method: M, path: S) -> Request pub fn request<M, S>(method: M, path: S) -> Request
where where
M: Into<String>, M: Into<String>,
@@ -68,54 +71,71 @@ where
Agent::new().request(method, path) Agent::new().request(method, path)
} }
/// Make a GET request.
pub fn get<S>(path: S) -> Request pub fn get<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("GET", path) request("GET", path)
} }
/// Make a HEAD request.
pub fn head<S>(path: S) -> Request pub fn head<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("HEAD", path) request("HEAD", path)
} }
/// Make a POST request.
pub fn post<S>(path: S) -> Request pub fn post<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("POST", path) request("POST", path)
} }
/// Make a PUT request.
pub fn put<S>(path: S) -> Request pub fn put<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("PUT", path) request("PUT", path)
} }
/// Make a DELETE request.
pub fn delete<S>(path: S) -> Request pub fn delete<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("DELETE", path) request("DELETE", path)
} }
/// Make a TRACE request.
pub fn trace<S>(path: S) -> Request pub fn trace<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("TRACE", path) request("TRACE", path)
} }
/// Make an OPTIONS request.
pub fn options<S>(path: S) -> Request pub fn options<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("OPTIONS", path) request("OPTIONS", path)
} }
/// Make an CONNECT request.
pub fn connect<S>(path: S) -> Request pub fn connect<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,
{ {
request("CONNECT", path) request("CONNECT", path)
} }
/// Make an PATCH request.
pub fn patch<S>(path: S) -> Request pub fn patch<S>(path: S) -> Request
where where
S: Into<String>, S: Into<String>,

View File

@@ -1,3 +1,19 @@
/// Create a `HashMap` from a shorthand notation.
///
/// ```
/// #[macro_use]
/// extern crate ureq;
///
/// fn main() {
/// let headers = map! {
/// "X-API-Key" => "foobar",
/// "Accept" => "application/json"
/// };
///
/// let agent = ureq::agent().set_map(headers).build();
/// }
/// ```
#[macro_export] #[macro_export]
macro_rules! map( macro_rules! map(
{ $($key:expr => $value:expr),* } => { { $($key:expr => $value:expr),* } => {

View File

@@ -1,13 +1,23 @@
use qstring::QString; use qstring::QString;
use serde_json; use super::SerdeValue;
use std::sync::Arc; use std::sync::Arc;
use std::io::Cursor; use std::io::Cursor;
use std::io::empty; use std::io::empty;
use serde_json;
lazy_static! { lazy_static! {
static ref URL_BASE: Url = { Url::parse("http://localhost/").expect("Failed to parse URL_BASE") }; static ref URL_BASE: Url = { Url::parse("http://localhost/").expect("Failed to parse URL_BASE") };
} }
/// Request instances are builders that creates a request.
///
/// ```
/// let mut request = ureq::get("https://www.google.com/");
///
/// let response = request
/// .query("foo", "bar baz") // add ?foo=bar%20baz
/// .call(); // run the request
/// ```
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Request { pub struct Request {
state: Arc<Mutex<Option<AgentState>>>, state: Arc<Mutex<Option<AgentState>>>,
@@ -28,7 +38,7 @@ pub struct Request {
enum Payload { enum Payload {
Empty, Empty,
Text(String), Text(String),
JSON(serde_json::Value), JSON(SerdeValue),
Reader(Box<Read + 'static>), Reader(Box<Read + 'static>),
} }
@@ -137,7 +147,7 @@ impl Request {
/// println!("{:?}", r); /// println!("{:?}", r);
/// } /// }
/// ``` /// ```
pub fn send_json(&mut self, data: serde_json::Value) -> Response { pub fn send_json(&mut self, data: SerdeValue) -> Response {
self.do_call(Payload::JSON(data)) self.do_call(Payload::JSON(data))
} }

View File

@@ -12,7 +12,11 @@ use error::Error;
const DEFAULT_CONTENT_TYPE: &'static str = "text/plain"; const DEFAULT_CONTENT_TYPE: &'static str = "text/plain";
const DEFAULT_CHARACTER_SET: &'static str = "utf-8"; const DEFAULT_CHARACTER_SET: &'static str = "utf-8";
/// Response instances are created as results of firing off requests.
///
///
pub struct Response { pub struct Response {
error: Option<Error>,
status_line: AsciiString, status_line: AsciiString,
index: (usize, usize), // index into status_line where we split: HTTP/1.1 200 OK index: (usize, usize), // index into status_line where we split: HTTP/1.1 200 OK
status: u16, status: u16,
@@ -32,22 +36,22 @@ impl ::std::fmt::Debug for Response {
} }
impl Response { impl Response {
/// The entire status line like: HTTP/1.1 200 OK /// The entire status line like: `HTTP/1.1 200 OK`
pub fn status_line(&self) -> &str { pub fn status_line(&self) -> &str {
self.status_line.as_str() self.status_line.as_str()
} }
/// The http version: HTTP/1.1 /// The http version: `HTTP/1.1`
pub fn http_version(&self) -> &str { pub fn http_version(&self) -> &str {
&self.status_line.as_str()[0..self.index.0] &self.status_line.as_str()[0..self.index.0]
} }
/// The status as a u16: 200 /// The status as a u16: `200`
pub fn status(&self) -> &u16 { pub fn status(&self) -> &u16 {
&self.status &self.status
} }
/// The status text: OK /// The status text: `OK`
pub fn status_text(&self) -> &str { pub fn status_text(&self) -> &str {
&self.status_line.as_str()[self.index.1 + 1..].trim() &self.status_line.as_str()[self.index.1 + 1..].trim()
} }
@@ -170,15 +174,14 @@ impl Response {
let is_chunked = self.header("transfer-encoding") let is_chunked = self.header("transfer-encoding")
.map(|enc| enc.len() > 0) // whatever it says, do chunked .map(|enc| enc.len() > 0) // whatever it says, do chunked
.unwrap_or(false); .unwrap_or(false);
let len = self.header("content-length").and_then(|l| l.parse::<usize>().ok()); let len = self.header("content-length")
.and_then(|l| l.parse::<usize>().ok());
let reader = self.stream.expect("No reader in response?!"); let reader = self.stream.expect("No reader in response?!");
match is_chunked { match is_chunked {
true => Box::new(chunked_transfer::Decoder::new(reader)), true => Box::new(chunked_transfer::Decoder::new(reader)),
false => { false => match len {
match len {
Some(len) => Box::new(LimitedRead::new(reader, len)), Some(len) => Box::new(LimitedRead::new(reader, len)),
None => Box::new(reader) as Box<Read>, None => Box::new(reader) as Box<Read>,
}
}, },
} }
} }
@@ -264,13 +267,11 @@ impl Response {
/// ///
/// assert_eq!(*resp.status(), 401); /// assert_eq!(*resp.status(), 401);
/// ``` /// ```
pub fn from_read(reader: impl Read) -> Self pub fn from_read(reader: impl Read) -> Self {
{
Self::do_from_read(reader).unwrap_or_else(|e| e.into()) Self::do_from_read(reader).unwrap_or_else(|e| e.into())
} }
fn do_from_read(mut reader: impl Read) -> Result<Response, Error> fn do_from_read(mut reader: impl Read) -> Result<Response, Error> {
{
// //
// HTTP/1.1 200 OK\r\n // HTTP/1.1 200 OK\r\n
let status_line = read_next_line(&mut reader).map_err(|_| Error::BadStatus)?; let status_line = read_next_line(&mut reader).map_err(|_| Error::BadStatus)?;
@@ -305,7 +306,6 @@ impl Response {
pub fn to_write_vec(&self) -> Vec<u8> { pub fn to_write_vec(&self) -> Vec<u8> {
self.stream.as_ref().unwrap().to_write_vec() self.stream.as_ref().unwrap().to_write_vec()
} }
} }
fn parse_status_line(line: &str) -> Result<((usize, usize), u16), Error> { fn parse_status_line(line: &str) -> Result<((usize, usize), u16), Error> {
@@ -405,8 +405,8 @@ impl Read for LimitedRead {
Ok(amount) => { Ok(amount) => {
self.position += amount; self.position += amount;
Ok(amount) Ok(amount)
}, }
Err(e) => Err(e) Err(e) => Err(e),
} }
} }
} }

View File

@@ -247,32 +247,32 @@ macro_rules! json_internal {
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
(null) => { (null) => {
$crate::Value::Null $crate::SerdeValue::Null
}; };
(true) => { (true) => {
$crate::Value::Bool(true) $crate::SerdeValue::Bool(true)
}; };
(false) => { (false) => {
$crate::Value::Bool(false) $crate::SerdeValue::Bool(false)
}; };
([]) => { ([]) => {
$crate::Value::Array(vec![]) $crate::SerdeValue::Array(vec![])
}; };
([ $($tt:tt)+ ]) => { ([ $($tt:tt)+ ]) => {
$crate::Value::Array(json_internal!(@array [] $($tt)+)) $crate::SerdeValue::Array(json_internal!(@array [] $($tt)+))
}; };
({}) => { ({}) => {
$crate::Value::Object($crate::Map::new()) $crate::SerdeValue::Object($crate::SerdeMap::new())
}; };
({ $($tt:tt)+ }) => { ({ $($tt:tt)+ }) => {
$crate::Value::Object({ $crate::SerdeValue::Object({
let mut object = $crate::Map::new(); let mut object = $crate::SerdeMap::new();
json_internal!(@object object () ($($tt)+) ($($tt)+)); json_internal!(@object object () ($($tt)+) ($($tt)+));
object object
}) })
@@ -281,6 +281,6 @@ macro_rules! json_internal {
// Any Serialize type: numbers, strings, struct literals, variables etc. // Any Serialize type: numbers, strings, struct literals, variables etc.
// Must be below every other rule. // Must be below every other rule.
($other:expr) => { ($other:expr) => {
$crate::to_value(&$other).unwrap() $crate::serde_to_value(&$other).unwrap()
}; };
} }