Skip to content

Commit

Permalink
Support multiple addresses when resolving names, close #968
Browse files Browse the repository at this point in the history
  • Loading branch information
slandelle committed Sep 11, 2015
1 parent 6ca14a1 commit 2515d28
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 169 deletions.
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package org.asynchttpclient.channel;

import static org.asynchttpclient.util.AsyncHttpProviderUtils.getExplicitPort;
import static org.asynchttpclient.util.ProxyUtils.avoidProxy;

import java.net.InetSocketAddress;
import java.net.UnknownHostException;

import org.asynchttpclient.AsyncHandler;
import org.asynchttpclient.Request;
import org.asynchttpclient.handler.AsyncHandlerExtensions;
import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.uri.Uri;

public abstract class ChannelConnector {

protected final AsyncHandlerExtensions asyncHandlerExtensions;
protected final InetSocketAddress localAddress;
protected final InetSocketAddress[] remoteAddresses;
protected volatile int i = 0;

public ChannelConnector(Request request, ProxyServer proxy, boolean useProxy, AsyncHandler<?> asyncHandler) throws UnknownHostException {

this.asyncHandlerExtensions = asyncHandler instanceof AsyncHandlerExtensions ? (AsyncHandlerExtensions) asyncHandler : null;
NameResolution[] resolutions;
Uri uri = request.getUri();
int port = getExplicitPort(uri);

if (request.getInetAddress() != null) {
resolutions = new NameResolution[] { new NameResolution(request.getInetAddress()) };

} else if (!useProxy || avoidProxy(proxy, uri.getHost())) {
resolutions = request.getNameResolver().resolve(uri.getHost());

} else {
resolutions = request.getNameResolver().resolve(proxy.getHost());
port = proxy.getPort();
}

if (asyncHandlerExtensions != null)
asyncHandlerExtensions.onDnsResolved(resolutions);

remoteAddresses = new InetSocketAddress[resolutions.length];
for (int i = 0; i < resolutions.length; i ++) {
remoteAddresses[i] = new InetSocketAddress(resolutions[i].address, port);
}

if (request.getLocalAddress() != null) {
localAddress = new InetSocketAddress(request.getLocalAddress(), 0);

} else {
localAddress = null;
}
}

protected boolean pickNextRemoteAddress() {
i++;
return i < remoteAddresses.length;
}
}
32 changes: 32 additions & 0 deletions api/src/main/java/org/asynchttpclient/channel/NameResolution.java
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package org.asynchttpclient.channel;

import java.net.InetAddress;

public class NameResolution {

public static final long UNKNOWN_EXPIRATION = 0;

public final InetAddress address;
public final long expiration;

public NameResolution(InetAddress address) {
this(address, UNKNOWN_EXPIRATION);
}

public NameResolution(InetAddress address, long expiration) {
this.address = address;
this.expiration = expiration;
}
}
10 changes: 7 additions & 3 deletions api/src/main/java/org/asynchttpclient/channel/NameResolver.java
Expand Up @@ -17,15 +17,19 @@


public interface NameResolver { public interface NameResolver {


InetAddress resolve(String name) throws UnknownHostException; NameResolution[] resolve(String name) throws UnknownHostException;


enum JdkNameResolver implements NameResolver { enum JdkNameResolver implements NameResolver {


INSTANCE; INSTANCE;


@Override @Override
public InetAddress resolve(String name) throws UnknownHostException { public NameResolution[] resolve(String name) throws UnknownHostException {
return InetAddress.getByName(name); InetAddress[] addresses = InetAddress.getAllByName(name);
NameResolution[] resolutions = new NameResolution[addresses.length];
for (int i = 0; i < addresses.length; i++)
resolutions[i] = new NameResolution(addresses[i]);
return resolutions;
} }
} }
} }
Expand Up @@ -15,6 +15,7 @@
import java.net.InetAddress; import java.net.InetAddress;


