Added HTTP routing support class; simplified HttpRoutePlanner API

This commit is contained in:
Oleg Kalnichevski 2018-01-03 15:36:27 +01:00
parent db4b6db79f
commit b45b72ef77
14 changed files with 159 additions and 136 deletions

View File

@ -38,6 +38,6 @@ public interface SchemePortResolver {
/**
* Returns the actual port for the host based on the protocol scheme.
*/
int resolve(HttpHost host) throws UnsupportedSchemeException;
int resolve(HttpHost host);
}

View File

@ -27,7 +27,6 @@
package org.apache.hc.client5.http.impl;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.UnsupportedSchemeException;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.HttpHost;
@ -44,7 +43,7 @@ public class DefaultSchemePortResolver implements SchemePortResolver {
public static final DefaultSchemePortResolver INSTANCE = new DefaultSchemePortResolver();
@Override
public int resolve(final HttpHost host) throws UnsupportedSchemeException {
public int resolve(final HttpHost host) {
Args.notNull(host, "HTTP host");
final int port = host.getPort();
if (port > 0) {
@ -56,7 +55,7 @@ public class DefaultSchemePortResolver implements SchemePortResolver {
} else if (name.equalsIgnoreCase("https")) {
return 443;
} else {
throw new UnsupportedSchemeException(name + " protocol is not supported");
return -1;
}
}

View File

@ -39,8 +39,8 @@ import org.apache.hc.client5.http.cookie.CookieSpecProvider;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http2.nio.pool.H2ConnPool;
@ -77,8 +77,7 @@ class InternalHttp2AsyncClient extends InternalAbstractHttpAsyncClient {
@Override
HttpRoute determineRoute(final HttpRequest request, final HttpClientContext clientContext) throws HttpException {
final HttpHost target = routePlanner.determineTargetHost(request, clientContext);
final HttpRoute route = routePlanner.determineRoute(target, clientContext);
final HttpRoute route = routePlanner.determineRoute(RoutingSupport.determineHost(request), clientContext);
if (route.isTunnelled()) {
throw new HttpException("HTTP/2 tunneling not supported");
}

View File

@ -40,8 +40,8 @@ import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.ProtocolVersion;
@ -83,8 +83,7 @@ class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClient {
@Override
HttpRoute determineRoute(final HttpRequest request, final HttpClientContext clientContext) throws HttpException {
final HttpHost target = routePlanner.determineTargetHost(request, clientContext);
final HttpRoute route = routePlanner.determineRoute(target, clientContext);
final HttpRoute route = routePlanner.determineRoute(RoutingSupport.determineHost(request), clientContext);
final ProtocolVersion protocolVersion = clientContext.getProtocolVersion();
if (route.isTunnelled() && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) {
throw new HttpException("HTTP/2 tunneling not supported");

View File

@ -36,10 +36,10 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.UnsupportedSchemeException;
import org.apache.hc.client5.http.auth.AuthCache;
import org.apache.hc.client5.http.auth.AuthScheme;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.HttpHost;
@ -73,27 +73,13 @@ public class BasicAuthCache implements AuthCache {
public BasicAuthCache(final SchemePortResolver schemePortResolver) {
super();
this.map = new ConcurrentHashMap<>();
this.schemePortResolver = schemePortResolver != null ? schemePortResolver :
DefaultSchemePortResolver.INSTANCE;
this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
}
public BasicAuthCache() {
this(null);
}
protected HttpHost getKey(final HttpHost host) {
if (host.getPort() <= 0) {
final int port;
try {
port = schemePortResolver.resolve(host);
} catch (final UnsupportedSchemeException ignore) {
return host;
}
return new HttpHost(host.getHostName(), port, host.getSchemeName());
}
return host;
}
@Override
public void put(final HttpHost host, final AuthScheme authScheme) {
Args.notNull(host, "HTTP host");
@ -106,7 +92,8 @@ public class BasicAuthCache implements AuthCache {
try (final ObjectOutputStream out = new ObjectOutputStream(buf)) {
out.writeObject(authScheme);
}
this.map.put(getKey(host), buf.toByteArray());
final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
this.map.put(key, buf.toByteArray());
} catch (final IOException ex) {
if (log.isWarnEnabled()) {
log.warn("Unexpected I/O error while serializing auth scheme", ex);
@ -122,7 +109,8 @@ public class BasicAuthCache implements AuthCache {
@Override
public AuthScheme get(final HttpHost host) {
Args.notNull(host, "HTTP host");
final byte[] bytes = this.map.get(getKey(host));
final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
final byte[] bytes = this.map.get(key);
if (bytes != null) {
try {
final ByteArrayInputStream buf = new ByteArrayInputStream(bytes);
@ -147,7 +135,8 @@ public class BasicAuthCache implements AuthCache {
@Override
public void remove(final HttpHost host) {
Args.notNull(host, "HTTP host");
this.map.remove(getKey(host));
final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
this.map.remove(key);
}
@Override

View File

@ -46,6 +46,7 @@ import org.apache.hc.client5.http.impl.ExecSupport;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
@ -111,7 +112,7 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
final HttpHost host,
final HttpRequest request,
final HttpContext context) throws HttpException {
final HttpHost target = host != null ? host : this.routePlanner.determineTargetHost(request, context);
final HttpHost target = host != null ? host : RoutingSupport.determineHost(request);
return this.routePlanner.determineRoute(target, context);
}

View File

@ -33,6 +33,7 @@ import java.io.InterruptedIOException;
import org.apache.hc.client5.http.CancellableAware;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.config.Configurable;
import org.apache.hc.client5.http.config.RequestConfig;
@ -41,6 +42,7 @@ import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.protocol.RequestClientConnControl;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
@ -77,6 +79,7 @@ public class MinimalHttpClient extends CloseableHttpClient {
private final HttpClientConnectionManager connManager;
private final ConnectionReuseStrategy reuseStrategy;
private final SchemePortResolver schemePortResolver;
private final HttpRequestExecutor requestExecutor;
private final HttpProcessor httpProcessor;
@ -84,6 +87,7 @@ public class MinimalHttpClient extends CloseableHttpClient {
super();
this.connManager = Args.notNull(connManager, "HTTP connection manager");
this.reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE;
this.schemePortResolver = DefaultSchemePortResolver.INSTANCE;
this.requestExecutor = new HttpRequestExecutor(this.reuseStrategy);
this.httpProcessor = new DefaultHttpProcessor(
new RequestContent(),
@ -116,11 +120,7 @@ public class MinimalHttpClient extends CloseableHttpClient {
clientContext.setRequestConfig(config);
}
final HttpRoute route = new HttpRoute(target.getPort() > 0 ? target : new HttpHost(
target.getHostName(),
DefaultSchemePortResolver.INSTANCE.resolve(target),
target.getSchemeName()));
final HttpRoute route = new HttpRoute(RoutingSupport.normalize(target, schemePortResolver));
final ExecRuntime execRuntime = new ExecRuntimeImpl(log, connManager, requestExecutor,
request instanceof CancellableAware ? (CancellableAware) request : null);
try {

View File

@ -34,10 +34,10 @@ import java.util.concurrent.Future;
import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.UnsupportedSchemeException;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.nio.AsyncClientConnectionOperator;
import org.apache.hc.client5.http.nio.ManagedAsyncClientConnection;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.concurrent.ComplexFuture;
import org.apache.hc.core5.concurrent.FutureCallback;
@ -81,14 +81,7 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
Args.notNull(connectionInitiator, "Connection initiator");
Args.notNull(host, "Host");
final ComplexFuture<ManagedAsyncClientConnection> future = new ComplexFuture<>(callback);
final HttpHost remoteEndpoint;
try {
remoteEndpoint = host.getPort() > 0 ? host :
new HttpHost(host.getHostName(), schemePortResolver.resolve(host), host.getSchemeName());
} catch (final UnsupportedSchemeException ex) {
future.failed(ex);
return future;
}
final HttpHost remoteEndpoint = RoutingSupport.normalize(host, schemePortResolver);
final InetAddress remoteAddress = host.getAddress();
final TlsStrategy tlsStrategy = tlsStrategyLookup != null ? tlsStrategyLookup.lookup(host.getSchemeName()) : null;
final Future<IOSession> sessionFuture = sessionRequester.connect(

View File

@ -31,19 +31,17 @@ import java.net.InetAddress;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.UnsupportedSchemeException;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIAuthority;
/**
* Default implementation of an {@link HttpRoutePlanner}. It will not make use of
@ -58,8 +56,7 @@ public class DefaultRoutePlanner implements HttpRoutePlanner {
public DefaultRoutePlanner(final SchemePortResolver schemePortResolver) {
super();
this.schemePortResolver = schemePortResolver != null ? schemePortResolver :
DefaultSchemePortResolver.INSTANCE;
this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
}
@Override
@ -73,19 +70,9 @@ public class DefaultRoutePlanner implements HttpRoutePlanner {
if (proxy == null) {
proxy = determineProxy(host, context);
}
final HttpHost target;
if (host.getPort() <= 0) {
try {
target = new HttpHost(
host.getHostName(),
this.schemePortResolver.resolve(host),
host.getSchemeName());
} catch (final UnsupportedSchemeException ex) {
throw new HttpException(ex.getMessage());
}
} else {
target = host;
final HttpHost target = RoutingSupport.normalize(host, schemePortResolver);
if (target.getPort() < 0) {
throw new ProtocolException("Unroutable protocol scheme: " + target);
}
final boolean secure = target.getSchemeName().equalsIgnoreCase("https");
if (proxy == null) {
@ -95,20 +82,6 @@ public class DefaultRoutePlanner implements HttpRoutePlanner {
}
}
@Override
public final HttpHost determineTargetHost(final HttpRequest request, final HttpContext context) throws HttpException {
final URIAuthority authority = request.getAuthority();
if (authority != null) {
final String scheme = request.getScheme();
if (scheme == null) {
throw new ProtocolException("Protocol scheme is not specified");
}
return new HttpHost(authority, scheme);
} else {
return null;
}
}
/**
* This implementation returns null.
*

View File

@ -30,7 +30,6 @@ package org.apache.hc.client5.http.routing;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.protocol.HttpContext;
/**
@ -60,17 +59,4 @@ public interface HttpRoutePlanner {
*/
HttpRoute determineRoute(HttpHost target, HttpContext context) throws HttpException;
/**
* Determines the target host for the given request.
*
* @param request the request to be executed
* @param context the context to use for the subsequent execution.
* Implementations may accept {@code null}.
*
* @return the route that the request should take
*
* @throws HttpException in case of a problem
*/
HttpHost determineTargetHost(HttpRequest request, HttpContext context) throws HttpException;
}

View File

@ -0,0 +1,68 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.routing;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.net.URIAuthority;
public final class RoutingSupport {
public static HttpHost determineHost(final HttpRequest request) throws HttpException {
if (request == null) {
return null;
}
final URIAuthority authority = request.getAuthority();
if (authority != null) {
final String scheme = request.getScheme();
if (scheme == null) {
throw new ProtocolException("Protocol scheme is not specified");
}
return new HttpHost(authority, scheme);
} else {
return null;
}
}
public static HttpHost normalize(final HttpHost host, final SchemePortResolver schemePortResolver) {
if (host == null) {
return null;
}
if (host.getPort() < 0) {
final int port = (schemePortResolver != null ? schemePortResolver: DefaultSchemePortResolver.INSTANCE).resolve(host);
if (port > 0) {
return new HttpHost(host.getHostName(), port, host.getSchemeName());
}
}
return host;
}
}

View File

@ -27,12 +27,10 @@
package org.apache.hc.client5.http.impl.auth;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.auth.AuthScheme;
import org.apache.hc.core5.http.HttpHost;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
/**
* Unit tests for {@link BasicAuthCache}.
@ -67,24 +65,6 @@ public class TestBasicAuthCache {
Assert.assertNull(cache.get(new HttpHost("localhost", 80)));
}
@Test
public void testGetKey() throws Exception {
final BasicAuthCache cache = new BasicAuthCache();
final HttpHost target = new HttpHost("localhost", 443, "https");
Assert.assertSame(target, cache.getKey(target));
Assert.assertEquals(target, cache.getKey(new HttpHost("localhost", -1, "https")));
}
@Test
public void testGetKeyWithSchemeRegistry() throws Exception {
final SchemePortResolver schemePortResolver = Mockito.mock(SchemePortResolver.class);
final BasicAuthCache cache = new BasicAuthCache(schemePortResolver);
Mockito.when(schemePortResolver.resolve(new HttpHost("localhost", -1, "https"))).thenReturn(443);
final HttpHost target = new HttpHost("localhost", 443, "https");
Assert.assertSame(target, cache.getKey(target));
Assert.assertEquals(target, cache.getKey(new HttpHost("localhost", -1, "https")));
}
@Test
public void testStoreNonserializable() throws Exception {
final BasicAuthCache cache = new BasicAuthCache();

View File

@ -27,20 +27,14 @@
package org.apache.hc.client5.http.impl.routing;
import java.net.URI;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIAuthority;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -109,24 +103,4 @@ public class TestDefaultRoutePlanner {
routePlanner.determineRoute(null, context);
}
@Test
public void testDetermineHost() throws Exception {
final HttpContext context = new BasicHttpContext();
final HttpRequest request1 = new BasicHttpRequest("GET", "/");
final HttpHost host1 = routePlanner.determineTargetHost(request1, context);
Assert.assertThat(host1, CoreMatchers.nullValue());
final HttpRequest request2 = new BasicHttpRequest("GET", new URI("https://somehost:8443/"));
final HttpHost host2 = routePlanner.determineTargetHost(request2, context);
Assert.assertThat(host2, CoreMatchers.equalTo(new HttpHost("somehost", 8443, "https")));
}
@Test(expected = ProtocolException.class)
public void testDetermineHostMissingScheme() throws Exception {
final HttpContext context = new BasicHttpContext();
final HttpRequest request1 = new BasicHttpRequest("GET", "/");
request1.setAuthority(new URIAuthority("host"));
routePlanner.determineTargetHost(request1, context);
}
}

View File

@ -0,0 +1,62 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.routing;
import java.net.URI;
import org.apache.hc.client5.http.routing.RoutingSupport;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.net.URIAuthority;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Test;
public class TestRoutingSupport {
@Test
public void testDetermineHost() throws Exception {
final HttpRequest request1 = new BasicHttpRequest("GET", "/");
final HttpHost host1 = RoutingSupport.determineHost(request1);
Assert.assertThat(host1, CoreMatchers.nullValue());
final HttpRequest request2 = new BasicHttpRequest("GET", new URI("https://somehost:8443/"));
final HttpHost host2 = RoutingSupport.determineHost(request2);
Assert.assertThat(host2, CoreMatchers.equalTo(new HttpHost("somehost", 8443, "https")));
}
@Test(expected = ProtocolException.class)
public void testDetermineHostMissingScheme() throws Exception {
final HttpRequest request1 = new BasicHttpRequest("GET", "/");
request1.setAuthority(new URIAuthority("host"));
RoutingSupport.determineHost(request1);
}
}