reduce dependency tree by features

This commit is contained in:
Martin Algesten
2018-06-22 14:03:25 +02:00
parent 20bb7cbbde
commit cd4a1722fa
9 changed files with 140 additions and 42 deletions

View File

@@ -9,15 +9,21 @@ readme = "README.md"
keywords = ["web", "request", "http", "rest", "client"] keywords = ["web", "request", "http", "rest", "client"]
categories = ["web-programming::http-client"] categories = ["web-programming::http-client"]
[features]
default = ["tls"]
json = ["serde_json"]
charset = ["encoding"]
tls = ["native-tls"]
[dependencies] [dependencies]
ascii = "0.9" ascii = "0.9"
base64 = "0.9" base64 = "0.9"
chunked_transfer = "0.3" chunked_transfer = "0.3"
cookie = { version = "0.10", features = ["percent-encode"] } cookie = { version = "0.10", features = ["percent-encode"] }
dns-lookup = "0.9.1" dns-lookup = "0.9.1"
encoding = "0.2"
lazy_static = "1" lazy_static = "1"
qstring = "0.6" qstring = "0.6"
native-tls = "0.1"
serde_json = "1"
url = "1.6" url = "1.6"
native-tls = { version = "0.1", optional = true }
serde_json = { version = "1", optional = true }
encoding = { version = "0.2", optional = true }

View File

@@ -5,6 +5,7 @@
## Usage ## Usage
```rust ```rust
// requires feature: `ureq = { version = "*", features = ["json"] }`
#[macro_use] #[macro_use]
extern crate ureq; extern crate ureq;
@@ -25,6 +26,21 @@ fn main() {
} }
``` ```
## Features
To enable a minimal dependency tree, some features are off by default.
You can control them when including `ureq` as a dependency.
```
ureq = { version = "*", features = ["json", "charset"] }
```
* `tls` enables https. This is enabled by default.
* `json` enables `response.into_json()` and `request.send_json()` serde json.
* `charset` enables interpreting the charset part of
`Content-Type: text/plain; charset=iso-8859-1`. Without this, the library
defaults to rust's built in `utf-8`.
## Motivation ## Motivation
* Minimal dependency tree * Minimal dependency tree

View File

