I have a library which is being used by customer and they are passing DataRequest object which has userid, timeout and some other fields in it. Now I use this DataRequest object to make a URL and then I make an HTTP call using RestTemplate and my service returns back a JSON response which I use it to make a DataResponse object and return this DataResponse object back to them.
Below is my DataClient class used by customer by passing DataRequest object to it. I am using timeout value passed by customer in DataRequest to timeout the request if it is taking too much time in getSyncData method.
public class DataClient implements Client {
private final RestTemplate restTemplate = new RestTemplate();
private final ExecutorService service = Executors.newFixedThreadPool(10);
// this constructor will be called only once through my factory
// so initializing here
public DataClient() {
try {
restTemplate.setRequestFactory(clientHttpRequestFactory());
} catch (Exception ex) {
// log exception
}
}
#Override
public DataResponse getSyncData(DataRequest key) {
DataResponse response = null;
Future<DataResponse> responseFuture = null;
try {
responseFuture = getAsyncData(key);
response = responseFuture.get(key.getTimeout(), key.getTimeoutUnit());
} catch (TimeoutException ex) {
response = new DataResponse(DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR);
responseFuture.cancel(true);
// logging exception here
}
return response;
}
#Override
public Future<DataResponse> getAsyncData(DataRequest key) {
DataFetcherTask task = new DataFetcherTask(key, restTemplate);
Future<DataResponse> future = service.submit(task);
return future;
}
// how to set socket timeout value by using `key.getSocketTimeout()` instead of using hard coded 400
private ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
RequestConfig requestConfig =
RequestConfig.custom().setConnectionRequestTimeout(400).setConnectTimeout(400)
.setSocketTimeout(400).setStaleConnectionCheckEnabled(false).build();
SocketConfig socketConfig =
SocketConfig.custom().setSoKeepAlive(true).setTcpNoDelay(true).build();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager =
new PoolingHttpClientConnectionManager();
poolingHttpClientConnectionManager.setMaxTotal(300);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(200);
CloseableHttpClient httpClientBuilder =
HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager)
.setDefaultRequestConfig(requestConfig).setDefaultSocketConfig(socketConfig).build();
requestFactory.setHttpClient(httpClientBuilder);
return requestFactory;
}
}
DataFetcherTask class:
public class DataFetcherTask implements Callable<DataResponse> {
private final DataRequest key;
private final RestTemplate restTemplate;
public DataFetcherTask(DataRequest key, RestTemplate restTemplate) {
this.key = key;
this.restTemplate = restTemplate;
}
#Override
public DataResponse call() throws Exception {
// In a nutshell below is what I am doing here.
// 1. Make an url using DataRequest key.
// 2. And then execute the url RestTemplate.
// 3. Make a DataResponse object and return it.
}
}
Customer within our company will use my library like this as shown below by using my factory in their code base -
// if they are calling `getSyncData()` method
DataResponse response = DataClientFactory.getInstance().getSyncData(key);
// and if they want to call `getAsyncData()` method
Future<DataResponse> response = DataClientFactory.getInstance().getAsyncData(key);
I am implementing sync call as async + waiting since I want to throttle them with the number of threads otherwise they can bombard our service without any control.
Problem Statement:-
I am going to add another timeout variable called socket timeout in my DataRequest class and I want to use that variable value (key.getSocketTimeout()) in my clientHttpRequestFactory() method instead of using hard coded 400 value. What is the best and efficient way to do that?
Right now I am using Inversion of Control and passing RestTemplate in a constructor to share the RestTemplate between all my Task objects. I am confuse now how to use key.getSocketTimeout() value in my clientHttpRequestFactory() method. I think this is mostly design question of how to use RestTemplate efficiently here so that I can use key.getSocketTimeout() value in my clientHttpRequestFactory() method.
I have simplified the code so that idea gets clear what I am trying to do and I am on Java 7. Using ThreadLocal is the only option I have here or there is any better and optimized way?
As Peter explains, using ThreadLocal is not a good idea here.
But I also could not find a way to "pass the value up the chain of method calls".
If you use plain "Apache HttpClient", you can create an HttpGet/Put/etc. and simply call
httpRequest.setConfig(myRequestConfig). In other words: set a request configuration per request
(if nothing is set in the request, the request configuration from the HttpClient which executes the request is used).
In contrast, the RestTemplate
calls createRequest(URI, HttpMethod) (defined in HttpAccessor)
which uses the ClientHttpRequestFactory. In other words: there is no option to set a request configuration per request.
I'm not sure why Spring left this option out, it seems a reasonable functional requirement (or maybe I'm still missing something).
Some notes about the "they can bombard our service without any control":
This is one of the reasons to use the PoolingHttpClientConnectionManager:
by setting the appropriate maximum values, there can never be more than the specified maximum connections in use (and thus requests running) at the same time. The assumption here is that you re-use the same RestTemplate instance (and thus connection manager) for each request.
To catch a flood earlier, specify a maximum amount of waiting tasks in the threadpool and set a proper error-handler
(use the workQueue and handler in this constructor).
ThreadLocal is a way to pass dynamic value which normally you would pass via method properties, but you are using an API you can't/don't want to change.
You set the ThreadLocal (possible a data structure containing multiple values) at some level in the thread stack and you can use it further up the stack.
Is this the best approach? NO, you should really pass the value up the chain of method calls, but sometimes this is not practical.
Can you provide an example of how my code will look like with ThreadLocal
You might start with
static final ThreadLocal<Long> SOCKET_TIMEOUT = new ThreadLocal<>();
To set it you can do
SOCKET_TIMEOUT .set(key.getSocketTimeout());
and to get the value you can do
long socketTimeout = SOCKET_TIMEOUT.get();
Related
Trying to implement okhttp the correct way. I understand the OkHttpClient must be shared (Singleton), however I am not clearly understanding .newBuilder();
Sample Code:
// Instantiated once
private static OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(readTime, TimeUnit.MILLISECONDS)
.connectionPool(new ConnectionPool(200, connectTimeout, TimeUnit.MILLISECONDS));
.build();
public static String makeRestCall(String url, String data, Interceptor customInterceptor) {
// Questions on the line below
OkHttpClient newClient = client.newBuilder()
.addInterceptor(customInterceptor)
.build();
....
try (Response response = newClient.newCall(httpRequest).execute()) {
final ResponseBody body = response.body();
return body.string();
}
return "NO_DATA";
}
I have a few questions around .newBuilder()
When we add a new interceptor to newClient, does the original client also get updated by reference?
Classes calling makeRestCall decide on what customInteceptor they need. Is it ok to call .newBuilder() for every request?
I have been searching the documentation and playing with the implementation but haven't had clarity on the above.
Any assistance/pointers are appreciated.
When we add a new interceptor to newClient, does the original client also get updated by reference?
No, the original is unchanged. Its configuration is immutable.
Classes calling makeRestCall decide on what customInteceptor they need. Is it ok to call .newBuilder() for every request?
Absolutely. That operation is cheap because it only duplicates the configuration. Resource-intensive stuff like the connection pool and cache are not duplicated.
I know how to memoize a single object. However, I'd like to memoize only if some condition is met. I'm calling a service that sometimes returns a response that is not successful. I'd like to memoize only if the service's response if successful.
MyResponse myResponse = myService.call()
boolean success = myResponse.isSuccessful();
And my cache is created like so:
private Supplier<MyResponse> cache;
private void createCache() {
this.cache = Suppliers
.memoizeWithExpiration(myService::call, timeout,
TimeUnit.MINUTES);
}
Question: Is it possible to somehow cache the response only if the response is successful using the Supplier passed to the memoizeWithExpiration method?
The only workaround I found to do this is to, when retrieving the value, call cache.get() first, check if the object stored in cache is successful, and if it's not, call createCache() again to clear it and then get the value again. This way if the subsequent service call returns a valid object, it will get stored, and if not, every subsequent call will clear the cache and call the service again.
public MyResponse getResponse() {
MyResponse myResponse = cache.get();
if (myResponse.isSuccess()) {
return myResponse;
} else {
createCache();
return cache.get();
}
}
However, in this solution, if the cache is empty and the service returns unsuccessful response, it will get called again immediately.
You can create a method callUntilSuccess in Service class or in any other suitable place (here I'm assuming it is in your Service). You could also define a maximum number of tries in this method and after that it will return null, so you could avoid calling your service indefinitely (this suggestion isn't implemented in the code supplied bellow but it is very easy to do so). As the Guava method expects a Supplier, you can even create a lambda with this logic and pass it directly to the memoizeWithExpiration method.
public MyResponse callUntilSuccess() {
MyResponse response = myService.call();
while (!response.isSuccessful()) {
response = myService.call();
}
return response;
}
Then do the memoization in this way:
private void createCache() {
this.cache = Suppliers
.memoizeWithExpiration(myService::callUntilSuccess, timeout,
TimeUnit.MINUTES);
}
Could this be what you are looking for?
private void createCache() {
this.cache = Suppliers.memoizeWithExpiration(
Suppliers.compose(
response -> (response.isSuccess() ? response : null),
myService::call
),
timeout,
TimeUnit.MINUTES
);
}
Here, it will cache the response, or null, depending on whether it was successful.
More info on compose here https://github.com/google/guava/blob/master/guava/src/com/google/common/base/Suppliers.java#L45
EDIT:
If you need to cache the value on success, and leave the cache empty on failure, while returning the failed request, then you are almost there yourself, just change return logic a bit in getResponse, like this:
public MyResponse getResponse() {
final MyResponse myResponse = cache.get();
if (!myResponse.isSuccess()) {
this.createCache(); // clear cache
}
return myResponse; // don't call .get() again!
}
Suppose I have some service S that receives requests from client C.
S cannot response immediately due to heavy calculations, C also cannot wait until forever and has his own timeout period.
My idea is to implement the server side as described here:
REST and long running jobs, Farazdagi
In my ServerController I have a thread pool for deferred calculations and a concurrent map to store responses.
private final int NUMBER_OF_WORKERS = 10;
private Map<String, ResponseEntity<MathResponse>> responseMap = new ConcurrentHashMap<>();
private ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_WORKERS);
My /calculate mapping submits jobs to the thread pool and returns with 202 (Accepted) HTTP status and puts redirection link to Location header.
#RequestMapping(value = "/calculate", method = RequestMethod.POST)
public ResponseEntity<String> startWorkflow(#RequestBody MathRequest request, UriComponentsBuilder builder) {
UUID uuid = UUID.randomUUID();
executorService.submit(() -> {
// time-consuming calculations here
ResponseEntity<MathResponse>response = HardMath.execute(request)
responseMap.put(uuid.toString(), response);
});
HttpHeaders headers = new HttpHeaders();
UriComponents uriComponents = builder.path("/wf/queue/{id}").buildAndExpand(uuid.toString());
headers.setLocation(uriComponents.toUri());
return new ResponseEntity<>(headers, HttpStatus.ACCEPTED);
}
In /queue/id mapping I return result if it's in the map:
#RequestMapping(value = "/queue/{id}", method = RequestMethod.GET)
public ResponseEntity<MathResponse> getQueueInfo(#PathVariable("id") String id) {
ResponseEntity<MathResponse> defaultQueueResponse = new ResponseEntity<>(new MathResponse(), HttpStatus.OK);
return responseMap.getOrDefault(id, defaultQueueResponse);
}
I suppose that using such low-level things like ConcurrentHashMap is not a good idea. Are there any options in Spring that I could use instead of reinventing the wheel?
There's also the question of resilience; if the results are local to an instance of S (i.e. in an in-process Map) then if that instance of S crashes or is restarted then the results are lost and C would be forced to resubmit its request(s). If the results cache within S was backed by a resilient store then the results could survive a crash/restart of S.
Spring's caching abstraction with a backing store of <insert storage technology here> could help.
i am working on android app , I often get HTTP error 500 when i try to access the url due to bad connectivity which causes my app to fail . Response returned from URL is in JSON format so in order to Parse this json i used jackson api
JsonNode monthlyUrlNode = objectMapper.readValue(url, JsonNode.class);
In case of failure i want to reconnect to url with a delay of 30 seconds
i referred this Retry a connection on timeout in Java , but it is not of much use to me
Have you thought of using a Proxy object? Proxy let's you wrap an interface in a way that you can perform the same intervention independent of the method being called. Let's assume you've already created a client object for accessing interface SomeService. Then you can create a proxy class with a 30-second retry built in:
public class ServiceProxy implements InvocationHandler {
private final SomeService realClient;
public ServiceProxy(SomeService realClientObject) {
this.realClient = realClientObject;
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(realClient, args);
if (result instanceof URL) {
JsonNode urlNode = objectMapper.readValue(url, JsonNode.class);
if (some condition on urlNode) {
// Wait and retry
Thread.sleep(30000);
result = method.invoke(realClient, args);
}
}
return result;
}
}
You create the proxy object by passing it the original interface, like:
public class ProxyFactory {
public static SomeService get(SomeService originalClient) {
return (SomeService)Proxy.newProxyInstance(SomeService.class.getClassLoader(),
new Class[]{SomeService.class},
new ServiceProxy(originalClient));
}
}
If you need this sort of fine control, do not pass a URL to Jackson. Use an appropriate HTTP library to read the content from the URL into memory, retrying as needed, and then feed it to Jackson.
I am trying to nest two request factory calls in each other. I retrieve a post object and in the success-method i use the same object again (just for testing purposes, I get the same behavior for other request like for example persisting).
The problem is: Only the first request reaches the server.
I don't get any error message. If I debug the code, everything works until the second request is fired. Nothing happens then. The method on the backend is not called, the frontend shows no error, even if I implement the "onFailure"-method for the receiver of the second request.
public class RequestFactoryFindTest extends GWTTestCase{
/**
* must refer to a valid module that sources this class.
*/
public String getModuleName() {
return "com.Test.MyTest";
}
public void test(){
final ClientFactory clientFactory = GWT.create(ClientFactoryImpl.class);
final MyRequestFactory requestFactory = clientFactory.getRequestFactory();
final PostRequest request = requestFactory.postRequest();
request.findPost(1l).fire(new Receiver<PostProxy>() {
#Override
public void onSuccess(PostProxy response) {
final ClientFactory clientFactory = GWT.create(ClientFactoryImpl.class);
final MyRequestFactory requestFactory = clientFactory.getRequestFactory();
final PostRequest request = requestFactory.postRequest();
System.out.println("outer success");
request.findPost(1l).fire(new Receiver<PostProxy>() {
#Override
public void onSuccess(PostProxy response) {
System.out.println("inner success");
}
});
}
});
}
}
Can someone explain this?
Edit:
I tried a lot of stuff like to fire an event on the event bus, catch the event and do my inner request factory call there. But nothing worked. I think this is some Issue with the GWTTestcase in combination with RequestFactory.
I also changed my code, so i use only one clientFactory.
Try to create an event in the first onSuccess method. When your event is handled, you could send another request to the server.
Check out How to use the GWT EventBus to use the eventbus.
Thomas Broyer statement is also right. You should only use one RequestFactory and one ClientFactory!
This may be a problem when you are constructing your second client factory as per Thomas Broyer. You should probably go into your ClientFactory.java interface and at the top add the the single client factory instance. Also put a GWT.log("ON SUCCESS") at the top of your onSuccess(PostProxy response) to make sure it is getting there.
public interface ClientFactory {
public static final ClientFactory INSTANCE = GWT.create(ClientFactory.class);
...
Then you can simple do somehting like the following
final PostRequest request = ClientFactory.INSTANCE.getRequestFactory().postRequest();