View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.impl.nio;
29  
30  import static org.junit.jupiter.api.Assertions.assertEquals;
31  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
32  import static org.junit.jupiter.api.Assertions.assertNotNull;
33  import static org.junit.jupiter.api.Assertions.assertTrue;
34  import static org.junit.jupiter.api.Assertions.fail;
35  import static org.mockito.ArgumentMatchers.any;
36  
37  import java.io.IOException;
38  import java.net.InetAddress;
39  import java.net.InetSocketAddress;
40  import java.util.Arrays;
41  import java.util.List;
42  import java.util.concurrent.CompletableFuture;
43  import java.util.concurrent.ExecutionException;
44  import java.util.concurrent.Future;
45  
46  import org.apache.hc.client5.http.DnsResolver;
47  import org.apache.hc.core5.concurrent.FutureCallback;
48  import org.apache.hc.core5.net.NamedEndpoint;
49  import org.apache.hc.core5.reactor.ConnectionInitiator;
50  import org.apache.hc.core5.reactor.IOSession;
51  import org.apache.hc.core5.util.Timeout;
52  import org.junit.jupiter.api.BeforeEach;
53  import org.junit.jupiter.api.Test;
54  import org.mockito.Mockito;
55  
56  class MultihomeIOSessionRequesterTest {
57  
58      private DnsResolver dnsResolver;
59      private ConnectionInitiator connectionInitiator;
60      private MultihomeIOSessionRequester sessionRequester;
61      private NamedEndpoint namedEndpoint;
62  
63      @BeforeEach
64      void setUp() {
65          dnsResolver = Mockito.mock(DnsResolver.class);
66          connectionInitiator = Mockito.mock(ConnectionInitiator.class);
67          namedEndpoint = Mockito.mock(NamedEndpoint.class);
68          sessionRequester = new MultihomeIOSessionRequester(dnsResolver);
69      }
70  
71      @Test
72      void testConnectWithMultipleAddresses() throws Exception {
73          final InetAddress address1 = InetAddress.getByAddress(new byte[]{10, 0, 0, 1});
74          final InetAddress address2 = InetAddress.getByAddress(new byte[]{10, 0, 0, 2});
75          final List<InetSocketAddress> remoteAddresses = Arrays.asList(
76                  new InetSocketAddress(address1, 8080),
77                  new InetSocketAddress(address2, 8080)
78          );
79  
80          Mockito.when(namedEndpoint.getHostName()).thenReturn("somehost");
81          Mockito.when(namedEndpoint.getPort()).thenReturn(8080);
82          Mockito.when(dnsResolver.resolve("somehost", 8080)).thenReturn(remoteAddresses);
83  
84          Mockito.when(connectionInitiator.connect(any(), any(), any(), any(), any(), any()))
85                  .thenAnswer(invocation -> {
86                      final FutureCallback<IOSession> callback = invocation.getArgument(5);
87                      // Simulate a failure for the first connection attempt
88                      final CompletableFuture<IOSession> future = new CompletableFuture<>();
89                      callback.failed(new IOException("Simulated connection failure"));
90                      future.completeExceptionally(new IOException("Simulated connection failure"));
91                      return future;
92                  });
93  
94          final Future<IOSession> future = sessionRequester.connect(
95                  connectionInitiator,
96                  namedEndpoint,
97                  null,
98                  Timeout.ofMilliseconds(500),
99                  null,
100                 null
101         );
102 
103         assertTrue(future.isDone());
104         try {
105             future.get();
106             fail("Expected ExecutionException");
107         } catch (final ExecutionException ex) {
108             assertInstanceOf(IOException.class, ex.getCause());
109             assertEquals("Simulated connection failure", ex.getCause().getMessage());
110         }
111     }
112 
113     @Test
114     void testConnectSuccessfulAfterRetries() throws Exception {
115         final InetAddress address1 = InetAddress.getByAddress(new byte[]{10, 0, 0, 1});
116         final InetAddress address2 = InetAddress.getByAddress(new byte[]{10, 0, 0, 2});
117         final List<InetSocketAddress> remoteAddresses = Arrays.asList(
118                 new InetSocketAddress(address1, 8080),
119                 new InetSocketAddress(address2, 8080)
120         );
121 
122         Mockito.when(namedEndpoint.getHostName()).thenReturn("somehost");
123         Mockito.when(namedEndpoint.getPort()).thenReturn(8080);
124         Mockito.when(dnsResolver.resolve("somehost", 8080)).thenReturn(remoteAddresses);
125 
126         Mockito.when(connectionInitiator.connect(any(), any(), any(), any(), any(), any()))
127                 .thenAnswer(invocation -> {
128                     final FutureCallback<IOSession> callback = invocation.getArgument(5);
129                     final InetSocketAddress remoteAddress = invocation.getArgument(1);
130                     final CompletableFuture<IOSession> future = new CompletableFuture<>();
131                     if (remoteAddress.getAddress().equals(address1)) {
132                         // Fail the first address
133                         callback.failed(new IOException("Simulated connection failure"));
134                         future.completeExceptionally(new IOException("Simulated connection failure"));
135                     } else {
136                         // Succeed for the second address
137                         final IOSession mockSession = Mockito.mock(IOSession.class);
138                         callback.completed(mockSession);
139                         future.complete(mockSession);
140                     }
141                     return future;
142                 });
143 
144         final Future<IOSession> future = sessionRequester.connect(
145                 connectionInitiator,
146                 namedEndpoint,
147                 null,
148                 Timeout.ofMilliseconds(500),
149                 null,
150                 null
151         );
152 
153         assertTrue(future.isDone());
154         try {
155             final IOSession session = future.get();
156             assertNotNull(session);
157         } catch (final ExecutionException ex) {
158             fail("Did not expect an ExecutionException", ex);
159         }
160     }
161 }