refactor to body

This commit is contained in:
Martin Algesten
2018-06-30 14:11:54 +02:00
parent 0e4c326caf
commit 54558fbb26
6 changed files with 102 additions and 166 deletions

View File

@@ -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
View 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(())
}

View File

@@ -107,6 +107,7 @@ extern crate native_tls;
extern crate serde_json;
mod agent;
mod body;
mod conn;
mod error;
mod header;

View File

@@ -1,21 +1,9 @@
use qstring::QString;
use std::io::empty;
use std::io::Cursor;
use std::io::Read;
use std::sync::Arc;
#[cfg(feature = "json")]
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! {
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 {
fn new(agent: &Agent, method: String, path: String) -> Request {

View File

@@ -1,5 +1,4 @@
use agent::{SizedReader, Unit};
use chunked_transfer;
use agent::Unit;
use error::Error;
use std::io::{Cursor, Read, Result as IoResult, Write};
use std::net::SocketAddr;
@@ -10,8 +9,6 @@ use std::time::Duration;
#[cfg(feature = "tls")]
use native_tls::TlsStream;
const CHUNK_SIZE: usize = 1024 * 1024;
pub enum Stream {
Http(TcpStream),
#[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> {
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(())
}

View File

@@ -1,6 +1,7 @@
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 body::{Payload, SizedReader, send_body};
//
pub struct Unit {