@@ -89,7 +89,7 @@ impl Agent {
/// ``` /// ```
/// let agent = ureq::agent() /// let agent = ureq::agent()
/// .set("X-API-Key", "foobar") /// .set("X-API-Key", "foobar")
/// .set("Accept", "application/json") /// .set("Accept", "text/plain")
/// .build(); /// .build();
/// ///
/// let r = agent /// let r = agent
@@ -97,7 +97,7 @@ impl Agent {
/// .call(); /// .call();
/// ///
/// if r.ok() { /// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap()); /// println!("yay got {}", r.into_string().unwrap());
/// } else { /// } else {
/// println!("Oh no error!"); /// println!("Oh no error!");
/// } /// }
@@ -123,7 +123,7 @@ impl Agent {
/// let agent = ureq::agent() /// let agent = ureq::agent()
/// .set_map(map! { /// .set_map(map! {
/// "X-API-Key" => "foobar", /// "X-API-Key" => "foobar",
/// "Accept" => "application/json" /// "Accept" => "text/plain"
/// }) /// })
/// .build(); /// .build();
/// ///
@@ -132,7 +132,7 @@ impl Agent {
/// .call(); /// .call();
/// ///
/// if r.ok() { /// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap()); /// println!("yay got {}", r.into_string().unwrap());
/// } /// }
/// } /// }
/// ``` /// ```
@@ -402,6 +402,7 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "json")]
fn parse_simple_json() { fn parse_simple_json() {
let s = format!("HTTP/1.1 200 OK\r\n\r\n{{\"hello\":\"world\"}}"); let s = format!("HTTP/1.1 200 OK\r\n\r\n{{\"hello\":\"world\"}}");
let resp = s.parse::<Response>().unwrap(); let resp = s.parse::<Response>().unwrap();

View File

@@ -6,6 +6,7 @@
//! * Obvious API //! * Obvious API
//! //!
//! ``` //! ```
//! // requires feature: `ureq = { version = "*", features = ["json"] }`
//! #[macro_use] //! #[macro_use]
//! extern crate ureq; //! extern crate ureq;
//! //!
@@ -37,7 +38,14 @@
//! * [`.call()`](struct.Request.html#method.call) without a request body. //! * [`.call()`](struct.Request.html#method.call) without a request body.
//! * [`.send()`](struct.Request.html#method.send) with a request body as `Read`. //! * [`.send()`](struct.Request.html#method.send) with a request body as `Read`.
//! * [`.send_string()`](struct.Request.html#method.send_string) body as string. //! * [`.send_string()`](struct.Request.html#method.send_string) body as string.
//! * [`.send_json()`](struct.Request.html#method.send_json) body as serde json. //!
//! # JSON
//!
//! By enabling the `ureq = { version = "*", features = ["json"] }` feature,
//! the library supports serde json.
//!
//! * [`request.send_json()`](struct.Request.html#method.send_json) send body as serde json.
//! * [`response.into_json()`](struct.Response.html#method.into_json) transform response to json.
//! //!
//! # Agents //! # Agents
//! //!
@@ -70,12 +78,15 @@
//! //!
//! # Character encoding //! # Character encoding
//! //!
//! By enabling the `ureq = { version = "*", features = ["charset"] }` feature,
//! the library supports sending/receiving other character sets than `utf-8`.
//!
//! For [`response.into_string()`](struct.Response.html#method.into_string) we read the //! For [`response.into_string()`](struct.Response.html#method.into_string) we read the
//! header `Content-Type: text/plain; charset=iso-8859-1` and if it contains a charset //! header `Content-Type: text/plain; charset=iso-8859-1` and if it contains a charset
//! specification, we try to decode the body using that encoding. In the absence of, or failing //! specification, we try to decode the body using that encoding. In the absence of, or failing
//! to interpret the charset, we fall back on `utf-8`. //! to interpret the charset, we fall back on `utf-8`.
//! //!
//! Similarly when using [`.send_string()`](struct.Request.html#method.send_string), to //! Similarly when using [`request.send_string()`](struct.Request.html#method.send_string),
//! we first check if the user has set a `; charset=<whatwg charset>` and attempt //! we first check if the user has set a `; charset=<whatwg charset>` and attempt
//! to encode the request body using that. //! to encode the request body using that.
//! //!
@@ -84,18 +95,24 @@ extern crate base64;
extern crate chunked_transfer; extern crate chunked_transfer;
extern crate cookie; extern crate cookie;
extern crate dns_lookup; extern crate dns_lookup;
extern crate encoding;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate native_tls;
extern crate qstring; extern crate qstring;
extern crate serde_json;
extern crate url; extern crate url;
#[cfg(feature = "charset")]
extern crate encoding;
#[cfg(feature = "tls")]
extern crate native_tls;
#[cfg(feature = "json")]
extern crate serde_json;
mod agent; mod agent;
mod error; mod error;
mod header; mod header;
mod macros; mod macros;
#[cfg(feature = "json")]
mod serde_macros; mod serde_macros;
#[cfg(test)] #[cfg(test)]
@@ -107,6 +124,7 @@ pub use header::Header;
// re-export // re-export
pub use cookie::Cookie; pub use cookie::Cookie;
#[cfg(feature = "json")]
pub use serde_json::{to_value as serde_to_value, Map as SerdeMap, Value as SerdeValue}; pub use serde_json::{to_value as serde_to_value, Map as SerdeMap, Value as SerdeValue};
/// Agents are used to keep state between requests. /// Agents are used to keep state between requests.
@@ -214,6 +232,7 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "tls")]
fn connect_https_google() { fn connect_https_google() {
let resp = get("https://www.google.com/").call(); let resp = get("https://www.google.com/").call();
assert_eq!( assert_eq!(

View File

@@ -1,10 +1,13 @@
use super::SerdeValue;
use qstring::QString; use qstring::QString;
use serde_json;
use std::io::empty; use std::io::empty;
use std::io::Cursor; use std::io::Cursor;
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "json")]
use super::SerdeValue;
#[cfg(feature = "json")]
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") };
} }
@@ -48,7 +51,7 @@ impl ::std::fmt::Debug for Request {
enum Payload { enum Payload {
Empty, Empty,
Text(String, String), Text(String, String),
JSON(SerdeValue), #[cfg(feature = "json")] JSON(SerdeValue),
Reader(Box<Read + 'static>), Reader(Box<Read + 'static>),
} }
@@ -73,15 +76,21 @@ impl Payload {
fn into_read(self) -> SizedReader { fn into_read(self) -> SizedReader {
match self { match self {
Payload::Empty => SizedReader::new(None, Box::new(empty())), Payload::Empty => SizedReader::new(None, Box::new(empty())),
Payload::Text(text, charset) => { Payload::Text(text, _charset) => {
let encoding = encoding_from_whatwg_label(&charset) #[cfg(feature = "charset")]
let bytes = {
let encoding = encoding_from_whatwg_label(&_charset)
.or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET)) .or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET))
.unwrap(); .unwrap();
let bytes = encoding.encode(&text, EncoderTrap::Replace).unwrap(); encoding.encode(&text, EncoderTrap::Replace).unwrap()
};
#[cfg(not(feature = "charset"))]
let bytes = text.into_bytes();
let len = bytes.len(); let len = bytes.len();
let cursor = Cursor::new(bytes); let cursor = Cursor::new(bytes);
SizedReader::new(Some(len), Box::new(cursor)) SizedReader::new(Some(len), Box::new(cursor))
} }
#[cfg(feature = "json")]
Payload::JSON(v) => { Payload::JSON(v) => {
let bytes = serde_json::to_vec(&v).expect("Bad JSON in payload"); let bytes = serde_json::to_vec(&v).expect("Bad JSON in payload");
let len = bytes.len(); let len = bytes.len();
@@ -166,6 +175,8 @@ impl Request {
/// Send data a json value. /// Send data a json value.
/// ///
/// Requires feature `ureq = { version = "*", features = ["json"] }`
///
/// The `Content-Length` header is implicitly set to the length of the serialized value. /// The `Content-Length` header is implicitly set to the length of the serialized value.
/// ///
/// ``` /// ```
@@ -178,6 +189,7 @@ impl Request {
/// println!("{:?}", r); /// println!("{:?}", r);
/// } /// }
/// ``` /// ```
#[cfg(feature = "json")]
pub fn send_json(&mut self, data: SerdeValue) -> Response { pub fn send_json(&mut self, data: SerdeValue) -> Response {
self.do_call(Payload::JSON(data)) self.do_call(Payload::JSON(data))
} }
@@ -185,12 +197,19 @@ impl Request {
/// Send data as a string. /// Send data as a string.
/// ///
/// The `Content-Length` header is implicitly set to the length of the serialized value. /// The `Content-Length` header is implicitly set to the length of the serialized value.
/// Defaults to `utf-8`
///
/// ## Charset support
///
/// Requires feature `ureq = { version = "*", features = ["charset"] }`
/// ///
/// If a `Content-Type` header is present and it contains a charset specification, we /// If a `Content-Type` header is present and it contains a charset specification, we
/// attempt to encode the string using that character set. If it fails, we fall back /// attempt to encode the string using that character set. If it fails, we fall back
/// on utf-8. /// on utf-8.
/// ///
/// ``` /// ```
/// // this example requires features = ["charset"]
///
/// let r = ureq::post("/my_page") /// let r = ureq::post("/my_page")
/// .set("Content-Type", "text/plain; charset=iso-8859-1") /// .set("Content-Type", "text/plain; charset=iso-8859-1")
/// .send_string("Hällo Wörld!"); /// .send_string("Hällo Wörld!");
@@ -231,11 +250,11 @@ impl Request {
/// ``` /// ```
/// let r = ureq::get("/my_page") /// let r = ureq::get("/my_page")
/// .set("X-API-Key", "foobar") /// .set("X-API-Key", "foobar")
/// .set("Accept", "application/json") /// .set("Accept", "text/plain")
/// .call(); /// .call();
/// ///
/// if r.ok() { /// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap()); /// println!("yay got {}", r.into_string().unwrap());
/// } else { /// } else {
/// println!("Oh no error!"); /// println!("Oh no error!");
/// } /// }
@@ -308,12 +327,12 @@ impl Request {
/// let r = ureq::get("/my_page") /// let r = ureq::get("/my_page")
/// .set_map(map! { /// .set_map(map! {
/// "X-API-Key" => "foobar", /// "X-API-Key" => "foobar",
/// "Accept" => "application/json" /// "Accept" => "text/plain"
/// }) /// })
/// .call(); /// .call();
/// ///
/// if r.ok() { /// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap()); /// println!("yay got {}", r.into_string().unwrap());
/// } /// }
/// } /// }
/// ``` /// ```

View File

@@ -1,12 +1,15 @@
use ascii::AsciiString; use ascii::AsciiString;
use chunked_transfer; use chunked_transfer;
use encoding::label::encoding_from_whatwg_label;
use encoding::{DecoderTrap, EncoderTrap};
use std::io::Error as IoError; use std::io::Error as IoError;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::io::Read; use std::io::Read;
use std::io::Result as IoResult; use std::io::Result as IoResult;
#[cfg(feature = "charset")]
use encoding::label::encoding_from_whatwg_label;
#[cfg(feature = "charset")]
use encoding::{DecoderTrap, EncoderTrap};
use error::Error; use error::Error;
const DEFAULT_CONTENT_TYPE: &'static str = "text/plain"; const DEFAULT_CONTENT_TYPE: &'static str = "text/plain";
@@ -254,8 +257,8 @@ impl Response {
} }
} }
/// Turn this response into a String of the response body. Attempts to respect the /// Turn this response into a String of the response body. By default uses `utf-8`,
/// character encoding of the `Content-Type` header and falls back to `utf-8`. /// but can work with charset, see below.
/// ///
/// This is potentially memory inefficient for large bodies since the /// This is potentially memory inefficient for large bodies since the
/// implementation first reads the reader to end into a `Vec<u8>` and then /// implementation first reads the reader to end into a `Vec<u8>` and then
@@ -272,7 +275,19 @@ impl Response {
/// ///
/// assert!(text.contains("hello")); /// assert!(text.contains("hello"));
/// ``` /// ```
///
/// ## Charset support
///
/// Requires feature `ureq = { version = "*", features = ["charset"] }`
///
/// Attempts to respect the character encoding of the `Content-Type` header and
/// falls back to `utf-8`.
///
/// I.e. `Content-Length: text/plain; charset=iso-8859-1` would be decoded in latin-1.
///
pub fn into_string(self) -> IoResult<String> { pub fn into_string(self) -> IoResult<String> {
#[cfg(feature = "charset")]
{
let encoding = encoding_from_whatwg_label(self.charset()) let encoding = encoding_from_whatwg_label(self.charset())
.or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET)) .or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET))
.unwrap(); .unwrap();
@@ -280,9 +295,18 @@ impl Response {
self.into_reader().read_to_end(&mut buf)?; self.into_reader().read_to_end(&mut buf)?;
Ok(encoding.decode(&buf, DecoderTrap::Replace).unwrap()) Ok(encoding.decode(&buf, DecoderTrap::Replace).unwrap())
} }
#[cfg(not(feature = "charset"))]
{
let mut buf: Vec<u8> = vec![];
self.into_reader().read_to_end(&mut buf)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
}
/// Turn this response into a (serde) JSON value of the response body. /// Turn this response into a (serde) JSON value of the response body.
/// ///
/// Requires feature `ureq = { version = "*", features = ["json"] }`
///
/// Example: /// Example:
/// ///
/// ``` /// ```
@@ -294,6 +318,7 @@ impl Response {
/// ///
/// assert_eq!(json["hello"], "world"); /// assert_eq!(json["hello"], "world");
/// ``` /// ```
#[cfg(feature = "json")]
pub fn into_json(self) -> IoResult<serde_json::Value> { pub fn into_json(self) -> IoResult<serde_json::Value> {
let reader = self.into_reader(); let reader = self.into_reader();
serde_json::from_reader(reader).map_err(|e| { serde_json::from_reader(reader).map_err(|e| {
@@ -480,4 +505,3 @@ fn charset_from_content_type(header: Option<&str>) -> &str {
}) })
.unwrap_or(DEFAULT_CHARACTER_SET) .unwrap_or(DEFAULT_CHARACTER_SET)
} }

View File

@@ -1,13 +1,17 @@
use dns_lookup; use dns_lookup;
use native_tls::TlsConnector;
use native_tls::TlsStream;
use std::net::IpAddr; use std::net::IpAddr;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::net::TcpStream; use std::net::TcpStream;
use std::time::Duration; use std::time::Duration;
#[cfg(feature = "tls")]
use native_tls::TlsConnector;
#[cfg(feature = "tls")]
use native_tls::TlsStream;
pub enum Stream { pub enum Stream {
Http(TcpStream), Http(TcpStream),
#[cfg(feature = "tls")]
Https(TlsStream<TcpStream>), Https(TlsStream<TcpStream>),
Cursor(Cursor<Vec<u8>>), Cursor(Cursor<Vec<u8>>),
#[cfg(test)] #[cfg(test)]
@@ -65,6 +69,7 @@ fn connect_http(request: &Request, url: &Url) -> Result<Stream, Error> {
connect_host(request, hostname, port).map(|tcp| Stream::Http(tcp)) connect_host(request, hostname, port).map(|tcp| Stream::Http(tcp))
} }
#[cfg(feature = "tls")]
fn connect_https(request: &Request, url: &Url) -> Result<Stream, Error> { fn connect_https(request: &Request, url: &Url) -> Result<Stream, Error> {
// //
let hostname = url.host_str().unwrap(); let hostname = url.host_str().unwrap();
@@ -110,13 +115,18 @@ fn connect_host(request: &Request, hostname: &str, port: u16) -> Result<TcpStrea
Ok(stream) Ok(stream)
} }
#[cfg(not(test))]
fn connect_test(_request: &Request, url: &Url) -> Result<Stream, Error> {
Err(Error::UnknownScheme(url.scheme().to_string()))
}
#[cfg(test)] #[cfg(test)]
fn connect_test(request: &Request, url: &Url) -> Result<Stream, Error> { fn connect_test(request: &Request, url: &Url) -> Result<Stream, Error> {
use test; use test;
test::resolve_handler(request, url) test::resolve_handler(request, url)
} }
#[cfg(not(test))]
fn connect_test(_request: &Request, url: &Url) -> Result<Stream, Error> {
Err(Error::UnknownScheme(url.scheme().to_string()))
}
#[cfg(not(feature = "tls"))]
fn connect_https(request: &Request, url: &Url) -> Result<Stream, Error> {
Err(Error::UnknownScheme(url.scheme().to_string()))
}

View File

@@ -27,6 +27,7 @@ fn user_set_content_length_on_str() {
} }
#[test] #[test]
#[cfg(feature = "json")]
fn content_length_on_json() { fn content_length_on_json() {
test::set_handler("/content_length_on_json", |_req, _url| { test::set_handler("/content_length_on_json", |_req, _url| {
test::make_response(200, "OK", vec![], vec![]) test::make_response(200, "OK", vec![], vec![])
@@ -57,6 +58,7 @@ fn content_length_and_chunked() {
} }
#[test] #[test]
#[cfg(feature = "charset")]
fn str_with_encoding() { fn str_with_encoding() {
test::set_handler("/str_with_encoding", |_req, _url| { test::set_handler("/str_with_encoding", |_req, _url| {
test::make_response(200, "OK", vec![], vec![]) test::make_response(200, "OK", vec![], vec![])

View File

@@ -59,6 +59,7 @@ fn body_as_text() {
} }
#[test] #[test]
#[cfg(feature = "json")]
fn body_as_json() { fn body_as_json() {
test::set_handler("/body_as_json", |_req, _url| { test::set_handler("/body_as_json", |_req, _url| {
test::make_response( test::make_response(