import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHandler;
import org.asynchttpclient.channel.NameResolution;


/** /**
* This interface hosts new low level callback methods on {@link AsyncHandler}. * This interface hosts new low level callback methods on {@link AsyncHandler}.
Expand All @@ -30,11 +31,28 @@ public interface AsyncHandlerExtensions {
void onConnectionOpen(); void onConnectionOpen();


/** /**
* Notify the callback when a new connection was successfully opened. * Notify the callback after DNS resolution has completed.
*
* @param addresses the resolved addresses
*/
void onDnsResolved(NameResolution[] addresses);

/**
* Notify the callback after a successful connect
* *
* @param connection the connection * @param connection the connection
* @param address the connected addresses
*/ */
void onConnectionOpened(Object connection); void onConnectionSuccess(Object connection, InetAddress address);

/**
* Notify the callback after a failed connect.
* Might be called several times, or be followed by onConnectionSuccess
* when the name was resolved to multiple addresses.
*
* @param address the tentative addresses
*/
void onConnectionFailure(InetAddress address);


/** /**
* Notify the callback when trying to fetch a connection from the pool. * Notify the callback when trying to fetch a connection from the pool.
Expand Down Expand Up @@ -70,13 +88,6 @@ public interface AsyncHandlerExtensions {
*/ */
void onRetry(); void onRetry();


/**
* Notify the callback after DNS resolution has completed.
*
* @param address the resolved address
*/
void onDnsResolved(InetAddress address);

/** /**
* Notify the callback when the SSL handshake performed to establish an * Notify the callback when the SSL handshake performed to establish an
* HTTPS connection has been completed. * HTTPS connection has been completed.
Expand Down
30 changes: 15 additions & 15 deletions api/src/test/java/org/asynchttpclient/AsyncProvidersBasicTest.java
Expand Up @@ -16,6 +16,7 @@
package org.asynchttpclient; package org.asynchttpclient;


import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.asynchttpclient.test.EventCollectingHandler.*;
import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET;
import static org.asynchttpclient.test.TestUtils.findFreePort; import static org.asynchttpclient.test.TestUtils.findFreePort;
import static org.asynchttpclient.util.DateUtils.millisTime; import static org.asynchttpclient.util.DateUtils.millisTime;
Expand Down Expand Up @@ -46,8 +47,6 @@
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;


import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.AsyncHttpProviderConfig;
import org.asynchttpclient.AsyncHttpClientConfig.Builder; import org.asynchttpclient.AsyncHttpClientConfig.Builder;
import org.asynchttpclient.config.AsyncHttpClientConfigBean; import org.asynchttpclient.config.AsyncHttpClientConfigBean;
import org.asynchttpclient.cookie.Cookie; import org.asynchttpclient.cookie.Cookie;
Expand Down Expand Up @@ -1410,19 +1409,20 @@ public void testNewConnectionEventsFired() throws Exception {
client.executeRequest(request, handler).get(3, TimeUnit.SECONDS); client.executeRequest(request, handler).get(3, TimeUnit.SECONDS);
handler.waitForCompletion(3, TimeUnit.SECONDS); handler.waitForCompletion(3, TimeUnit.SECONDS);


List<String> expectedEvents = Arrays.asList( Object[] expectedEvents = new Object[] {
"ConnectionPool", CONNECTION_POOL_EVENT,
"ConnectionOpen", CONNECTION_OPEN_EVENT,
"DnsResolved", DNS_RESOLVED_EVENT,
"ConnectionOpened", CONNECTION_SUCCESS_EVENT,
"RequestSend", REQUEST_SEND_EVENT,
"HeadersWritten", HEADERS_WRITTEN_EVENT,
"StatusReceived", STATUS_RECEIVED_EVENT,
"HeadersReceived", HEADERS_RECEIVED_EVENT,
"ConnectionOffer", CONNECTION_OFFER_EVENT,
"Completed"); COMPLETED_EVENT

};
assertEquals(handler.firedEvents, expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray()));
assertEquals(handler.firedEvents.toArray(), expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray()));
} }
} }
} }
30 changes: 15 additions & 15 deletions api/src/test/java/org/asynchttpclient/BasicHttpsTest.java
Expand Up @@ -15,6 +15,7 @@
*/ */
package org.asynchttpclient; package org.asynchttpclient;


