I'm writing a custom RestService(wrapper of Apache Http Client 4.5.10
) and RestServiceAsync(wrapper of Apache httpasyncclient 4.1.4
) in Spring MVC monolithic application.
Below is my IRestService
public interface IRestService {
public <T> T request(HttpUriRequest request, final ResponseParser<T> responseParser);
public <T> Optional<T> request(final HttpMethod method);
public <T> T call(HttpUriRequest request, ResponseHandler<T> responseHandler);
default <T> Optional<T> get() {
return request(HttpMethod.GET);
}
public default <T> Optional<T> get(String URL) {
return get(URL, Collections.EMPTY_MAP, ResponseParsers.STRING);
}
public default <T> Optional<T> get(String URL, boolean isAsync) {
return get(URL, Collections.EMPTY_MAP, ResponseParsers.STRING);
}
public default <T> Optional<T> get(String URL, Map params) {
return get(URL, params, ResponseParsers.STRING);
}
public default <T> Optional<T> get(String url, Map<String, String> params, ResponseParser<T> responseParser) {
return request(url, params, HttpMethod.GET, responseParser);
}
public default <T> Optional<T> get(String URL, ResponseParser<T> responseParser) {
return request(URL, Collections.EMPTY_MAP, HttpMethod.GET, responseParser);
}
public default <T> Optional<T> request(String url, Map params, HttpMethod httpMethod, ResponseParser<T> responseParser) {
return request(url, null, params, httpMethod, responseParser);
}
public <T> Optional<T> request(String url, HttpEntity entity, Map params, HttpMethod httpMethod, ResponseParser<T> responseParser);
HttpResponse execute(HttpUriRequest request, Closeable httpClient) ;
}
Below is my RestServiceBase
public abstract class RestServiceBase implements IRestService{
@Override
public <T> Optional<T> request(HttpMethod method) {
return Optional.empty();
}
@Override
public <T> Optional<T> request(String url, HttpEntity entity, Map params, HttpMethod httpMethod, ResponseParser<T> responseParser) {
T respose = null;
try {
final HttpUriRequest httpUriRequest = buildHttpUriRequest(httpMethod, url, entity, params);
respose = (T) request(httpUriRequest, responseParser);
} catch (HttpException | URISyntaxException e) {
e.printStackTrace();
}
return Optional.ofNullable(respose);
}
public <T> T request(HttpUriRequest request, final ResponseParser<T> responseParser) {
return call(request, new BaseResponseHandler<>(entity -> {
// Gets the content as a string
String content = entity != null ? EntityUtils.toString(entity, "UTF-8") : null;
// Parses the response
return responseParser.parse(content);
}));
}
private HttpUriRequest buildHttpUriRequest(HttpMethod httpMethod, String url, HttpEntity entity,
Map<String, String> params) throws HttpException, URISyntaxException {
URI uri = buildURI(url, params);
final RequestBuilder requestBuilder = resolveRequestBuilder(httpMethod,uri);
Optional.ofNullable(entity).ifPresent(requestBuilder::setEntity);
return requestBuilder.build();
}
private URI buildURI(String url, Map<String, String> params) throws URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(url);
Optional.ofNullable(params.entrySet())
.ifPresent(map -> map.forEach(e -> uriBuilder.setParameter(e.getKey(), e.getValue())));
return uriBuilder.build();
}
private RequestBuilder resolveRequestBuilder(HttpMethod method, URI uri) throws HttpException {
switch (method) {
case GET:
return RequestBuilder.get().setUri(uri);
case PUT:
return RequestBuilder.put().setUri(uri);
case POST:
return RequestBuilder.post().setUri(uri);
case DELETE:
return RequestBuilder.delete().setUri(uri);
default:
throw new HttpException("Unsupported HttpMethod " + method);
}
}
}
Below is my RestService( which uses Synchronous version of Apache http client)
@Service
public class RestService extends RestServiceBase{
@Autowired
HttpClientFactory httpClientFactory;
public HttpResponse execute(HttpUriRequest request, Closeable httpClient) {
if(! (httpClient instanceof CloseableHttpClient))
throw new RuntimeException("UnSupported HttpClient Exception");
try {
CloseableHttpClient closeableHttpClient = (CloseableHttpClient) httpClient;
return closeableHttpClient.execute(request);
} catch (IOException e) {
throw new RuntimeException();
}
}
private CloseableHttpClient getHttpClient() {
ApacheHttpClient apacheHttpClient = (ApacheHttpClient) httpClientFactory.getApacheHttpClient();
return (CloseableHttpClient) apacheHttpClient.getHttpClient();
}
public <T> T call(HttpUriRequest request, ResponseHandler<T> responseHandler) {
try {
try (CloseableHttpClient httpClient = getHttpClient()) {
HttpResponse response = execute(request, httpClient);
// Entity response
HttpEntity entity = response.getEntity();
try {
return responseHandler.handleResponse(request, response, entity);
} finally {
EntityUtils.consume(entity);
}
}
} catch (IOException e) {
throw new ClientGeneralException(request, e);
} finally {
((HttpRequestBase) request).releaseConnection();
}
}
}
Below is my RestService( which uses Asynchronous version of Apache http client)
@Service
public class RestServiceAsync extends RestServiceBase{
@Autowired
HttpClientFactory httpClientFactory;
public HttpResponse execute(HttpUriRequest request, Closeable httpClient) {
if(! (httpClient instanceof CloseableHttpAsyncClient))
throw new RuntimeException("UnSupported HttpClient Exception");
try {
CloseableHttpAsyncClient closeableHttpClient = (CloseableHttpAsyncClient) httpClient;
Future<HttpResponse> future = closeableHttpClient.execute(request, null);
HttpResponse response = future.get();
return response;
//return (HttpResponse) closeableHttpClient.execute(request,null);
} catch (InterruptedException | ExecutionException e) {
request.abort();
throw new RuntimeException();
}
}
private CloseableHttpAsyncClient getHttpClient() {
ApacheAsyncClient apacheAsyncClient = (ApacheAsyncClient) httpClientFactory.getApacheAsyncHttpClient();
return (CloseableHttpClient) apacheAsyncClient.getHttpClient();
}
public <T> T call(HttpUriRequest request, ResponseHandler<T> responseHandler) {
try {
try (CloseableHttpAsyncClient httpClient = getHttpClient()) {
HttpResponse response = execute(request, httpClient);
// Entity response
HttpEntity entity = response.getEntity();
try {
return responseHandler.handleResponse(request, response, entity);
} finally {
EntityUtils.consume(entity);
}
}
} catch (IOException e) {
throw new ClientGeneralException(request, e);
} finally {
((HttpRequestBase) request).releaseConnection();
}
}
}
Below is my sample use case of RestServiceAsync
@Service
ReportsService(){
@Autowired
@Qualifier("restServiceAsync")
RestService restService;
public getReport(){
restService.get("http://localhost/reports");
}
}
I want to introduce Response read timeout
so that main thread thats executing RestServiceAsync.execute
method wont wait more than the desired amount of time for different use cases. Below were my approaches for doing this
1. Introduce a private timeout
variable and use setter method in RestServiceAsync
and do HttpResponse response = future.get(timeout, TimeUnit.Seconds);
. This approach is not viable since RestServiceAsync
is a singleton bean setting timeout
variable would reflect for all the users of the application
2. Send timeout
as a parameter from the method call as restService.get("http://localhost/reports",30)
; this would work but I have to update all the relevant methods in IRestService
.
I am wondering if there is an alternative efficient approach to introduce timeout
without having to touch all the methods in IRestService
but making changes only in RestServiceAsync
PS: ApacheAsyncClient uses PoolingNHttpClientConnectionManager
and ApacheHttpClient uses PoolingHttpClientConnectionManager