I am using RestTemplate along with its factory HttpComponentsClientHttpRequestFactory in one of my projects. In this project, I need to make a Http url call to my server which is running a restful service which returns back the response as a JSON String.
Below is my code -
public class GetUserClientData {
public String getData(KeyHolder keys) {
return new HTTPRequestAccess(keys).makeHttpRequest();
}
}
Below is my class which wraps the HttpClient part -
public class HTTPRequestAccess {
// should this be static?
private RestTemplate restTemplate;
private KeyHolder keys;
private int timeout;
public HTTPRequestAccess(KeyHolder keys){
this(keys.getTimeoutValue()); // setting timeout to RestTemplate
this.keys = keys;
}
public HTTPRequestAccess(int timeout) {
this.timeout = timeout;
restTemplate = new RestTemplate(clientHttpRequestFactory());
}
private ClientHttpRequestFactory clientHttpRequestFactory() {
// is this not expensive that every time we are creating this new object?
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(timeout);
factory.setConnectTimeout(timeout);
return factory;
}
public String makeHttpRequest() {
String response = null;
try {
// some logic here
String url = generateURL();
response = restTemplate.getForObject(url, String.class);
// some logic here
} catch (RestClientException ex) {
// log exception and do some stuff
} catch (Exception ex) {
// log exception
}
return response;
}
}
Should RestTemplate and HttpComponentsClientHttpRequestFactory be static here in my HTTPRequestAccess class as if I see it correctly, I am recreating the whole connection pool for each request in RestTemplate which is not the right way I guess because each factory has connection and thread pool and they are pretty heavy object I guess.
In general what is the best way to use RestTemplate along with its factory HttpComponentsClientHttpRequestFactory in a multithreading environment? I guess RestTemplate is thread safe but I don't think HttpComponentsClientHttpRequestFactory is thread safe. Correct me if I am wrong? I will be running this library under heavy load.
I am using spring-web-3.2.8.RELEASE version.
In one of my projects, I had created a static instance of HttpComponentsClientHttpRequestFactory and passed it to every RestTemplate.
Though, in here, it is suggested to also have a global instance of RestTemplate.
Maybe irrelevant, but one important point is to pass HttpClients.createDefault() to your HttpComponentsClientHttpRequestFactory while constructing it since by default, this factory uses system properties to create HttpClient for your factory and that may cause a lot of pain in production environment.
You may also pass your custom HttpClient.
Related
I am writing a junit test using okhttp3.mockwebserver for a retrofit2 rest api.
The trimmed down api looks like this:
public interface MyApi{
#POST("/api/get-orders")
retrofit2.Response<Set<String>> getOrders();
#POST("/api/cxl-order")
retrofit2.Response<String> cancelOrder(String ordeId);
}
The api is then injected to another class which delegates the calls thusly:
public class MyExchange{
private final MyApi api;
public MyExchange(MyApi api){
this.api = api;
}
public final Set<String> getOrders(){
Response<Set<String>> resp = api.getOrders();
//parse the response
Set<String> result = parse( resp );
return result;
}
public final boolean cancelOrder( String orderId ){
api.cancelOrder( orderId );
//Nested Call
Set<String> orders = getOrders();
return !orders.contains(orderId);
}
}
I do the following in my test:
#Test
public void cancel_order(){
MockWebServer server = new MockWebServer();
server.start();
String orderId ="OrderId_123";
MyApi mockApi = new Retrofit.Builder().baseUrl("/").build().create(MyApi.class);
MyExchange exchange = new MyExchange(mockApi);
server.enqueue( new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK, orderId));
server.enqueue( new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK, Set.of()));
exchange.cancelOrder(orderId);
}
Because the implementation of cancelOrder() calls api.cancelOrder() and then api.getOrders(), I added two mocked responses corresponding to each. However, looks like only the first mocked responses gets returned. For the second (getOrders), the mock server actually tries to connect over REST and then fails by timing out.
Any ideas as to how to mock responses for nested calls?
Cheers!
I ended up using the Dispatcher to check the path of the request.
If the path ends in "get-orders", I send mocked response for Orders otherwise for cancel orders.
Dispatcher dispatcher = (request) -> {
if( request.getPath().endsWith("get-orders"){
return mock response for orders
}else if( request.getPath().endsWith("cxl-orders"){
return mock response for cancel orders
}
}
mockServer.setDispatcher(dispatcher);
Currently, I am trying to create an utility class which is to serve as the calling point of any HTTP request executed throughout the application. My implementation currently looks somewhat like this:
public class HttpHelper {
private final static HttpClientConnectionManager poolingConnManager = new PoolingHttpClientConnectionManager();
private final static CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(poolingConnManager).build();
public static String GET(String endpoint, HashMap<String, String> headers) {
/* httpClient.execute(...) */
}
}
Thus, HTTP requests can be smoothly executed from anywhere in the application through:
String result = HttpHelper.GET(URL, headers);
Would this be an appropriate way of doing it? Is the pool of connections managed efficiently in such a class?
public void Method1() {
restTemplate = new RestTemplate();
}
public void Method2() {
restTemplate = new RestTemplate();
}
public void Method50() {
restTemplate = new RestTemplate();
}
....
Cant we create a mock of restTemplate and reuse it in all methods ?
Yes. You can just create a single instance and re-use it in all the cases. If you use a real RestTemplate in an integration test (say hitting an in-memory service or similar) this will be safe too as RestTemplate is thread-safe.
I am using Apache HttpClient 4.5's Fluent API, the following way:
CloseableHttpClient client = HttpClientBuilder.create().build();
Executor executor = Executor.newInstance(client);
Response resp = executor.execute(Request.Get(url));
Unfortunately, I can't find a proper way of getting redirect locations (the RedirectLocation class).
They are normally stored in a HttpContext object; but when using the Fluent API, its instance is created locally in Executor.execute(...) and never exposed:
public Response execute(final Request request) {
final HttpClientContext localContext = HttpClientContext.create();
/* ... */
return new Response(request.internalExecute(this.httpclient, localContext));
}
I've tried to override Executor.execute(...) method by creating a decorator/proxy class; by creating a child class; even by copy-pasting its source into my own package.
None of these solutions were feasible (for one, Executor invokes package-local methods of other classes).
The only workaround I've managed to find so far was to implement my own RedirectStrategy and pass it to HttpClient:
public class MyRedirectStrategy extends DefaultRedirectStrategy {
private HttpContext context;
public RedirectLocations getRedirectLocations() {
return (RedirectLocations) context.getAttribute(REDIRECT_LOCATIONS);
}
#Override
public URI getLocationURI(final HttpRequest request, final HttpResponse response, final HttpContext context) {
this.context = context; // to keep the HttpContext!
return super.getLocationURI(request, response, context);
}
}
/* ... */
RedirectStrategy stra = new MyRedirectStrategy();
CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(stra).build();
Executor executor = Executor.newInstance(client);
Response resp = executor.execute(Request.Get(url));
for (final String redirectedUri : stra.getRedirectLocations()) {
/* process redirectedUri's */
}
However, I don't think it is a proper solution. To my knowledge, RedirectStrategies were intended to be immutable, stateless classes, as they are passed to the HttpClient, which can be shared by multiple threads/connections.
In other words: logically, the HttpContext is not a property of a RedirectStrategy.
Any ideas?
Thanks!
You cannot. HC fluent API hides HttpContext instance way from the consumer. Consider using HttpClient APIs directly.
How to unit test the below method with the EasyMock. I tried to mock WebResource but it is returning me a NullPointerException.
public void connect()
{
Client client = setUpClient();
WebResource jobBuilder = client.resource("URL");
String jobXml = jobBuilder.accept(MediaType.APPLICATION_XML)
.type(MediaType.APPLICATION_JSON)
.entity(request)
.post(String.class);
}
public Client setUpClient()
{
ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(JacksonJsonProvider.class);
Client client = Client.create(cc);
return client;
}
You clearly have to read up on the Inversion of Control Principle (http://en.wikipedia.org/wiki/Inversion_of_control), if not for the sake of the better designed code, then for the sake of unit testing. The client object in the method above is created inside the method itself, using the static factory method Client.create(). There is no good way to inject a mock collaborator with that approach. You should either allow injection of the client via a setter or a constructor, or delegate its creation to some sort of a factory.
If you use the 1st approach, you can inject the mock client directly via the setter or constructor during the unit test setup.
If you use the 2nd approach, you can provide a factory that would return a mock client when called.
EDIT 5/03:
Here's example of making your code testable by providing an object factory for a 3rd party library object:
public class ClassToTest {
private ClientFactory factory;
public ClassTotest() {
this(new ClientFactory());
}
public ClassToTest(ClientFactory factory) {
this.factory = factory;
}
public void connect() {
Client client = factory.getClient();
WebResource jobBuilder = client.resource("URL");
String jobXml = jobBuilder.accept(MediaType.APPLICATION_XML)
.type(MediaType.APPLICATION_JSON)
.entity(request)
.post(String.class);
}
}
public class ClientFactory() {
public Client getClient() {
ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(JacksonJsonProvider.class);
Client client = Client.create(cc);
return client;
}
}
Now, in your application code you can instantiate your class using the no-argument constructor. In the unit test you would use the other constructor. This way you would be able to inject a mock Client that you would script for the purpose of testing WebResource.
Hope this helps.