diff --git a/tests/blocking.rs b/tests/blocking.rs index 17ca03d..c6003d8 100644 --- a/tests/blocking.rs +++ b/tests/blocking.rs @@ -1,35 +1,15 @@ -#[macro_use] mod support; +use support::*; #[test] fn test_response_text() { - let server = server! { - request: b"\ - GET /text HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - " - }; + let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let url = format!("http://{}/text", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"5" - ); + assert_eq!(res.content_length(), Some(5)); let body = res.text().unwrap(); assert_eq!(b"Hello", body.as_bytes()); @@ -37,34 +17,20 @@ fn test_response_text() { #[test] fn test_response_non_utf_8_text() { - let server = server! { - request: b"\ - GET /text HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 4\r\n\ - Content-Type: text/plain; charset=gbk\r\n\ - \r\n\ - \xc4\xe3\xba\xc3\ - " - }; + let server = server::http(move |_req| { + async { + http::Response::builder() + .header("content-type", "text/plain; charset=gbk") + .body(b"\xc4\xe3\xba\xc3"[..].into()) + .unwrap() + } + }); let url = format!("http://{}/text", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"4" - ); + assert_eq!(res.content_length(), Some(4)); let body = res.text().unwrap(); assert_eq!("你好", &body); @@ -73,33 +39,13 @@ fn test_response_non_utf_8_text() { #[test] fn test_response_json() { - let server = server! { - request: b"\ - GET /json HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 7\r\n\ - \r\n\ - \"Hello\"\ - " - }; + let server = server::http(move |_req| async { http::Response::new("\"Hello\"".into()) }); let url = format!("http://{}/json", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"7" - ); + assert_eq!(res.content_length(), Some(7)); let body = res.json::().unwrap(); assert_eq!("Hello", body); @@ -107,66 +53,27 @@ fn test_response_json() { #[test] fn test_response_copy_to() { - let server = server! { - request: b"\ - GET /1 HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - " - }; + let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let url = format!("http://{}/1", server.addr()); - let res = reqwest::blocking::get(&url).unwrap(); + let mut res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"5" - ); - assert_eq!("Hello".to_owned(), res.text().unwrap()); + let mut dst = Vec::new(); + res.copy_to(&mut dst).unwrap(); + assert_eq!(dst, b"Hello"); } #[test] fn test_get() { - let server = server! { - request: b"\ - GET /1 HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |_req| async { http::Response::default() }); let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"0" - ); assert_eq!(res.remote_addr(), Some(server.addr())); assert_eq!(res.text().unwrap().len(), 0) @@ -174,24 +81,17 @@ fn test_get() { #[test] fn test_post() { - let server = server! { - request: b"\ - POST /2 HTTP/1.1\r\n\ - content-length: 5\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - Hello\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: post\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |mut req| { + async move { + assert_eq!(req.method(), "POST"); + assert_eq!(req.headers()["content-length"], "5"); + + let data = req.body_mut().next().await.unwrap().unwrap(); + assert_eq!(&*data, b"Hello"); + + http::Response::default() + } + }); let url = format!("http://{}/2", server.addr()); let res = reqwest::blocking::Client::new() @@ -202,36 +102,25 @@ fn test_post() { assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"post"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"0" - ); - - assert_eq!(res.text().unwrap().len(), 0) } #[test] fn test_post_form() { - let server = server! { - request: b"\ - POST /form HTTP/1.1\r\n\ - content-type: application/x-www-form-urlencoded\r\n\ - content-length: 24\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - hello=world&sean=monstar\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: post-form\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |mut req| { + async move { + assert_eq!(req.method(), "POST"); + assert_eq!(req.headers()["content-length"], "24"); + assert_eq!( + req.headers()["content-type"], + "application/x-www-form-urlencoded" + ); + + let data = req.body_mut().next().await.unwrap().unwrap(); + assert_eq!(&*data, b"hello=world&sean=monstar"); + + http::Response::default() + } + }); let form = &[("hello", "world"), ("sean", "monstar")]; @@ -250,22 +139,14 @@ fn test_post_form() { /// returns a error. #[test] fn test_error_for_status_4xx() { - let server = server! { - request: b"\ - GET /1 HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 400 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |_req| { + async { + http::Response::builder() + .status(400) + .body(Default::default()) + .unwrap() + } + }); let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); @@ -279,22 +160,14 @@ fn test_error_for_status_4xx() { /// returns a error. #[test] fn test_error_for_status_5xx() { - let server = server! { - request: b"\ - GET /1 HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 500 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |_req| { + async { + http::Response::builder() + .status(500) + .body(Default::default()) + .unwrap() + } + }); let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); @@ -309,144 +182,75 @@ fn test_error_for_status_5xx() { #[test] fn test_default_headers() { - use reqwest::header; - let mut headers = header::HeaderMap::with_capacity(1); - headers.insert(header::COOKIE, header::HeaderValue::from_static("a=b;c=d")); + let server = server::http(move |req| { + async move { + assert_eq!(req.headers()["reqwest-test"], "orly"); + http::Response::default() + } + }); + + let mut headers = http::HeaderMap::with_capacity(1); + headers.insert("reqwest-test", "orly".parse().unwrap()); let client = reqwest::blocking::Client::builder() .default_headers(headers) .build() .unwrap(); - let server = server! { - request: b"\ - GET /1 HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - cookie: a=b;c=d\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/1", server.addr()); let res = client.get(&url).send().unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"0" - ); - - let server = server! { - request: b"\ - GET /2 HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - cookie: a=b;c=d\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - - let url = format!("http://{}/2", server.addr()); - let res = client.get(&url).send().unwrap(); - - assert_eq!(res.url().as_str(), &url); - assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"0" - ); } #[test] fn test_override_default_headers() { - use reqwest::header; - let mut headers = header::HeaderMap::with_capacity(1); + let server = server::http(move |req| { + async move { + // not 'iamatoken' + assert_eq!(req.headers()[&http::header::AUTHORIZATION], "secret"); + http::Response::default() + } + }); + + let mut headers = http::HeaderMap::with_capacity(1); headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_static("iamatoken"), + http::header::AUTHORIZATION, + http::header::HeaderValue::from_static("iamatoken"), ); let client = reqwest::blocking::Client::builder() .default_headers(headers) .build() .unwrap(); - let server = server! { - request: b"\ - GET /3 HTTP/1.1\r\n\ - authorization: secret\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/3", server.addr()); let res = client .get(&url) .header( - header::AUTHORIZATION, - header::HeaderValue::from_static("secret"), + http::header::AUTHORIZATION, + http::header::HeaderValue::from_static("secret"), ) .send() .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"0" - ); } #[test] fn test_appended_headers_not_overwritten() { - let client = reqwest::blocking::Client::new(); + let server = server::http(move |req| { + async move { + let mut accepts = req.headers().get_all("accept").into_iter(); + assert_eq!(accepts.next().unwrap(), "application/json"); + assert_eq!(accepts.next().unwrap(), "application/json+hal"); + assert_eq!(accepts.next(), None); - let server = server! { - request: b"\ - GET /4 HTTP/1.1\r\n\ - accept: application/json\r\n\ - accept: application/json+hal\r\n\ - user-agent: $USERAGENT\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + http::Response::default() + } + }); + + let client = reqwest::blocking::Client::new(); let url = format!("http://{}/4", server.addr()); let res = client @@ -458,11 +262,6 @@ fn test_appended_headers_not_overwritten() { assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"0" - ); // make sure this also works with default headers use reqwest::header; @@ -476,24 +275,6 @@ fn test_appended_headers_not_overwritten() { .build() .unwrap(); - let server = server! { - request: b"\ - GET /4 HTTP/1.1\r\n\ - accept: application/json\r\n\ - accept: application/json+hal\r\n\ - user-agent: $USERAGENT\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/4", server.addr()); let res = client .get(&url) @@ -504,9 +285,4 @@ fn test_appended_headers_not_overwritten() { assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!(res.headers().get(reqwest::header::SERVER).unwrap(), &"test"); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"0" - ); } diff --git a/tests/client.rs b/tests/client.rs index e541f9e..c490bc3 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -1,44 +1,35 @@ -#[macro_use] mod support; +use support::*; -use std::io::Write; -use std::time::Duration; - -use reqwest::multipart::{Form, Part}; -use reqwest::{Body, Client}; - -use bytes::Bytes; +use reqwest::Client; #[tokio::test] -async fn gzip_response() { - gzip_case(10_000, 4096).await; -} +async fn auto_headers() { + let server = server::http(move |req| { + async move { + assert_eq!(req.method(), "GET"); -#[tokio::test] -async fn gzip_single_byte_chunks() { - gzip_case(10, 1).await; + assert_eq!(req.headers()["accept"], "*/*"); + assert_eq!(req.headers()["user-agent"], DEFAULT_USER_AGENT); + assert_eq!(req.headers()["accept-encoding"], "gzip"); + + http::Response::default() + } + }); + + let url = format!("http://{}/1", server.addr()); + let res = reqwest::get(&url).await.unwrap(); + + assert_eq!(res.url().as_str(), &url); + assert_eq!(res.status(), reqwest::StatusCode::OK); + assert_eq!(res.remote_addr(), Some(server.addr())); } #[tokio::test] async fn response_text() { let _ = env_logger::try_init(); - let server = server! { - request: b"\ - GET /text HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - " - }; + let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let client = Client::new(); @@ -55,22 +46,7 @@ async fn response_text() { async fn response_json() { let _ = env_logger::try_init(); - let server = server! { - request: b"\ - GET /json HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 7\r\n\ - \r\n\ - \"Hello\"\ - " - }; + let server = server::http(move |_req| async { http::Response::new("\"Hello\"".into()) }); let client = Client::new(); @@ -83,287 +59,29 @@ async fn response_json() { assert_eq!("Hello", text); } -#[tokio::test] -async fn multipart() { - use futures_util::{future, stream}; - - let _ = env_logger::try_init(); - - let stream = reqwest::Body::wrap_stream(stream::once(future::ready(Ok::<_, reqwest::Error>( - "part1 part2".to_owned(), - )))); - let part = Part::stream(stream); - - let form = Form::new().text("foo", "bar").part("part_stream", part); - - let expected_body = format!( - "\ - 24\r\n\ - --{0}\r\n\r\n\ - 2E\r\n\ - Content-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n\ - 3\r\n\ - bar\r\n\ - 2\r\n\ - \r\n\r\n\ - 24\r\n\ - --{0}\r\n\r\n\ - 36\r\n\ - Content-Disposition: form-data; name=\"part_stream\"\r\n\r\n\r\n\ - B\r\n\ - part1 part2\r\n\ - 2\r\n\ - \r\n\r\n\ - 26\r\n\ - --{0}--\r\n\r\n\ - 0\r\n\r\n\ - ", - form.boundary() - ); - - let server = server! { - request: format!("\ - POST /multipart/1 HTTP/1.1\r\n\ - content-type: multipart/form-data; boundary={}\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - transfer-encoding: chunked\r\n\ - \r\n\ - {}\ - ", form.boundary(), expected_body), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: multipart\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - - let url = format!("http://{}/multipart/1", server.addr()); - - let client = Client::new(); - - let res = client - .post(&url) - .multipart(form) - .send() - .await - .expect("Failed to post multipart"); - assert_eq!(res.url().as_str(), &url); - assert_eq!(res.status(), reqwest::StatusCode::OK); -} - -#[tokio::test] -async fn request_timeout() { - let _ = env_logger::try_init(); - - let server = server! { - request: b"\ - GET /slow HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - ", - read_timeout: Duration::from_secs(2) - }; - - let client = Client::builder() - .timeout(Duration::from_millis(500)) - .build() - .unwrap(); - - let url = format!("http://{}/slow", server.addr()); - - let res = client.get(&url).send().await; - - let err = res.unwrap_err(); - - assert!(err.is_timeout()); - assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); -} - -#[tokio::test] -async fn response_timeout() { - let _ = env_logger::try_init(); - - let server = server! { - request: b"\ - GET /slow HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - ", - write_timeout: Duration::from_secs(2) - }; - - let client = Client::builder() - .timeout(Duration::from_millis(500)) - .build() - .unwrap(); - - let url = format!("http://{}/slow", server.addr()); - let res = client.get(&url).send().await.expect("Failed to get"); - let body = res.text().await; - - let err = body.unwrap_err(); - - assert!(err.is_timeout()); -} - -async fn gzip_case(response_size: usize, chunk_size: usize) { - let content: String = (0..response_size) - .into_iter() - .map(|i| format!("test {}", i)) - .collect(); - let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap(); - match encoder.write(content.as_bytes()) { - Ok(n) => assert!(n > 0, "Failed to write to encoder."), - _ => panic!("Failed to gzip encode string."), - }; - - let gzipped_content = encoder.finish().into_result().unwrap(); - - let mut response = format!( - "\ - HTTP/1.1 200 OK\r\n\ - Server: test-accept\r\n\ - Content-Encoding: gzip\r\n\ - Content-Length: {}\r\n\ - \r\n", - &gzipped_content.len() - ) - .into_bytes(); - response.extend(&gzipped_content); - - let server = server! { - request: b"\ - GET /gzip HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - chunk_size: chunk_size, - write_timeout: Duration::from_millis(10), - response: response - }; - - let client = Client::new(); - - let res = client - .get(&format!("http://{}/gzip", server.addr())) - .send() - .await - .expect("response"); - - let body = res.text().await.expect("text"); - assert_eq!(body, content); -} - -#[tokio::test] -async fn body_stream() { - let _ = env_logger::try_init(); - - let source = futures_util::stream::iter::>>(vec![ - Ok(Bytes::from_static(b"123")), - Ok(Bytes::from_static(b"4567")), - ]); - - let expected_body = "3\r\n123\r\n4\r\n4567\r\n0\r\n\r\n"; - - let server = server! { - request: format!("\ - POST /post HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - transfer-encoding: chunked\r\n\ - \r\n\ - {}\ - ", expected_body), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: post\r\n\ - Content-Length: 7\r\n\ - \r\n\ - " - }; - - let url = format!("http://{}/post", server.addr()); - - let client = Client::new(); - - let res = client - .post(&url) - .body(Body::wrap_stream(source)) - .send() - .await - .expect("Failed to post"); - - assert_eq!(res.url().as_str(), &url); - assert_eq!(res.status(), reqwest::StatusCode::OK); -} - #[tokio::test] async fn body_pipe_response() { let _ = env_logger::try_init(); - let server = server! { - request: b"\ - GET /get HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: pipe\r\n\ - Content-Length: 7\r\n\ - \r\n\ - pipe me\ - "; + let server = server::http(move |mut req| { + async move { + if req.uri() == "/get" { + http::Response::new("pipe me".into()) + } else { + assert_eq!(req.uri(), "/pipe"); + assert_eq!(req.headers()["transfer-encoding"], "chunked"); - request: b"\ - POST /pipe HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - transfer-encoding: chunked\r\n\ - \r\n\ - 7\r\n\ - pipe me\r\n\ - 0\r\n\r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: pipe\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let mut full: Vec = Vec::new(); + while let Some(item) = req.body_mut().next().await { + full.extend(&*item.unwrap()); + } + + assert_eq!(full, b"pipe me"); + + http::Response::default() + } + } + }); let client = Client::new(); diff --git a/tests/cookie.rs b/tests/cookie.rs index a7198c9..b163f1e 100644 --- a/tests/cookie.rs +++ b/tests/cookie.rs @@ -1,38 +1,32 @@ -#[macro_use] mod support; +use support::*; -#[test] -fn cookie_response_accessor() { - let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); - let client = reqwest::r#async::Client::new(); +#[tokio::test] +async fn cookie_response_accessor() { + let server = server::http(move |_req| { + async move { + http::Response::builder() + .header("Set-Cookie", "key=val") + .header( + "Set-Cookie", + "expires=1; Expires=Wed, 21 Oct 2015 07:28:00 GMT", + ) + .header("Set-Cookie", "path=1; Path=/the-path") + .header("Set-Cookie", "maxage=1; Max-Age=100") + .header("Set-Cookie", "domain=1; Domain=mydomain") + .header("Set-Cookie", "secure=1; Secure") + .header("Set-Cookie", "httponly=1; HttpOnly") + .header("Set-Cookie", "samesitelax=1; SameSite=Lax") + .header("Set-Cookie", "samesitestrict=1; SameSite=Strict") + .body(Default::default()) + .unwrap() + } + }); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Set-Cookie: key=val\r\n\ - Set-Cookie: expires=1; Expires=Wed, 21 Oct 2015 07:28:00 GMT\r\n\ - Set-Cookie: path=1; Path=/the-path\r\n\ - Set-Cookie: maxage=1; Max-Age=100\r\n\ - Set-Cookie: domain=1; Domain=mydomain\r\n\ - Set-Cookie: secure=1; Secure\r\n\ - Set-Cookie: httponly=1; HttpOnly\r\n\ - Set-Cookie: samesitelax=1; SameSite=Lax\r\n\ - Set-Cookie: samesitestrict=1; SameSite=Strict\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let client = reqwest::Client::new(); let url = format!("http://{}/", server.addr()); - let res = rt.block_on(client.get(&url).send()).unwrap(); + let res = client.get(&url).send().await.unwrap(); let cookies = res.cookies().collect::>(); @@ -79,273 +73,143 @@ fn cookie_response_accessor() { assert!(cookies[8].same_site_strict()); } -#[test] -fn cookie_store_simple() { - let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); - let client = reqwest::r#async::Client::builder() +#[tokio::test] +async fn cookie_store_simple() { + let server = server::http(move |req| { + async move { + if req.uri() == "/2" { + assert_eq!(req.headers()["cookie"], "key=val"); + } + http::Response::builder() + .header("Set-Cookie", "key=val; HttpOnly") + .body(Default::default()) + .unwrap() + } + }); + + let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Set-Cookie: key=val; HttpOnly\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + client.get(&url).send().await.unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - cookie: key=val\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + let url = format!("http://{}/2", server.addr()); + client.get(&url).send().await.unwrap(); } -#[test] -fn cookie_store_overwrite_existing() { - let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); - let client = reqwest::r#async::Client::builder() +#[tokio::test] +async fn cookie_store_overwrite_existing() { + let server = server::http(move |req| { + async move { + if req.uri() == "/" { + http::Response::builder() + .header("Set-Cookie", "key=val") + .body(Default::default()) + .unwrap() + } else if req.uri() == "/2" { + assert_eq!(req.headers()["cookie"], "key=val"); + http::Response::builder() + .header("Set-Cookie", "key=val2") + .body(Default::default()) + .unwrap() + } else { + assert_eq!(req.uri(), "/3"); + assert_eq!(req.headers()["cookie"], "key=val2"); + http::Response::default() + } + } + }); + + let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Set-Cookie: key=val\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + client.get(&url).send().await.unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - cookie: key=val\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Set-Cookie: key=val2\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + let url = format!("http://{}/2", server.addr()); + client.get(&url).send().await.unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - cookie: key=val2\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + let url = format!("http://{}/3", server.addr()); + client.get(&url).send().await.unwrap(); } -#[test] -fn cookie_store_max_age() { - let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); - let client = reqwest::r#async::Client::builder() +#[tokio::test] +async fn cookie_store_max_age() { + let server = server::http(move |req| { + async move { + assert_eq!(req.headers().get("cookie"), None); + http::Response::builder() + .header("Set-Cookie", "key=val; Max-Age=0") + .body(Default::default()) + .unwrap() + } + }); + + let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); - - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Set-Cookie: key=val; Max-Age=0\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); - - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + client.get(&url).send().await.unwrap(); + client.get(&url).send().await.unwrap(); } -#[test] -fn cookie_store_expires() { - let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); +#[tokio::test] +async fn cookie_store_expires() { + let server = server::http(move |req| { + async move { + assert_eq!(req.headers().get("cookie"), None); + http::Response::builder() + .header( + "Set-Cookie", + "key=val; Expires=Wed, 21 Oct 2015 07:28:00 GMT", + ) + .body(Default::default()) + .unwrap() + } + }); + let client = reqwest::r#async::Client::builder() .cookie_store(true) .build() .unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Set-Cookie: key=val; Expires=Wed, 21 Oct 2015 07:28:00 GMT\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); - - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + client.get(&url).send().await.unwrap(); + client.get(&url).send().await.unwrap(); } -#[test] -fn cookie_store_path() { - let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); +#[tokio::test] +async fn cookie_store_path() { + let server = server::http(move |req| { + async move { + if req.uri() == "/" { + assert_eq!(req.headers().get("cookie"), None); + http::Response::builder() + .header("Set-Cookie", "key=val; Path=/subpath") + .body(Default::default()) + .unwrap() + } else { + assert_eq!(req.uri(), "/subpath"); + assert_eq!(req.headers()["cookie"], "key=val"); + http::Response::default() + } + } + }); + let client = reqwest::r#async::Client::builder() .cookie_store(true) .build() .unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Set-Cookie: key=val; Path=/subpath\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + client.get(&url).send().await.unwrap(); + client.get(&url).send().await.unwrap(); - let server = server! { - request: b"\ - GET / HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let url = format!("http://{}/", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); - - let server = server! { - request: b"\ - GET /subpath HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - cookie: key=val\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; let url = format!("http://{}/subpath", server.addr()); - rt.block_on(client.get(&url).send()).unwrap(); + client.get(&url).send().await.unwrap(); } diff --git a/tests/gzip.rs b/tests/gzip.rs index f251c04..76e2532 100644 --- a/tests/gzip.rs +++ b/tests/gzip.rs @@ -1,13 +1,101 @@ -#[macro_use] mod support; +use support::*; use std::io::Write; -use std::time::Duration; #[tokio::test] -async fn test_gzip_response() { - let content: String = (0..50).into_iter().map(|i| format!("test {}", i)).collect(); - let chunk_size = content.len() / 3; +async fn gzip_response() { + gzip_case(10_000, 4096).await; +} + +#[tokio::test] +async fn gzip_single_byte_chunks() { + gzip_case(10, 1).await; +} + +#[tokio::test] +async fn test_gzip_empty_body() { + let server = server::http(move |req| { + async move { + assert_eq!(req.method(), "HEAD"); + + http::Response::builder() + .header("content-encoding", "gzip") + .header("content-length", 100) + .body(Default::default()) + .unwrap() + } + }); + + let client = reqwest::Client::new(); + let res = client + .head(&format!("http://{}/gzip", server.addr())) + .send() + .await + .unwrap(); + + let body = res.text().await.unwrap(); + + assert_eq!(body, ""); +} + +#[tokio::test] +async fn test_accept_header_is_not_changed_if_set() { + let server = server::http(move |req| { + async move { + assert_eq!(req.headers()["accept"], "application/json"); + assert_eq!(req.headers()["accept-encoding"], "gzip"); + http::Response::default() + } + }); + + let client = reqwest::Client::new(); + + let res = client + .get(&format!("http://{}/accept", server.addr())) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .send() + .await + .unwrap(); + + assert_eq!(res.status(), reqwest::StatusCode::OK); +} + +#[tokio::test] +async fn test_accept_encoding_header_is_not_changed_if_set() { + let server = server::http(move |req| { + async move { + assert_eq!(req.headers()["accept"], "*/*"); + assert_eq!(req.headers()["accept-encoding"], "identity"); + http::Response::default() + } + }); + + let client = reqwest::Client::new(); + + let res = client + .get(&format!("http://{}/accept-encoding", server.addr())) + .header( + reqwest::header::ACCEPT_ENCODING, + reqwest::header::HeaderValue::from_static("identity"), + ) + .send() + .await + .unwrap(); + + assert_eq!(res.status(), reqwest::StatusCode::OK); +} + +async fn gzip_case(response_size: usize, chunk_size: usize) { + use futures_util::stream::StreamExt; + + let content: String = (0..response_size) + .into_iter() + .map(|i| format!("test {}", i)) + .collect(); let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap(); match encoder.write(content.as_bytes()) { Ok(n) => assert!(n > 0, "Failed to write to encoder."), @@ -28,147 +116,38 @@ async fn test_gzip_response() { .into_bytes(); response.extend(&gzipped_content); - let server = server! { - request: b"\ - GET /gzip HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - chunk_size: chunk_size, - write_timeout: Duration::from_millis(10), - response: response - }; - let url = format!("http://{}/gzip", server.addr()); - let res = reqwest::get(&url).await.unwrap(); + let server = server::http(move |req| { + assert_eq!(req.headers()["accept-encoding"], "gzip"); - let body = res.text().await.unwrap(); + let gzipped = gzipped_content.clone(); + async move { + let len = gzipped.len(); + let stream = futures_util::stream::unfold((gzipped, 0), move |(gzipped, pos)| { + async move { + let chunk = gzipped.chunks(chunk_size).nth(pos)?.to_vec(); + Some((chunk, (gzipped, pos + 1))) + } + }); + + let body = hyper::Body::wrap_stream(stream.map(Ok::<_, std::convert::Infallible>)); + + http::Response::builder() + .header("content-encoding", "gzip") + .header("content-length", len) + .body(body) + .unwrap() + } + }); + + let client = reqwest::Client::new(); + + let res = client + .get(&format!("http://{}/gzip", server.addr())) + .send() + .await + .expect("response"); + + let body = res.text().await.expect("text"); assert_eq!(body, content); } - -#[tokio::test] -async fn test_gzip_empty_body() { - let server = server! { - request: b"\ - HEAD /gzip HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-accept\r\n\ - Content-Encoding: gzip\r\n\ - Content-Length: 100\r\n\ - \r\n" - }; - - let client = reqwest::Client::new(); - let res = client - .head(&format!("http://{}/gzip", server.addr())) - .send() - .await - .unwrap(); - - let body = res.text().await.unwrap(); - - assert_eq!(body, ""); -} - -#[tokio::test] -async fn test_gzip_invalid_body() { - let server = server! { - request: b"\ - GET /gzip HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-accept\r\n\ - Content-Encoding: gzip\r\n\ - Content-Length: 100\r\n\ - \r\n\ - 0" - }; - let url = format!("http://{}/gzip", server.addr()); - let res = reqwest::get(&url).await.unwrap(); - // this tests that the request.send() didn't error, but that the error - // is in reading the body - - res.text().await.unwrap_err(); -} - -#[tokio::test] -async fn test_accept_header_is_not_changed_if_set() { - let server = server! { - request: b"\ - GET /accept HTTP/1.1\r\n\ - accept: application/json\r\n\ - user-agent: $USERAGENT\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-accept\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let client = reqwest::Client::new(); - - let res = client - .get(&format!("http://{}/accept", server.addr())) - .header( - reqwest::header::ACCEPT, - reqwest::header::HeaderValue::from_static("application/json"), - ) - .send() - .await - .unwrap(); - - assert_eq!(res.status(), reqwest::StatusCode::OK); -} - -#[tokio::test] -async fn test_accept_encoding_header_is_not_changed_if_set() { - let server = server! { - request: b"\ - GET /accept-encoding HTTP/1.1\r\n\ - accept-encoding: identity\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-accept-encoding\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - let client = reqwest::Client::new(); - - let res = client - .get(&format!("http://{}/accept-encoding", server.addr())) - .header( - reqwest::header::ACCEPT_ENCODING, - reqwest::header::HeaderValue::from_static("identity"), - ) - .send() - .await - .unwrap(); - - assert_eq!(res.status(), reqwest::StatusCode::OK); -} diff --git a/tests/multipart.rs b/tests/multipart.rs index 5735c72..a2eda93 100644 --- a/tests/multipart.rs +++ b/tests/multipart.rs @@ -1,5 +1,5 @@ -#[macro_use] mod support; +use support::*; #[tokio::test] async fn text_part() { @@ -17,25 +17,26 @@ async fn text_part() { form.boundary() ); - let server = server! { - request: format!("\ - POST /multipart/1 HTTP/1.1\r\n\ - content-type: multipart/form-data; boundary={}\r\n\ - content-length: 125\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - {}\ - ", form.boundary(), expected_body), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: multipart\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let ct = format!("multipart/form-data; boundary={}", form.boundary()); + + let server = server::http(move |mut req| { + let ct = ct.clone(); + let expected_body = expected_body.clone(); + async move { + assert_eq!(req.method(), "POST"); + assert_eq!(req.headers()["content-type"], ct); + assert_eq!(req.headers()["content-length"], "125"); + + let mut full: Vec = Vec::new(); + while let Some(item) = req.body_mut().next().await { + full.extend(&*item.unwrap()); + } + + assert_eq!(full, expected_body.as_bytes()); + + http::Response::default() + } + }); let url = format!("http://{}/multipart/1", server.addr()); @@ -50,9 +51,74 @@ async fn text_part() { assert_eq!(res.status(), reqwest::StatusCode::OK); } +#[tokio::test] +async fn stream_part() { + use futures_util::{future, stream}; + + let _ = env_logger::try_init(); + + let stream = reqwest::Body::wrap_stream(stream::once(future::ready(Ok::<_, reqwest::Error>( + "part1 part2".to_owned(), + )))); + let part = reqwest::multipart::Part::stream(stream); + + let form = reqwest::multipart::Form::new() + .text("foo", "bar") + .part("part_stream", part); + + let expected_body = format!( + "\ + --{0}\r\n\ + Content-Disposition: form-data; name=\"foo\"\r\n\ + \r\n\ + bar\r\n\ + --{0}\r\n\ + Content-Disposition: form-data; name=\"part_stream\"\r\n\ + \r\n\ + part1 part2\r\n\ + --{0}--\r\n\ + ", + form.boundary() + ); + + let ct = format!("multipart/form-data; boundary={}", form.boundary()); + + let server = server::http(move |mut req| { + let ct = ct.clone(); + let expected_body = expected_body.clone(); + async move { + assert_eq!(req.method(), "POST"); + assert_eq!(req.headers()["content-type"], ct); + assert_eq!(req.headers()["transfer-encoding"], "chunked"); + + let mut full: Vec = Vec::new(); + while let Some(item) = req.body_mut().next().await { + full.extend(&*item.unwrap()); + } + + assert_eq!(full, expected_body.as_bytes()); + + http::Response::default() + } + }); + + let url = format!("http://{}/multipart/1", server.addr()); + + let client = reqwest::Client::new(); + + let res = client + .post(&url) + .multipart(form) + .send() + .await + .expect("Failed to post multipart"); + assert_eq!(res.url().as_str(), &url); + assert_eq!(res.status(), reqwest::StatusCode::OK); +} + #[cfg(feature = "blocking")] #[test] -fn file() { +fn blocking_file_part() { let _ = env_logger::try_init(); let form = reqwest::blocking::multipart::Form::new() @@ -73,25 +139,30 @@ fn file() { fcontents ); - let server = server! { - request: format!("\ - POST /multipart/2 HTTP/1.1\r\n\ - content-type: multipart/form-data; boundary={}\r\n\ - content-length: {}\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - {}\ - ", form.boundary(), expected_body.len(), expected_body), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: multipart\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let ct = format!("multipart/form-data; boundary={}", form.boundary()); + + let server = server::http(move |mut req| { + let ct = ct.clone(); + let expected_body = expected_body.clone(); + async move { + assert_eq!(req.method(), "POST"); + assert_eq!(req.headers()["content-type"], ct); + // files know their exact size + assert_eq!( + req.headers()["content-length"], + expected_body.len().to_string() + ); + + let mut full: Vec = Vec::new(); + while let Some(item) = req.body_mut().next().await { + full.extend(&*item.unwrap()); + } + + assert_eq!(full, expected_body.as_bytes()); + + http::Response::default() + } + }); let url = format!("http://{}/multipart/2", server.addr()); diff --git a/tests/proxy.rs b/tests/proxy.rs index 2c62c32..d269200 100644 --- a/tests/proxy.rs +++ b/tests/proxy.rs @@ -1,30 +1,21 @@ -#[macro_use] mod support; +use support::*; use std::env; #[tokio::test] async fn http_proxy() { - let server = server! { - request: b"\ - GET http://hyper.rs/prox HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: hyper.rs\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: proxied\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let url = "http://hyper.rs/prox"; + let server = server::http(move |req| { + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), url); + assert_eq!(req.headers()["host"], "hyper.rs"); + + async { http::Response::default() } + }); let proxy = format!("http://{}", server.addr()); - let url = "http://hyper.rs/prox"; let res = reqwest::Client::builder() .proxy(reqwest::Proxy::http(&proxy).unwrap()) .build() @@ -36,35 +27,25 @@ async fn http_proxy() { assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!( - res.headers().get(reqwest::header::SERVER).unwrap(), - &"proxied" - ); } #[tokio::test] async fn http_proxy_basic_auth() { - let server = server! { - request: b"\ - GET http://hyper.rs/prox HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - proxy-authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\ - host: hyper.rs\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: proxied\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let url = "http://hyper.rs/prox"; + let server = server::http(move |req| { + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), url); + assert_eq!(req.headers()["host"], "hyper.rs"); + assert_eq!( + req.headers()["proxy-authorization"], + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" + ); + + async { http::Response::default() } + }); let proxy = format!("http://{}", server.addr()); - let url = "http://hyper.rs/prox"; let res = reqwest::Client::builder() .proxy( reqwest::Proxy::http(&proxy) @@ -80,35 +61,25 @@ async fn http_proxy_basic_auth() { assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!( - res.headers().get(reqwest::header::SERVER).unwrap(), - &"proxied" - ); } #[tokio::test] async fn http_proxy_basic_auth_parsed() { - let server = server! { - request: b"\ - GET http://hyper.rs/prox HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - proxy-authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\ - host: hyper.rs\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: proxied\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let url = "http://hyper.rs/prox"; + let server = server::http(move |req| { + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), url); + assert_eq!(req.headers()["host"], "hyper.rs"); + assert_eq!( + req.headers()["proxy-authorization"], + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" + ); + + async { http::Response::default() } + }); let proxy = format!("http://Aladdin:open sesame@{}", server.addr()); - let url = "http://hyper.rs/prox"; let res = reqwest::Client::builder() .proxy(reqwest::Proxy::http(&proxy).unwrap()) .build() @@ -120,30 +91,16 @@ async fn http_proxy_basic_auth_parsed() { assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!( - res.headers().get(reqwest::header::SERVER).unwrap(), - &"proxied" - ); } #[tokio::test] async fn test_no_proxy() { - let server = server! { - request: b"\ - GET /4 HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |req| { + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), "/4"); + + async { http::Response::default() } + }); let proxy = format!("http://{}", server.addr()); let url = format!("http://{}/4", server.addr()); @@ -164,28 +121,20 @@ async fn test_no_proxy() { #[tokio::test] async fn test_using_system_proxy() { - let server = server! { - request: b"\ - GET http://hyper.rs/prox HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: hyper.rs\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: proxied\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let url = "http://hyper.rs/prox"; + let server = server::http(move |req| { + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), url); + assert_eq!(req.headers()["host"], "hyper.rs"); + + async { http::Response::default() } + }); + // save system setting first. let system_proxy = env::var("http_proxy"); // set-up http proxy. env::set_var("http_proxy", format!("http://{}", server.addr())); - let url = "http://hyper.rs/prox"; let res = reqwest::Client::builder() .use_sys_proxy() .build() @@ -197,10 +146,6 @@ async fn test_using_system_proxy() { assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!( - res.headers().get(reqwest::header::SERVER).unwrap(), - &"proxied" - ); // reset user setting. match system_proxy { @@ -208,3 +153,30 @@ async fn test_using_system_proxy() { Ok(proxy) => env::set_var("http_proxy", proxy), } } + +#[tokio::test] +async fn http_over_http() { + let url = "http://hyper.rs/prox"; + + let server = server::http(move |req| { + assert_eq!(req.method(), "GET"); + assert_eq!(req.uri(), url); + assert_eq!(req.headers()["host"], "hyper.rs"); + + async { http::Response::default() } + }); + + let proxy = format!("http://{}", server.addr()); + + let res = reqwest::Client::builder() + .proxy(reqwest::Proxy::http(&proxy).unwrap()) + .build() + .unwrap() + .get(url) + .send() + .await + .unwrap(); + + assert_eq!(res.url().as_str(), url); + assert_eq!(res.status(), reqwest::StatusCode::OK); +} diff --git a/tests/redirect.rs b/tests/redirect.rs index b2ece7b..f863fd4 100644 --- a/tests/redirect.rs +++ b/tests/redirect.rs @@ -1,47 +1,32 @@ -#[macro_use] mod support; +use support::*; #[tokio::test] async fn test_redirect_301_and_302_and_303_changes_post_to_get() { let client = reqwest::Client::new(); - let codes = [301, 302, 303]; + let codes = [301u16, 302, 303]; - for code in codes.iter() { - let redirect = server! { - request: format!("\ - POST /{} HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", code), - response: format!("\ - HTTP/1.1 {} reason\r\n\ - Server: test-redirect\r\n\ - Content-Length: 0\r\n\ - Location: /dst\r\n\ - Connection: close\r\n\ - \r\n\ - ", code) - ; + for &code in codes.iter() { + let redirect = server::http(move |req| { + async move { + if req.method() == "POST" { + assert_eq!(req.uri(), &*format!("/{}", code)); + http::Response::builder() + .status(code) + .header("location", "/dst") + .header("server", "test-redirect") + .body(Default::default()) + .unwrap() + } else { + assert_eq!(req.method(), "GET"); - request: format!("\ - GET /dst HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - referer: http://$HOST/{}\r\n\ - host: $HOST\r\n\ - \r\n\ - ", code), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-dst\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + http::Response::builder() + .header("server", "test-dst") + .body(Default::default()) + .unwrap() + } + } + }); let url = format!("http://{}/{}", redirect.addr(), code); let dst = format!("http://{}/{}", redirect.addr(), "dst"); @@ -58,43 +43,28 @@ async fn test_redirect_301_and_302_and_303_changes_post_to_get() { #[tokio::test] async fn test_redirect_307_and_308_tries_to_get_again() { let client = reqwest::Client::new(); - let codes = [307, 308]; - for code in codes.iter() { - let redirect = server! { - request: format!("\ - GET /{} HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", code), - response: format!("\ - HTTP/1.1 {} reason\r\n\ - Server: test-redirect\r\n\ - Content-Length: 0\r\n\ - Location: /dst\r\n\ - Connection: close\r\n\ - \r\n\ - ", code) - ; + let codes = [307u16, 308]; + for &code in codes.iter() { + let redirect = server::http(move |req| { + async move { + assert_eq!(req.method(), "GET"); + if req.uri() == &*format!("/{}", code) { + http::Response::builder() + .status(code) + .header("location", "/dst") + .header("server", "test-redirect") + .body(Default::default()) + .unwrap() + } else { + assert_eq!(req.uri(), "/dst"); - request: format!("\ - GET /dst HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - referer: http://$HOST/{}\r\n\ - host: $HOST\r\n\ - \r\n\ - ", code), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-dst\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + http::Response::builder() + .header("server", "test-dst") + .body(Default::default()) + .unwrap() + } + } + }); let url = format!("http://{}/{}", redirect.addr(), code); let dst = format!("http://{}/{}", redirect.addr(), "dst"); @@ -111,47 +81,32 @@ async fn test_redirect_307_and_308_tries_to_get_again() { #[tokio::test] async fn test_redirect_307_and_308_tries_to_post_again() { let client = reqwest::Client::new(); - let codes = [307, 308]; - for code in codes.iter() { - let redirect = server! { - request: format!("\ - POST /{} HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - content-length: 5\r\n\ - \r\n\ - Hello\ - ", code), - response: format!("\ - HTTP/1.1 {} reason\r\n\ - Server: test-redirect\r\n\ - Content-Length: 0\r\n\ - Location: /dst\r\n\ - Connection: close\r\n\ - \r\n\ - ", code) - ; + let codes = [307u16, 308]; + for &code in codes.iter() { + let redirect = server::http(move |mut req| { + async move { + assert_eq!(req.method(), "POST"); - request: format!("\ - POST /dst HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - referer: http://$HOST/{}\r\n\ - host: $HOST\r\n\ - content-length: 5\r\n\ - \r\n\ - Hello\ - ", code), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-dst\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let data = req.body_mut().next().await.unwrap().unwrap(); + assert_eq!(&*data, b"Hello"); + + if req.uri() == &*format!("/{}", code) { + http::Response::builder() + .status(code) + .header("location", "/dst") + .header("server", "test-redirect") + .body(Default::default()) + .unwrap() + } else { + assert_eq!(req.uri(), "/dst"); + + http::Response::builder() + .header("server", "test-dst") + .body(Default::default()) + .unwrap() + } + } + }); let url = format!("http://{}/{}", redirect.addr(), code); let dst = format!("http://{}/{}", redirect.addr(), "dst"); @@ -169,30 +124,25 @@ async fn test_redirect_307_and_308_tries_to_post_again() { #[test] fn test_redirect_307_does_not_try_if_reader_cannot_reset() { let client = reqwest::blocking::Client::new(); - let codes = [307, 308]; + let codes = [307u16, 308]; for &code in codes.iter() { - let redirect = server! { - request: format!("\ - POST /{} HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - transfer-encoding: chunked\r\n\ - \r\n\ - 5\r\n\ - Hello\r\n\ - 0\r\n\r\n\ - ", code), - response: format!("\ - HTTP/1.1 {} reason\r\n\ - Server: test-redirect\r\n\ - Content-Length: 0\r\n\ - Location: /dst\r\n\ - Connection: close\r\n\ - \r\n\ - ", code) - }; + let redirect = server::http(move |mut req| { + async move { + assert_eq!(req.method(), "POST"); + assert_eq!(req.uri(), &*format!("/{}", code)); + assert_eq!(req.headers()["transfer-encoding"], "chunked"); + + let data = req.body_mut().next().await.unwrap().unwrap(); + assert_eq!(&*data, b"Hello"); + + http::Response::builder() + .status(code) + .header("location", "/dst") + .header("server", "test-redirect") + .body(Default::default()) + .unwrap() + } + }); let url = format!("http://{}/{}", redirect.addr(), code); let res = client @@ -201,50 +151,46 @@ fn test_redirect_307_does_not_try_if_reader_cannot_reset() { .send() .unwrap(); assert_eq!(res.url().as_str(), url); - assert_eq!(res.status(), reqwest::StatusCode::from_u16(code).unwrap()); + assert_eq!(res.status(), code); } } #[tokio::test] async fn test_redirect_removes_sensitive_headers() { - let end_server = server! { - request: b"\ - GET /otherhost HTTP/1.1\r\n\ - accept-encoding: gzip\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + use tokio::sync::watch; - let mid_server = server! { - request: b"\ - GET /sensitive HTTP/1.1\r\n\ - cookie: foo=bar\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: format!("\ - HTTP/1.1 302 Found\r\n\ - Server: test\r\n\ - Location: http://{}/otherhost\r\n\ - Content-Length: 0\r\n\ - \r\n\ - ", end_server.addr()) - }; + let (tx, rx) = watch::channel(None); + + let end_server = server::http(move |req| { + let mut rx = rx.clone(); + async move { + assert_eq!(req.headers().get("cookie"), None); + + let mid_addr = rx.recv().await.unwrap().unwrap(); + assert_eq!( + req.headers()["referer"], + format!("http://{}/sensitive", mid_addr) + ); + http::Response::default() + } + }); + + let end_addr = end_server.addr(); + + let mid_server = server::http(move |req| { + async move { + assert_eq!(req.headers()["cookie"], "foo=bar"); + http::Response::builder() + .status(302) + .header("location", format!("http://{}/end", end_addr)) + .body(Default::default()) + .unwrap() + } + }); + + tx.broadcast(Some(mid_server.addr())).unwrap(); reqwest::Client::builder() - .referer(false) .build() .unwrap() .get(&format!("http://{}/sensitive", mid_server.addr())) @@ -259,23 +205,17 @@ async fn test_redirect_removes_sensitive_headers() { #[tokio::test] async fn test_redirect_policy_can_return_errors() { - let server = server! { - request: b"\ - GET /loop HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 302 Found\r\n\ - Server: test\r\n\ - Location: /loop\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |req| { + async move { + assert_eq!(req.uri(), "/loop"); + http::Response::builder() + .status(302) + .header("location", "/loop") + .body(Default::default()) + .unwrap() + } + }); + let url = format!("http://{}/loop", server.addr()); let err = reqwest::get(&url).await.unwrap_err(); assert!(err.is_redirect()); @@ -283,23 +223,16 @@ async fn test_redirect_policy_can_return_errors() { #[tokio::test] async fn test_redirect_policy_can_stop_redirects_without_an_error() { - let server = server! { - request: b"\ - GET /no-redirect HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 302 Found\r\n\ - Server: test-dont\r\n\ - Location: /dont\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |req| { + async move { + assert_eq!(req.uri(), "/no-redirect"); + http::Response::builder() + .status(302) + .header("location", "/dont") + .body(Default::default()) + .unwrap() + } + }); let url = format!("http://{}/no-redirect", server.addr()); @@ -314,48 +247,27 @@ async fn test_redirect_policy_can_stop_redirects_without_an_error() { assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::FOUND); - assert_eq!( - res.headers().get(reqwest::header::SERVER).unwrap(), - &"test-dont" - ); } #[tokio::test] async fn test_referer_is_not_set_if_disabled() { - let server = server! { - request: b"\ - GET /no-refer HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 302 Found\r\n\ - Server: test-no-referer\r\n\ - Content-Length: 0\r\n\ - Location: /dst\r\n\ - Connection: close\r\n\ - \r\n\ - " - ; + let server = server::http(move |req| { + async move { + if req.uri() == "/no-refer" { + http::Response::builder() + .status(302) + .header("location", "/dst") + .body(Default::default()) + .unwrap() + } else { + assert_eq!(req.uri(), "/dst"); + assert_eq!(req.headers().get("referer"), None); + + http::Response::default() + } + } + }); - request: b"\ - GET /dst HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-dst\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; reqwest::Client::builder() .referer(false) .build() @@ -368,23 +280,15 @@ async fn test_referer_is_not_set_if_disabled() { #[tokio::test] async fn test_invalid_location_stops_redirect_gh484() { - let server = server! { - request: b"\ - GET /yikes HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 302 Found\r\n\ - Server: test-yikes\r\n\ - Location: http://www.yikes{KABOOM}\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; + let server = server::http(move |_req| { + async move { + http::Response::builder() + .status(302) + .header("location", "http://www.yikes{KABOOM}") + .body(Default::default()) + .unwrap() + } + }); let url = format!("http://{}/yikes", server.addr()); @@ -392,66 +296,38 @@ async fn test_invalid_location_stops_redirect_gh484() { assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::FOUND); - assert_eq!( - res.headers().get(reqwest::header::SERVER).unwrap(), - &"test-yikes" - ); } #[cfg(feature = "cookies")] #[tokio::test] async fn test_redirect_302_with_set_cookies() { let code = 302; + let server = server::http(move |req| { + async move { + if req.uri() == "/302" { + http::Response::builder() + .status(302) + .header("location", "/dst") + .header("set-cookie", "key=value") + .body(Default::default()) + .unwrap() + } else { + assert_eq!(req.uri(), "/dst"); + assert_eq!(req.headers()["cookie"], "key=value"); + http::Response::default() + } + } + }); + + let url = format!("http://{}/{}", server.addr(), code); + let dst = format!("http://{}/{}", server.addr(), "dst"); + let client = reqwest::ClientBuilder::new() .cookie_store(true) .build() .unwrap(); - let server = server! { - request: format!("\ - GET /{} HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", code), - response: format!("\ - HTTP/1.1 {} reason\r\n\ - Server: test-redirect\r\n\ - Content-Length: 0\r\n\ - Location: /dst\r\n\ - Connection: close\r\n\ - Set-Cookie: key=value\r\n\ - \r\n\ - ", code) - ; - - request: format!("\ - GET /dst HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - referer: http://$HOST/{}\r\n\ - cookie: key=value\r\n\ - host: $HOST\r\n\ - \r\n\ - ", code), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Server: test-dst\r\n\ - Content-Length: 0\r\n\ - \r\n\ - " - }; - - let url = format!("http://{}/{}", server.addr(), code); - let dst = format!("http://{}/{}", server.addr(), "dst"); let res = client.get(&url).send().await.unwrap(); assert_eq!(res.url().as_str(), dst); assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!( - res.headers().get(reqwest::header::SERVER).unwrap(), - &"test-dst" - ); } diff --git a/tests/support/mod.rs b/tests/support/mod.rs index dc77a29..6434fb6 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -1,2 +1,6 @@ -#[macro_use] pub mod server; + +// TODO: remove once done converting to new support server? +#[allow(unused)] +pub static DEFAULT_USER_AGENT: &'static str = + concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); diff --git a/tests/support/server.rs b/tests/support/server.rs index ecee108..84ea36b 100644 --- a/tests/support/server.rs +++ b/tests/support/server.rs @@ -1,14 +1,18 @@ -//! A server builder helper for the integration tests. - -use std::io::{Read, Write}; +use std::convert::Infallible; +use std::future::Future; use std::net; -use std::sync::mpsc; +use std::sync::mpsc as std_mpsc; use std::thread; use std::time::Duration; +use tokio::sync::oneshot; + +pub use http::Response; + pub struct Server { addr: net::SocketAddr, - panic_rx: mpsc::Receiver<()>, + panic_rx: std_mpsc::Receiver<()>, + shutdown_tx: Option>, } impl Server { @@ -19,7 +23,11 @@ impl Server { impl Drop for Server { fn drop(&mut self) { - if !thread::panicking() { + if let Some(tx) = self.shutdown_tx.take() { + let _ = tx.send(()); + } + + if !::std::thread::panicking() { self.panic_rx .recv_timeout(Duration::from_secs(3)) .expect("test server should not panic"); @@ -27,197 +35,46 @@ impl Drop for Server { } } -#[derive(Debug, Default)] -pub struct Txn { - pub request: Vec, - pub response: Vec, +pub fn http(func: F) -> Server +where + F: Fn(http::Request) -> Fut + Clone + Send + 'static, + Fut: Future> + Send + 'static, +{ + let srv = hyper::Server::bind(&([127, 0, 0, 1], 0).into()).serve( + hyper::service::make_service_fn(move |_| { + let func = func.clone(); + async move { + Ok::<_, Infallible>(hyper::service::service_fn(move |req| { + let fut = func(req); + async move { Ok::<_, Infallible>(fut.await) } + })) + } + }), + ); - pub read_timeout: Option, - pub read_closes: bool, - pub response_timeout: Option, - pub write_timeout: Option, - pub chunk_size: Option, -} + let addr = srv.local_addr(); + let (shutdown_tx, shutdown_rx) = oneshot::channel(); + let srv = srv.with_graceful_shutdown(async move { + let _ = shutdown_rx.await; + }); -static DEFAULT_USER_AGENT: &'static str = - concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); - -pub fn spawn(txns: Vec) -> Server { - let listener = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let addr = listener.local_addr().unwrap(); - let (panic_tx, panic_rx) = mpsc::channel(); + let (panic_tx, panic_rx) = std_mpsc::channel(); let tname = format!( "test({})-support-server", thread::current().name().unwrap_or("") ); - thread::Builder::new().name(tname).spawn(move || { - 'txns: for txn in txns { - let mut expected = txn.request; - let reply = txn.response; - let (mut socket, _addr) = listener.accept().unwrap(); + thread::Builder::new() + .name(tname) + .spawn(move || { + let mut rt = tokio::runtime::current_thread::Runtime::new().expect("rt new"); + rt.block_on(srv).unwrap(); + let _ = panic_tx.send(()); + }) + .expect("thread spawn"); - socket.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); - - replace_expected_vars(&mut expected, addr.to_string().as_ref(), DEFAULT_USER_AGENT.as_ref()); - - if let Some(dur) = txn.read_timeout { - thread::park_timeout(dur); - } - - let mut buf = vec![0; expected.len() + 256]; - - let mut n = 0; - while n < expected.len() { - match socket.read(&mut buf[n..]) { - Ok(0) => { - if !txn.read_closes { - panic!("server unexpected socket closed"); - } else { - continue 'txns; - } - }, - Ok(nread) => n += nread, - Err(err) => { - println!("server read error: {}", err); - break; - } - } - } - - if txn.read_closes { - socket.set_read_timeout(Some(Duration::from_secs(1))).unwrap(); - match socket.read(&mut [0; 256]) { - Ok(0) => { - continue 'txns - }, - Ok(_) => { - panic!("server read expected EOF, found more bytes"); - }, - Err(err) => { - panic!("server read expected EOF, got error: {}", err); - } - } - } - - match (std::str::from_utf8(&expected), std::str::from_utf8(&buf[..n])) { - (Ok(expected), Ok(received)) => { - if expected.len() > 300 && std::env::var("REQWEST_TEST_BODY_FULL").is_err() { - assert_eq!( - expected.len(), - received.len(), - "expected len = {}, received len = {}; to skip length check and see exact contents, re-run with REQWEST_TEST_BODY_FULL=1", - expected.len(), - received.len(), - ); - } - assert_eq!(expected, received) - }, - _ => { - assert_eq!( - expected.len(), - n, - "expected len = {}, received len = {}", - expected.len(), - n, - ); - assert_eq!(expected, &buf[..n]) - }, - } - - if let Some(dur) = txn.response_timeout { - thread::park_timeout(dur); - } - - if let Some(dur) = txn.write_timeout { - let headers_end = b"\r\n\r\n"; - let headers_end = reply.windows(headers_end.len()).position(|w| w == headers_end).unwrap() + 4; - socket.write_all(&reply[..headers_end]).unwrap(); - - let body = &reply[headers_end..]; - - if let Some(chunk_size) = txn.chunk_size { - for content in body.chunks(chunk_size) { - thread::park_timeout(dur); - socket.write_all(&content).unwrap(); - } - } else { - thread::park_timeout(dur); - socket.write_all(&body).unwrap(); - } - } else { - socket.write_all(&reply).unwrap(); - } - } - let _ = panic_tx.send(()); - }).expect("server thread spawn"); - - Server { addr, panic_rx } -} - -fn replace_expected_vars(bytes: &mut Vec, host: &[u8], ua: &[u8]) { - // plenty horrible, but these are just tests, and gets the job done - let mut index = 0; - loop { - if index == bytes.len() { - return; - } - - for b in (&bytes[index..]).iter() { - index += 1; - if *b == b'$' { - break; - } - } - - let has_host = (&bytes[index..]).starts_with(b"HOST"); - if has_host { - bytes.drain(index - 1..index + 4); - for (i, b) in host.iter().enumerate() { - bytes.insert(index - 1 + i, *b); - } - } else { - let has_ua = (&bytes[index..]).starts_with(b"USERAGENT"); - if has_ua { - bytes.drain(index - 1..index + 9); - for (i, b) in ua.iter().enumerate() { - bytes.insert(index - 1 + i, *b); - } - } - } + Server { + addr, + panic_rx, + shutdown_tx: Some(shutdown_tx), } } - -#[macro_export] -macro_rules! server { - ($($($f:ident: $v:expr),+);*) => ({ - let txns = vec![ - $(__internal__txn! { - $($f: $v,)+ - }),* - ]; - crate::support::server::spawn(txns) - }) -} - -#[macro_export] -macro_rules! __internal__txn { - ($($field:ident: $val:expr,)+) => ( - crate::support::server::Txn { - $( $field: __internal__prop!($field: $val), )+ - .. Default::default() - } - ) -} - -#[macro_export] -macro_rules! __internal__prop { - (request: $val:expr) => { - From::from(&$val[..]) - }; - (response: $val:expr) => { - From::from(&$val[..]) - }; - ($field:ident: $val:expr) => { - From::from($val) - }; -} diff --git a/tests/timeouts.rs b/tests/timeouts.rs index 84bf3ed..1628a44 100644 --- a/tests/timeouts.rs +++ b/tests/timeouts.rs @@ -1,7 +1,64 @@ -#[macro_use] mod support; +use support::*; -use std::time::Duration; +use std::time::{Duration, Instant}; + +#[tokio::test] +async fn request_timeout() { + let _ = env_logger::try_init(); + + let server = server::http(move |_req| { + async { + // delay returning the response + tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await; + http::Response::default() + } + }); + + let client = reqwest::Client::builder() + .timeout(Duration::from_millis(500)) + .build() + .unwrap(); + + let url = format!("http://{}/slow", server.addr()); + + let res = client.get(&url).send().await; + + let err = res.unwrap_err(); + + assert!(err.is_timeout()); + assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); +} + +#[tokio::test] +async fn response_timeout() { + let _ = env_logger::try_init(); + + let server = server::http(move |_req| { + async { + // immediate response, but delayed body + let body = hyper::Body::wrap_stream(futures_util::stream::once(async { + tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await; + Ok::<_, std::convert::Infallible>("Hello") + })); + + http::Response::new(body) + } + }); + + let client = reqwest::Client::builder() + .timeout(Duration::from_millis(500)) + .build() + .unwrap(); + + let url = format!("http://{}/slow", server.addr()); + let res = client.get(&url).send().await.expect("Failed to get"); + let body = res.text().await; + + let err = body.unwrap_err(); + + assert!(err.is_timeout()); +} /// Tests that internal client future cancels when the oneshot channel /// is canceled. @@ -17,24 +74,13 @@ fn timeout_closes_connection() { .build() .unwrap(); - let server = server! { - request: b"\ - GET /closes HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - ", - read_timeout: Duration::from_secs(2), - read_closes: true - }; + let server = server::http(move |_req| { + async { + // delay returning the response + tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await; + http::Response::default() + } + }); let url = format!("http://{}/closes", server.addr()); let err = client.get(&url).send().unwrap_err(); @@ -47,7 +93,7 @@ fn timeout_closes_connection() { #[test] fn write_timeout_large_body() { let _ = env_logger::try_init(); - let body = String::from_utf8(vec![b'x'; 20_000]).unwrap(); + let body = vec![b'x'; 20_000]; let len = 8192; // Make Client drop *after* the Server, so the background doesn't @@ -57,28 +103,15 @@ fn write_timeout_large_body() { .build() .unwrap(); - let server = server! { - request: format!("\ - POST /write-timeout HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - content-length: {}\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - {}\ - ", body.len(), body), - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - ", - read_timeout: Duration::from_secs(2), - read_closes: true - }; + let server = server::http(move |_req| { + async { + // delay returning the response + tokio::timer::delay(Instant::now() + Duration::from_secs(2)).await; + http::Response::default() + } + }); - let cursor = std::io::Cursor::new(body.into_bytes()); + let cursor = std::io::Cursor::new(body); let url = format!("http://{}/write-timeout", server.addr()); let err = client .post(&url) @@ -89,78 +122,3 @@ fn write_timeout_large_body() { assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } - -#[tokio::test] -async fn test_response_timeout() { - let _ = env_logger::try_init(); - let server = server! { - request: b"\ - GET /response-timeout HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 0\r\n\ - ", - response_timeout: Duration::from_secs(1) - }; - - let url = format!("http://{}/response-timeout", server.addr()); - let err = reqwest::Client::builder() - .timeout(Duration::from_millis(500)) - .build() - .unwrap() - .get(&url) - .send() - .await - .unwrap_err(); - - assert!(err.is_timeout()); - assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); -} - -#[tokio::test] -async fn test_read_timeout() { - let _ = env_logger::try_init(); - let server = server! { - request: b"\ - GET /read-timeout HTTP/1.1\r\n\ - user-agent: $USERAGENT\r\n\ - accept: */*\r\n\ - accept-encoding: gzip\r\n\ - host: $HOST\r\n\ - \r\n\ - ", - response: b"\ - HTTP/1.1 200 OK\r\n\ - Content-Length: 5\r\n\ - \r\n\ - Hello\ - ", - write_timeout: Duration::from_secs(1) - }; - - let url = format!("http://{}/read-timeout", server.addr()); - let res = reqwest::Client::builder() - .timeout(Duration::from_millis(500)) - .build() - .unwrap() - .get(&url) - .send() - .await - .unwrap(); - - assert_eq!(res.url().as_str(), &url); - assert_eq!(res.status(), reqwest::StatusCode::OK); - assert_eq!( - res.headers().get(reqwest::header::CONTENT_LENGTH).unwrap(), - &"5" - ); - - let err = res.text().await.unwrap_err(); - assert!(err.is_timeout()); -}