use futures_lite::future::block_on;
use isahc::{prelude::*, HttpClient, Request};
use std::{
io::{self, Write},
net::{Shutdown, TcpListener, TcpStream},
thread,
time::Duration,
};
use testserver::mock;
#[test]
fn accept_headers_populated_by_default() {
let m = mock!();
isahc::get(m.url()).unwrap();
m.request().expect_header("accept", "*/*");
m.request()
.expect_header("accept-encoding", "deflate, gzip");
}
#[test]
fn user_agent_contains_expected_format() {
let m = mock!();
isahc::get(m.url()).unwrap();
m.request()
.expect_header_regex("user-agent", r"^curl/\S+ isahc/\S+$");
}
#[test]
fn setting_an_empty_header_sends_a_header_with_no_value() {
let m = mock!();
Request::get(m.url())
.header("an-empty-header", "")
.body(())
.unwrap()
.send()
.unwrap();
m.request().expect_header("an-empty-header", "");
}
#[test]
fn setting_a_blank_header_sends_a_header_with_no_value() {
let m = mock!();
Request::get(m.url())
.header("an-empty-header", " ")
.body(())
.unwrap()
.send()
.unwrap();
m.request().expect_header("an-empty-header", "");
}
#[test]
fn override_client_default_user_agent() {
let m = mock!();
let client = HttpClient::builder()
.default_header("user-agent", "foo")
.build()
.unwrap();
client.get(m.url()).unwrap();
m.request().expect_header("user-agent", "foo");
}
#[test]
fn set_title_case_headers_to_true() {
let m = mock!();
let client = HttpClient::builder()
.default_header("foo-BAR", "baz")
.title_case_headers(true)
.build()
.unwrap();
client.get(m.url()).unwrap();
assert_eq!(m.request().method(), "GET");
m.request().expect_header("Foo-Bar", "baz");
}
#[test]
fn header_can_be_inserted_in_httpclient_builder() {
let m = mock!();
let client = HttpClient::builder()
.default_header("X-header", "some-value1")
.build()
.unwrap();
let request = Request::builder()
.method("GET")
.uri(m.url())
.body(())
.unwrap();
let _ = client.send(request).unwrap();
m.request().expect_header("accept", "*/*");
m.request()
.expect_header("accept-encoding", "deflate, gzip");
m.request().expect_header("X-header", "some-value1");
}
#[test]
fn headers_in_request_builder_must_override_headers_in_httpclient_builder() {
let m = mock!();
let client = HttpClient::builder()
.default_header("X-header", "some-value1")
.build()
.unwrap();
let request = Request::builder()
.method("GET")
.header("X-header", "some-value2")
.uri(m.url())
.body(())
.unwrap();
let _ = client.send(request).unwrap();
m.request().expect_header("accept", "*/*");
m.request()
.expect_header("accept-encoding", "deflate, gzip");
m.request().expect_header("X-header", "some-value2");
}
#[test]
fn multiple_headers_with_same_key_can_be_inserted_in_httpclient_builder() {
let m = mock!();
let client = HttpClient::builder()
.default_header("X-header", "some-value1")
.default_header("X-header", "some-value2")
.build()
.unwrap();
let request = Request::builder()
.method("GET")
.uri(m.url())
.body(())
.unwrap();
let _ = client.send(request).unwrap();
m.request().expect_header("accept", "*/*");
m.request()
.expect_header("accept-encoding", "deflate, gzip");
m.request().expect_header("X-header", "some-value1");
m.request().expect_header("X-header", "some-value2");
}
#[test]
fn headers_in_request_builder_must_override_multiple_headers_in_httpclient_builder() {
let m = mock!();
let client = HttpClient::builder()
.default_header("X-header", "some-value1")
.default_header("X-header", "some-value2")
.build()
.unwrap();
let request = Request::builder()
.method("GET")
.header("X-header", "some-value3")
.uri(m.url())
.body(())
.unwrap();
let _ = client.send(request).unwrap();
m.request().expect_header("accept", "*/*");
m.request()
.expect_header("accept-encoding", "deflate, gzip");
m.request().expect_header("X-header", "some-value3");
}
#[test]
fn trailer_headers() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!("http://{}", listener.local_addr().unwrap());
thread::spawn(move || {
let mut stream = listener.accept().unwrap().0;
consume_request_in_background(&stream);
stream
.write_all(
b"\
HTTP/1.1 200 OK\r\n\
transfer-encoding: chunked\r\n\
trailer: foo\r\n\
\r\n\
2\r\n\
OK\r\n\
0\r\n\
foo: bar\r\n\
\r\n\
",
)
.unwrap();
let _ = stream.shutdown(Shutdown::Write);
});
let mut body = None;
let response = isahc::get(url).unwrap().map(|b| {
body = Some(b);
});
thread::spawn(move || {
io::copy(body.as_mut().unwrap(), &mut io::sink()).unwrap();
});
assert_eq!(response.trailer().wait().get("foo").unwrap(), "bar");
}
#[test]
fn trailer_headers_async() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!("http://{}", listener.local_addr().unwrap());
thread::spawn(move || {
let mut stream = listener.accept().unwrap().0;
consume_request_in_background(&stream);
stream
.write_all(
b"\
HTTP/1.1 200 OK\r\n\
transfer-encoding: chunked\r\n\
trailer: foo\r\n\
\r\n\
2\r\n\
OK\r\n\
0\r\n\
foo: bar\r\n\
\r\n\
",
)
.unwrap();
let _ = stream.shutdown(Shutdown::Write);
});
block_on(async move {
let mut body = None;
let response = isahc::get_async(url).await.unwrap().map(|b| {
body = Some(b);
});
thread::spawn(move || {
block_on(async move {
futures_lite::io::copy(body.as_mut().unwrap(), &mut futures_lite::io::sink())
.await
.unwrap();
})
});
assert_eq!(
response.trailer().wait_async().await.get("foo").unwrap(),
"bar"
);
});
}
#[test]
fn trailer_headers_timeout() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!("http://{}", listener.local_addr().unwrap());
thread::spawn(move || {
let mut stream = listener.accept().unwrap().0;
stream.set_nodelay(true).unwrap();
consume_request_in_background(&stream);
stream
.write_all(
b"\
HTTP/1.1 200 OK\r\n\
transfer-encoding: chunked\r\n\
trailer: foo\r\n\
\r\n",
)
.unwrap();
for _ in 0..1000 {
stream.write_all(b"5\r\nhello\r\n").unwrap();
}
stream.write_all(b"0\r\n").unwrap();
thread::sleep(Duration::from_millis(200));
stream.write_all(b"foo: bar\r\n\r\n").unwrap();
let _ = stream.shutdown(Shutdown::Write);
});
let response = isahc::get(url).unwrap();
assert!(
response
.trailer()
.wait_timeout(Duration::from_millis(10))
.is_none()
);
}
fn consume_request_in_background(stream: &TcpStream) {
let mut stream = stream.try_clone().unwrap();
thread::spawn(move || {
let _ = io::copy(&mut stream, &mut io::sink());
let _ = stream.shutdown(Shutdown::Read);
});
}