// Copyright 2016 `multipart` Crate Developers | |
// | |
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or | |
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or | |
// http://opensource.org/licenses/MIT>, at your option. This file may not be | |
// copied, modified, or distributed except according to those terms. | |
//! Mocked types for client-side and server-side APIs. | |
use std::cell::{Cell, RefCell}; | |
use std::io::{self, Read, Write}; | |
use std::{fmt, thread}; | |
use rand::{self, Rng, ThreadRng}; | |
/// A mock implementation of `client::HttpRequest` which can spawn an `HttpBuffer`. | |
/// | |
/// `client::HttpRequest` impl requires the `client` feature. | |
#[derive(Default, Debug)] | |
pub struct ClientRequest { | |
boundary: Option<String>, | |
content_len: Option<u64>, | |
} | |
#[cfg(feature = "client")] | |
impl ::client::HttpRequest for ClientRequest { | |
type Stream = HttpBuffer; | |
type Error = io::Error; | |
fn apply_headers(&mut self, boundary: &str, content_len: Option<u64>) -> bool { | |
self.boundary = Some(boundary.into()); | |
self.content_len = content_len; | |
true | |
} | |
/// ## Panics | |
/// If `apply_headers()` was not called. | |
fn open_stream(self) -> Result<HttpBuffer, io::Error> { | |
debug!("ClientRequest::open_stream called! {:?}", self); | |
let boundary = self.boundary.expect("ClientRequest::set_headers() was not called!"); | |
Ok(HttpBuffer::new_empty(boundary, self.content_len)) | |
} | |
} | |
/// A writable buffer which stores the boundary and content-length, if provided. | |
/// | |
/// Implements `client::HttpStream` if the `client` feature is enabled. | |
pub struct HttpBuffer { | |
/// The buffer containing the raw bytes. | |
pub buf: Vec<u8>, | |
/// The multipart boundary. | |
pub boundary: String, | |
/// The value of the content-length header, if set. | |
pub content_len: Option<u64>, | |
rng: ThreadRng, | |
} | |
impl HttpBuffer { | |
/// Create an empty buffer with the given boundary and optional content-length. | |
pub fn new_empty(boundary: String, content_len: Option<u64>) -> HttpBuffer { | |
Self::with_buf(Vec::new(), boundary, content_len) | |
} | |
/// Wrap the given buffer with the given boundary and optional content-length. | |
pub fn with_buf(buf: Vec<u8>, boundary: String, content_len: Option<u64>) -> Self { | |
HttpBuffer { | |
buf, | |
boundary, | |
content_len, | |
rng: rand::thread_rng() | |
} | |
} | |
/// Get a `ServerRequest` wrapping the data in this buffer. | |
pub fn for_server(&self) -> ServerRequest { | |
ServerRequest { | |
data: &self.buf, | |
boundary: &self.boundary, | |
content_len: self.content_len, | |
rng: rand::thread_rng(), | |
} | |
} | |
} | |
impl Write for HttpBuffer { | |
/// To simulate a network connection, this will copy a random number of bytes | |
/// from `buf` to the buffer. | |
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
if buf.is_empty() { | |
debug!("HttpBuffer::write() was passed a zero-sized buffer."); | |
return Ok(0); | |
} | |
// Simulate the randomness of a network connection by not always reading everything | |
let len = self.rng.gen_range(1, buf.len() + 1); | |
self.buf.write(&buf[..len]) | |
} | |
fn flush(&mut self) -> io::Result<()> { | |
self.buf.flush() | |
} | |
} | |
#[cfg(feature = "client")] | |
impl ::client::HttpStream for HttpBuffer { | |
type Request = ClientRequest; | |
type Response = HttpBuffer; | |
type Error = io::Error; | |
/// Returns `Ok(self)`. | |
fn finish(self) -> Result<Self, io::Error> { Ok(self) } | |
} | |
impl fmt::Debug for HttpBuffer { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
f.debug_struct("multipart::mock::HttpBuffer") | |
.field("buf", &self.buf) | |
.field("boundary", &self.boundary) | |
.field("content_len", &self.content_len) | |
.finish() | |
} | |
} | |
/// A mock implementation of `server::HttpRequest` that can be read. | |
/// | |
/// Implements `server::HttpRequest` if the `server` feature is enabled. | |
pub struct ServerRequest<'a> { | |
/// Slice of the source `HttpBuffer::buf` | |
pub data: &'a [u8], | |
/// The multipart boundary. | |
pub boundary: &'a str, | |
/// The value of the content-length header, if set. | |
pub content_len: Option<u64>, | |
rng: ThreadRng, | |
} | |
impl<'a> ServerRequest<'a> { | |
/// Create a new `ServerRequest` with the given data and boundary. | |
/// | |
/// Assumes `content_len: None` | |
pub fn new(data: &'a [u8], boundary: &'a str) -> Self { | |
ServerRequest { | |
data, | |
boundary, | |
content_len: None, | |
rng: rand::thread_rng(), | |
} | |
} | |
} | |
impl<'a> Read for ServerRequest<'a> { | |
/// To simulate a network connection, this will copy a random number of bytes | |
/// from the buffer to `out`. | |
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> { | |
if out.is_empty() { | |
debug!("ServerRequest::read() was passed a zero-sized buffer."); | |
return Ok(0); | |
} | |
// Simulate the randomness of a network connection by not always reading everything | |
let len = self.rng.gen_range(1, out.len() + 1); | |
self.data.read(&mut out[..len]) | |
} | |
} | |
#[cfg(feature = "server")] | |
impl<'a> ::server::HttpRequest for ServerRequest<'a> { | |
type Body = Self; | |
fn multipart_boundary(&self) -> Option<&str> { Some(self.boundary) } | |
fn body(self) -> Self::Body { | |
self | |
} | |
} | |
/// A `Write` adapter that duplicates all data written to the inner writer as well as stdout. | |
pub struct StdoutTee<'s, W> { | |
inner: W, | |
stdout: io::StdoutLock<'s>, | |
} | |
impl<'s, W> StdoutTee<'s, W> { | |
/// Constructor | |
pub fn new(inner: W, stdout: &'s io::Stdout) -> Self { | |
Self { | |
inner, stdout: stdout.lock(), | |
} | |
} | |
} | |
impl<'s, W: Write> Write for StdoutTee<'s, W> { | |
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
self.inner.write_all(buf)?; | |
self.stdout.write(buf) | |
} | |
fn flush(&mut self) -> io::Result<()> { | |
self.inner.flush()?; | |
self.stdout.flush() | |
} | |
} |