separate out response
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
use cookie::{Cookie, CookieJar};
|
use cookie::{Cookie, CookieJar};
|
||||||
use std::str::FromStr;
|
use error::Error;
|
||||||
|
use response::{self, Response};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use header::{add_header, get_header, get_all_headers, has_header, Header};
|
use header::{add_header, get_all_headers, get_header, has_header, Header};
|
||||||
|
|
||||||
// to get to share private fields
|
// to get to share private fields
|
||||||
include!("request.rs");
|
include!("request.rs");
|
||||||
include!("response.rs");
|
|
||||||
include!("conn.rs");
|
include!("conn.rs");
|
||||||
include!("stream.rs");
|
include!("stream.rs");
|
||||||
include!("unit.rs");
|
include!("unit.rs");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use chunked_transfer;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 1024 * 1024;
|
const CHUNK_SIZE: usize = 1024 * 1024;
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ mod agent;
|
|||||||
mod error;
|
mod error;
|
||||||
mod header;
|
mod header;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
mod response;
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
mod serde_macros;
|
mod serde_macros;
|
||||||
@@ -117,9 +118,10 @@ mod serde_macros;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
pub use agent::{Agent, Request, Response};
|
pub use agent::{Agent, Request};
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use header::Header;
|
pub use header::Header;
|
||||||
|
pub use response::Response;
|
||||||
|
|
||||||
// re-export
|
// re-export
|
||||||
pub use cookie::Cookie;
|
pub use cookie::Cookie;
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
use qstring::QString;
|
use qstring::QString;
|
||||||
use std::io::empty;
|
use std::io::empty;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
use std::io::Read;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
use super::SerdeValue;
|
use super::SerdeValue;
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
|
#[cfg(feature = "charset")]
|
||||||
|
use encoding::label::encoding_from_whatwg_label;
|
||||||
|
#[cfg(feature = "charset")]
|
||||||
|
use encoding::EncoderTrap;
|
||||||
|
#[cfg(feature = "charset")]
|
||||||
|
use response::DEFAULT_CHARACTER_SET;
|
||||||
|
|
||||||
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") };
|
||||||
}
|
}
|
||||||
@@ -202,7 +211,7 @@ impl Request {
|
|||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
let text = data.into();
|
let text = data.into();
|
||||||
let charset = charset_from_content_type(self.header("content-type")).to_string();
|
let charset = response::charset_from_content_type(self.header("content-type")).to_string();
|
||||||
self.do_call(Payload::Text(text, charset))
|
self.do_call(Payload::Text(text, charset))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
101
src/response.rs
101
src/response.rs
@@ -1,19 +1,26 @@
|
|||||||
|
use agent::Stream;
|
||||||
use ascii::AsciiString;
|
use ascii::AsciiString;
|
||||||
use chunked_transfer;
|
use chunked_transfer;
|
||||||
|
use header::Header;
|
||||||
|
use std::io::Cursor;
|
||||||
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;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
#[cfg(feature = "charset")]
|
#[cfg(feature = "charset")]
|
||||||
use encoding::label::encoding_from_whatwg_label;
|
use encoding::label::encoding_from_whatwg_label;
|
||||||
#[cfg(feature = "charset")]
|
#[cfg(feature = "charset")]
|
||||||
use encoding::{DecoderTrap, EncoderTrap};
|
use encoding::DecoderTrap;
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
|
||||||
const DEFAULT_CONTENT_TYPE: &'static str = "text/plain";
|
pub const DEFAULT_CONTENT_TYPE: &'static str = "text/plain";
|
||||||
const DEFAULT_CHARACTER_SET: &'static str = "utf-8";
|
pub const DEFAULT_CHARACTER_SET: &'static str = "utf-8";
|
||||||
|
|
||||||
/// Response instances are created as results of firing off requests.
|
/// Response instances are created as results of firing off requests.
|
||||||
///
|
///
|
||||||
@@ -243,7 +250,6 @@ impl Response {
|
|||||||
/// assert_eq!(bytes.len(), len);
|
/// assert_eq!(bytes.len(), len);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn into_reader(self) -> impl Read {
|
pub fn into_reader(self) -> impl Read {
|
||||||
|
|
||||||
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);
|
||||||
@@ -388,11 +394,6 @@ impl Response {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_stream(&mut self, stream: Stream, is_head: bool) {
|
|
||||||
self.is_head = is_head;
|
|
||||||
self.stream = Some(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
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()
|
||||||
@@ -431,7 +432,7 @@ impl FromStr for Response {
|
|||||||
let bytes = s.as_bytes().to_owned();
|
let bytes = s.as_bytes().to_owned();
|
||||||
let mut cursor = Cursor::new(bytes);
|
let mut cursor = Cursor::new(bytes);
|
||||||
let mut resp = Self::do_from_read(&mut cursor)?;
|
let mut resp = Self::do_from_read(&mut cursor)?;
|
||||||
resp.set_stream(Stream::Cursor(cursor), false);
|
set_stream(&mut resp, Stream::Cursor(cursor), false);
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -447,6 +448,11 @@ impl Into<Response> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_stream(resp: &mut Response, stream: Stream, is_head: bool) {
|
||||||
|
resp.is_head = is_head;
|
||||||
|
resp.stream = Some(stream);
|
||||||
|
}
|
||||||
|
|
||||||
// application/x-www-form-urlencoded, application/json, and multipart/form-data
|
// application/x-www-form-urlencoded, application/json, and multipart/form-data
|
||||||
|
|
||||||
fn read_next_line<R: Read>(reader: &mut R) -> IoResult<AsciiString> {
|
fn read_next_line<R: Read>(reader: &mut R) -> IoResult<AsciiString> {
|
||||||
@@ -510,7 +516,7 @@ impl Read for LimitedRead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn charset_from_content_type(header: Option<&str>) -> &str {
|
pub fn charset_from_content_type(header: Option<&str>) -> &str {
|
||||||
header
|
header
|
||||||
.and_then(|header| {
|
.and_then(|header| {
|
||||||
header.find(";").and_then(|semi| {
|
header.find(";").and_then(|semi| {
|
||||||
@@ -521,3 +527,76 @@ fn charset_from_content_type(header: Option<&str>) -> &str {
|
|||||||
})
|
})
|
||||||
.unwrap_or(DEFAULT_CHARACTER_SET)
|
.unwrap_or(DEFAULT_CHARACTER_SET)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn content_type_without_charset() {
|
||||||
|
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\nOK";
|
||||||
|
let resp = s.parse::<Response>().unwrap();
|
||||||
|
assert_eq!("application/json", resp.content_type());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn content_type_with_charset() {
|
||||||
|
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json; charset=iso-8859-4\r\n\r\nOK";
|
||||||
|
let resp = s.parse::<Response>().unwrap();
|
||||||
|
assert_eq!("application/json", resp.content_type());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn content_type_default() {
|
||||||
|
let s = "HTTP/1.1 200 OK\r\n\r\nOK";
|
||||||
|
let resp = s.parse::<Response>().unwrap();
|
||||||
|
assert_eq!("text/plain", resp.content_type());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn charset() {
|
||||||
|
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json; charset=iso-8859-4\r\n\r\nOK";
|
||||||
|
let resp = s.parse::<Response>().unwrap();
|
||||||
|
assert_eq!("iso-8859-4", resp.charset());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn charset_default() {
|
||||||
|
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\nOK";
|
||||||
|
let resp = s.parse::<Response>().unwrap();
|
||||||
|
assert_eq!("utf-8", resp.charset());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chunked_transfer() {
|
||||||
|
let s = "HTTP/1.1 200 OK\r\nTransfer-Encoding: Chunked\r\n\r\n3\r\nhel\r\nb\r\nlo world!!!\r\n0\r\n\r\n";
|
||||||
|
let resp = s.parse::<Response>().unwrap();
|
||||||
|
assert_eq!("hello world!!!", resp.into_string().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
fn parse_simple_json() {
|
||||||
|
let s = format!("HTTP/1.1 200 OK\r\n\r\n{{\"hello\":\"world\"}}");
|
||||||
|
let resp = s.parse::<Response>().unwrap();
|
||||||
|
let v = resp.into_json().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
v,
|
||||||
|
"{\"hello\":\"world\"}"
|
||||||
|
.parse::<serde_json::Value>()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_borked_header() {
|
||||||
|
let s = format!("HTTP/1.1 BORKED\r\n");
|
||||||
|
let resp: Response = s.parse::<Response>().unwrap_err().into();
|
||||||
|
assert_eq!(resp.http_version(), "HTTP/1.1");
|
||||||
|
assert_eq!(*resp.status(), 500);
|
||||||
|
assert_eq!(resp.status_text(), "Bad Status");
|
||||||
|
assert_eq!(resp.content_type(), "text/plain");
|
||||||
|
let v = resp.into_string().unwrap();
|
||||||
|
assert_eq!(v, "Bad Status\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::net::SocketAddr;
|
|||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::io::Result as IoResult;
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use native_tls::TlsConnector;
|
use native_tls::TlsConnector;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use agent::Unit;
|
|
||||||
use agent::Stream;
|
use agent::Stream;
|
||||||
|
use agent::Unit;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ impl Unit {
|
|||||||
.map_err(|_| Error::BadUrl(format!("Bad redirection: {}", location)))?;
|
.map_err(|_| Error::BadUrl(format!("Bad redirection: {}", location)))?;
|
||||||
|
|
||||||
// perform the redirect differently depending on 3xx code.
|
// perform the redirect differently depending on 3xx code.
|
||||||
return match resp.status {
|
return match resp.status() {
|
||||||
301 | 302 | 303 => {
|
301 | 302 | 303 => {
|
||||||
send_body(body, self.is_chunked, &mut stream)?;
|
send_body(body, self.is_chunked, &mut stream)?;
|
||||||
let empty = Payload::Empty.into_read();
|
let empty = Payload::Empty.into_read();
|
||||||
@@ -168,7 +168,7 @@ impl Unit {
|
|||||||
send_body(body, self.is_chunked, &mut stream)?;
|
send_body(body, self.is_chunked, &mut stream)?;
|
||||||
|
|
||||||
// since it is not a redirect, give away the incoming stream to the response object
|
// since it is not a redirect, give away the incoming stream to the response object
|
||||||
resp.set_stream(stream, self.is_head);
|
response::set_stream(&mut resp, stream, self.is_head);
|
||||||
|
|
||||||
// release the response
|
// release the response
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
|
|||||||
Reference in New Issue
Block a user