#[cfg(feature = "wasmedge_rustls")]
use crate::tls;
use crate::{
error,
response::{find_slice, Headers, Response, CR_LF_2},
uri::Uri,
};
use std::{
convert::TryFrom,
fmt,
io::{self, ErrorKind, Read, Write},
path::Path,
time::{Duration, Instant},
};
#[cfg(not(target_arch = "wasm32"))]
use std::net::TcpStream;
#[cfg(target_arch = "wasm32")]
use wasmedge_wasi_socket::{TcpStream, ToSocketAddrs};
const CR_LF: &str = "\r\n";
const BUF_SIZE: usize = 8 * 1024;
const SMALL_BUF_SIZE: usize = 8 * 10;
const TEST_FREQ: usize = 100;
pub struct Counter {
count: usize,
stop: usize,
}
impl Counter {
pub const fn new(stop: usize) -> Counter {
Counter { count: 0, stop }
}
}
impl Iterator for Counter {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
let breakpoint = self.count == self.stop;
if breakpoint {
self.count = 0;
}
Some(breakpoint)
}
}
pub fn copy_with_timeout<R, W>(reader: &mut R, writer: &mut W, deadline: Instant) -> io::Result<u64>
where
R: Read + ?Sized,
W: Write + ?Sized,
{
let mut buf = [0; BUF_SIZE];
let mut copied = 0;
let mut counter = Counter::new(TEST_FREQ);
loop {
let len = match reader.read(&mut buf) {
Ok(0) => return Ok(copied),
Ok(len) => len,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
writer.write_all(&buf[..len])?;
copied += len as u64;
if counter.next().unwrap() && Instant::now() >= deadline {
return Ok(copied);
}
}
}
pub fn copy_exact<R, W>(reader: &mut R, writer: &mut W, num_bytes: usize) -> io::Result<()>
where
R: Read + ?Sized,
W: Write + ?Sized,
{
let mut buf = vec![0u8; num_bytes];
reader.read_exact(&mut buf)?;
writer.write_all(&mut buf)
}
pub fn copy_until<R>(
reader: &mut R,
val: &[u8],
deadline: Instant,
) -> Result<[Vec<u8>; 2], io::Error>
where
R: Read + ?Sized,
{
let mut buf = [0; SMALL_BUF_SIZE];
let mut writer = Vec::with_capacity(SMALL_BUF_SIZE);
let mut counter = Counter::new(TEST_FREQ);
let mut split_idx = 0;
loop {
let len = match reader.read(&mut buf) {
Ok(0) => break,
Ok(len) => len,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
writer.write_all(&buf[..len])?;
if let Some(i) = find_slice(&writer, val) {
split_idx = i;
break;
}
if counter.next().unwrap() && Instant::now() >= deadline {
split_idx = writer.len();
break;
}
}
Ok([writer[..split_idx].to_vec(), writer[split_idx..].to_vec()])
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Method {
GET,
HEAD,
POST,
PUT,
DELETE,
OPTIONS,
PATCH,
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Method::*;
let method = match self {
GET => "GET",
HEAD => "HEAD",
POST => "POST",
PUT => "PUT",
DELETE => "DELETE",
OPTIONS => "OPTIONS",
PATCH => "PATCH",
};
write!(f, "{}", method)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum HttpVersion {
Http10,
Http11,
Http20,
}
impl HttpVersion {
pub const fn as_str(self) -> &'static str {
use self::HttpVersion::*;
match self {
Http10 => "HTTP/1.0",
Http11 => "HTTP/1.1",
Http20 => "HTTP/2.0",
}
}
}
impl fmt::Display for HttpVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RequestBuilder<'a> {
uri: &'a Uri<'a>,
method: Method,
version: HttpVersion,
headers: Headers,
body: Option<&'a [u8]>,
timeout: Option<Duration>,
}
impl<'a> RequestBuilder<'a> {
pub fn new(uri: &'a Uri<'a>) -> RequestBuilder<'a> {
RequestBuilder {
headers: Headers::default_http(uri),
uri,
method: Method::GET,
version: HttpVersion::Http11,
body: None,
timeout: None,
}
}
pub fn method<T>(&mut self, method: T) -> &mut Self
where
Method: From<T>,
{
self.method = Method::from(method);
self
}
pub fn version<T>(&mut self, version: T) -> &mut Self
where
HttpVersion: From<T>,
{
self.version = HttpVersion::from(version);
self
}
pub fn headers<T>(&mut self, headers: T) -> &mut Self
where
Headers: From<T>,
{
self.headers = Headers::from(headers);
self
}
pub fn header<T, U>(&mut self, key: &T, val: &U) -> &mut Self
where
T: ToString + ?Sized,
U: ToString + ?Sized,
{
self.headers.insert(key, val);
self
}
pub fn body(&mut self, body: &'a [u8]) -> &mut Self {
self.body = Some(body);
self
}
pub fn timeout<T>(&mut self, timeout: Option<T>) -> &mut Self
where
Duration: From<T>,
{
self.timeout = timeout.map(Duration::from);
self
}
pub fn send<T, U>(&self, stream: &mut T, writer: &mut U) -> Result<Response, error::Error>
where
T: Write + Read,
U: Write,
{
self.write_msg(stream, &self.parse_msg())?;
let head_deadline = match self.timeout {
Some(t) => Instant::now() + t,
None => Instant::now() + Duration::from_secs(360),
};
let (res, body_part) = self.read_head(stream, head_deadline)?;
if self.method == Method::HEAD {
return Ok(res);
}
if let Some(v) = res.headers().get("Transfer-Encoding") {
if *v == "chunked" {
let mut dechunked = crate::chunked::Reader::new(body_part.as_slice().chain(stream));
if let Some(timeout) = self.timeout {
let deadline = Instant::now() + timeout;
copy_with_timeout(&mut dechunked, writer, deadline)?;
} else {
io::copy(&mut dechunked, writer)?;
}
return Ok(res);
}
}
writer.write_all(&body_part)?;
if let Some(timeout) = self.timeout {
let deadline = Instant::now() + timeout;
copy_with_timeout(stream, writer, deadline)?;
} else {
let num_bytes = res.content_len();
match num_bytes {
Some(0) => {}
Some(num_bytes) => {
copy_exact(stream, writer, num_bytes - body_part.len())?;
}
None => {
io::copy(stream, writer)?;
}
}
}
Ok(res)
}
pub fn write_msg<T, U>(&self, stream: &mut T, msg: &U) -> Result<(), io::Error>
where
T: Write,
U: AsRef<[u8]>,
{
stream.write_all(msg.as_ref())?;
stream.flush()?;
Ok(())
}
pub fn read_head<T: Read>(
&self,
stream: &mut T,
deadline: Instant,
) -> Result<(Response, Vec<u8>), error::Error> {
let [head, body_part] = copy_until(stream, &CR_LF_2, deadline)?;
Ok((Response::from_head(&head)?, body_part))
}
pub fn parse_msg(&self) -> Vec<u8> {
let request_line = format!(
"{} {} {}{}",
self.method,
self.uri.resource(),
self.version,
CR_LF
);
let headers: String = self
.headers
.iter()
.map(|(k, v)| format!("{}: {}{}", k.as_ref(), v, CR_LF))
.collect();
let mut request_msg = (request_line + &headers + CR_LF).as_bytes().to_vec();
if let Some(b) = &self.body {
request_msg.extend(*b);
}
request_msg
}
pub fn build(self) -> Request<'a> {
Request {
inner: self,
connect_timeout: Some(Duration::from_secs(60)),
read_timeout: Some(Duration::from_secs(60)),
write_timeout: Some(Duration::from_secs(60)),
root_cert_file_pem: None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Request<'a> {
inner: RequestBuilder<'a>,
connect_timeout: Option<Duration>,
read_timeout: Option<Duration>,
write_timeout: Option<Duration>,
root_cert_file_pem: Option<&'a Path>,
}
impl<'a> Request<'a> {
pub fn new(uri: &'a Uri) -> Request<'a> {
let mut builder = RequestBuilder::new(&uri);
builder.header("Connection", "Close");
Request {
inner: builder,
connect_timeout: Some(Duration::from_secs(60)),
read_timeout: Some(Duration::from_secs(60)),
write_timeout: Some(Duration::from_secs(60)),
root_cert_file_pem: None,
}
}
pub fn method<T>(&mut self, method: T) -> &mut Self
where
Method: From<T>,
{
self.inner.method(method);
self
}
pub fn version<T>(&mut self, version: T) -> &mut Self
where
HttpVersion: From<T>,
{
self.inner.version(version);
self
}
pub fn headers<T>(&mut self, headers: T) -> &mut Self
where
Headers: From<T>,
{
self.inner.headers(headers);
self
}
pub fn header<T, U>(&mut self, key: &T, val: &U) -> &mut Self
where
T: ToString + ?Sized,
U: ToString + ?Sized,
{
self.inner.header(key, val);
self
}
pub fn body(&mut self, body: &'a [u8]) -> &mut Self {
self.inner.body(body);
self
}
pub fn timeout<T>(&mut self, timeout: Option<T>) -> &mut Self
where
Duration: From<T>,
{
self.inner.timeout = timeout.map(Duration::from);
self
}
pub fn connect_timeout<T>(&mut self, timeout: Option<T>) -> &mut Self
where
Duration: From<T>,
{
self.connect_timeout = timeout.map(Duration::from);
self
}
pub fn read_timeout<T>(&mut self, timeout: Option<T>) -> &mut Self
where
Duration: From<T>,
{
self.read_timeout = timeout.map(Duration::from);
self
}
pub fn write_timeout<T>(&mut self, timeout: Option<T>) -> &mut Self
where
Duration: From<T>,
{
self.write_timeout = timeout.map(Duration::from);
self
}
pub fn root_cert_file_pem(&mut self, file_path: &'a Path) -> &mut Self {
self.root_cert_file_pem = Some(file_path);
self
}
pub fn send<T: Write>(&self, writer: &mut T) -> Result<Response, error::Error> {
let host = self
.inner
.uri
.host()
.ok_or(error::Error::Parse(error::ParseErr::UriErr))?;
let port = self.inner.uri.corr_port();
#[cfg(target_arch = "wasm32")]
let mut stream = {
let mut addrs = (host, port).to_socket_addrs()?;
let addr = addrs
.next()
.ok_or(error::Error::Parse(error::ParseErr::UriErr))?;
TcpStream::connect(&addr)?
};
#[cfg(not(target_arch = "wasm32"))]
let mut stream = TcpStream::connect((host, port))?;
if self.inner.uri.scheme() == "https" {
#[cfg(feature = "wasmedge_rustls")]
{
let cnf = tls::Config::default();
let mut stream = cnf.connect(host, stream)?;
self.inner.send(&mut stream, writer)
}
#[cfg(not(feature = "wasmedge_rustls"))]
return Err(error::Error::Tls);
} else {
self.inner.send(&mut stream, writer)
}
}
}
pub fn get<T: AsRef<str>, U: Write>(uri: T, writer: &mut U) -> Result<Response, error::Error> {
let uri = Uri::try_from(uri.as_ref())?;
Request::new(&uri).send(writer)
}
pub fn head<T: AsRef<str>>(uri: T) -> Result<Response, error::Error> {
let mut writer = Vec::new();
let uri = Uri::try_from(uri.as_ref())?;
Request::new(&uri).method(Method::HEAD).send(&mut writer)
}
pub fn post<T: AsRef<str>, U: Write>(
uri: T,
body: &[u8],
writer: &mut U,
) -> Result<Response, error::Error> {
let uri = Uri::try_from(uri.as_ref())?;
Request::new(&uri)
.method(Method::POST)
.header("Content-Length", &body.len())
.body(body)
.send(writer)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{error::Error, response::StatusCode};
use std::io::Cursor;
const UNSUCCESS_CODE: StatusCode = StatusCode::new(400);
const URI: &str = "http://doc.rust-lang.org/std/string/index.html";
const URI_S: &str = "https://doc.rust-lang.org/std/string/index.html";
const BODY: [u8; 14] = [78, 97, 109, 101, 61, 74, 97, 109, 101, 115, 43, 74, 97, 121];
const RESPONSE: &[u8; 129] = b"HTTP/1.1 200 OK\r\n\
Date: Sat, 11 Jan 2003 02:44:04 GMT\r\n\
Content-Type: text/html\r\n\
Content-Length: 100\r\n\r\n\
<html>hello</html>\r\n\r\nhello";
const RESPONSE_H: &[u8; 102] = b"HTTP/1.1 200 OK\r\n\
Date: Sat, 11 Jan 2003 02:44:04 GMT\r\n\
Content-Type: text/html\r\n\
Content-Length: 100\r\n\r\n";
#[test]
fn counter_new() {
let counter = Counter::new(200);
assert_eq!(counter.count, 0);
assert_eq!(counter.stop, 200);
}
#[test]
fn counter_next() {
let mut counter = Counter::new(5);
assert_eq!(counter.next(), Some(false));
assert_eq!(counter.next(), Some(false));
assert_eq!(counter.next(), Some(false));
assert_eq!(counter.next(), Some(false));
assert_eq!(counter.next(), Some(true));
assert_eq!(counter.next(), Some(false));
assert_eq!(counter.next(), Some(false));
}
#[test]
fn copy_data_until() {
let mut reader = Vec::new();
reader.extend(&RESPONSE[..]);
let mut reader = Cursor::new(reader);
let [head, _body] = copy_until(
&mut reader,
&CR_LF_2,
Instant::now() + Duration::from_secs(360),
)
.unwrap();
assert_eq!(&head[..], &RESPONSE_H[..]);
}
#[test]
fn method_display() {
const METHOD: Method = Method::HEAD;
assert_eq!(&format!("{}", METHOD), "HEAD");
}
#[test]
fn request_b_new() {
RequestBuilder::new(&Uri::try_from(URI).unwrap());
RequestBuilder::new(&Uri::try_from(URI_S).unwrap());
}
#[test]
fn request_b_method() {
let uri = Uri::try_from(URI).unwrap();
let mut req = RequestBuilder::new(&uri);
let req = req.method(Method::HEAD);
assert_eq!(req.method, Method::HEAD);
}
#[test]
fn request_b_headers() {
let mut headers = Headers::new();
headers.insert("Accept-Charset", "utf-8");
headers.insert("Accept-Language", "en-US");
headers.insert("Host", "doc.rust-lang.org");
headers.insert("Connection", "Close");
let uri = Uri::try_from(URI).unwrap();
let mut req = RequestBuilder::new(&uri);
let req = req.headers(headers.clone());
assert_eq!(req.headers, headers);
}
#[test]
fn request_b_header() {
let uri = Uri::try_from(URI).unwrap();
let mut req = RequestBuilder::new(&uri);
let k = "Connection";
let v = "Close";
let mut expect_headers = Headers::new();
expect_headers.insert("Host", "doc.rust-lang.org");
expect_headers.insert("Referer", "http://doc.rust-lang.org/std/string/index.html");
expect_headers.insert(k, v);
let req = req.header(k, v);
assert_eq!(req.headers, expect_headers);
}
#[test]
fn request_b_body() {
let uri = Uri::try_from(URI).unwrap();
let mut req = RequestBuilder::new(&uri);
let req = req.body(&BODY);
assert_eq!(req.body, Some(BODY.as_ref()));
}
#[test]
fn request_b_timeout() {
let uri = Uri::try_from(URI).unwrap();
let mut req = RequestBuilder::new(&uri);
let timeout = Some(Duration::from_secs(360));
req.timeout(timeout);
assert_eq!(req.timeout, timeout);
}
#[ignore]
#[test]
fn request_b_send() {
let mut writer = Vec::new();
let uri = Uri::try_from(URI).unwrap();
let mut stream = TcpStream::connect((uri.host().unwrap_or(""), uri.corr_port())).unwrap();
RequestBuilder::new(&Uri::try_from(URI).unwrap())
.header("Connection", "Close")
.send(&mut stream, &mut writer)
.unwrap();
}
#[test]
fn request_b_parse_msg() {
let uri = Uri::try_from(URI).unwrap();
let req = RequestBuilder::new(&uri);
const DEFAULT_MSG: &str = "GET /std/string/index.html HTTP/1.1\r\n\
Referer: http://doc.rust-lang.org/std/string/index.html\r\n\
Host: doc.rust-lang.org\r\n\r\n";
let msg = req.parse_msg();
let msg = String::from_utf8_lossy(&msg).into_owned();
for line in DEFAULT_MSG.lines() {
assert!(msg.contains(line));
}
for line in msg.lines() {
assert!(DEFAULT_MSG.contains(line));
}
}
#[test]
fn request_new() {
let uri = Uri::try_from(URI).unwrap();
Request::new(&uri);
}
#[test]
fn request_method() {
let uri = Uri::try_from(URI).unwrap();
let mut req = Request::new(&uri);
req.method(Method::HEAD);
assert_eq!(req.inner.method, Method::HEAD);
}
#[test]
fn request_headers() {
let mut headers = Headers::new();
headers.insert("Accept-Charset", "utf-8");
headers.insert("Accept-Language", "en-US");
headers.insert("Host", "doc.rust-lang.org");
headers.insert("Connection", "Close");
let uri = Uri::try_from(URI).unwrap();
let mut req = Request::new(&uri);
let req = req.headers(headers.clone());
assert_eq!(req.inner.headers, headers);
}
#[test]
fn request_header() {
let uri = Uri::try_from(URI).unwrap();
let mut req = Request::new(&uri);
let k = "Accept-Language";
let v = "en-US";
let mut expect_headers = Headers::new();
expect_headers.insert("Host", "doc.rust-lang.org");
expect_headers.insert("Referer", "http://doc.rust-lang.org/std/string/index.html");
expect_headers.insert("Connection", "Close");
expect_headers.insert(k, v);
let req = req.header(k, v);
assert_eq!(req.inner.headers, expect_headers);
}
#[test]
fn request_body() {
let uri = Uri::try_from(URI).unwrap();
let mut req = Request::new(&uri);
let req = req.body(&BODY);
assert_eq!(req.inner.body, Some(BODY.as_ref()));
}
#[test]
fn request_timeout() {
let uri = Uri::try_from(URI).unwrap();
let mut request = Request::new(&uri);
let timeout = Some(Duration::from_secs(360));
request.timeout(timeout);
assert_eq!(request.inner.timeout, timeout);
}
#[ignore]
#[test]
fn request_connect_timeout() {
let uri = Uri::try_from(URI).unwrap();
let mut request = Request::new(&uri);
request.connect_timeout(Some(Duration::from_nanos(1)));
assert_eq!(request.connect_timeout, Some(Duration::from_nanos(1)));
let err = request.send(&mut io::sink()).unwrap_err();
match err {
Error::IO(err) => assert_eq!(err.kind(), io::ErrorKind::TimedOut),
other => panic!("Expected error to be io::Error, got: {:?}", other),
};
}
#[ignore]
#[test]
fn request_read_timeout() {
let uri = Uri::try_from(URI).unwrap();
let mut request = Request::new(&uri);
request.read_timeout(Some(Duration::from_nanos(1)));
assert_eq!(request.read_timeout, Some(Duration::from_nanos(1)));
let err = request.send(&mut io::sink()).unwrap_err();
match err {
Error::IO(err) => match err.kind() {
io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => {}
other => panic!(
"Expected error kind to be one of WouldBlock/TimedOut, got: {:?}",
other
),
},
other => panic!("Expected error to be io::Error, got: {:?}", other),
};
}
#[test]
fn request_write_timeout() {
let uri = Uri::try_from(URI).unwrap();
let mut request = Request::new(&uri);
request.write_timeout(Some(Duration::from_nanos(100)));
assert_eq!(request.write_timeout, Some(Duration::from_nanos(100)));
}
#[test]
fn request_send() {
let mut writer = Vec::new();
let uri = Uri::try_from(URI).unwrap();
let res = Request::new(&uri).send(&mut writer).unwrap();
assert_ne!(res.status_code(), UNSUCCESS_CODE);
}
#[ignore]
#[test]
fn request_get() {
let mut writer = Vec::new();
let res = get(URI, &mut writer).unwrap();
assert_ne!(res.status_code(), UNSUCCESS_CODE);
let mut writer = Vec::with_capacity(200);
let res = get(URI_S, &mut writer).unwrap();
assert_ne!(res.status_code(), UNSUCCESS_CODE);
}
#[ignore]
#[test]
fn request_head() {
let res = head(URI).unwrap();
assert_ne!(res.status_code(), UNSUCCESS_CODE);
let res = head(URI_S).unwrap();
assert_ne!(res.status_code(), UNSUCCESS_CODE);
}
#[ignore]
#[test]
fn request_post() {
let mut writer = Vec::new();
let res = post(URI, &BODY, &mut writer).unwrap();
assert_ne!(res.status_code(), UNSUCCESS_CODE);
let mut writer = Vec::with_capacity(200);
let res = post(URI_S, &BODY, &mut writer).unwrap();
assert_ne!(res.status_code(), UNSUCCESS_CODE);
}
}