refactor to body
This commit is contained in:
70
src/agent.rs
70
src/agent.rs
@@ -357,74 +357,4 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////// RESPONSE TESTS /////////////////////////////
|
|
||||||
|
|
||||||
#[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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
98
src/body.rs
Normal file
98
src/body.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
use chunked_transfer;
|
||||||
|
use std::io::{empty, Cursor, Read, Result as IoResult, Write};
|
||||||
|
use stream::Stream;
|
||||||
|
|
||||||
|
#[cfg(feature = "charset")]
|
||||||
|
use encoding::label::encoding_from_whatwg_label;
|
||||||
|
#[cfg(feature = "charset")]
|
||||||
|
use encoding::EncoderTrap;
|
||||||
|
#[cfg(feature = "charset")]
|
||||||
|
use response::DEFAULT_CHARACTER_SET;
|
||||||
|
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
use super::SerdeValue;
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
const CHUNK_SIZE: usize = 1024 * 1024;
|
||||||
|
|
||||||
|
pub enum Payload {
|
||||||
|
Empty,
|
||||||
|
Text(String, String),
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
JSON(SerdeValue),
|
||||||
|
Reader(Box<Read + 'static>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Payload {
|
||||||
|
fn default() -> Payload {
|
||||||
|
Payload::Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SizedReader {
|
||||||
|
pub size: Option<usize>,
|
||||||
|
pub reader: Box<Read + 'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SizedReader {
|
||||||
|
fn new(size: Option<usize>, reader: Box<Read + 'static>) -> Self {
|
||||||
|
SizedReader { size, reader }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Payload {
|
||||||
|
pub fn into_read(self) -> SizedReader {
|
||||||
|
match self {
|
||||||
|
Payload::Empty => SizedReader::new(None, Box::new(empty())),
|
||||||
|
Payload::Text(text, _charset) => {
|
||||||
|
#[cfg(feature = "charset")]
|
||||||
|
let bytes = {
|
||||||
|
let encoding = encoding_from_whatwg_label(&_charset)
|
||||||
|
.or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET))
|
||||||
|
.unwrap();
|
||||||
|
encoding.encode(&text, EncoderTrap::Replace).unwrap()
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "charset"))]
|
||||||
|
let bytes = text.into_bytes();
|
||||||
|
let len = bytes.len();
|
||||||
|
let cursor = Cursor::new(bytes);
|
||||||
|
SizedReader::new(Some(len), Box::new(cursor))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
Payload::JSON(v) => {
|
||||||
|
let bytes = serde_json::to_vec(&v).expect("Bad JSON in payload");
|
||||||
|
let len = bytes.len();
|
||||||
|
let cursor = Cursor::new(bytes);
|
||||||
|
SizedReader::new(Some(len), Box::new(cursor))
|
||||||
|
}
|
||||||
|
Payload::Reader(read) => SizedReader::new(None, read),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_body(body: SizedReader, do_chunk: bool, stream: &mut Stream) -> IoResult<()> {
|
||||||
|
if do_chunk {
|
||||||
|
pipe(body.reader, chunked_transfer::Encoder::new(stream))?;
|
||||||
|
} else {
|
||||||
|
pipe(body.reader, stream)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pipe<R, W>(mut reader: R, mut writer: W) -> IoResult<()>
|
||||||
|
where
|
||||||
|
R: Read,
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
let mut buf = [0_u8; CHUNK_SIZE];
|
||||||
|
loop {
|
||||||
|
let len = reader.read(&mut buf)?;
|
||||||
|
if len == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
writer.write_all(&buf[0..len])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -107,6 +107,7 @@ extern crate native_tls;
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
mod agent;
|
mod agent;
|
||||||
|
mod body;
|
||||||
mod conn;
|
mod conn;
|
||||||
mod error;
|
mod error;
|
||||||
mod header;
|
mod header;
|
||||||
|
|||||||
@@ -1,21 +1,9 @@
|
|||||||
use qstring::QString;
|
use qstring::QString;
|
||||||
use std::io::empty;
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::io::Read;
|
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")]
|
|
||||||
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") };
|
||||||
@@ -60,59 +48,6 @@ impl ::std::fmt::Debug for Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Payload {
|
|
||||||
Empty,
|
|
||||||
Text(String, String),
|
|
||||||
#[cfg(feature = "json")] JSON(SerdeValue),
|
|
||||||
Reader(Box<Read + 'static>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Payload {
|
|
||||||
fn default() -> Payload {
|
|
||||||
Payload::Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SizedReader {
|
|
||||||
pub size: Option<usize>,
|
|
||||||
pub reader: Box<Read + 'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizedReader {
|
|
||||||
fn new(size: Option<usize>, reader: Box<Read + 'static>) -> Self {
|
|
||||||
SizedReader { size, reader }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Payload {
|
|
||||||
fn into_read(self) -> SizedReader {
|
|
||||||
match self {
|
|
||||||
Payload::Empty => SizedReader::new(None, Box::new(empty())),
|
|
||||||
Payload::Text(text, _charset) => {
|
|
||||||
#[cfg(feature = "charset")]
|
|
||||||
let bytes = {
|
|
||||||
let encoding = encoding_from_whatwg_label(&_charset)
|
|
||||||
.or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET))
|
|
||||||
.unwrap();
|
|
||||||
encoding.encode(&text, EncoderTrap::Replace).unwrap()
|
|
||||||
};
|
|
||||||
#[cfg(not(feature = "charset"))]
|
|
||||||
let bytes = text.into_bytes();
|
|
||||||
let len = bytes.len();
|
|
||||||
let cursor = Cursor::new(bytes);
|
|
||||||
SizedReader::new(Some(len), Box::new(cursor))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "json")]
|
|
||||||
Payload::JSON(v) => {
|
|
||||||
let bytes = serde_json::to_vec(&v).expect("Bad JSON in payload");
|
|
||||||
let len = bytes.len();
|
|
||||||
let cursor = Cursor::new(bytes);
|
|
||||||
SizedReader::new(Some(len), Box::new(cursor))
|
|
||||||
}
|
|
||||||
Payload::Reader(read) => SizedReader::new(None, read),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
fn new(agent: &Agent, method: String, path: String) -> Request {
|
fn new(agent: &Agent, method: String, path: String) -> Request {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use agent::{SizedReader, Unit};
|
use agent::Unit;
|
||||||
use chunked_transfer;
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use std::io::{Cursor, Read, Result as IoResult, Write};
|
use std::io::{Cursor, Read, Result as IoResult, Write};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@@ -10,8 +9,6 @@ use std::time::Duration;
|
|||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use native_tls::TlsStream;
|
use native_tls::TlsStream;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 1024 * 1024;
|
|
||||||
|
|
||||||
pub enum Stream {
|
pub enum Stream {
|
||||||
Http(TcpStream),
|
Http(TcpStream),
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
@@ -139,29 +136,3 @@ pub fn connect_test(unit: &Unit) -> Result<Stream, Error> {
|
|||||||
pub fn connect_https(unit: &Unit) -> Result<Stream, Error> {
|
pub fn connect_https(unit: &Unit) -> Result<Stream, Error> {
|
||||||
Err(Error::UnknownScheme(unit.url.scheme().to_string()))
|
Err(Error::UnknownScheme(unit.url.scheme().to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_body(body: SizedReader, do_chunk: bool, stream: &mut Stream) -> IoResult<()> {
|
|
||||||
if do_chunk {
|
|
||||||
pipe(body.reader, chunked_transfer::Encoder::new(stream))?;
|
|
||||||
} else {
|
|
||||||
pipe(body.reader, stream)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pipe<R, W>(mut reader: R, mut writer: W) -> IoResult<()>
|
|
||||||
where
|
|
||||||
R: Read,
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
let mut buf = [0_u8; CHUNK_SIZE];
|
|
||||||
loop {
|
|
||||||
let len = reader.read(&mut buf)?;
|
|
||||||
if len == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
writer.write_all(&buf[0..len])?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
use stream::{connect_http, connect_https, connect_test, send_body};
|
use stream::{connect_http, connect_https, connect_test};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use body::{Payload, SizedReader, send_body};
|
||||||
//
|
//
|
||||||
|
|
||||||
pub struct Unit {
|
pub struct Unit {
|
||||||
|
|||||||
Reference in New Issue
Block a user