use crate::{parse, response::EffectiveUri, Metrics, Body, Error};
use crossbeam_channel::{Receiver, Sender, TryRecvError};
use crossbeam_utils::atomic::AtomicCell;
use curl::easy::{InfoType, ReadError, SeekResult, WriteError};
use curl_sys::CURL;
use futures_io::{AsyncRead, AsyncWrite};
use futures_util::pin_mut;
use futures_util::task::AtomicWaker;
use http::{Response, Uri};
use sluice::pipe;
use std::{
ascii,
ffi::CStr,
fmt,
future::Future,
io,
os::raw::c_char,
pin::Pin,
ptr,
sync::Arc,
task::{Context, Poll, Waker},
};
pub(crate) struct RequestHandler {
shared: Arc<Shared>,
sender: Option<Sender<Result<http::response::Builder, Error>>>,
request_body: Body,
request_body_waker: Option<Waker>,
response_status_code: Option<http::StatusCode>,
response_version: Option<http::Version>,
response_headers: http::HeaderMap,
response_body_writer: pipe::PipeWriter,
response_body_waker: Option<Waker>,
metrics: Option<Metrics>,
handle: *mut CURL,
}
#[allow(unsafe_code)]
unsafe impl Send for RequestHandler {}
#[derive(Debug)]
struct Shared {
id: AtomicCell<usize>,
waker: AtomicWaker,
completed: AtomicCell<bool>,
future_dropped: AtomicCell<bool>,
response_body_dropped: AtomicCell<bool>,
}
impl RequestHandler {
pub(crate) fn new(request_body: Body) -> (Self, RequestHandlerFuture) {
let (sender, receiver) = crossbeam_channel::bounded(1);
let shared = Arc::new(Shared {
id: AtomicCell::new(usize::max_value()),
waker: AtomicWaker::default(),
completed: AtomicCell::new(false),
future_dropped: AtomicCell::new(false),
response_body_dropped: AtomicCell::new(false),
});
let (response_body_reader, response_body_writer) = pipe::pipe();
(
Self {
sender: Some(sender),
shared: shared.clone(),
request_body,
request_body_waker: None,
response_status_code: None,
response_version: None,
response_headers: http::HeaderMap::new(),
response_body_writer,
response_body_waker: None,
metrics: None,
handle: ptr::null_mut(),
},
RequestHandlerFuture {
receiver,
shared,
response_body_reader: Some(response_body_reader),
},
)
}
pub(crate) fn init(
&mut self,
id: usize,
handle: *mut CURL,
request_waker: Waker,
response_waker: Waker,
) {
debug_assert!(self.shared.id.load() == usize::max_value());
debug_assert!(self.request_body_waker.is_none());
debug_assert!(self.response_body_waker.is_none());
log::debug!("initializing handler for request [id={}]", id);
self.shared.id.store(id);
self.handle = handle;
self.request_body_waker = Some(request_waker);
self.response_body_waker = Some(response_waker);
}
pub(crate) fn on_result(&mut self, result: Result<(), curl::Error>) {
self.shared.completed.store(true);
match result {
Ok(()) => self.flush_response_headers(),
Err(e) => {
log::debug!("curl error: {}", e);
self.complete(Err(e.into()));
}
}
}
fn flush_response_headers(&mut self) {
if self.sender.is_some() {
let mut builder = http::Response::builder();
if let Some(status) = self.response_status_code.take() {
builder.status(status);
}
if let Some(version) = self.response_version.take() {
builder.version(version);
}
for (name, values) in self.response_headers.drain() {
for value in values {
builder.header(&name, value);
}
}
if let Some(uri) = self.get_effective_uri() {
builder.extension(EffectiveUri(uri));
}
if let Some(metrics) = self.metrics.clone() {
builder.extension(metrics);
}
self.complete(Ok(builder));
}
}
fn complete(&mut self, result: Result<http::response::Builder, Error>) {
if let Some(sender) = self.sender.take() {
if let Err(e) = result.as_ref() {
log::warn!(
"request completed with error [id={:?}]: {}",
self.shared.id,
e
);
}
match sender.send(result) {
Ok(()) => {
self.shared.waker.wake();
}
Err(_) => {
log::debug!("request canceled by user [id={:?}]", self.shared.id);
}
}
}
}
#[allow(unsafe_code)]
fn get_effective_uri(&mut self) -> Option<Uri> {
Some(self.handle)
.filter(|ptr| !ptr.is_null())
.and_then(|handle| unsafe {
let mut ptr = ptr::null::<c_char>();
if curl_sys::curl_easy_getinfo(handle, curl_sys::CURLINFO_EFFECTIVE_URL, &mut ptr)
!= curl_sys::CURLE_OK
{
None
} else {
Some(ptr)
}
})
.filter(|ptr| !ptr.is_null())
.map(|ptr| unsafe { CStr::from_ptr(ptr) })
.and_then(|cstr| cstr.to_str().ok())
.and_then(|s| s.parse().ok())
}
}
impl curl::easy::Handler for RequestHandler {
fn header(&mut self, data: &[u8]) -> bool {
if self.shared.future_dropped.load() {
return false;
}
if let Some((version, status)) = parse::parse_status_line(data) {
self.response_version = Some(version);
self.response_status_code = Some(status);
self.response_headers.clear();
return true;
}
if let Some((name, value)) = parse::parse_header(data) {
self.response_headers.append(name, value);
return true;
}
if data == b"\r\n" {
return true;
}
false
}
fn read(&mut self, data: &mut [u8]) -> Result<usize, ReadError> {
if self.shared.future_dropped.load() {
return Err(ReadError::Abort);
}
if let Some(waker) = self.request_body_waker.as_ref() {
let mut context = Context::from_waker(waker);
match Pin::new(&mut self.request_body).poll_read(&mut context, data) {
Poll::Pending => Err(ReadError::Pause),
Poll::Ready(Ok(len)) => Ok(len),
Poll::Ready(Err(e)) => {
log::error!("error reading request body: {}", e);
Err(ReadError::Abort)
}
}
} else {
log::error!("request has not been initialized!");
Err(ReadError::Abort)
}
}
fn seek(&mut self, whence: io::SeekFrom) -> SeekResult {
if whence == io::SeekFrom::Start(0) && self.request_body.reset() {
SeekResult::Ok
} else {
log::warn!("seek requested for request body, but it is not supported");
SeekResult::CantSeek
}
}
fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> {
log::trace!("received {} bytes of data", data.len());
if self.shared.response_body_dropped.load() {
return Ok(0);
}
self.flush_response_headers();
if let Some(waker) = self.response_body_waker.as_ref() {
let mut context = Context::from_waker(waker);
match Pin::new(&mut self.response_body_writer).poll_write(&mut context, data) {
Poll::Pending => Err(WriteError::Pause),
Poll::Ready(Ok(len)) => Ok(len),
Poll::Ready(Err(e)) => {
if e.kind() == io::ErrorKind::BrokenPipe {
log::warn!(
"failed to write response body because the response reader was dropped"
);
} else {
log::error!("error writing response body to buffer: {}", e);
}
Ok(0)
}
}
} else {
log::error!("request has not been initialized!");
Ok(0)
}
}
#[allow(unsafe_code)]
fn progress(
&mut self,
dltotal: f64,
dlnow: f64,
ultotal: f64,
ulnow: f64,
) -> bool {
let metrics = self.metrics.get_or_insert_with(Metrics::new);
metrics.inner.upload_progress.store(ulnow);
metrics.inner.upload_total.store(ultotal);
metrics.inner.download_progress.store(dlnow);
metrics.inner.download_total.store(dltotal);
if !self.handle.is_null() {
unsafe {
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_SPEED_UPLOAD,
metrics.inner.upload_speed.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_SPEED_DOWNLOAD,
metrics.inner.download_speed.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_NAMELOOKUP_TIME,
metrics.inner.namelookup_time.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_CONNECT_TIME,
metrics.inner.connect_time.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_APPCONNECT_TIME,
metrics.inner.appconnect_time.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_PRETRANSFER_TIME,
metrics.inner.pretransfer_time.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_STARTTRANSFER_TIME,
metrics.inner.starttransfer_time.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_TOTAL_TIME,
metrics.inner.total_time.as_ptr(),
);
curl_sys::curl_easy_getinfo(
self.handle,
curl_sys::CURLINFO_REDIRECT_TIME,
metrics.inner.redirect_time.as_ptr(),
);
}
}
true
}
fn debug(&mut self, kind: InfoType, data: &[u8]) {
fn format_byte_string(bytes: impl AsRef<[u8]>) -> String {
String::from_utf8(
bytes
.as_ref()
.iter()
.flat_map(|byte| ascii::escape_default(*byte))
.collect(),
)
.unwrap_or_else(|_| String::from("<binary>"))
}
match kind {
InfoType::Text => log::debug!(target: "isahc::curl", "{}", String::from_utf8_lossy(data).trim_end()),
InfoType::HeaderIn | InfoType::DataIn => log::trace!(target: "isahc::wire", "<< {}", format_byte_string(data)),
InfoType::HeaderOut | InfoType::DataOut => log::trace!(target: "isahc::wire", ">> {}", format_byte_string(data)),
_ => (),
}
}
}
impl fmt::Debug for RequestHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RequestHandler({:?})", self.shared.id)
}
}
pub(crate) struct RequestHandlerFuture {
receiver: Receiver<Result<http::response::Builder, Error>>,
shared: Arc<Shared>,
response_body_reader: Option<pipe::PipeReader>,
}
impl RequestHandlerFuture {
pub(crate) fn join(mut self) -> Result<Response<ResponseBodyReader>, Error> {
match self.receiver.recv() {
Ok(Ok(builder)) => self.complete(builder),
Ok(Err(e)) => Err(e),
Err(_) => Err(Error::Aborted),
}
}
fn complete(
&mut self,
mut builder: http::response::Builder,
) -> Result<Response<ResponseBodyReader>, Error> {
let body = ResponseBodyReader {
inner: self.response_body_reader.take().unwrap(),
shared: self.shared.clone(),
content_length: builder
.headers_ref()
.unwrap()
.get(http::header::CONTENT_LENGTH)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok()),
};
match builder.body(body) {
Ok(response) => Ok(response),
Err(e) => Err(Error::InvalidHttpFormat(e)),
}
}
}
impl Future for RequestHandlerFuture {
type Output = Result<Response<ResponseBodyReader>, Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.shared.waker.register(cx.waker());
match self.receiver.try_recv() {
Err(TryRecvError::Empty) => Poll::Pending,
Ok(Ok(builder)) => Poll::Ready(self.complete(builder)),
Ok(Err(e)) => Poll::Ready(Err(e)),
Err(TryRecvError::Disconnected) => Poll::Ready(Err(Error::Aborted)),
}
}
}
impl fmt::Debug for RequestHandlerFuture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RequestHandlerFuture({:?})", self.shared.id)
}
}
impl Drop for RequestHandlerFuture {
fn drop(&mut self) {
self.shared.future_dropped.store(true);
}
}
#[derive(Debug)]
pub(crate) struct ResponseBodyReader {
inner: pipe::PipeReader,
shared: Arc<Shared>,
content_length: Option<u64>,
}
impl ResponseBodyReader {
pub(crate) fn len(&self) -> Option<u64> {
self.content_length
}
}
impl AsyncRead for ResponseBodyReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let inner = &mut self.inner;
pin_mut!(inner);
match inner.poll_read(cx, buf) {
Poll::Ready(Ok(0)) => {
if !self.shared.completed.load() {
Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into()))
} else {
Poll::Ready(Ok(0))
}
}
poll => poll,
}
}
}
impl Drop for ResponseBodyReader {
fn drop(&mut self) {
self.shared.response_body_dropped.store(true);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn is_send<T: Send>() {}
#[test]
fn traits() {
is_send::<RequestHandlerFuture>();
}
}