use async_std::io::{self, BufReader, Read, Write};
use async_std::prelude::*;
use async_std::task::{Context, Poll};
use futures_core::ready;
use http_types::{ensure, ensure_eq, format_err};
use http_types::{
headers::{HeaderName, HeaderValue, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
Body, Request, Response, StatusCode,
};
use std::convert::TryFrom;
use std::pin::Pin;
use std::str::FromStr;
use crate::chunked::ChunkedDecoder;
use crate::date::fmt_http_date;
use crate::MAX_HEADERS;
#[doc(hidden)]
#[derive(Debug)]
pub struct Encoder {
cursor: usize,
headers: Vec<u8>,
headers_done: bool,
request: Request,
body_done: bool,
body_bytes_read: usize,
}
impl Encoder {
pub(crate) fn new(headers: Vec<u8>, request: Request) -> Self {
Self {
request,
headers,
cursor: 0,
headers_done: false,
body_done: false,
body_bytes_read: 0,
}
}
}
pub async fn connect<RW>(mut stream: RW, req: Request) -> http_types::Result<Response>
where
RW: Read + Write + Send + Sync + Unpin + 'static,
{
let mut req = encode(req).await?;
log::trace!("> {:?}", &req);
io::copy(&mut req, &mut stream).await?;
let res = decode(stream).await?;
log::trace!("< {:?}", &res);
Ok(res)
}
#[doc(hidden)]
async fn encode(req: Request) -> http_types::Result<Encoder> {
let mut buf: Vec<u8> = vec![];
let mut url = req.url().path().to_owned();
if let Some(fragment) = req.url().fragment() {
url.push('#');
url.push_str(fragment);
}
if let Some(query) = req.url().query() {
url.push('?');
url.push_str(query);
}
let val = format!("{} {} HTTP/1.1\r\n", req.method(), url);
log::trace!("> {}", &val);
buf.write_all(val.as_bytes()).await?;
let host = req.url().host_str();
let host = host.ok_or_else(|| format_err!("Missing hostname"))?;
let val = if let Some(port) = req.url().port() {
format!("host: {}:{}\r\n", host, port)
} else {
format!("host: {}\r\n", host)
};
log::trace!("> {}", &val);
buf.write_all(val.as_bytes()).await?;
if let Some(len) = req.len() {
let val = format!("content-length: {}\r\n", len);
log::trace!("> {}", &val);
buf.write_all(val.as_bytes()).await?;
} else {
panic!("chunked encoding is not implemented yet");
}
let date = fmt_http_date(std::time::SystemTime::now());
buf.write_all(b"date: ").await?;
buf.write_all(date.as_bytes()).await?;
buf.write_all(b"\r\n").await?;
for (header, values) in req.iter() {
for value in values.iter() {
let val = format!("{}: {}\r\n", header, value);
log::trace!("> {}", &val);
buf.write_all(val.as_bytes()).await?;
}
}
buf.write_all(b"\r\n").await?;
Ok(Encoder::new(buf, req))
}
#[doc(hidden)]
pub async fn decode<R>(reader: R) -> http_types::Result<Response>
where
R: Read + Unpin + Send + Sync + 'static,
{
let mut reader = BufReader::new(reader);
let mut buf = Vec::new();
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
let mut httparse_res = httparse::Response::new(&mut headers);
loop {
let bytes_read = reader.read_until(b'\n', &mut buf).await?;
assert!(bytes_read != 0, "Empty response");
let idx = buf.len() - 1;
if idx >= 3 && &buf[idx - 3..=idx] == b"\r\n\r\n" {
break;
}
}
let status = httparse_res.parse(&buf)?;
ensure!(!status.is_partial(), "Malformed HTTP head");
let code = httparse_res.code;
let code = code.ok_or_else(|| format_err!("No status code found"))?;
let version = httparse_res.version;
let version = version.ok_or_else(|| format_err!("No version found"))?;
ensure_eq!(version, 1, "Unsupported HTTP version");
let mut res = Response::new(StatusCode::try_from(code)?);
for header in httparse_res.headers.iter() {
let name = HeaderName::from_str(header.name)?;
let value = HeaderValue::from_str(std::str::from_utf8(header.value)?)?;
res.insert_header(name, value)?;
}
if res.header(&DATE).is_none() {
let date = fmt_http_date(std::time::SystemTime::now());
res.insert_header(DATE, &format!("date: {}\r\n", date)[..])?;
}
let content_length = res.header(&CONTENT_LENGTH);
let transfer_encoding = res.header(&TRANSFER_ENCODING);
ensure!(
content_length.is_none() || transfer_encoding.is_none(),
"Unexpected Content-Length header"
);
match transfer_encoding {
Some(encoding) if !encoding.is_empty() => {
if encoding.last().unwrap().as_str() == "chunked" {
let trailers_sender = res.send_trailers();
let reader = BufReader::new(ChunkedDecoder::new(reader, trailers_sender));
res.set_body(Body::from_reader(reader, None));
return Ok(res);
}
}
_ => {
}
}
match content_length {
Some(len) => {
let len = len.last().unwrap().as_str().parse::<usize>()?;
res.set_body(Body::from_reader(reader.take(len as u64), Some(len)));
}
None => {}
}
Ok(res)
}
impl Read for Encoder {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let mut bytes_read = 0;
if !self.headers_done {
let len = std::cmp::min(self.headers.len() - self.cursor, buf.len());
let range = self.cursor..self.cursor + len;
buf[0..len].copy_from_slice(&mut self.headers[range]);
self.cursor += len;
if self.cursor == self.headers.len() {
self.headers_done = true;
}
bytes_read += len;
}
if !self.body_done {
let n = ready!(Pin::new(&mut self.request).poll_read(cx, &mut buf[bytes_read..]))?;
bytes_read += n;
self.body_bytes_read += n;
if bytes_read == 0 {
self.body_done = true;
}
}
Poll::Ready(Ok(bytes_read as usize))
}
}