View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.rest.client;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.http.HttpHeaders.*;
21  import static org.apache.juneau.http.HttpResponses.*;
22  import static org.junit.jupiter.api.Assertions.*;
23  
24  import java.io.*;
25  import java.util.concurrent.*;
26  import java.util.concurrent.atomic.*;
27  
28  import org.apache.http.*;
29  import org.apache.http.auth.*;
30  import org.apache.http.client.config.*;
31  import org.apache.http.client.methods.*;
32  import org.apache.http.concurrent.*;
33  import org.apache.http.impl.client.*;
34  import org.apache.http.message.*;
35  import org.apache.http.params.*;
36  import org.apache.http.protocol.*;
37  import org.apache.juneau.*;
38  import org.apache.juneau.common.utils.*;
39  import org.apache.juneau.parser.*;
40  import org.apache.juneau.reflect.*;
41  import org.apache.juneau.rest.annotation.*;
42  import org.apache.juneau.rest.mock.*;
43  import org.apache.juneau.rest.servlet.*;
44  import org.junit.jupiter.api.*;
45  
46  class RestClient_Test extends TestBase {
47  
48  	public static class ABean {
49  		public int f;
50  		static ABean get() {
51  			var x = new ABean();
52  			x.f = 1;
53  			return x;
54  		}
55  	}
56  
57  	private static ABean bean = ABean.get();
58  
59  	@Rest
60  	public static class A extends BasicRestObject {
61  		@RestGet
62  		public ABean bean() {
63  			return bean;
64  		}
65  		@RestGet(path="/echo/*")
66  		public String echo(org.apache.juneau.rest.RestRequest req) {
67  			return req.toString();
68  		}
69  	}
70  
71  	//------------------------------------------------------------------------------------------------------------------
72  	// Override client and builder.
73  	//------------------------------------------------------------------------------------------------------------------
74  
75  	public static class A2 extends RestClient.Builder {
76  		public A2() {}  // NOSONAR
77  	}
78  
79  	@Test void a02_basic_useNoArgConstructor() {
80  		assertDoesNotThrow(()->new A2().build());
81  	}
82  
83  	@Test void a03_basic_close() throws IOException {
84  		RestClient.create().build().close();
85  		RestClient.create().build().closeQuietly();
86  		RestClient.create().keepHttpClientOpen().build().close();
87  		RestClient.create().keepHttpClientOpen().build().closeQuietly();
88  		RestClient.create().httpClient(null).keepHttpClientOpen().build().close();
89  
90  		var es = new ThreadPoolExecutor(1,1,30,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
91  		RestClient.create().executorService(es,true).build().close();
92  		RestClient.create().executorService(es,true).build().closeQuietly();
93  		RestClient.create().executorService(es,false).build().close();
94  		RestClient.create().executorService(es,false).build().closeQuietly();
95  
96  		RestClient.create().debug().build().close();
97  		assertDoesNotThrow(()->RestClient.create().debug().build().closeQuietly());
98  	}
99  
100 
101 	@Test void a04_request_whenClosed() {
102 		var rc = client().build();
103 		rc.closeQuietly();
104 		assertThrowsWithMessage(Exception.class, "RestClient.close() has already been called", ()->rc.request("get","/bean",null));
105 	}
106 
107 	@Test void a05_request_whenClosed_withStackCreation() {
108 		var rc = client().debug().build();
109 		rc.closeQuietly();
110 		assertThrowsWithMessage(Exception.class, "RestClient.close() has already been called", ()->rc.request("get","/bean",null));
111 	}
112 
113 	@Test void a06_request_runCalledTwice() {
114 		assertThrowsWithMessage(RestCallException.class, "run() already called.", ()->{RestRequest r = client().build().get("/echo"); r.run(); r.run();});
115 	}
116 
117 	//------------------------------------------------------------------------------------------------------------------
118 	// Overridden methods
119 	//------------------------------------------------------------------------------------------------------------------
120 
121 	public static class B4 extends RestClient {
122 		private static boolean CREATE_REQUEST_CALLED, CREATE_RESPONSE_CALLED;
123 		public B4(RestClient.Builder b) {
124 			super(b);
125 		}
126 		@Override
127 		protected RestRequest createRequest(java.net.URI uri, String method, boolean hasBody) throws RestCallException {
128 			CREATE_REQUEST_CALLED = true;
129 			return super.createRequest(uri, method, hasBody);
130 		}
131 		@Override
132 		protected RestResponse createResponse(RestRequest req, HttpResponse httpResponse, Parser parser) throws RestCallException {
133 			CREATE_RESPONSE_CALLED = true;
134 			return super.createResponse(req, httpResponse, parser);
135 		}
136 		@Override /* HttpClient */
137 		public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException {
138 			return new BasicHttpResponse(new ProtocolVersion("http",1,1),200,null);
139 		}
140 	}
141 
142 	@Test void b04_restClient_overrideCreateRequest() throws Exception {
143 		RestClient.create().json5().build(B4.class).get("foo").run();
144 		assertTrue(B4.CREATE_REQUEST_CALLED);
145 		assertTrue(B4.CREATE_RESPONSE_CALLED);
146 	}
147 
148 	//------------------------------------------------------------------------------------------------------------------
149 	// Passthrough methods for HttpClientBuilder.
150 	//------------------------------------------------------------------------------------------------------------------
151 
152 	public static class C01 implements HttpRequestInterceptor, HttpResponseInterceptor {
153 		@Override
154 		public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
155 			request.setHeader("A1","1");
156 		}
157 		@Override
158 		public void process(HttpResponse response, HttpContext context) throws HttpException,IOException {
159 			response.setHeader("B1","1");
160 		}
161 	}
162 
163 	@Test void c01_httpClient_interceptors() throws Exception {
164 		HttpRequestInterceptor x1 = (request, context) -> request.setHeader("A1","1");
165 		HttpResponseInterceptor x2 = (response, context) -> response.setHeader("B1","1");
166 		HttpRequestInterceptor x3 = (request, context) -> request.setHeader("A2","2");
167 		HttpResponseInterceptor x4 = (response, context) -> response.setHeader("B2","2");
168 
169 		client().addInterceptorFirst(x1).addInterceptorLast(x2).addInterceptorFirst(x3).addInterceptorLast(x4)
170 			.build().get("/echo").run().assertContent().isContains("A1: 1","A2: 2").assertHeader("B1").is("1").assertHeader("B2").is("2");
171 		client().interceptors(C01.class).build().get("/echo").run().assertContent().isContains("A1: 1").assertHeader("B1").is("1");
172 		client().interceptors(new C01()).build().get("/echo").run().assertContent().isContains("A1: 1").assertHeader("B1").is("1");
173 	}
174 
175 	@Test void c02_httpClient_httpProcessor() throws RestCallException {
176 		var x = new HttpProcessor() {
177 			@Override
178 			public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
179 				request.setHeader("A1","1");
180 			}
181 			@Override
182 			public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
183 				response.setHeader("B1","1");
184 			}
185 		};
186 		client().httpProcessor(x).build().get("/echo").run().assertContent().isContains("A1: 1").assertHeader("B1").is("1");
187 	}
188 
189 	@Test void c03_httpClient_requestExecutor() throws RestCallException {
190 		var b1 = new AtomicBoolean();
191 		var x = new HttpRequestExecutor() {
192 			@Override
193 			public HttpResponse execute(HttpRequest request, HttpClientConnection conn, HttpContext context) throws HttpException, IOException {
194 				b1.set(true);
195 				return super.execute(request, conn, context);
196 			}
197 		};
198 		client().requestExecutor(x).build().get("/echo").run().assertContent().isContains("GET /echo HTTP/1.1");
199 		assertTrue(b1.get());
200 	}
201 
202 	@Test void c04_httpClient_defaultHeaders() throws RestCallException {
203 		client().headersDefault(stringHeader("Foo","bar")).build().get("/echo").run().assertContent().isContains("GET /echo HTTP/1.1","Foo: bar");
204 	}
205 
206 	@Test void c05_httpClient_httpClientBuilderMethods() {
207 		assertDoesNotThrow(()->RestClient.create().disableRedirectHandling().redirectStrategy(DefaultRedirectStrategy.INSTANCE).defaultCookieSpecRegistry(null).sslHostnameVerifier(null).publicSuffixMatcher(null).sslContext(null).sslSocketFactory(null).maxConnTotal(10).maxConnPerRoute(10).defaultSocketConfig(null).defaultConnectionConfig(null).connectionTimeToLive(100,TimeUnit.DAYS).connectionManager(null).connectionManagerShared(true).connectionReuseStrategy(null).keepAliveStrategy(null).targetAuthenticationStrategy(null).proxyAuthenticationStrategy(null).userTokenHandler(null).disableConnectionState().schemePortResolver(null).disableCookieManagement().disableContentCompression().disableAuthCaching().retryHandler(null).disableAutomaticRetries().proxy(null).routePlanner(null).connectionBackoffStrategy(null).backoffManager(null).serviceUnavailableRetryStrategy(null).defaultCookieStore(null).defaultCredentialsProvider(null).defaultAuthSchemeRegistry(null).contentDecoderRegistry(null).defaultRequestConfig(null).useSystemProperties().evictExpiredConnections().evictIdleConnections(1,TimeUnit.DAYS));
208 	}
209 
210 	@SuppressWarnings("deprecation")
211 	@Test void c06_httpClient_unusedHttpClientMethods() {
212 		var x = RestClient.create().build();
213 		assertThrows(UnsupportedOperationException.class, x::getParams);
214 		assertNotNull(x.getConnectionManager());
215 	}
216 
217 	@Test void c07_httpClient_executeHttpUriRequest() throws Exception {
218 		var x = new HttpGet("http://localhost/bean");
219 		x.addHeader("Accept","text/json5");
220 		var res = MockRestClient.create(A.class).build().execute(x);
221 		assertEquals("{f:1}",IOUtils.read(res.getEntity().getContent()));
222 	}
223 
224 	@Test void c08_httpClient_executeHttpHostHttpRequest() throws Exception {
225 		var x = new HttpGet("http://localhost/bean");
226 		var target = new HttpHost("localhost");
227 		x.addHeader("Accept","text/json5");
228 		var res = MockRestClient.create(A.class).build().execute(target,x);
229 		assertEquals("{f:1}",IOUtils.read(res.getEntity().getContent()));
230 	}
231 
232 	@Test void c09_httpClient_executeHttpHostHttpRequestHttpContext() throws Exception {
233 		var x = new HttpGet("http://localhost/bean");
234 		var target = new HttpHost("localhost");
235 		var context = new BasicHttpContext();
236 		x.addHeader("Accept","text/json5");
237 		var res = MockRestClient.create(A.class).build().execute(target,x,context);
238 		assertEquals("{f:1}",IOUtils.read(res.getEntity().getContent()));
239 	}
240 
241 	@Test void c10_httpClient_executeResponseHandler() throws Exception {
242 		var x = new HttpGet("http://localhost/bean");
243 		x.addHeader("Accept","text/json5");
244 		var res = MockRestClient.create(A.class).build().execute(x,new BasicResponseHandler());
245 		assertEquals("{f:1}",res);
246 	}
247 
248 	@Test void c11_httpClient_executeHttpUriRequestResponseHandlerHttpContext() throws Exception {
249 		var x = new HttpGet("http://localhost/bean");
250 		x.addHeader("Accept","text/json5");
251 		var res = MockRestClient.create(A.class).build().execute(x,new BasicResponseHandler(),new BasicHttpContext());
252 		assertEquals("{f:1}",res);
253 	}
254 
255 	@Test void c12_httpClient_executeHttpHostHttpRequestResponseHandlerHttpContext() throws Exception {
256 		var x = new HttpGet("http://localhost/bean");
257 		x.addHeader("Accept","text/json5");
258 		var res = MockRestClient.create(A.class).build().execute(new HttpHost("localhost"),x,new BasicResponseHandler(),new BasicHttpContext());
259 		assertEquals("{f:1}",res);
260 	}
261 
262 	@Test void c13_httpClient_executeHttpHostHttpRequestResponseHandler() throws Exception {
263 		var x = new HttpGet("http://localhost/bean");
264 		x.addHeader("Accept","text/json5");
265 		var res = MockRestClient.create(A.class).build().execute(new HttpHost("localhost"),x,new BasicResponseHandler());
266 		assertEquals("{f:1}",res);
267 	}
268 
269 	@Test void c14_httpClient_requestConfig() throws Exception {
270 		var req = client().build().get("/bean").config(RequestConfig.custom().setMaxRedirects(1).build());
271 		req.run().assertContent("{f:1}");
272 		assertEquals(1, req.getConfig().getMaxRedirects());
273 	}
274 
275 	@Test void c15_httpClient_pooled() throws Exception {
276 		var x1 = RestClient.create().json5().pooled().build();
277 		var x2 = RestClient.create().json5().build();
278 		var x3 = client().pooled().build();
279 		assertEquals("PoolingHttpClientConnectionManager",ClassInfo.of(x1.httpClient).getDeclaredField(x -> x.hasName("connManager")).accessible().get(x1.httpClient).getClass().getSimpleName());
280 		assertEquals("BasicHttpClientConnectionManager",ClassInfo.of(x2.httpClient).getDeclaredField(x -> x.hasName("connManager")).accessible().get(x2.httpClient).getClass().getSimpleName());
281 		assertEquals("MockHttpClientConnectionManager",ClassInfo.of(x3.httpClient).getDeclaredField(x -> x.hasName("connManager")).accessible().get(x3.httpClient).getClass().getSimpleName());
282 	}
283 
284 	//------------------------------------------------------------------------------------------------------------------
285 	// Authentication
286 	//------------------------------------------------------------------------------------------------------------------
287 
288 	@Rest
289 	public static class D extends BasicRestObject {
290 		@RestGet
291 		public String echo(@org.apache.juneau.http.annotation.Header("Authorization") String auth, org.apache.juneau.rest.RestResponse res) {
292 			if (auth == null) {
293 				throw unauthorized().setHeader2("WWW-Authenticate","BASIC realm=\"foo\"");
294 			}
295 			assertEquals("Basic dXNlcjpwdw==",auth);
296 			return "OK";
297 		}
298 	}
299 
300 	@Test void d01_basicAuth() throws RestCallException {
301 		client(D.class).basicAuth(AuthScope.ANY_HOST,AuthScope.ANY_PORT,"user","pw").build().get("/echo").run().assertContent().isContains("OK");
302 	}
303 
304 	//------------------------------------------------------------------------------------------------------------------
305 	// Other.
306 	//------------------------------------------------------------------------------------------------------------------
307 
308 	@Test void e01_other_completeFuture() throws Exception {
309 		client().build().get("/bean").completeFuture().get().assertStatus(200);
310 	}
311 
312 	public static class E2 implements Cancellable {
313 		@Override
314 		public boolean cancel() {
315 			return false;
316 		}
317 	}
318 
319 	@Test void e02_httpRequestBase_setCancellable() throws Exception {
320 		client().build().get("/bean").cancellable(new E2()).run().assertStatus(200);
321 	}
322 
323 	@Test void e03_httpRequestBase_protocolVersion() throws Exception {
324 		client().build().get("/bean").protocolVersion(new ProtocolVersion("http", 2, 0)).run().assertStatus(200);
325 		var x = client().build().get("/bean").protocolVersion(new ProtocolVersion("http", 2, 0)).getProtocolVersion();
326 		assertEquals(2,x.getMajor());
327 	}
328 
329 	@SuppressWarnings("deprecation")
330 	@Test void e04_httpRequestBase_completed() throws Exception {
331 		client().build().get("/bean").completed().run().assertStatus(200);
332 	}
333 
334 	@Test void e05_httpUriRequest_abort() throws Exception {
335 		var x = client().build().get("/bean");
336 		x.abort();
337 		assertTrue(x.isAborted());
338 	}
339 
340 	@Test void e06_httpMessage_getRequestLine() throws Exception {
341 		var x = client().build().get("/bean");
342 		assertEquals("GET",x.getRequestLine().getMethod());
343 	}
344 
345 	@Test void e07_httpMessage_containsHeader() throws Exception {
346 		var x = client().build().get("/bean").header("Foo", "bar");
347 		assertTrue(x.containsHeader("Foo"));
348 	}
349 
350 	@Test void e08_httpMessage_getFirstHeader_getLastHeader() throws Exception {
351 		var x = client().build().get("/bean").header("Foo","bar").header("Foo","baz");
352 		assertEquals("bar",x.getFirstHeader("Foo").getValue());
353 		assertEquals("baz",x.getLastHeader("Foo").getValue());
354 	}
355 
356 	@Test void e09_httpMessage_addHeader() throws Exception {
357 		var x = client().build().get("/bean");
358 		x.addHeader(header("Foo","bar"));
359 		x.addHeader("Foo","baz");
360 		assertEquals("bar",x.getFirstHeader("Foo").getValue());
361 		assertEquals("baz",x.getLastHeader("Foo").getValue());
362 	}
363 
364 	@Test void e10_httpMessage_setHeader() throws Exception {
365 		var x = client().build().get("/bean");
366 		x.setHeader(header("Foo","bar"));
367 		x.setHeader(header("Foo","baz"));
368 		assertEquals("baz",x.getFirstHeader("Foo").getValue());
369 		assertEquals("baz",x.getLastHeader("Foo").getValue());
370 		x.setHeader("Foo","qux");
371 		assertEquals("qux",x.getFirstHeader("Foo").getValue());
372 		assertEquals("qux",x.getLastHeader("Foo").getValue());
373 	}
374 
375 	@Test void e11_httpMessage_setHeaders() throws Exception {
376 		var x = client().build().get("/bean");
377 		x.setHeaders(new Header[]{header("Foo","bar")});
378 		assertEquals("bar",x.getFirstHeader("Foo").getValue());
379 	}
380 
381 	@Test void e12_httpMessage_removeHeaders() throws Exception {
382 		var x = client().build().get("/bean");
383 		x.setHeaders(new Header[]{header("Foo","bar")});
384 		x.removeHeaders("Foo");
385 		assertNull(x.getFirstHeader("Foo"));
386 	}
387 
388 	@Test void e13_httpMessage_removeHeader() throws Exception {
389 		var x = client().build().get("/bean");
390 		x.setHeaders(new Header[]{header("Foo","bar")});
391 		x.removeHeader(header("Foo","bar"));
392 		//assertNull(x.getFirstHeader("Foo"));  // Bug in HttpClient API?
393 	}
394 
395 	@Test void e14_httpMessage_headerIterator() throws Exception {
396 		var x = client().build().get("/bean");
397 		x.setHeaders(new Header[]{header("Foo","bar")});
398 		assertEquals("Foo: bar", x.headerIterator().next().toString());
399 		assertEquals("Foo: bar", x.headerIterator("Foo").next().toString());
400 	}
401 
402 	@SuppressWarnings("deprecation")
403 	@Test void e15_httpMessage_getParams() throws Exception {
404 		var p = new BasicHttpParams();
405 		var x = client().build().get("/bean");
406 		x.setParams(p);
407 		assertEquals(p, x.getParams());
408 	}
409 
410 	@Test void e16_toMap() throws Exception {
411 		assertNotNull(client().build().toString());
412 		assertNotNull(client().build().get("/bean").toString());
413 	}
414 
415 	//------------------------------------------------------------------------------------------------------------------
416 	// Helper methods.
417 	//------------------------------------------------------------------------------------------------------------------
418 
419 	private static RestClient.Builder client() {
420 		return MockRestClient.create(A.class).json5();
421 	}
422 
423 	private static RestClient.Builder client(Class<?> c) {
424 		return MockRestClient.create(c).noTrace().json5();
425 	}
426 
427 	private static Header header(String name, Object val) {
428 		return basicHeader(name, val);
429 	}
430 }