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  package org.apache.hc.client5.http.impl.classic;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.util.Collections;
34  
35  import org.apache.hc.client5.http.AuthenticationStrategy;
36  import org.apache.hc.client5.http.HttpRoute;
37  import org.apache.hc.client5.http.auth.AuthExchange;
38  import org.apache.hc.client5.http.auth.AuthScope;
39  import org.apache.hc.client5.http.auth.ChallengeType;
40  import org.apache.hc.client5.http.auth.StandardAuthScheme;
41  import org.apache.hc.client5.http.classic.ExecChain;
42  import org.apache.hc.client5.http.classic.ExecRuntime;
43  import org.apache.hc.client5.http.classic.methods.HttpGet;
44  import org.apache.hc.client5.http.classic.methods.HttpPost;
45  import org.apache.hc.client5.http.entity.EntityBuilder;
46  import org.apache.hc.client5.http.impl.auth.BasicScheme;
47  import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
48  import org.apache.hc.client5.http.protocol.HttpClientContext;
49  import org.apache.hc.core5.http.ClassicHttpRequest;
50  import org.apache.hc.core5.http.ClassicHttpResponse;
51  import org.apache.hc.core5.http.HttpException;
52  import org.apache.hc.core5.http.HttpHeaders;
53  import org.apache.hc.core5.http.HttpHost;
54  import org.apache.hc.core5.http.HttpResponse;
55  import org.apache.hc.core5.http.ProtocolException;
56  import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
57  import org.junit.jupiter.api.Assertions;
58  import org.junit.jupiter.api.BeforeEach;
59  import org.junit.jupiter.api.Test;
60  import org.mockito.Mock;
61  import org.mockito.Mockito;
62  import org.mockito.MockitoAnnotations;
63  import org.mockito.stubbing.Answer;
64  
65  @SuppressWarnings({"static-access"}) // test code
66  public class TestProtocolExec {
67  
68      @Mock
69      private AuthenticationStrategy targetAuthStrategy;
70      @Mock
71      private AuthenticationStrategy proxyAuthStrategy;
72      @Mock
73      private ExecChain chain;
74      @Mock
75      private ExecRuntime execRuntime;
76  
77      private ProtocolExec protocolExec;
78      private HttpHost target;
79      private HttpHost proxy;
80  
81      @BeforeEach
82      public void setup() throws Exception {
83          MockitoAnnotations.openMocks(this);
84          protocolExec = new ProtocolExec(targetAuthStrategy, proxyAuthStrategy, null, true);
85          target = new HttpHost("foo", 80);
86          proxy = new HttpHost("bar", 8888);
87      }
88  
89      @Test
90      public void testUserInfoInRequestURI() throws Exception {
91          final HttpRoute route = new HttpRoute(new HttpHost("somehost", 8080));
92          final ClassicHttpRequest request = new HttpGet("http://somefella:secret@bar/test");
93          final HttpClientContext context = HttpClientContext.create();
94  
95          final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
96          Assertions.assertThrows(ProtocolException.class, () -> protocolExec.execute(request, scope, chain));
97      }
98  
99      @Test
100     public void testPostProcessHttpException() throws Exception {
101         final HttpRoute route = new HttpRoute(target);
102         final ClassicHttpRequest request = new HttpGet("/test");
103         final HttpClientContext context = HttpClientContext.create();
104 
105         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
106 
107         Mockito.when(chain.proceed(
108                 Mockito.any(),
109                 Mockito.any())).thenReturn(response);
110         Mockito.doThrow(new HttpException("Ooopsie")).when(chain).proceed(Mockito.any(), Mockito.any());
111         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
112         Assertions.assertThrows(HttpException.class, () ->
113                 protocolExec.execute(request, scope, chain));
114         Mockito.verify(execRuntime).discardEndpoint();
115     }
116 
117     @Test
118     public void testPostProcessIOException() throws Exception {
119         final HttpRoute route = new HttpRoute(target);
120         final ClassicHttpRequest request = new HttpGet("/test");
121         final HttpClientContext context = HttpClientContext.create();
122 
123         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
124         Mockito.when(chain.proceed(
125                 Mockito.any(),
126                 Mockito.any())).thenReturn(response);
127         Mockito.doThrow(new IOException("Ooopsie")).when(chain).proceed(Mockito.any(), Mockito.any());
128         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
129         Assertions.assertThrows(IOException.class, () ->
130                 protocolExec.execute(request, scope, chain));
131         Mockito.verify(execRuntime).discardEndpoint();
132     }
133 
134     @Test
135     public void testPostProcessRuntimeException() throws Exception {
136         final HttpRoute route = new HttpRoute(target);
137         final ClassicHttpRequest request = new HttpGet("/test");
138         final HttpClientContext context = HttpClientContext.create();
139 
140         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
141         Mockito.when(chain.proceed(
142                 Mockito.any(),
143                 Mockito.any())).thenReturn(response);
144         Mockito.doThrow(new RuntimeException("Ooopsie")).when(chain).proceed(Mockito.any(), Mockito.any());
145         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
146         Assertions.assertThrows(RuntimeException.class, () ->
147                 protocolExec.execute(request, scope, chain));
148         Mockito.verify(execRuntime).discardEndpoint();
149     }
150 
151     @Test
152     public void testExecRequestRetryOnAuthChallenge() throws Exception {
153         final HttpRoute route = new HttpRoute(target);
154         final HttpClientContext context = new HttpClientContext();
155         final ClassicHttpRequest request = new HttpGet("http://foo/test");
156         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
157         response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
158         final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
159         response1.setEntity(EntityBuilder.create()
160                 .setStream(inStream1)
161                 .build());
162         final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
163         final InputStream inStream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
164         response2.setEntity(EntityBuilder.create()
165                 .setStream(inStream2)
166                 .build());
167 
168         context.setCredentialsProvider(CredentialsProviderBuilder.create()
169                 .add(new AuthScope(target), "user", "pass".toCharArray())
170                 .build());
171 
172         Mockito.when(chain.proceed(
173                 Mockito.same(request),
174                 Mockito.any())).thenReturn(response1, response2);
175         Mockito.when(targetAuthStrategy.select(
176                 Mockito.eq(ChallengeType.TARGET),
177                 Mockito.any(),
178                 Mockito.<HttpClientContext>any())).thenReturn(Collections.singletonList(new BasicScheme()));
179         Mockito.when(execRuntime.isConnectionReusable()).thenReturn(true);
180 
181         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
182         final ClassicHttpResponse finalResponse = protocolExec.execute(request, scope, chain);
183         Mockito.verify(chain, Mockito.times(2)).proceed(request, scope);
184         Mockito.verify(inStream1).close();
185         Mockito.verify(inStream2, Mockito.never()).close();
186 
187         Assertions.assertNotNull(finalResponse);
188         Assertions.assertEquals(200, finalResponse.getCode());
189     }
190 
191     @Test
192     public void testExecEntityEnclosingRequestRetryOnAuthChallenge() throws Exception {
193         final HttpRoute route = new HttpRoute(target);
194         final ClassicHttpRequest request = new HttpGet("http://foo/test");
195         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
196         response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
197         final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
198         response1.setEntity(EntityBuilder.create()
199                 .setStream(inStream1)
200                 .build());
201         final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
202         final InputStream inStream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
203         response2.setEntity(EntityBuilder.create()
204                 .setStream(inStream2)
205                 .build());
206 
207         final HttpClientContext context = new HttpClientContext();
208 
209         final AuthExchange authExchange = new AuthExchange();
210         authExchange.setState(AuthExchange.State.SUCCESS);
211         authExchange.select(new BasicScheme() {
212 
213             @Override
214             public boolean isConnectionBased() {
215                 return true;
216             }
217 
218         });
219         context.setAuthExchange(target, authExchange);
220 
221         context.setCredentialsProvider(CredentialsProviderBuilder.create()
222                 .add(new AuthScope(target), "user", "pass".toCharArray())
223                 .build());
224 
225         Mockito.when(chain.proceed(
226                 Mockito.same(request),
227                 Mockito.any())).thenReturn(response1, response2);
228 
229         Mockito.when(targetAuthStrategy.select(
230                 Mockito.eq(ChallengeType.TARGET),
231                 Mockito.any(),
232                 Mockito.<HttpClientContext>any())).thenReturn(Collections.singletonList(new BasicScheme()));
233 
234         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
235         final ClassicHttpResponse finalResponse = protocolExec.execute(request, scope, chain);
236         Mockito.verify(chain, Mockito.times(2)).proceed(request, scope);
237         Mockito.verify(execRuntime).disconnectEndpoint();
238         Mockito.verify(inStream2, Mockito.never()).close();
239 
240         Assertions.assertNotNull(finalResponse);
241         Assertions.assertEquals(200, finalResponse.getCode());
242         Assertions.assertNotNull(authExchange.getAuthScheme());
243     }
244 
245     @Test
246     public void testExecEntityEnclosingRequest() throws Exception {
247         final HttpRoute route = new HttpRoute(target);
248         final HttpClientContext context = new HttpClientContext();
249         final HttpPost request = new HttpPost("http://foo/test");
250         final InputStream inStream0 = new ByteArrayInputStream(new byte[] {1, 2, 3});
251         request.setEntity(EntityBuilder.create()
252                 .setStream(inStream0)
253                 .build());
254         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
255         response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
256         final InputStream inStream1 = new ByteArrayInputStream(new byte[] {1, 2, 3});
257         response1.setEntity(EntityBuilder.create()
258                 .setStream(inStream1)
259                 .build());
260 
261         context.setCredentialsProvider(CredentialsProviderBuilder.create()
262                 .add(new AuthScope(target), "user", "pass".toCharArray())
263                 .build());
264 
265         Mockito.when(chain.proceed(
266                 Mockito.same(request),
267                 Mockito.any())).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
268                     final Object[] args = invocationOnMock.getArguments();
269                     final ClassicHttpRequest requestEE = (ClassicHttpRequest) args[0];
270                     requestEE.getEntity().writeTo(new ByteArrayOutputStream());
271                     return response1;
272                 });
273 
274         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
275         final ClassicHttpResponse response = protocolExec.execute(request, scope, chain);
276         Assertions.assertEquals(401, response.getCode());
277     }
278 
279 }