View Javadoc

1   package com.atlassian.sal.core.net;
2   
3   import java.io.IOException;
4   import java.net.SocketException;
5   import java.util.concurrent.atomic.AtomicInteger;
6   
7   import com.atlassian.sal.api.net.Request.MethodType;
8   import com.atlassian.sal.api.net.ResponseConnectTimeoutException;
9   import com.atlassian.sal.api.net.ResponseException;
10  import com.atlassian.sal.api.net.ResponseHandler;
11  import com.atlassian.sal.api.net.ResponseProtocolException;
12  import com.atlassian.sal.api.net.ResponseReadTimeoutException;
13  import com.atlassian.sal.api.net.ResponseStatusException;
14  import com.atlassian.sal.api.net.ResponseTransportException;
15  import com.atlassian.sal.api.user.UserManager;
16  import com.atlassian.sal.core.net.auth.HttpClientAuthenticator;
17  import com.atlassian.sal.core.trusted.CertificateFactory;
18  import com.atlassian.security.auth.trustedapps.EncryptedCertificate;
19  
20  import org.apache.commons.httpclient.ConnectTimeoutException;
21  import org.apache.commons.httpclient.Header;
22  import org.apache.commons.httpclient.HostConfiguration;
23  import org.apache.commons.httpclient.HttpClient;
24  import org.apache.commons.httpclient.HttpException;
25  import org.apache.commons.httpclient.HttpMethod;
26  import org.apache.commons.httpclient.NoHttpResponseException;
27  import org.apache.commons.httpclient.methods.GetMethod;
28  import org.apache.commons.httpclient.methods.PostMethod;
29  import org.apache.commons.httpclient.methods.PutMethod;
30  import org.apache.commons.httpclient.params.HttpClientParams;
31  import org.easymock.classextension.EasyMock;
32  import org.easymock.classextension.IMocksControl;
33  import org.junit.After;
34  import org.junit.Test;
35  import org.junit.runner.RunWith;
36  import org.mockito.Mock;
37  import org.mockito.Mockito;
38  import org.mockito.runners.MockitoJUnitRunner;
39  
40  import junit.framework.TestCase;
41  
42  import static org.easymock.EasyMock.isA;
43  import static org.hamcrest.Matchers.hasItemInArray;
44  import static org.junit.Assert.assertThat;
45  import static org.mockito.Mockito.mock;
46  import static org.mockito.Mockito.times;
47  import static org.mockito.Mockito.when;
48  
49  @RunWith (MockitoJUnitRunner.class)
50  public class TestHttpClientRequest extends TestCase
51  {
52      @Mock
53      private CertificateFactory certificateFactory;
54      @Mock
55      private UserManager userManager;
56      @Mock
57      private HttpClient httpClient;
58      @Mock
59      private ResponseHandler responseHandler;
60      @Mock
61      private PutMethod putMethod;
62      @Mock
63      private PostMethod postMethod;
64      @Mock
65      private GetMethod getMethod;
66      @Mock
67      private EncryptedCertificate encryptedCertificate;
68  
69  
70      @After
71      public void tearDown()
72      {
73          System.clearProperty(HttpClientParams.MAX_REDIRECTS);
74      }
75  
76      @Test
77      public void testAuthentication() throws IOException, ResponseException
78      {
79          final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
80          final HttpClient mockHttpClient = httpClientMockControl.createMock(HttpClient.class);
81          httpClientMockControl.replay();
82  
83  
84          // counter how many time was authentication called
85          final AtomicInteger authenticatorCounter = new AtomicInteger(0);
86  
87          // lets create new GET request to http://url
88          HttpClientRequest request = new HttpClientRequest(mockHttpClient, MethodType.GET, "http://url",
89                  mock(CertificateFactory.class), mock(UserManager.class));
90  
91          // this is our authenticator
92          final HttpClientAuthenticator authenticator = new HttpClientAuthenticator()
93          {
94              public void process(HttpClient httpClient, HttpMethod method)
95              {
96                  assertEquals("It should use mockClient", httpClient, mockHttpClient);
97                  assertTrue("We asked it for GetMethod", method instanceof GetMethod);
98                  authenticatorCounter.addAndGet(1);
99              }
100         };
101 
102         // lets add 2 authenticator to the request
103         request.addAuthentication(authenticator);
104         request.addAuthentication(authenticator);
105 
106         // and we are ready to execute the request
107         request.execute(EasyMock.createMock(ResponseHandler.class));
108 
109         // and assert that authenticators were used
110         assertEquals("Two authenticator should be called.", 2, authenticatorCounter.intValue());
111     }
112 
113     @Test
114     public void verifyThatTrustedAuthentcationUsesAbsoluteUrl() throws ResponseException
115     {
116         // the url of interest
117         String targetUrl = "http://url/abc";
118         // create a certificate
119         String id = "1";
120         when(encryptedCertificate.getID()).thenReturn(id);
121 
122         // make sure that certificate is returned for the correct url
123         when(certificateFactory.createCertificate("barry", targetUrl)).thenReturn(encryptedCertificate);
124 
125         // build the request
126         HttpClientRequestFactory httpClientRequestFactory = new HttpClientRequestFactory(certificateFactory, userManager);
127         HttpClientRequest request = httpClientRequestFactory.createRequest(MethodType.GET, targetUrl);
128 
129         // add the trusted authenticaTION
130         request.addTrustedTokenAuthentication("barry");
131 
132         // get the trusted authenticaTOR
133         HttpClientAuthenticator httpClientAuthenticator = request.getAuthenticators().get(0);
134 
135         // verify that applying the authenticator applies the trusted headers
136         HttpMethod httpMethod = new GetMethod();
137         httpClientAuthenticator.process(httpClient, httpMethod);
138 
139         // look for a sample trusted header.
140         assertThat(httpMethod.getRequestHeaders(), hasItemInArray(new Header("X-Seraph-Trusted-App-ID", id)));
141     }
142 
143     @Test(expected = IllegalArgumentException.class)
144     public void verifyThatRelativeUrlCannotBeUsed() throws ResponseException
145     {
146         // the url of interest
147         String targetUrl = "/abc";
148         // create a certificate
149         String id = "1";
150         when(encryptedCertificate.getID()).thenReturn(id);
151 
152         // make sure that certificate is returned for the correct url
153         when(certificateFactory.createCertificate("barry", targetUrl)).thenReturn(encryptedCertificate);
154 
155         // build the request
156         HttpClientRequestFactory httpClientRequestFactory = new HttpClientRequestFactory(certificateFactory, userManager);
157         httpClientRequestFactory.createRequest(MethodType.GET, targetUrl);
158     }
159 
160     @Test
161     public void testConnectTimeout() throws IOException, ResponseException
162     {
163         // create HttpClient that will throw a ConnectTimeoutException
164         final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
165         final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
166         httpClientMock.executeMethod(isA(GetMethod.class));
167         httpClientMockControl.andThrow(new ConnectTimeoutException("errordescription"));
168         httpClientMockControl.replay();
169 
170         HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
171 
172         try
173         {
174             request.execute(EasyMock.createMock(ResponseHandler.class));
175             fail("Should throw ResponseConnectTimeoutException");
176         }
177         catch (ResponseConnectTimeoutException e)
178         {
179             // expect Exception
180             assertEquals(ConnectTimeoutException.class, e.getCause().getClass());
181             assertEquals("errordescription", e.getMessage());
182         }
183     }
184 
185     @Test
186     public void testReadTimeout() throws IOException, ResponseException
187     {
188         // create HttpClient that will throw a NoHttpResponseException
189         final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
190         final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
191         httpClientMock.executeMethod(isA(GetMethod.class));
192         httpClientMockControl.andThrow(new NoHttpResponseException("errordescription"));
193         httpClientMockControl.replay();
194 
195         HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
196 
197         try
198         {
199             request.execute(EasyMock.createMock(ResponseHandler.class));
200             fail("Should throw ResponseConnectTimeoutException");
201         }
202         catch (ResponseReadTimeoutException e)
203         {
204             // expect Exception
205             assertEquals(NoHttpResponseException.class, e.getCause().getClass());
206             assertEquals("errordescription", e.getMessage());
207         }
208     }
209 
210     @Test
211     public void testHttpException() throws IOException, ResponseException
212     {
213         // create HttpClient that will throw an HttpException
214         final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
215         final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
216         httpClientMock.executeMethod(isA(GetMethod.class));
217         httpClientMockControl.andThrow(new HttpException("errordescription"));
218         httpClientMockControl.replay();
219 
220         HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
221 
222         try
223         {
224             request.execute(EasyMock.createMock(ResponseHandler.class));
225             fail("Should throw ResponseProtocolException");
226         }
227         catch (ResponseProtocolException e)
228         {
229             // expect Exception
230             assertEquals(HttpException.class, e.getCause().getClass());
231             assertEquals("errordescription", e.getMessage());
232         }
233     }
234 
235     @Test
236     public void testSocketException() throws IOException, ResponseException
237     {
238         // create HttpClient that will throw a SocketException
239         final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
240         final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
241         httpClientMock.executeMethod(isA(GetMethod.class));
242         httpClientMockControl.andThrow(new SocketException("errordescription"));
243         httpClientMockControl.replay();
244 
245         HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
246 
247         try
248         {
249             request.execute(EasyMock.createMock(ResponseHandler.class));
250             fail("Should throw ResponseTransportException");
251         }
252         catch (ResponseTransportException e)
253         {
254             // expect Exception
255             assertEquals(SocketException.class, e.getCause().getClass());
256             assertEquals("errordescription", e.getMessage());
257         }
258     }
259 
260     @Test
261     public void testStatusException() throws IOException, ResponseException
262     {
263         // create mock GetMethod that returns a 400 error
264         final IMocksControl mockControl = EasyMock.createNiceControl();
265         final GetMethod mockGetMethod = mockControl.createMock(GetMethod.class);
266         mockGetMethod.getStatusCode();
267         mockControl.andReturn(400);
268         mockControl.anyTimes();
269         mockControl.replay();
270 
271         // create HttpClient that will return this response
272         final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
273         final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
274         httpClientMock.executeMethod(mockGetMethod);
275         httpClientMockControl.andReturn(400);
276         httpClientMockControl.replay();
277 
278         HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
279 
280         try
281         {
282             request.execute();
283             fail("Should throw ResponseStatusException");
284         }
285         catch (ResponseStatusException e)
286         {
287             // expect Exception
288             assertNotNull(e.getResponse());
289             assertEquals(400, e.getResponse().getStatusCode());
290         }
291     }
292     
293     @Test
294     public void testMaxNumberOfRedirectionReached() throws IOException, ResponseException
295     {
296         final GetMethod mockGetMethod = mock(GetMethod.class);
297         when(mockGetMethod.getResponseHeader("location")).thenReturn(new Header("location", "http://someRedirectionUrl"));
298 
299         final HttpClient httpClientMock = mock(HttpClient.class);
300         when(httpClientMock.getHostConfiguration()).thenReturn(new HostConfiguration());
301         when(httpClientMock.executeMethod(mockGetMethod)).thenReturn(302);
302 
303         HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
304 
305         // now use it
306         try
307         {
308             request.execute(mock(ResponseHandler.class));
309             fail("Should throw ResponseProtocolException - maximum retries reached.");
310         }
311         catch (ResponseProtocolException e)
312         {
313             // expect Exception
314         }
315 
316         // and assert results
317         // methods are called once for the initial call and then once per redirect.
318         Mockito.verify(mockGetMethod, times(HttpClientRequest.MAX_REDIRECTS + 1)).getResponseHeader("location");
319         Mockito.verify(httpClientMock, times(HttpClientRequest.MAX_REDIRECTS + 1)).executeMethod(mockGetMethod);
320     }
321 
322     @Test
323     public void testOverriddenMaxNumberOfRedirectionReached() throws IOException, ResponseException
324     {
325         // override the default MAX_REQUESTS
326         int redirectsAllowed = 10;
327         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + redirectsAllowed);
328 
329         final GetMethod mockGetMethod = mock(GetMethod.class);
330         when(mockGetMethod.getResponseHeader("location")).thenReturn(new Header("location", "http://someRedirectionUrl"));
331 
332         final HttpClient httpClientMock = mock(HttpClient.class);
333         when(httpClientMock.getHostConfiguration()).thenReturn(new HostConfiguration());
334         when(httpClientMock.executeMethod(mockGetMethod)).thenReturn(302);
335 
336         HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
337 
338         // now use it
339         try
340         {
341             request.execute(mock(ResponseHandler.class));
342             fail("Should throw ResponseProtocolException - maximum retries reached.");
343         }
344         catch (ResponseProtocolException e)
345         {
346             // expect Exception
347         }
348 
349         // and assert results
350         // methods are called once for the initial call and then once per redirect.
351         Mockito.verify(mockGetMethod, times(redirectsAllowed + 1)).getResponseHeader("location");
352         Mockito.verify(httpClientMock, times(redirectsAllowed + 1)).executeMethod(mockGetMethod);
353     }
354 
355     @Test
356     public void testNoFollowRedirect() throws IOException
357     {
358         // create mock GetMethod - it should not redirect automatically
359         final IMocksControl mockControl = EasyMock.createNiceControl();
360         final GetMethod mockGetMethod = mockControl.createMock(GetMethod.class);
361         mockGetMethod.getResponseHeader("location");
362         mockControl.andReturn(new Header("location", "http://someRedirectionUrl")).anyTimes();
363         mockControl.replay();
364 
365         // create HttpClient that will return 301 Moved Permanently
366         final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
367         final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
368         httpClientMock.executeMethod(mockGetMethod);
369         httpClientMockControl.andReturn(302).anyTimes();
370         httpClientMockControl.replay();
371 
372         HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
373         request.setFollowRedirects(false);
374                
375         // now use it
376         try
377         {
378             request.execute(EasyMock.createMock(ResponseHandler.class));
379             
380         }
381         catch (ResponseException e)
382         {
383             fail(e.getMessage());
384         }
385 
386         // and assert results
387         mockControl.verify();
388     }
389 
390     @Test
391     public void testFollowRedirectForPostMethodsNotPossible() throws Exception
392     {
393         // create a request that will return mockGetMethod
394         HttpClientRequest request = createMockRequest(MethodType.POST, "http://url");
395         try
396         {
397             request.setFollowRedirects(true);
398             fail("Should have thrown an exception because we can't follow redirects for Post methods");
399         } catch (IllegalStateException ex)
400         {
401             //Expected
402             assertEquals("Entity enclosing requests cannot be redirected without user intervention!", ex.getMessage());
403         }
404     }
405 
406     @Test
407     public void testFollowRedirectForPutMethodsNotPossible() throws Exception
408     {
409         HttpClientRequest request = createMockRequest(MethodType.PUT, "http://url");
410 
411         try
412         {
413             request.setFollowRedirects(true);
414             fail("Should have thrown an exception because we can't follow redirects for Post methods");
415         } catch (IllegalStateException ex)
416         {
417             //Expected
418             assertEquals("Entity enclosing requests cannot be redirected without user intervention!", ex.getMessage());
419         }
420     }
421 
422     @Test
423     public void testExecutePostMethodNoFollowRedirects() throws Exception
424     {
425         final HttpClient httpClientMock = mock(HttpClient.class);
426         final PostMethod postMethod = mock(PostMethod.class);
427 
428         HttpClientRequest request = createMockRequest(httpClientMock, postMethod, MethodType.POST, "http://url");
429 
430         request.execute(new ResponseHandler<HttpClientResponse>(){
431             public void handle(final HttpClientResponse response) throws ResponseException
432             {
433 
434             }
435         });
436         Mockito.verify(postMethod).setFollowRedirects(false);
437     }
438 
439     @Test
440     public void testExecutePutMethodNoFollowRedirects() throws Exception
441     {
442         final HttpClient httpClientMock = mock(HttpClient.class);
443         final PutMethod putMethod = mock(PutMethod.class);
444 
445         HttpClientRequest request = createMockRequest(httpClientMock, putMethod, MethodType.PUT, "http://url");
446 
447         request.execute(new ResponseHandler<HttpClientResponse>(){
448             public void handle(final HttpClientResponse response) throws ResponseException
449             {
450 
451             }
452         });
453         Mockito.verify(putMethod).setFollowRedirects(false);
454     }
455 
456     @Test
457     public void testAddRequestParametersFails()
458     {
459         // Lets try to add parameters to GET method
460         try
461         {
462             HttpClientRequest request = createMockRequest(MethodType.GET, "http://url");
463             request.addRequestParameters("doIt", "quickly!");
464             fail("Should throw exception that only the POST method can have parameters.");
465         }
466         catch (UnsupportedOperationException e)
467         {
468             // expected
469         }
470 
471         // Lets try to add parameters to PUT method
472         try
473         {
474             HttpClientRequest request = createMockRequest(MethodType.PUT, "http://url");
475             request.addRequestParameters("Isaid", "doIt", "now");
476             fail("Should throw exception that only the POST method can have parameters.");
477         }
478         catch (UnsupportedOperationException e)
479         {
480             // expected
481         }
482 
483         // Lets try to add uneven amount of parameters to POST method
484         try
485         {
486             HttpClientRequest request = createMockRequest(MethodType.POST, "http://url");
487             request.addRequestParameters("doIt", "quickly!", "now");
488             fail("Should throw exception that You must enter an even number of arguments.");
489         }
490         catch (IllegalArgumentException e)
491         {
492             // expected
493         }
494     }
495 
496     @Test
497     public void testAddRequestParameters() throws IOException, ResponseException
498     {
499         // create mock PostMethod - someone should call addParamater() on it
500         final IMocksControl mockControl = EasyMock.createNiceControl();
501         final PostMethod mockPostMethod = mockControl.createMock(PostMethod.class);
502         mockPostMethod.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
503         mockPostMethod.addParameter("a", "b");
504         mockPostMethod.addParameter("a", "b");
505         mockPostMethod.addParameter("a", "c");
506         mockPostMethod.addParameter("x", "y");
507         mockControl.replay();
508 
509 
510         final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
511         final HttpClient mockHttpClient = httpClientMockControl.createMock(HttpClient.class);
512         httpClientMockControl.replay();
513 
514 
515         // create a request that will return mockPostMethod
516         HttpClientRequest request = createMockRequest(mockHttpClient, mockPostMethod, MethodType.POST, "http://url");
517 
518         // now use it
519         request.addRequestParameters("a", "b", "a", "b", "a", "c", "x", "y");
520         request.execute(EasyMock.createMock(ResponseHandler.class));
521 
522         // and assert results
523         mockControl.verify();
524     }
525 
526     private HttpClientRequest createMockRequest(MethodType methodType, String url)
527     {
528         return createMockRequest(EasyMock.createMock(HttpClient.class), methodType, url);
529     }
530 
531     private HttpClientRequest createMockRequest(HttpClient client, MethodType methodType, String url)
532     {
533         return new HttpClientRequest(client, methodType, url,
534                                      mock(CertificateFactory.class), mock(UserManager.class));
535     }
536 
537     private HttpClientRequest createMockRequest(HttpClient client, final HttpMethod method, MethodType methodType,
538                                                 String url)
539     {
540         return new HttpClientRequest(client, methodType, url, mock(CertificateFactory.class), mock(UserManager.class))
541             {
542                 @Override
543                 protected HttpMethod makeMethod()
544                 {
545                     return method;
546                 }
547             };
548     }
549 }