Issue #408 (Http client does not work on https with proxy)

Fixed by not considering authority-form targets to be URIs, so that
the request is correctly copied after a 407.
This commit is contained in:
Simone Bordet 2016-03-10 10:08:50 +01:00
parent e6c2c81bea
commit 4039f00bda
5 changed files with 70 additions and 8 deletions

View File

@ -26,6 +26,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
@ -171,8 +172,13 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION); copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION);
copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION); copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION);
newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult)) newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult));
.send(null);
Connection connection = (Connection)request.getAttributes().get(HttpRequest.CONNECTION_ATTRIBUTE);
if (connection != null)
connection.send(newRequest, null);
else
newRequest.send(null);
} }
catch (Throwable x) catch (Throwable x)
{ {

View File

@ -35,7 +35,6 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -105,7 +104,7 @@ public abstract class HttpConnection implements Connection
URI uri = request.getURI(); URI uri = request.getURI();
if (proxy != null && !HttpMethod.CONNECT.is(method) && uri != null) if (proxy != null && uri != null)
{ {
path = uri.toString(); path = uri.toString();
request.path(path); request.path(path);

View File

@ -146,6 +146,13 @@ public class HttpProxy extends ProxyConfiguration.Proxy
.idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS) .idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS)
.timeout(connectTimeout, TimeUnit.MILLISECONDS); .timeout(connectTimeout, TimeUnit.MILLISECONDS);
// In case the proxy replies with a 407, we want
// to use the same connection for resending the
// request (this time with the Proxy-Authorization
// header), so we save it as an attribute to be
// used to send the next request.
connect.attribute(HttpRequest.CONNECTION_ATTRIBUTE, connection);
connection.send(connect, result -> connection.send(connect, result ->
{ {
if (result.isFailed()) if (result.isFailed())

View File

@ -59,6 +59,7 @@ import org.eclipse.jetty.util.Fields;
public class HttpRequest implements Request public class HttpRequest implements Request
{ {
private static final URI NULL_URI = URI.create("null:0"); private static final URI NULL_URI = URI.create("null:0");
static final String CONNECTION_ATTRIBUTE = HttpRequest.class.getName() + ".connection";
private final HttpFields headers = new HttpFields(); private final HttpFields headers = new HttpFields();
private final Fields params = new Fields(true); private final Fields params = new Fields(true);
@ -171,8 +172,6 @@ public class HttpRequest implements Request
else else
{ {
String rawPath = uri.getRawPath(); String rawPath = uri.getRawPath();
if (uri.isOpaque())
rawPath = path;
if (rawPath == null) if (rawPath == null)
rawPath = ""; rawPath = "";
this.path = rawPath; this.path = rawPath;
@ -789,7 +788,7 @@ public class HttpRequest implements Request
URI result = newURI(path); URI result = newURI(path);
if (result == null) if (result == null)
return NULL_URI; return NULL_URI;
if (!result.isAbsolute() && !result.isOpaque()) if (!result.isAbsolute())
result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path); result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path);
return result; return result;
} }
@ -801,7 +800,8 @@ public class HttpRequest implements Request
// Handle specially the "OPTIONS *" case, since it is possible to create a URI from "*" (!). // Handle specially the "OPTIONS *" case, since it is possible to create a URI from "*" (!).
if ("*".equals(uri)) if ("*".equals(uri))
return null; return null;
return new URI(uri); URI result = new URI(uri);
return result.isOpaque() ? null : result;
} }
catch (URISyntaxException x) catch (URISyntaxException x)
{ {

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.proxy;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.Socket; import java.net.Socket;
import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -38,6 +39,7 @@ import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
@ -449,6 +451,54 @@ public class ForwardProxyTLSServerTest
} }
} }
@Test
public void testProxyAuthentication() throws Exception
{
startTLSServer(new ServerHandler());
String proxyRealm = "ProxyRealm";
startProxy(new ConnectHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
if (proxyAuth == null)
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + proxyRealm + "\"");
return;
}
super.handle(target, baseRequest, request, response);
}
});
HttpClient httpClient = new HttpClient(newSslContextFactory());
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
URI proxyURI = URI.create("https://localhost:" + proxyConnector.getLocalPort());
httpClient.getAuthenticationStore().addAuthentication(new BasicAuthentication(proxyURI, proxyRealm, "proxyUser", "proxyPassword"));
httpClient.start();
try
{
String body = "BODY";
ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.method(HttpMethod.GET)
.path("/echo")
.param("body", body)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
String content = response.getContentAsString();
Assert.assertEquals(body, content);
}
finally
{
httpClient.stop();
}
}
@Test @Test
@Ignore("External Proxy Server no longer stable enough for testing") @Ignore("External Proxy Server no longer stable enough for testing")
public void testExternalProxy() throws Exception public void testExternalProxy() throws Exception