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.net.URI;
34  import java.util.Collections;
35  import java.util.Map;
36  
37  import org.apache.hc.client5.http.AuthenticationStrategy;
38  import org.apache.hc.client5.http.HttpRoute;
39  import org.apache.hc.client5.http.auth.AuthChallenge;
40  import org.apache.hc.client5.http.auth.AuthExchange;
41  import org.apache.hc.client5.http.auth.AuthScheme;
42  import org.apache.hc.client5.http.auth.AuthScope;
43  import org.apache.hc.client5.http.auth.ChallengeType;
44  import org.apache.hc.client5.http.auth.Credentials;
45  import org.apache.hc.client5.http.auth.CredentialsProvider;
46  import org.apache.hc.client5.http.auth.StandardAuthScheme;
47  import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
48  import org.apache.hc.client5.http.classic.ExecChain;
49  import org.apache.hc.client5.http.classic.ExecRuntime;
50  import org.apache.hc.client5.http.classic.methods.HttpGet;
51  import org.apache.hc.client5.http.classic.methods.HttpPost;
52  import org.apache.hc.client5.http.entity.EntityBuilder;
53  import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
54  import org.apache.hc.client5.http.impl.auth.BasicScheme;
55  import org.apache.hc.client5.http.impl.auth.NTLMScheme;
56  import org.apache.hc.client5.http.protocol.HttpClientContext;
57  import org.apache.hc.core5.http.ClassicHttpRequest;
58  import org.apache.hc.core5.http.ClassicHttpResponse;
59  import org.apache.hc.core5.http.EntityDetails;
60  import org.apache.hc.core5.http.HttpException;
61  import org.apache.hc.core5.http.HttpHeaders;
62  import org.apache.hc.core5.http.HttpHost;
63  import org.apache.hc.core5.http.HttpResponse;
64  import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
65  import org.apache.hc.core5.http.protocol.HttpContext;
66  import org.apache.hc.core5.http.protocol.HttpProcessor;
67  import org.junit.Assert;
68  import org.junit.Before;
69  import org.junit.Test;
70  import org.mockito.Mock;
71  import org.mockito.Mockito;
72  import org.mockito.MockitoAnnotations;
73  import org.mockito.invocation.InvocationOnMock;
74  import org.mockito.stubbing.Answer;
75  
76  @SuppressWarnings({"static-access"}) // test code
77  public class TestProtocolExec {
78  
79      @Mock
80      private HttpProcessor httpProcessor;
81      @Mock
82      private AuthenticationStrategy targetAuthStrategy;
83      @Mock
84      private AuthenticationStrategy proxyAuthStrategy;
85      @Mock
86      private ExecChain chain;
87      @Mock
88      private ExecRuntime execRuntime;
89  
90      private ProtocolExec protocolExec;
91      private HttpHost target;
92      private HttpHost proxy;
93  
94      @Before
95      public void setup() throws Exception {
96          MockitoAnnotations.initMocks(this);
97          protocolExec = new ProtocolExec(httpProcessor, targetAuthStrategy, proxyAuthStrategy);
98          target = new HttpHost("foo", 80);
99          proxy = new HttpHost("bar", 8888);
100     }
101 
102     @Test
103     public void testFundamentals() throws Exception {
104         final HttpRoute route = new HttpRoute(target);
105         final ClassicHttpRequest request = new HttpGet("/test");
106         final HttpClientContext context = HttpClientContext.create();
107 
108         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
109 
110         Mockito.when(chain.proceed(
111                 Mockito.<ClassicHttpRequest>any(),
112                 Mockito.<ExecChain.Scope>any())).thenReturn(response);
113 
114         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
115         protocolExec.execute(request, scope, chain);
116 
117         Mockito.verify(httpProcessor).process(request, null, context);
118         Mockito.verify(chain).proceed(request, scope);
119         Mockito.verify(httpProcessor).process(response, null, context);
120 
121         Assert.assertEquals(route, context.getHttpRoute());
122         Assert.assertSame(request, context.getRequest());
123         Assert.assertSame(response, context.getResponse());
124     }
125 
126     @Test
127     public void testUserInfoInRequestURI() throws Exception {
128         final HttpRoute route = new HttpRoute(new HttpHost("somehost", 8080));
129         final ClassicHttpRequest request = new HttpGet("http://somefella:secret@bar/test");
130         final HttpClientContext context = HttpClientContext.create();
131         context.setCredentialsProvider(new BasicCredentialsProvider());
132 
133         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
134         Mockito.when(chain.proceed(
135                 Mockito.<ClassicHttpRequest>any(),
136                 Mockito.<ExecChain.Scope>any())).thenReturn(response);
137 
138         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
139         protocolExec.execute(request, scope, chain);
140         Assert.assertEquals(new URI("http://bar/test"), request.getUri());
141         final CredentialsProvider credentialsProvider = context.getCredentialsProvider();
142         final Credentials creds = credentialsProvider.getCredentials(new AuthScope(null, "bar", -1, null, null), null);
143         Assert.assertNotNull(creds);
144         Assert.assertEquals("somefella", creds.getUserPrincipal().getName());
145     }
146 
147     @Test(expected = HttpException.class)
148     public void testPostProcessHttpException() throws Exception {
149         final HttpRoute route = new HttpRoute(target);
150         final ClassicHttpRequest request = new HttpGet("/test");
151         final HttpClientContext context = HttpClientContext.create();
152 
153         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
154 
155         Mockito.when(chain.proceed(
156                 Mockito.<ClassicHttpRequest>any(),
157                 Mockito.<ExecChain.Scope>any())).thenReturn(response);
158         Mockito.doThrow(new HttpException("Ooopsie")).when(httpProcessor).process(
159                 Mockito.same(response), Mockito.<EntityDetails>isNull(), Mockito.<HttpContext>any());
160         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
161         try {
162             protocolExec.execute(request, scope, chain);
163         } catch (final Exception ex) {
164             Mockito.verify(execRuntime).discardEndpoint();
165             throw ex;
166         }
167     }
168 
169     @Test(expected = IOException.class)
170     public void testPostProcessIOException() throws Exception {
171         final HttpRoute route = new HttpRoute(target);
172         final ClassicHttpRequest request = new HttpGet("/test");
173         final HttpClientContext context = HttpClientContext.create();
174 
175         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
176         Mockito.when(chain.proceed(
177                 Mockito.<ClassicHttpRequest>any(),
178                 Mockito.<ExecChain.Scope>any())).thenReturn(response);
179         Mockito.doThrow(new IOException("Ooopsie")).when(httpProcessor).process(
180                 Mockito.same(response), Mockito.<EntityDetails>isNull(), Mockito.<HttpContext>any());
181         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
182         try {
183             protocolExec.execute(request, scope, chain);
184         } catch (final Exception ex) {
185             Mockito.verify(execRuntime).discardEndpoint();
186             throw ex;
187         }
188     }
189 
190     @Test(expected = RuntimeException.class)
191     public void testPostProcessRuntimeException() throws Exception {
192         final HttpRoute route = new HttpRoute(target);
193         final ClassicHttpRequest request = new HttpGet("/test");
194         final HttpClientContext context = HttpClientContext.create();
195 
196         final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);
197         Mockito.when(chain.proceed(
198                 Mockito.<ClassicHttpRequest>any(),
199                 Mockito.<ExecChain.Scope>any())).thenReturn(response);
200         Mockito.doThrow(new RuntimeException("Ooopsie")).when(httpProcessor).process(
201                 Mockito.same(response), Mockito.<EntityDetails>isNull(), Mockito.<HttpContext>any());
202         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
203         try {
204             protocolExec.execute(request, scope, chain);
205         } catch (final Exception ex) {
206             Mockito.verify(execRuntime).discardEndpoint();
207             throw ex;
208         }
209     }
210 
211     @Test
212     public void testExecRequestRetryOnAuthChallenge() throws Exception {
213         final HttpRoute route = new HttpRoute(target);
214         final HttpClientContext context = new HttpClientContext();
215         final ClassicHttpRequest request = new HttpGet("http://foo/test");
216         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
217         response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
218         final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
219         response1.setEntity(EntityBuilder.create()
220                 .setStream(inStream1)
221                 .build());
222         final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
223         final InputStream inStream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
224         response2.setEntity(EntityBuilder.create()
225                 .setStream(inStream2)
226                 .build());
227 
228         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
229         credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
230         context.setCredentialsProvider(credentialsProvider);
231 
232         Mockito.when(chain.proceed(
233                 Mockito.same(request),
234                 Mockito.<ExecChain.Scope>any())).thenReturn(response1, response2);
235         Mockito.when(targetAuthStrategy.select(
236                 Mockito.eq(ChallengeType.TARGET),
237                 Mockito.<Map<String, AuthChallenge>>any(),
238                 Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
239         Mockito.when(execRuntime.isConnectionReusable()).thenReturn(true);
240 
241         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
242         final ClassicHttpResponse finalResponse = protocolExec.execute(request, scope, chain);
243         Mockito.verify(chain, Mockito.times(2)).proceed(request, scope);
244         Mockito.verify(inStream1).close();
245         Mockito.verify(inStream2, Mockito.never()).close();
246 
247         Assert.assertNotNull(finalResponse);
248         Assert.assertEquals(200, finalResponse.getCode());
249     }
250 
251     @Test
252     public void testExecEntityEnclosingRequestRetryOnAuthChallenge() throws Exception {
253         final HttpRoute route = new HttpRoute(target);
254         final ClassicHttpRequest request = new HttpGet("http://foo/test");
255         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
256         response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
257         final InputStream inStream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
258         response1.setEntity(EntityBuilder.create()
259                 .setStream(inStream1)
260                 .build());
261         final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
262         final InputStream inStream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
263         response2.setEntity(EntityBuilder.create()
264                 .setStream(inStream2)
265                 .build());
266 
267         final HttpClientContext context = new HttpClientContext();
268 
269         final AuthExchange authExchange = new AuthExchange();
270         authExchange.setState(AuthExchange.State.SUCCESS);
271         authExchange.select(new NTLMScheme());
272         context.setAuthExchange(target, authExchange);
273 
274         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
275         credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
276         context.setCredentialsProvider(credentialsProvider);
277 
278         Mockito.when(chain.proceed(
279                 Mockito.same(request),
280                 Mockito.<ExecChain.Scope>any())).thenReturn(response1, response2);
281 
282         Mockito.when(targetAuthStrategy.select(
283                 Mockito.eq(ChallengeType.TARGET),
284                 Mockito.<Map<String, AuthChallenge>>any(),
285                 Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
286 
287         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
288         final ClassicHttpResponse finalResponse = protocolExec.execute(request, scope, chain);
289         Mockito.verify(chain, Mockito.times(2)).proceed(request, scope);
290         Mockito.verify(execRuntime).disconnectEndpoint();
291         Mockito.verify(inStream2, Mockito.never()).close();
292 
293         Assert.assertNotNull(finalResponse);
294         Assert.assertEquals(200, finalResponse.getCode());
295         Assert.assertNotNull(authExchange.getAuthScheme());
296     }
297 
298     @Test
299     public void testExecEntityEnclosingRequest() throws Exception {
300         final HttpRoute route = new HttpRoute(target);
301         final HttpClientContext context = new HttpClientContext();
302         final HttpPost request = new HttpPost("http://foo/test");
303         final InputStream inStream0 = new ByteArrayInputStream(new byte[] {1, 2, 3});
304         request.setEntity(EntityBuilder.create()
305                 .setStream(inStream0)
306                 .build());
307         final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
308         response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.BASIC + " realm=test");
309         final InputStream inStream1 = new ByteArrayInputStream(new byte[] {1, 2, 3});
310         response1.setEntity(EntityBuilder.create()
311                 .setStream(inStream1)
312                 .build());
313 
314         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
315         credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
316         context.setCredentialsProvider(credentialsProvider);
317 
318         Mockito.when(chain.proceed(
319                 Mockito.same(request),
320                 Mockito.<ExecChain.Scope>any())).thenAnswer(new Answer<HttpResponse>() {
321 
322             @Override
323             public HttpResponse answer(final InvocationOnMock invocationOnMock) throws Throwable {
324                 final Object[] args = invocationOnMock.getArguments();
325                 final ClassicHttpRequest requestEE = (ClassicHttpRequest) args[0];
326                 requestEE.getEntity().writeTo(new ByteArrayOutputStream());
327                 return response1;
328             }
329 
330         });
331 
332         Mockito.when(targetAuthStrategy.select(
333                 Mockito.eq(ChallengeType.TARGET),
334                 Mockito.<Map<String, AuthChallenge>>any(),
335                 Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
336 
337         final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
338         final ClassicHttpResponse response = protocolExec.execute(request, scope, chain);
339         Assert.assertEquals(401, response.getCode());
340     }
341 
342 }