import static org.asynchttpclient.test.EventCollectingHandler.*;
import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE; import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE;
import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING; import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING;
import static org.asynchttpclient.test.TestUtils.createSSLContext; import static org.asynchttpclient.test.TestUtils.createSSLContext;
Expand All @@ -28,7 +29,6 @@
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;


import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
Expand Down Expand Up @@ -125,20 +125,20 @@ public void testNormalEventsFired() throws InterruptedException, TimeoutExceptio
client.preparePost(getTargetUrl()).setBody("whatever").execute(handler).get(3, TimeUnit.SECONDS); client.preparePost(getTargetUrl()).setBody("whatever").execute(handler).get(3, TimeUnit.SECONDS);
handler.waitForCompletion(3, TimeUnit.SECONDS); handler.waitForCompletion(3, TimeUnit.SECONDS);


List<String> expectedEvents = Arrays.asList( Object[] expectedEvents = new Object[] {
"ConnectionPool", CONNECTION_POOL_EVENT,
"ConnectionOpen", CONNECTION_OPEN_EVENT,
"DnsResolved", DNS_RESOLVED_EVENT,
"SslHandshakeCompleted", SSL_HANDSHAKE_COMPLETED_EVENT,
"ConnectionOpened", CONNECTION_SUCCESS_EVENT,
"RequestSend", REQUEST_SEND_EVENT,
"HeadersWritten", HEADERS_WRITTEN_EVENT,
"StatusReceived", STATUS_RECEIVED_EVENT,
"HeadersReceived", HEADERS_RECEIVED_EVENT,
"ConnectionOffer", CONNECTION_OFFER_EVENT,
"Completed"); COMPLETED_EVENT};


assertEquals(handler.firedEvents, expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray())); assertEquals(handler.firedEvents.toArray(), expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray()));
} }
} }
} }
Expand Up @@ -19,6 +19,7 @@
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull; import static org.testng.Assert.assertNull;
import static org.testng.Assert.fail; import static org.testng.Assert.fail;
import static org.asynchttpclient.test.EventCollectingHandler.*;


import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
Expand Down Expand Up @@ -285,17 +286,17 @@ public void testPooledEventsFired() throws Exception {
client.executeRequest(request, secondHandler).get(3, TimeUnit.SECONDS); client.executeRequest(request, secondHandler).get(3, TimeUnit.SECONDS);
secondHandler.waitForCompletion(3, TimeUnit.SECONDS); secondHandler.waitForCompletion(3, TimeUnit.SECONDS);


List<String> expectedEvents = Arrays.asList( Object[] expectedEvents = new Object[] {
"ConnectionPool", CONNECTION_POOL_EVENT,
"ConnectionPooled", CONNECTION_POOLED_EVENT,
"RequestSend", REQUEST_SEND_EVENT,
"HeadersWritten", HEADERS_WRITTEN_EVENT,
"StatusReceived", STATUS_RECEIVED_EVENT,
"HeadersReceived", HEADERS_RECEIVED_EVENT,
"ConnectionOffer", CONNECTION_OFFER_EVENT,
"Completed"); COMPLETED_EVENT};


assertEquals(secondHandler.firedEvents, expectedEvents, "Got " + Arrays.toString(secondHandler.firedEvents.toArray())); assertEquals(secondHandler.firedEvents.toArray(), expectedEvents, "Got " + Arrays.toString(secondHandler.firedEvents.toArray()));
} }
} }
} }

0 comments on commit 2515d28

Please sign in to comment.