mirror of https://github.com/linkerd/linkerd2.git
				
				
				
			
		
			
				
	
	
		
			769 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Rust
		
	
	
	
			
		
		
	
	
			769 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Rust
		
	
	
	
| #![deny(warnings)]
 | |
| #[macro_use]
 | |
| mod support;
 | |
| use self::support::*;
 | |
| 
 | |
| macro_rules! assert_contains {
 | |
|     ($scrape:expr, $contains:expr) => {
 | |
|         assert_eventually!($scrape.contains($contains), "metrics scrape:\n{:8}\ndid not contain:\n{:8}", $scrape, $contains)
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn outbound_http1() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1().route("/", "hello h1").run();
 | |
|     let ctrl = controller::new()
 | |
|         .destination_and_close("transparency.test.svc.cluster.local", srv.addr)
 | |
|         .run();
 | |
|     let proxy = proxy::new().controller(ctrl).outbound(srv).run();
 | |
|     let client = client::http1(proxy.outbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
|     assert_eq!(client.get("/"), "hello h1");
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn inbound_http1() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1().route("/", "hello h1").run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
|     assert_eq!(client.get("/"), "hello h1");
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_connect_not_supported() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::tcp()
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::tcp(proxy.inbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
|     tcp_client.write("CONNECT foo.bar:443 HTTP/1.1\r\nHost: foo.bar:443\r\n\r\n");
 | |
| 
 | |
|     let expected = "HTTP/1.1 502 Bad Gateway\r\n";
 | |
|     assert_eq!(s(&tcp_client.read()[..expected.len()]), expected);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_removes_connection_headers() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", |req| {
 | |
|             assert!(!req.headers().contains_key("x-foo-bar"));
 | |
|             Response::builder()
 | |
|                 .header("x-server-quux", "lorem ipsum")
 | |
|                 .header("connection", "close, x-server-quux")
 | |
|                 .body("".into())
 | |
|                 .unwrap()
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
|     let res = client.request(client.request_builder("/")
 | |
|         .header("x-foo-bar", "baz")
 | |
|         .header("connection", "x-foo-bar, close"));
 | |
| 
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
|     assert!(!res.headers().contains_key("x-server-quux"));
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http10_with_host() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let host = "transparency.test.svc.cluster.local";
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", move |req| {
 | |
|             assert_eq!(req.version(), http::Version::HTTP_10);
 | |
|             assert_eq!(req.headers().get("host").unwrap(), host);
 | |
|             Response::builder()
 | |
|                 .version(http::Version::HTTP_10)
 | |
|                 .body("".into())
 | |
|                 .unwrap()
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1(proxy.inbound, host);
 | |
| 
 | |
|     let res = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_10)
 | |
|         .header("host", host));
 | |
| 
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res.version(), http::Version::HTTP_10);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http10_without_host() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", move |req| {
 | |
|             assert_eq!(req.version(), http::Version::HTTP_10);
 | |
|             assert!(!req.headers().contains_key("host"));
 | |
|             assert_eq!(req.uri().to_string(), "/");
 | |
|             Response::builder()
 | |
|                 .version(http::Version::HTTP_10)
 | |
|                 .body("".into())
 | |
|                 .unwrap()
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::tcp(proxy.inbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
| 
 | |
|     tcp_client.write("\
 | |
|         GET / HTTP/1.0\r\n\
 | |
|         \r\n\
 | |
|     ");
 | |
| 
 | |
|     let expected = "HTTP/1.0 200 OK\r\n";
 | |
|     assert_eq!(s(&tcp_client.read()[..expected.len()]), expected);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http11_absolute_uri_differs_from_host() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     // We shouldn't touch the URI or the Host, just pass directly as we got.
 | |
|     let auth = "transparency.test.svc.cluster.local";
 | |
|     let host = "foo.bar";
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", move |req| {
 | |
|             assert_eq!(req.headers()["host"], host);
 | |
|             assert_eq!(req.uri().to_string(), format!("http://{}/", auth));
 | |
|             Response::new("".into())
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1_absolute_uris(proxy.inbound, auth);
 | |
| 
 | |
|     let res = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", host));
 | |
| 
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res.version(), http::Version::HTTP_11);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn outbound_tcp() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let msg1 = "custom tcp hello";
 | |
|     let msg2 = "custom tcp bye";
 | |
| 
 | |
|     let srv = server::tcp()
 | |
|         .accept(move |read| {
 | |
|             assert_eq!(read, msg1.as_bytes());
 | |
|             msg2
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .outbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::tcp(proxy.outbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
| 
 | |
|     tcp_client.write(msg1);
 | |
|     assert_eq!(tcp_client.read(), msg2.as_bytes());
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn inbound_tcp() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let msg1 = "custom tcp hello";
 | |
|     let msg2 = "custom tcp bye";
 | |
| 
 | |
|     let srv = server::tcp()
 | |
|         .accept(move |read| {
 | |
|             assert_eq!(read, msg1.as_bytes());
 | |
|             msg2
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::tcp(proxy.inbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
| 
 | |
|     tcp_client.write(msg1);
 | |
|     assert_eq!(tcp_client.read(), msg2.as_bytes());
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn tcp_server_first() {
 | |
|     use std::sync::mpsc;
 | |
| 
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let msg1 = "custom tcp server starts";
 | |
|     let msg2 = "custom tcp client second";
 | |
| 
 | |
|     let (tx, rx) = mpsc::channel();
 | |
| 
 | |
|     let srv = server::tcp()
 | |
|         .accept_fut(move |sock| {
 | |
|             tokio_io::io::write_all(sock, msg1.as_bytes())
 | |
|                 .and_then(move |(sock, _)| {
 | |
|                     tokio_io::io::read(sock, vec![0; 512])
 | |
|                 })
 | |
|                 .map(move |(_sock, vec, n)| {
 | |
|                     assert_eq!(&vec[..n], msg2.as_bytes());
 | |
|                     tx.send(()).unwrap();
 | |
|                 })
 | |
|                 .map_err(|e| panic!("tcp server error: {}", e))
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .disable_inbound_ports_protocol_detection(vec![srv.addr.port()])
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::tcp(proxy.inbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
| 
 | |
|     assert_eq!(tcp_client.read(), msg1.as_bytes());
 | |
|     tcp_client.write(msg2);
 | |
|     rx.recv_timeout(Duration::from_secs(5)).unwrap();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn tcp_with_no_orig_dst() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::tcp()
 | |
|         .accept(move |_| "don't read me")
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     // no outbound configured for proxy
 | |
|     let client = client::tcp(proxy.outbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
|     tcp_client.write("custom tcp hello");
 | |
| 
 | |
|     let read = tcp_client
 | |
|         .try_read()
 | |
|         // This read might be an error, or an empty vec
 | |
|         .unwrap_or_else(|_| Vec::new());
 | |
|     assert_eq!(read, b"");
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn tcp_connections_close_if_client_closes() {
 | |
|     use std::sync::mpsc;
 | |
| 
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let msg1 = "custom tcp hello";
 | |
|     let msg2 = "custom tcp bye";
 | |
| 
 | |
|     let (tx, rx) = mpsc::channel();
 | |
| 
 | |
|     let srv = server::tcp()
 | |
|         .accept_fut(move |sock| {
 | |
|             tokio_io::io::read(sock, vec![0; 1024])
 | |
|                 .and_then(move |(sock, vec, n)| {
 | |
|                     assert_eq!(&vec[..n], msg1.as_bytes());
 | |
| 
 | |
|                     tokio_io::io::write_all(sock, msg2.as_bytes())
 | |
|                 }).and_then(|(sock, _)| {
 | |
|                     // lets read again, but we should get eof
 | |
|                     tokio_io::io::read(sock, [0; 16])
 | |
|                 })
 | |
|                 .map(move |(_sock, _vec, n)| {
 | |
|                     assert_eq!(n, 0);
 | |
|                     tx.send(()).unwrap();
 | |
|                 })
 | |
|                 .map_err(|e| panic!("tcp server error: {}", e))
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::tcp(proxy.inbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
|     tcp_client.write(msg1);
 | |
|     assert_eq!(tcp_client.read(), msg2.as_bytes());
 | |
| 
 | |
|     drop(tcp_client);
 | |
| 
 | |
|     // rx will be fulfilled when our tcp accept_fut sees
 | |
|     // a socket disconnect, which is what we are testing for.
 | |
|     // the timeout here is just to prevent this test from hanging
 | |
|     rx.recv_timeout(Duration::from_secs(5)).unwrap();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http11_upgrade_not_supported() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     // our h1 proxy will strip the Connection header
 | |
|     // and headers it mentions
 | |
|     let msg1 = "\
 | |
|         GET /chat HTTP/1.1\r\n\
 | |
|         Host: foo.bar\r\n\
 | |
|         Connection: Upgrade\r\n\
 | |
|         Upgrade: websocket\r\n\
 | |
|         \r\n\
 | |
|         ";
 | |
| 
 | |
|     // but let's pretend the server tries to upgrade
 | |
|     // anyways
 | |
|     let msg2 = "\
 | |
|         HTTP/1.1 101 Switching Protocols\r\n\
 | |
|         Upgrade: websocket\r\n\
 | |
|         Connection: Upgrade\r\n\
 | |
|         \r\n\
 | |
|         ";
 | |
| 
 | |
|     let srv = server::tcp()
 | |
|         .accept(move |read| {
 | |
|             let head = s(&read);
 | |
|             assert!(!head.contains("Upgrade: websocket"));
 | |
|             msg2
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::tcp(proxy.inbound);
 | |
| 
 | |
|     let tcp_client = client.connect();
 | |
| 
 | |
|     tcp_client.write(msg1);
 | |
| 
 | |
|     let expected = "HTTP/1.1 500 ";
 | |
|     assert_eq!(s(&tcp_client.read()[..expected.len()]), expected);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_requests_without_body_doesnt_add_transfer_encoding() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", |req| {
 | |
|             let has_body_header = req.headers().contains_key("transfer-encoding")
 | |
|                 || req.headers().contains_key("content-length");
 | |
|             let status = if  has_body_header {
 | |
|                 StatusCode::BAD_REQUEST
 | |
|             } else {
 | |
|                 StatusCode::OK
 | |
|             };
 | |
|             let mut res = Response::new("".into());
 | |
|             *res.status_mut() = status;
 | |
|             res
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
|     let methods = &[
 | |
|         "GET",
 | |
|         "POST",
 | |
|         "PUT",
 | |
|         "DELETE",
 | |
|         "HEAD",
 | |
|         "PATCH",
 | |
|     ];
 | |
| 
 | |
|     for &method in methods {
 | |
|         let resp = client.request(
 | |
|             client
 | |
|                 .request_builder("/")
 | |
|                 .method(method)
 | |
|         );
 | |
| 
 | |
|         assert_eq!(resp.status(), StatusCode::OK, "method={:?}", method);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_content_length_zero_is_preserved() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", |req| {
 | |
|             let status = if req.headers()["content-length"] == "0" {
 | |
|                 StatusCode::OK
 | |
|             } else {
 | |
|                 StatusCode::BAD_REQUEST
 | |
|             };
 | |
|             Response::builder()
 | |
|                 .status(status)
 | |
|                 .header("content-length", "0")
 | |
|                 .body("".into())
 | |
|                 .unwrap()
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
| 
 | |
|     let methods = &[
 | |
|         "GET",
 | |
|         "POST",
 | |
|         "PUT",
 | |
|         "DELETE",
 | |
|         "HEAD",
 | |
|         "PATCH",
 | |
|     ];
 | |
| 
 | |
|     for &method in methods {
 | |
|         let resp = client.request(
 | |
|             client
 | |
|                 .request_builder("/")
 | |
|                 .method(method)
 | |
|                 .header("content-length", "0")
 | |
|         );
 | |
| 
 | |
|         assert_eq!(resp.status(), StatusCode::OK, "method={:?}", method);
 | |
|         assert_eq!(resp.headers()["content-length"], "0", "method={:?}", method);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_bodyless_responses() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let req_status_header = "x-test-status-requested";
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", move |req| {
 | |
|             let status = req.headers()
 | |
|                 .get(req_status_header)
 | |
|                 .map(|val| {
 | |
|                     val.to_str()
 | |
|                         .expect("req_status_header should be ascii")
 | |
|                         .parse::<u16>()
 | |
|                         .expect("req_status_header should be numbers")
 | |
|                 })
 | |
|                 .unwrap_or(200);
 | |
| 
 | |
|             Response::builder()
 | |
|                 .status(status)
 | |
|                 .body("".into())
 | |
|                 .unwrap()
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
|     // https://tools.ietf.org/html/rfc7230#section-3.3.3
 | |
|     // > response to a HEAD request, any 1xx, 204, or 304 cannot contain a body
 | |
| 
 | |
|     //TODO: the proxy doesn't support CONNECT requests yet, but when we do,
 | |
|     //they should be tested here as well. As RFC7230 says, a 2xx response to
 | |
|     //a CONNECT request is not allowed to contain a body (but 4xx, 5xx can!).
 | |
| 
 | |
|     let resp = client.request(
 | |
|         client
 | |
|             .request_builder("/")
 | |
|             .method("HEAD")
 | |
|     );
 | |
| 
 | |
|     assert_eq!(resp.status(), StatusCode::OK);
 | |
|     assert!(!resp.headers().contains_key("content-length"));
 | |
|     assert!(!resp.headers().contains_key("transfer-encoding"));
 | |
| 
 | |
|     let statuses = &[
 | |
|         //TODO: test some 1xx status codes.
 | |
|         //The current test server doesn't support sending 1xx responses
 | |
|         //easily. We could test this by making a new unit test with the
 | |
|         //server being a TCP server, and write the response manually.
 | |
|         StatusCode::NO_CONTENT, // 204
 | |
|         StatusCode::NOT_MODIFIED, // 304
 | |
|     ];
 | |
| 
 | |
|     for &status in statuses {
 | |
|         let resp = client.request(
 | |
|             client
 | |
|                 .request_builder("/")
 | |
|                 .header(req_status_header, status.as_str())
 | |
|         );
 | |
| 
 | |
|         assert_eq!(resp.status(), status);
 | |
|         assert!(!resp.headers().contains_key("content-length"), "content-length with status={:?}", status);
 | |
|         assert!(!resp.headers().contains_key("transfer-encoding"), "transfer-encoding with status={:?}", status);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_head_responses() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_fn("/", move |req| {
 | |
|             assert_eq!(req.method(), "HEAD");
 | |
|             Response::builder()
 | |
|                 .header("content-length", "55")
 | |
|                 .body("".into())
 | |
|                 .unwrap()
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
|     let client = client::http1(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
|     let resp = client.request(
 | |
|         client
 | |
|             .request_builder("/")
 | |
|             .method("HEAD")
 | |
|     );
 | |
| 
 | |
|     assert_eq!(resp.status(), StatusCode::OK);
 | |
|     assert_eq!(resp.headers()["content-length"], "55");
 | |
| 
 | |
|     let body = resp.into_body()
 | |
|         .concat2()
 | |
|         .wait()
 | |
|         .expect("response body concat");
 | |
| 
 | |
|     assert_eq!(body, "");
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_response_end_of_file() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     // test both http/1.0 and 1.1
 | |
|     let srv = server::tcp()
 | |
|         .accept(move |_read| {
 | |
|             "\
 | |
|             HTTP/1.0 200 OK\r\n\
 | |
|             \r\n\
 | |
|             body till eof\
 | |
|             "
 | |
|         })
 | |
|         .accept(move |_read| {
 | |
|             "\
 | |
|             HTTP/1.1 200 OK\r\n\
 | |
|             \r\n\
 | |
|             body till eof\
 | |
|             "
 | |
|         })
 | |
|         .run();
 | |
|     let proxy = proxy::new()
 | |
|         .inbound(srv)
 | |
|         .run();
 | |
| 
 | |
|     let client = client::http1(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
| 
 | |
|     let versions = &[
 | |
|         "1.0",
 | |
|         // TODO: We may wish to enforce not translating eof bodies to chunked,
 | |
|         // even if the client is 1.1. However, there also benefits of translating:
 | |
|         // the client can reuse the connection, and delimited messages are much
 | |
|         // safer than those that end with the connection (as it's difficult to
 | |
|         // notice if a full response was received).
 | |
|         //
 | |
|         // Either way, hyper's server does not provide the ability to do this,
 | |
|         // so we cannot test for it at the moment.
 | |
|         //"1.1",
 | |
|     ];
 | |
| 
 | |
|     for v in versions {
 | |
|         let resp = client.request(
 | |
|             client
 | |
|                 .request_builder("/")
 | |
|                 .method("GET")
 | |
|         );
 | |
| 
 | |
|         assert_eq!(resp.status(), StatusCode::OK, "HTTP/{}", v);
 | |
|         assert!(!resp.headers().contains_key("transfer-encoding"), "HTTP/{} transfer-encoding", v);
 | |
|         assert!(!resp.headers().contains_key("content-length"), "HTTP/{} content-length", v);
 | |
| 
 | |
|         let body = resp.into_body()
 | |
|             .concat2()
 | |
|             .wait()
 | |
|             .expect("response body concat");
 | |
| 
 | |
|         assert_eq!(body, "body till eof", "HTTP/{} body", v);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_one_connection_per_host() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_empty_ok("/")
 | |
|         .run();
 | |
|     let proxy = proxy::new().inbound(srv).run();
 | |
| 
 | |
|     let client = client::http1(proxy.inbound, "foo.bar");
 | |
| 
 | |
|     let inbound = &proxy.inbound_server.as_ref()
 | |
|         .expect("no inbound server!");
 | |
| 
 | |
|     // Make a request with the header "Host: foo.bar". After the request, the
 | |
|     // server should have seen one connection.
 | |
|     let res1 = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "foo.bar")
 | |
|     );
 | |
|     assert_eq!(res1.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res1.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 1);
 | |
| 
 | |
|     // Another request with the same host. The proxy may reuse the connection.
 | |
|     let res1 = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "foo.bar")
 | |
|     );
 | |
|     assert_eq!(res1.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res1.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 1);
 | |
| 
 | |
|     // Make a request with a different Host header. This request must use a new
 | |
|     // connection.
 | |
|     let res2 = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "bar.baz"));
 | |
|     assert_eq!(res2.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res2.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 2);
 | |
| 
 | |
|     let res2 = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "bar.baz"));
 | |
|     assert_eq!(res2.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res2.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 2);
 | |
| 
 | |
|     // Make a request with a different Host header. This request must use a new
 | |
|     // connection.
 | |
|     let res3 = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "quuuux.com"));
 | |
|     assert_eq!(res3.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res3.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 3);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn http1_requests_without_host_have_unique_connections() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     let srv = server::http1()
 | |
|         .route_empty_ok("/")
 | |
|         .run();
 | |
|     let proxy = proxy::new().inbound(srv).run();
 | |
| 
 | |
|     let client = client::http1(proxy.inbound, "foo.bar");
 | |
| 
 | |
|     let inbound = &proxy.inbound_server.as_ref()
 | |
|         .expect("no inbound server!");
 | |
| 
 | |
|     // Make a request with no Host header and no authority in the request path.
 | |
|     let res = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "")
 | |
|     );
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 1);
 | |
| 
 | |
|     // Another request with no Host. The proxy must open a new connection
 | |
|     // for that request.
 | |
|     let res = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "")
 | |
|     );
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 2);
 | |
| 
 | |
|     // Make a request with a host header. It must also receive its
 | |
|     // own connection.
 | |
|     let res = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "foo.bar")
 | |
|     );
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 3);
 | |
| 
 | |
|     // Another request with no Host. The proxy must open a new connection
 | |
|     // for that request.
 | |
|     let res = client.request(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_11)
 | |
|         .header("host", "")
 | |
|     );
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
|     assert_eq!(res.version(), http::Version::HTTP_11);
 | |
|     assert_eq!(inbound.connections(), 4);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| #[cfg_attr(not(feature = "flaky_tests"), ignore)]
 | |
| fn retry_reconnect_errors() {
 | |
|     let _ = env_logger::try_init();
 | |
| 
 | |
|     // Used to delay `listen` in the server, to force connection refused errors.
 | |
|     let (tx, rx) = oneshot::channel();
 | |
| 
 | |
|     let srv = server::http2()
 | |
|         .route("/", "hello retry")
 | |
|         .delay_listen(rx.map_err(|_| ()));
 | |
|     let proxy = proxy::new().inbound(srv).run();
 | |
|     let client = client::http2(proxy.inbound, "transparency.test.svc.cluster.local");
 | |
|     let metrics = client::http1(proxy.metrics, "localhost");
 | |
| 
 | |
|     let fut = client.request_async(client.request_builder("/")
 | |
|         .version(http::Version::HTTP_2));
 | |
| 
 | |
|     // wait until metrics has seen our connection, this can be flaky depending on
 | |
|     // all the other threads currently running...
 | |
|     assert_contains!(
 | |
|         metrics.get("/metrics"),
 | |
|         "tcp_open_total{direction=\"inbound\",peer=\"src\"} 1"
 | |
|     );
 | |
| 
 | |
|     drop(tx); // start `listen` now
 | |
|     let res = fut.wait().expect("response");
 | |
|     assert_eq!(res.status(), http::StatusCode::OK);
 | |
| }
 |