BasicHttpProxyToHttpsTest.java
/*
* Copyright (c) 2016-2023 AsyncHttpClient Project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.asynchttpclient;
import io.github.artsok.RepeatedIfExceptionsTest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.asynchttpclient.Realm.AuthScheme;
import org.asynchttpclient.test.EchoHandler;
import org.eclipse.jetty.proxy.ConnectHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Future;
import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHENTICATE;
import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHORIZATION;
import static io.netty.handler.codec.http.HttpHeaderNames.USER_AGENT;
import static org.asynchttpclient.Dsl.asyncHttpClient;
import static org.asynchttpclient.Dsl.config;
import static org.asynchttpclient.Dsl.get;
import static org.asynchttpclient.Dsl.proxyServer;
import static org.asynchttpclient.Dsl.realm;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUserAgent;
import static org.asynchttpclient.test.TestUtils.addHttpConnector;
import static org.asynchttpclient.test.TestUtils.addHttpsConnector;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Test that validates that when having an HTTP proxy and trying to access an HTTPS
* through the proxy the proxy credentials and a custom user-agent (if set) should be passed during the CONNECT request.
*/
public class BasicHttpProxyToHttpsTest {
private static final Logger LOGGER = LoggerFactory.getLogger(BasicHttpProxyToHttpsTest.class);
private static final String CUSTOM_USER_AGENT = "custom-user-agent";
private int httpPort;
private int proxyPort;
private Server httpServer;
private Server proxy;
@BeforeEach
public void setUpGlobal() throws Exception {
// HTTP server
httpServer = new Server();
ServerConnector connector1 = addHttpsConnector(httpServer);
httpServer.setHandler(new EchoHandler());
httpServer.start();
httpPort = connector1.getLocalPort();
// proxy
proxy = new Server();
ServerConnector connector2 = addHttpConnector(proxy);
ConnectHandler connectHandler = new ConnectHandler() {
@Override
// This proxy receives a CONNECT request from the client before making the real request for the target host.
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) {
// If the userAgent of the CONNECT request is the same as the default userAgent,
// then the custom userAgent was not properly propagated and the test should fail.
String userAgent = request.getHeader(USER_AGENT.toString());
if (userAgent.equals(defaultUserAgent())) {
return false;
}
// If the authentication failed, the test should also fail.
String authorization = request.getHeader(PROXY_AUTHORIZATION.toString());
if (authorization == null) {
response.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
response.setHeader(PROXY_AUTHENTICATE.toString(), "Basic realm=\"Fake Realm\"");
return false;
}
if ("Basic am9obmRvZTpwYXNz".equals(authorization)) {
return true;
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
};
proxy.setHandler(connectHandler);
proxy.start();
proxyPort = connector2.getLocalPort();
LOGGER.info("Local HTTP Server (" + httpPort + "), Proxy (" + proxyPort + ") started successfully");
}
@AfterEach
public void tearDownGlobal() throws Exception {
httpServer.stop();
proxy.stop();
}
@RepeatedIfExceptionsTest(repeats = 5)
public void nonPreemptiveProxyAuthWithHttpsTarget() throws Exception {
try (AsyncHttpClient client = asyncHttpClient(config().setUseInsecureTrustManager(true))) {
String targetUrl = "https://localhost:" + httpPort + "/foo/bar";
Request request = get(targetUrl)
.setProxyServer(proxyServer("127.0.0.1", proxyPort).setRealm(realm(AuthScheme.BASIC, "johndoe", "pass")))
.setHeader("user-agent", CUSTOM_USER_AGENT)
// .setRealm(realm(AuthScheme.BASIC, "user", "passwd"))
.build();
Future<Response> responseFuture = client.executeRequest(request);
Response response = responseFuture.get();
assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK);
assertEquals("/foo/bar", response.getHeader("X-pathInfo"));
}
}
}