use anyhow_ext::{Context, Result, anyhow};
use async_std::io::ReadExt;
use encoding_rs::GBK;
use hashbrown::HashMap;
use indexmap::IndexSet;
use std::{net::SocketAddr, vec};
use tracing::error;
use crate::{
client::{read_until_v, return_stream_to_pool},
error::ZjhttpcError,
misc::HttpVersion,
proxy::HttpsProxyOption,
stream::BoxedStream,
};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
pub struct ChunkedDecoderStream {
inner: Option<BoxedStream>,
state: DecoderState,
chunk_remaining: usize,
line_buffer: Vec<u8>,
trailer_buffer: Vec<u8>,
completion_flag: Arc<AtomicBool>,
addr: SocketAddr,
is_tls: bool,
proxy_used: Option<HttpsProxyOption>,
}
pub struct BodyFixedLengthStream {
inner: Option<BoxedStream>,
remaining: usize,
completion_flag: Arc<AtomicBool>,
addr: SocketAddr,
is_tls: bool,
proxy_used: Option<HttpsProxyOption>,
}
pub struct BodyUnknownLengthStream {
inner: Option<BoxedStream>,
completion_flag: Arc<AtomicBool>,
addr: SocketAddr,
is_tls: bool,
proxy_used: Option<HttpsProxyOption>,
}
#[derive(Debug, Clone, PartialEq)]
enum DecoderState {
ReadingChunkSize,
ReadingChunkData,
ReadingChunkTrailer,
Complete,
}
impl ChunkedDecoderStream {
pub fn new(inner: BoxedStream) -> Self {
Self {
inner: Some(inner),
state: DecoderState::ReadingChunkSize,
chunk_remaining: 0,
line_buffer: Vec::new(),
trailer_buffer: Vec::new(),
completion_flag: Arc::new(AtomicBool::new(false)),
addr: std::net::SocketAddr::from(([0, 0, 0, 0], 0)),
is_tls: false,
proxy_used: None,
}
}
pub fn new_with_completion_flag(
inner: BoxedStream,
completion_flag: Arc<AtomicBool>,
addr: SocketAddr,
is_tls: bool,
proxy_used: Option<HttpsProxyOption>,
) -> Self {
Self {
inner: Some(inner),
state: DecoderState::ReadingChunkSize,
chunk_remaining: 0,
line_buffer: Vec::new(),
trailer_buffer: Vec::new(),
completion_flag,
addr,
is_tls,
proxy_used,
}
}
pub fn is_fully_consumed(&self) -> bool {
self.completion_flag.load(Ordering::Relaxed)
}
fn return_stream_to_pool(&mut self) {
if let Some(stream) = self.inner.take() {
let stream_info = crate::client::StreamInfo {
addr: self.addr,
is_tls: self.is_tls,
proxy_used: self.proxy_used.clone(),
};
return_stream_to_pool(stream, stream_info);
}
}
async fn read_chunk_size(&mut self) -> Result<bool> {
let inner_stream = self
.inner
.as_mut()
.ok_or_else(|| anyhow!("stream is None"))?;
self.line_buffer.clear();
let n = read_until_v(inner_stream, b"\r\n", &mut self.line_buffer).await?;
let mut chunk_size_str = String::from_utf8_lossy(&self.line_buffer[..n]);
if chunk_size_str.trim().is_empty() {
self.line_buffer.clear();
let n = read_until_v(inner_stream, b"\r\n", &mut self.line_buffer).await?;
chunk_size_str = String::from_utf8_lossy(&self.line_buffer[..n]);
}
let chunk_size = usize::from_str_radix(chunk_size_str.trim(), 16).map_err(|e| {
anyhow!(
"invalid chunk size '{:?}': {}",
chunk_size_str.as_bytes(),
e
)
})?;
if chunk_size == 0 {
self.line_buffer.clear();
let n = read_until_v(inner_stream, b"\r\n", &mut self.line_buffer).await?;
if n != 2 {
let x = String::from_utf8_lossy(&self.line_buffer[..n]);
return Err(anyhow!(
"not possible, it's not \\r\\n after zero in chunk. x={x}, n={n}"
));
}
return Ok(false); }
self.chunk_remaining = chunk_size;
Ok(true) }
async fn read_chunk_trailer(&mut self) -> Result<()> {
let inner_stream = self
.inner
.as_mut()
.ok_or_else(|| anyhow!("stream is None"))?;
self.trailer_buffer.clear();
let n = read_until_v(inner_stream, b"\r\n", &mut self.trailer_buffer).await?;
if n != 2 {
let x = String::from_utf8_lossy(&self.trailer_buffer[..n]);
return Err(anyhow!(
"not possible, it's not \\r\\n after chunk data. x={x}, n={n}"
));
}
Ok(())
}
}
impl async_std::io::Read for ChunkedDecoderStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
if self.completion_flag.load(Ordering::Relaxed) {
return std::task::Poll::Ready(Ok(0));
}
loop {
match &self.state {
DecoderState::Complete => {
self.completion_flag.store(true, Ordering::Relaxed);
self.return_stream_to_pool();
return std::task::Poll::Ready(Ok(0));
}
DecoderState::ReadingChunkSize => {
match async_std::task::block_on(self.read_chunk_size()) {
Ok(true) => {
self.state = DecoderState::ReadingChunkData;
continue;
}
Ok(false) => {
self.state = DecoderState::Complete;
self.completion_flag.store(true, Ordering::Relaxed);
self.return_stream_to_pool();
return std::task::Poll::Ready(Ok(0));
}
Err(e) => {
return std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Other,
e,
)));
}
}
}
DecoderState::ReadingChunkTrailer => {
match async_std::task::block_on(self.read_chunk_trailer()) {
Ok(_) => {
self.state = DecoderState::ReadingChunkSize;
continue;
}
Err(e) => {
return std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Other,
e,
)));
}
}
}
DecoderState::ReadingChunkData => {
if self.chunk_remaining == 0 {
self.state = DecoderState::ReadingChunkTrailer;
continue;
}
let to_read = std::cmp::min(buf.len(), self.chunk_remaining);
let mut temp_buf = vec![0u8; to_read];
if let Some(inner_stream) = &mut self.inner {
match std::pin::Pin::new(inner_stream).poll_read(cx, &mut temp_buf) {
std::task::Poll::Ready(Ok(n)) => {
if n == 0 {
return std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"unexpected end of stream while reading chunk data",
)));
}
buf[..n].copy_from_slice(&temp_buf[..n]);
self.chunk_remaining -= n;
return std::task::Poll::Ready(Ok(n));
}
std::task::Poll::Ready(Err(e)) => {
return std::task::Poll::Ready(Err(e));
}
std::task::Poll::Pending => return std::task::Poll::Pending,
}
} else {
return std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Other,
"stream is None",
)));
}
}
}
}
}
}
impl async_std::io::Write for ChunkedDecoderStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"ChunkedDecoderStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl BodyFixedLengthStream {
pub fn new(inner: BoxedStream, content_length: usize) -> Self {
Self {
inner: Some(inner),
remaining: content_length,
completion_flag: Arc::new(AtomicBool::new(false)),
addr: std::net::SocketAddr::from(([0, 0, 0, 0], 0)),
is_tls: false,
proxy_used: None,
}
}
pub fn new_with_completion_flag(
inner: BoxedStream,
content_length: usize,
completion_flag: Arc<AtomicBool>,
addr: SocketAddr,
is_tls: bool,
proxy_used: Option<HttpsProxyOption>,
) -> Self {
Self {
inner: Some(inner),
remaining: content_length,
completion_flag,
addr,
is_tls,
proxy_used,
}
}
pub fn is_fully_consumed(&self) -> bool {
self.completion_flag.load(Ordering::Relaxed)
}
fn return_stream_to_pool(&mut self) {
if let Some(stream) = self.inner.take() {
let stream_info = crate::client::StreamInfo {
addr: self.addr,
is_tls: self.is_tls,
proxy_used: self.proxy_used.clone(),
};
return_stream_to_pool(stream, stream_info);
}
}
}
impl async_std::io::Read for BodyFixedLengthStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
if self.completion_flag.load(Ordering::Relaxed) || self.remaining == 0 {
self.completion_flag.store(true, Ordering::Relaxed);
self.return_stream_to_pool();
return std::task::Poll::Ready(Ok(0));
}
let to_read = std::cmp::min(buf.len(), self.remaining);
if to_read == 0 {
self.completion_flag.store(true, Ordering::Relaxed);
self.return_stream_to_pool();
return std::task::Poll::Ready(Ok(0));
}
if let Some(inner_stream) = &mut self.inner {
match std::pin::Pin::new(inner_stream).poll_read(cx, &mut buf[..to_read]) {
std::task::Poll::Ready(Ok(n)) => {
if n == 0 {
return std::task::Poll::Ready(Ok(0));
}
self.remaining -= n;
if self.remaining == 0 {
self.completion_flag.store(true, Ordering::Relaxed);
self.return_stream_to_pool();
}
std::task::Poll::Ready(Ok(n))
}
std::task::Poll::Ready(Err(e)) => std::task::Poll::Ready(Err(e)),
std::task::Poll::Pending => std::task::Poll::Pending,
}
} else {
return std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Other,
"stream is None",
)));
}
}
}
impl async_std::io::Write for BodyFixedLengthStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"BodyFixedLengthStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl BodyUnknownLengthStream {
pub fn new_with_completion_flag(
inner: BoxedStream,
completion_flag: Arc<AtomicBool>,
addr: SocketAddr,
is_tls: bool,
proxy_used: Option<HttpsProxyOption>,
) -> Self {
Self {
inner: Some(inner),
completion_flag,
addr,
is_tls,
proxy_used,
}
}
pub fn is_fully_consumed(&self) -> bool {
self.completion_flag.load(Ordering::Relaxed)
}
fn return_stream_to_pool(&mut self) {
if let Some(stream) = self.inner.take() {
let stream_info = crate::client::StreamInfo {
addr: self.addr,
is_tls: self.is_tls,
proxy_used: self.proxy_used.clone(),
};
return_stream_to_pool(stream, stream_info);
}
}
}
impl async_std::io::Read for BodyUnknownLengthStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
if self.completion_flag.load(Ordering::Relaxed) {
return std::task::Poll::Ready(Ok(0));
}
if let Some(inner_stream) = &mut self.inner {
match std::pin::Pin::new(inner_stream).poll_read(cx, buf) {
std::task::Poll::Ready(Ok(0)) => {
self.completion_flag.store(true, Ordering::Relaxed);
self.return_stream_to_pool();
std::task::Poll::Ready(Ok(0))
}
std::task::Poll::Ready(Ok(n)) => std::task::Poll::Ready(Ok(n)),
std::task::Poll::Ready(Err(e)) => std::task::Poll::Ready(Err(e)),
std::task::Poll::Pending => std::task::Poll::Pending,
}
} else {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Other,
"stream is None",
)))
}
}
}
impl async_std::io::Write for BodyUnknownLengthStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"BodyUnknownLengthStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for BodyFixedLengthStream {}
impl crate::stream::RWStream for ChunkedDecoderStream {}
impl crate::stream::RWStream for BodyUnknownLengthStream {}
pub struct Response {
pub addr: SocketAddr,
pub is_tls: bool,
pub http_version: HttpVersion,
pub status_code: u16,
pub headers: HashMap<String, IndexSet<String>>,
pub body_raw_stream: Option<BoxedStream>,
pub proxy_used: Option<HttpsProxyOption>,
body_completion_flag: Arc<AtomicBool>,
pub read_body_timeout: Option<std::time::Duration>,
}
impl Drop for Response {
fn drop(&mut self) {
if self.body_completion_flag.load(Ordering::Relaxed) {
if let Some(stream) = self.body_raw_stream.take() {
let stream_info = crate::client::StreamInfo {
addr: self.addr,
is_tls: self.is_tls,
proxy_used: self.proxy_used.clone(),
};
return_stream_to_pool(stream, stream_info);
}
} else {
}
}
}
impl Response {
pub fn new_from_parse_result(
http_version: &str,
status_code: &str,
headers_vec: Vec<(String, String)>,
stream: BoxedStream,
is_tls: bool,
addr: SocketAddr,
proxy_used: Option<HttpsProxyOption>,
read_body_timeout: Option<std::time::Duration>,
) -> Result<Self, ZjhttpcError> {
let http_version = match http_version {
"1.1" => HttpVersion::V1_1,
"1.0" => HttpVersion::V1_0,
others => return Err(ZjhttpcError::InvalidHttpResponseVersion(others.to_string())),
};
let status_code: u16 = status_code
.parse()
.map_err(|_e| ZjhttpcError::InvalidHttpResponseStatusCode(status_code.to_string()))?;
let mut headers: HashMap<String, IndexSet<String>> = HashMap::new();
for (key, value) in headers_vec {
match headers.get_mut(&key) {
Some(set) => {
set.insert(value);
}
None => {
let mut set = IndexSet::new();
set.insert(value);
headers.insert(key, set);
}
};
}
let resp = Response {
is_tls,
http_version,
status_code,
headers,
body_raw_stream: Some(stream),
addr,
proxy_used,
body_completion_flag: Arc::new(AtomicBool::new(false)),
read_body_timeout,
};
return Ok(resp);
}
pub fn status_code(&self) -> u16 {
self.status_code
}
pub fn is_success(&self) -> bool {
(200u16..300u16).contains(&self.status_code)
}
pub fn header_one(&self, header_name: impl AsRef<str>) -> Option<&str> {
self.headers
.get(&header_name.as_ref().to_ascii_lowercase())
.map(|x| x.first().map(|x| x.as_str()))
.flatten()
}
pub fn header_all(&self, key: impl AsRef<str>) -> Vec<&str> {
let key = key.as_ref().to_ascii_lowercase();
self.headers
.get(&key)
.map(|set| set.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
pub fn read_cookies(&self) -> Vec<crate::cookie::Cookie> {
self.header_all(crate::header::SET_COOKIE)
.iter()
.flat_map(|&value| crate::cookie::Cookie::parse_from_set_cookie(std::iter::once(value)))
.collect()
}
pub async fn body_string(&mut self) -> Result<String> {
if self.is_body_read_complete() {
return Err(anyhow!("response body has been read"));
}
if let Some(mut stream) = self.body_managed_stream() {
let mut bytes: Vec<u8> = Vec::new();
let mut buf = [0u8; 1024];
let read_future = async {
while let n = stream.read(&mut buf).await.dot()?
&& n > 0
{
bytes.extend_from_slice(&buf[..n]);
}
Ok::<(), anyhow::Error>(())
};
if let Some(timeout) = self.read_body_timeout {
async_std::future::timeout(timeout, read_future)
.await
.map_err(|_| anyhow!("read body timeout after {:?}", timeout))??;
} else {
read_future.await?;
}
if let Some(x) = self.headers.get("content-type")
&& x.last()
.map(|x| x.to_lowercase().contains("charset=gbk"))
.unwrap_or(false)
{
let (cow, _encoding, had_errors) = GBK.decode(&bytes.as_slice());
if had_errors {
error!("GBK decode with errors");
}
return Ok(cow.to_string());
} else {
return Ok(String::from_utf8_lossy(&bytes).to_string());
}
} else {
return Ok(String::new());
}
}
pub fn body_managed_stream(&mut self) -> Option<BoxedStream> {
if self.is_body_read_complete() {
return None;
}
let is_chunked = self
.headers
.get("transfer-encoding")
.map(|set| set.iter().any(|v| v.contains("chunked")))
.unwrap_or(false);
let content_length = self.content_length();
if let Some(stream) = self.body_raw_stream.take() {
if is_chunked {
let decoder = ChunkedDecoderStream::new_with_completion_flag(
stream,
self.body_completion_flag.clone(),
self.addr,
self.is_tls,
self.proxy_used.clone(),
);
Some(Box::new(decoder))
} else if let Some(length) = content_length {
let fixed_length_stream = BodyFixedLengthStream::new_with_completion_flag(
stream,
length as usize,
self.body_completion_flag.clone(),
self.addr,
self.is_tls,
self.proxy_used.clone(),
);
Some(Box::new(fixed_length_stream))
} else {
let unknown_length_stream = BodyUnknownLengthStream::new_with_completion_flag(
stream,
self.body_completion_flag.clone(),
self.addr,
self.is_tls,
self.proxy_used.clone(),
);
Some(Box::new(unknown_length_stream))
}
} else {
None
}
}
pub async fn body_bytes(&mut self) -> Result<Vec<u8>> {
if self.is_body_read_complete() {
return Err(anyhow!("response body has been read"));
}
if let Some(mut stream) = self.body_managed_stream() {
let mut bytes: Vec<u8> = Vec::new();
let mut buf = [0u8; 8192];
let read_future = async {
while let n = stream.read(&mut buf).await.dot()?
&& n > 0
{
bytes.extend_from_slice(&buf[..n]);
}
Ok::<(), anyhow::Error>(())
};
if let Some(timeout) = self.read_body_timeout {
async_std::future::timeout(timeout, read_future)
.await
.map_err(|_| anyhow!("read body timeout after {:?}", timeout))??;
} else {
read_future.await?;
}
Ok(bytes)
} else {
Ok(Vec::new())
}
}
pub async fn body_json(&mut self) -> Result<serde_json::Value> {
let bytes = self.body_bytes().await?;
serde_json::from_slice(&bytes).map_err(|e| anyhow!("JSON parsing failed: {}", e))
}
pub fn content_length(&self) -> Option<u64> {
self.headers
.get("content-length")
.and_then(|vec| vec.first())
.and_then(|s| s.parse::<u64>().ok())
}
pub fn mark_body_read_complete(&mut self) {
self.body_completion_flag.store(true, Ordering::Relaxed);
}
pub fn is_body_read_complete(&self) -> bool {
self.body_completion_flag.load(Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests {
use async_std::task;
use crate::{client::ZJHttpClient, requestx::Request};
use super::*;
#[test]
fn new_from_parse_result_and_basic_getters() {
let x = "\r\nf5e\r\n".trim();
println!("{x}");
}
#[test]
#[tracing_test::traced_test]
#[ignore] fn test_chunked() {
task::block_on(async {
let mut req = Request::new("GET", "http://127.0.0.1:8888/test/gb2312.txt").unwrap();
let mut resp = ZJHttpClient::builder()
.build()
.unwrap()
.send(&mut req)
.await
.unwrap();
let s = resp.body_string().await.unwrap();
println!("{}", s);
});
}
#[test]
fn test_body_stream_basic() {
let x = "\r\nf5e\r\n".trim();
println!("{x}");
}
#[test]
fn test_body_fixed_length_stream() {
use async_std::io::ReadExt;
struct TestStream {
data: Vec<u8>,
position: usize,
}
impl async_std::io::Read for TestStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestStream {}
let data = b"Hello, World!";
let test_stream = TestStream {
data: data.to_vec(),
position: 0,
};
let boxed_stream = Box::new(test_stream) as BoxedStream;
let mut fixed_stream = BodyFixedLengthStream::new(boxed_stream, data.len());
let mut buffer = Vec::new();
let result = async_std::task::block_on(fixed_stream.read_to_end(&mut buffer));
assert!(result.is_ok());
assert_eq!(buffer, data);
let mut small_buffer = [0u8; 10];
let result = async_std::task::block_on(fixed_stream.read(&mut small_buffer));
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_body_fixed_length_stream_partial_read() {
use async_std::io::ReadExt;
struct TestStream {
data: Vec<u8>,
position: usize,
}
impl async_std::io::Read for TestStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestStream {}
let data = b"Hello, World!";
let test_stream = TestStream {
data: data.to_vec(),
position: 0,
};
let boxed_stream = Box::new(test_stream) as BoxedStream;
let mut fixed_stream = BodyFixedLengthStream::new(boxed_stream, data.len());
let mut buffer = [0u8; 5];
let result = async_std::task::block_on(fixed_stream.read(&mut buffer));
assert!(result.is_ok());
assert_eq!(result.unwrap(), 5);
assert_eq!(&buffer[..5], b"Hello");
let mut remaining_buffer = Vec::new();
let result = async_std::task::block_on(fixed_stream.read_to_end(&mut remaining_buffer));
assert!(result.is_ok());
assert_eq!(remaining_buffer, b", World!");
}
#[test]
fn test_chunked_decoder_stream_basic() {
use async_std::io::ReadExt;
struct TestChunkedStream {
data: Vec<u8>,
position: usize,
}
impl TestChunkedStream {
fn new(chunked_data: &[u8]) -> Self {
Self {
data: chunked_data.to_vec(),
position: 0,
}
}
}
impl async_std::io::Read for TestChunkedStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestChunkedStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestChunkedStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestChunkedStream {}
let chunked_data = b"5\r\nHello\r\n6\r\n World\r\n0\r\n\r\n";
let test_stream = TestChunkedStream::new(chunked_data);
let boxed_stream = Box::new(test_stream) as BoxedStream;
let mut decoder = ChunkedDecoderStream::new(boxed_stream);
let mut buffer = Vec::new();
let result = async_std::task::block_on(decoder.read_to_end(&mut buffer));
assert!(result.is_ok());
assert_eq!(buffer, b"Hello World");
assert!(decoder.is_fully_consumed());
}
#[test]
fn test_body_unknown_length_stream_basic() {
use async_std::io::ReadExt;
struct TestStream {
data: Vec<u8>,
position: usize,
}
impl TestStream {
fn new(data: &[u8]) -> Self {
Self {
data: data.to_vec(),
position: 0,
}
}
}
impl async_std::io::Read for TestStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestStream {}
let data = b"Test data for unknown length stream";
let test_stream = TestStream::new(data);
let boxed_stream = Box::new(test_stream) as BoxedStream;
let completion_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let mut unknown_stream = BodyUnknownLengthStream::new_with_completion_flag(
boxed_stream,
completion_flag,
std::net::SocketAddr::from(([127, 0, 0, 1], 8080)),
false,
None,
);
let mut buffer = Vec::new();
let result = async_std::task::block_on(unknown_stream.read_to_end(&mut buffer));
assert!(result.is_ok());
assert_eq!(buffer, data);
assert!(unknown_stream.is_fully_consumed());
}
#[test]
fn test_response_chunked_encoding_detection() {
use hashbrown::HashMap;
use indexmap::IndexSet;
let mut headers = HashMap::new();
let mut transfer_encoding_set = IndexSet::new();
transfer_encoding_set.insert("chunked".to_string());
headers.insert("transfer-encoding".to_string(), transfer_encoding_set);
let is_chunked = headers
.get("transfer-encoding")
.map(|set| set.iter().any(|v| v.contains("chunked")))
.unwrap_or(false);
assert!(is_chunked);
let mut headers2 = HashMap::new();
let mut content_length_set = IndexSet::new();
content_length_set.insert("123".to_string());
headers2.insert("content-length".to_string(), content_length_set);
let is_chunked2 = headers2
.get("transfer-encoding")
.map(|set| set.iter().any(|v| v.contains("chunked")))
.unwrap_or(false);
assert!(!is_chunked2);
}
#[test]
fn test_response_content_length_parsing() {
use hashbrown::HashMap;
use indexmap::IndexSet;
let mut headers = HashMap::new();
let mut content_length_set = IndexSet::new();
content_length_set.insert("1024".to_string());
headers.insert("content-length".to_string(), content_length_set);
let content_length = headers
.get("content-length")
.and_then(|vec| vec.first())
.and_then(|s| s.parse::<u64>().ok());
assert_eq!(content_length, Some(1024u64));
let mut headers2 = HashMap::new();
let mut content_length_set2 = IndexSet::new();
content_length_set2.insert("invalid".to_string());
headers2.insert("content-length".to_string(), content_length_set2);
let content_length2 = headers2
.get("content-length")
.and_then(|vec| vec.first())
.and_then(|s| s.parse::<u64>().ok());
assert_eq!(content_length2, None);
let content_length3: Option<u64> = None;
assert_eq!(content_length3, None);
}
#[test]
fn test_body_stream_completion_flag_behavior() {
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
let completion_flag = Arc::new(AtomicBool::new(false));
assert!(!completion_flag.load(Ordering::Relaxed));
completion_flag.store(true, Ordering::Relaxed);
assert!(completion_flag.load(Ordering::Relaxed));
}
#[test]
fn test_response_body_successfully_readed_flag() {
use hashbrown::HashMap;
use std::net::SocketAddr;
let response = Response {
addr: SocketAddr::from(([127, 0, 0, 1], 8080)),
is_tls: false,
http_version: HttpVersion::V1_1,
status_code: 200,
headers: HashMap::new(),
body_raw_stream: None,
proxy_used: None,
body_completion_flag: Arc::new(AtomicBool::new(false)),
read_body_timeout: None,
};
assert!(!response.is_body_read_complete());
assert_eq!(response.status_code(), 200);
assert!(response.is_success());
}
#[test]
fn test_mark_body_read_complete() {
use hashbrown::HashMap;
use std::net::SocketAddr;
let mut response = Response {
addr: SocketAddr::from(([127, 0, 0, 1], 8080)),
is_tls: false,
http_version: HttpVersion::V1_1,
status_code: 200,
headers: HashMap::new(),
body_raw_stream: None,
proxy_used: None,
body_completion_flag: Arc::new(AtomicBool::new(false)),
read_body_timeout: None,
};
assert!(!response.is_body_read_complete());
response.mark_body_read_complete();
assert!(response.is_body_read_complete());
}
#[test]
fn test_completion_flag_with_managed_stream() {
use hashbrown::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
let completion_flag = Arc::new(AtomicBool::new(false));
let response = Response {
addr: SocketAddr::from(([127, 0, 0, 1], 8080)),
is_tls: false,
http_version: HttpVersion::V1_1,
status_code: 200,
headers: HashMap::new(),
body_raw_stream: None,
proxy_used: None,
body_completion_flag: completion_flag.clone(),
read_body_timeout: None,
};
assert!(!response.is_body_read_complete());
response.body_completion_flag.store(true, Ordering::Relaxed);
assert!(response.is_body_read_complete());
}
#[test]
fn test_body_fixed_length_stream_zero_length() {
use async_std::io::ReadExt;
struct TestStream {
data: Vec<u8>,
position: usize,
}
impl async_std::io::Read for TestStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestStream {}
let data = b"Some data";
let test_stream = TestStream {
data: data.to_vec(),
position: 0,
};
let boxed_stream = Box::new(test_stream) as BoxedStream;
let mut fixed_stream = BodyFixedLengthStream::new(boxed_stream, 0);
let mut buffer = [0u8; 10];
let result = async_std::task::block_on(fixed_stream.read(&mut buffer));
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
assert!(fixed_stream.is_fully_consumed());
}
#[test]
fn test_body_bytes_method() {
use async_std::io::ReadExt;
struct TestStream {
data: Vec<u8>,
position: usize,
}
impl TestStream {
fn new(data: &[u8]) -> Self {
Self {
data: data.to_vec(),
position: 0,
}
}
}
impl async_std::io::Read for TestStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestStream {}
let data = b"Hello, World! This is test data for body_bytes method.";
let test_stream = TestStream::new(data);
let boxed_stream = Box::new(test_stream) as BoxedStream;
let mut headers = hashbrown::HashMap::new();
let mut content_length_set = indexmap::IndexSet::new();
content_length_set.insert(data.len().to_string());
headers.insert("content-length".to_string(), content_length_set);
let mut response = Response {
addr: std::net::SocketAddr::from(([127, 0, 0, 1], 8080)),
is_tls: false,
http_version: HttpVersion::V1_1,
status_code: 200,
headers,
body_raw_stream: Some(boxed_stream),
proxy_used: None,
body_completion_flag: Arc::new(AtomicBool::new(false)),
read_body_timeout: None,
};
let result = async_std::task::block_on(response.body_bytes());
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(bytes, data);
}
#[test]
fn test_body_json_method() {
use async_std::io::ReadExt;
struct TestStream {
data: Vec<u8>,
position: usize,
}
impl TestStream {
fn new(data: &[u8]) -> Self {
Self {
data: data.to_vec(),
position: 0,
}
}
}
impl async_std::io::Read for TestStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestStream {}
let json_data = br#"{"name": "test", "value": 42, "active": true}"#;
let test_stream = TestStream::new(json_data);
let boxed_stream = Box::new(test_stream) as BoxedStream;
let mut headers = hashbrown::HashMap::new();
let mut content_length_set = indexmap::IndexSet::new();
content_length_set.insert(json_data.len().to_string());
headers.insert("content-length".to_string(), content_length_set);
let mut response = Response {
addr: std::net::SocketAddr::from(([127, 0, 0, 1], 8080)),
is_tls: false,
http_version: HttpVersion::V1_1,
status_code: 200,
headers,
body_raw_stream: Some(boxed_stream),
proxy_used: None,
body_completion_flag: Arc::new(AtomicBool::new(false)),
read_body_timeout: None,
};
let result = async_std::task::block_on(response.body_json());
assert!(result.is_ok());
let json_value = result.unwrap();
assert_eq!(json_value["name"], "test");
assert_eq!(json_value["value"], 42);
assert_eq!(json_value["active"], true);
}
#[test]
fn test_body_json_invalid_json() {
use async_std::io::ReadExt;
struct TestStream {
data: Vec<u8>,
position: usize,
}
impl TestStream {
fn new(data: &[u8]) -> Self {
Self {
data: data.to_vec(),
position: 0,
}
}
}
impl async_std::io::Read for TestStream {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
let remaining = self.data.len() - self.position;
let to_read = std::cmp::min(buf.len(), remaining);
if to_read > 0 {
buf[..to_read]
.copy_from_slice(&self.data[self.position..self.position + to_read]);
self.position += to_read;
}
std::task::Poll::Ready(Ok(to_read))
}
}
impl async_std::io::Write for TestStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::task::Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"TestStream is read-only",
)))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
impl crate::stream::RWStream for TestStream {}
let invalid_json = b"{ invalid json }";
let test_stream = TestStream::new(invalid_json);
let boxed_stream = Box::new(test_stream) as BoxedStream;
let mut response = Response {
addr: std::net::SocketAddr::from(([127, 0, 0, 1], 8080)),
is_tls: false,
http_version: HttpVersion::V1_1,
status_code: 200,
headers: hashbrown::HashMap::new(),
body_raw_stream: Some(boxed_stream),
proxy_used: None,
body_completion_flag: Arc::new(AtomicBool::new(false)),
read_body_timeout: None,
};
let result = async_std::task::block_on(response.body_json());
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("JSON parsing failed"));
}
}