A client request is handled by doGET which calls a third-party API. This third-party API then does not send a response back to doGET but calls my other endpoint to be handled by doPOST. I now need to write a response from doPOST into doGET.
Required workflow is as follows -
The third-party API will callback doPOST
doPOST should be using Callable Task that executor service uses
Then I will have Future returned from executor service submit call as shown above
The doGET API will do a blocking .get() operation on this future
Once the reply from doPOST is received, doGET will return the same response back to the client
To make this happen, I am trying to setup executor and future mechanism as follows -
public HttpServletResponse doGET(Request jettyReq, HttpServletRequest request, HttpServletResponse response) throws ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(10);
// Placeholder for the value which will arrive in the future
Future<WriteDoGetResponse> future = executor.submit(new WriteDoGetResponse());
// perform some unrelated actions here
// check future status that gives me an actual response object
HttpServletResponse modifiedResponse = future.get();
// if hashmap has future for request-id
executor.shutdown();
return modifiedResponse;
}
public WriteDoGetResponse doPOST(Request jettyReq, HttpServletRequest request, HttpServletResponse response) {
}
I have the callable task in the other class for now but this is not something I am looking to solve.
static class WritedoGetResponse implements Callable<HttpServletResponse> {
#Override
public HttpServletResponse call() throws Exception {
return null;
}
But I need help with how do I make my doPOST api callable? I need a mechanism to solve this. Because executor.submit() takes an instance and I can not make doPOST implement callable. Any idea?
Related
I have 2 APIs (doGET and doPOST) and I am trying to use an async mechanism to make doPOST write httpServletResponse for doGET request.
My control flow -
Client make a requestA (getData) call
Java server does some processing and makes a call to out of environment 3rd party API
3rd party API does not return response but calls my another endpoint doPOST
doPOST now need to write an object of httpServletResponse into doGET
doGET returns this object as soon as doPOST is done.
To solve this problem, I figured out I can use some asynchronous programming mechanism like CompletableFuture in java. But I am confused about how to exactly set this mechanism in my code. Here is what I have done so far -
doGET
public void doGET(HttpServletRequest request, HttpServletResponse response) {
// some processing
// Call 3rd Party API
CompletableFuture<HttpServletRequest> completableFuture = CompletableFuture.supplyAsync(() -> doPOST());
while (!completableFuture.isDone()) {
System.out.println("CompletableFuture is not finished yet...");
}
HttpServletRequest result = completableFuture.get();
response = result;
}
I have not been able to figure out how can I set up completableFuture for this. Need help here.
doPOST
public HttpServletResponse doPOST(HttpServletRequest request, HttpServletResponse response) {
// receive 3rd party request
// add data from 3rd party request into a new response object
// add response object into hashmap
}
How can I properly make this work?
You may have a map of request ids vs. an object as property of your class exposing the two methods:
private final Map<String, HttpRequestResponse> requests = new HashMap<>();
... where the class HttpRequestResponse is a simple wrapper for a request (that you receive upon doGET) and a response (that will be supplied by doPOST):
class HttpRequestResponse {
private final HttpServletRequest request;
private final CompletableFuture<HttpServletResponse> responseSupplier;
public HttpRequestResponse(HttpServletRequest request, CompletableFuture<HttpServletResponse> responseSupplier) {
this.request = request;
this.responseSupplier = responseSupplier;
}
public void supplyResponse(HttpServletResponse response) {
this.responseSupplier.complete(response); //<-- this will release the .get()
}
//getters
public CompletableFuture<HttpServletResponse> getSupplier() {
return responseSupplier;
}
}
Upon receiving the request on doGET, you will create the instance and put it into the map, then wait for the result:
public void doGET(HttpServletRequest request, HttpServletResponse response) {
HttpRequestResponse responseSupplier = new HttpRequestResponse(request, new CompletableFuture<>());
requests.put(yourId, responseSupplier); //add supplier to the map (so that doPOST can retrieve it later)
//perform request to your 3rd party API
response = responseSupplier.getSupplier().get(); //<- wait until someone completes the future
}
On the other hand, upon receiving the response from the 3rd party API on doPOST, you will need to get the future by its id, remove it from the map and complete it:
public void doPOST(HttpServletRequest request, HttpServletResponse response) {
HttpRequestResponse responseSupplier = requests.remove(yourId); //<-- removes the supplier from the map and returns it to you
responseSupplier.getSupplier().complete(<your response>); //<-- once you complete the future with a result, the .get() which is hanging on doGET will return
}
Question: why do you want to use asynchronous pattern if the doGET waits anyway for the response to be ready before continuing?
I guess once you put this in place, the doGET can become asynchronous as well and return the execution id (which the client can then listen for a result).
Imagine I have the following the following 2 ClientHttpRequestInterceptors:
public class RequestLoggerInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
log.info("HTTP request: {}", httpRequest);
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}
public class ResponseLoggerInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
log.info("HTTP response: {}", response);
return response
}
}
And I add them both to the same RestTemplate:
ClientHttpRequestInterceptor requestLogger = new RequestLoggerInterceptor();
ClientHttpRequestInterceptor responseLoggerr = new ResponseLoggerInterceptor();
RestTemplate template = new RestTemplate();
template.setInterceptors(Arrays.asList(requestLogger, responseLogger));
What will be the behaviour when a request is executed using this RestTemplate?
In both ClientHttpRequestInterceptors, the clientHttpRequestExecution.execute method is called. Does this mean that the request is executed twice?
From the documentation of ClientHttpRequestExecution
Represents the context of a client-side HTTP request execution. Used
to invoke the next interceptor in the interceptor chain, or - if the
calling interceptor is last - execute the request itself.
Hence the request will not execute twice.
It will execute in following way,
start clientHttpRequestExecution.execute
call first interceptor requestLogger.intercept
call back clientHttpRequestExecution.execute
call second interceptor responseLogger.intercept
call back clientHttpRequestExecution.execute
no interceptor left, call actual ClientHttpRequest.execute
For more details, read InterceptingClientHttpRequest InterceptingRequestExecution.execute method.
RestTemplate allows you to register multiple HTTP request interceptors by implementing the ClientHttpRequestInterceptor(doc) interface.
Every such interceptor is a pass through for the HTTP request, eventually executing the request after passing through all the interceptors.
The signature of the method to be implemented in the interceptor is
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
where the implementation of the execute method of ClientHttpRequestExecution(doc) interface takes care of passing the request to the next interceptor or executing the request
if there are no more interceptors left to be intercepted by.
Since Spring 5.0.x, interceptors are sorted by Spring using AnnotationAwareOrderComparator(doc)
and hence the sorting can be defined with #Order or #Priority too. So if you are in the said Spring version and you annotate the interceptors as below
#Order(1)
public class ResponseLoggerInterceptor implements ClientHttpRequestInterceptor {
...
}
#Order(2)
public class RequestLoggerInterceptor implements ClientHttpRequestInterceptor {
...
}
the request will first be intercepted by ResponseLoggerInterceptor and then by RequestLoggerInterceptor.
So in your case, the request will be intercepted by RequestLoggerInterceptor and ResponseLoggerInterceptor according to the sorted order and then finally executed. The request will be executed only once.
Note:
RestTemplate will be deprecated in a future version and Spring suggests users to use WebClient instead of RestTemplate from version 5.0 onwards.
No, the request is only executed once at the end. When you set the interceptors in the rest template the underlying ClientHttpRequestFactory is wrapped as InterceptingclientHttpRequestFactory with interceptors.
When you made the request using rest template the request is read by interceptors followed by request execution and the response is processed by interceptors. That is all done using ClientHttpRequestExecution.
I would like to extract the URI of an incoming request.
I have the following code in my application - a #RequestMapping which is #Async. I would like to extract the path URI via request.getRequestURI() but it returns null when the #Async annotation is present, otherwise, when it is not, the output is as desired. Is this intended behaviour, if so, why? And how can I obtain the same output with #Async? Removing #Async is not an easy option for me as I would like to use it for performance.
#Async
#RequestMapping("{name}/**")
#ResponseBody
public void incomingRequest(
#PathVariable("name") String name,
HttpMethod method,
HttpServletRequest request,
HttpServletResponse response)
{
String URI = request.getRequestURI(); // <-- null with #Async
}
It seems that in general, all request related parameters are being lost, which is not what I want; I need to extract them for my program.
The closest clue to the reason of empty HttpServletRequest I could find was comment to the answer on this post: What is a life of HttpServletRequest object?
To solve your issue you can try 2 approaches here:
Since your method is void you can manually start a new thread from incomingRequest method using, for example, CompletableFuture.
Or, try with returning CompletableFuture from your method, like so:
#Async
#RequestMapping("{name}/**")
#ResponseBody
public CompletableFuture<Void> incomingRequest(
#PathVariable("name") String name,
HttpMethod method,
HttpServletRequest request,
HttpServletResponse response)
{
String URI = request.getRequestURI(); // should get the right non null path
return CompletableFuture.completedFuture(null);
}
Is it possible to get HttpServletRequest from the ServletContext?
Is it possible to get HttpServletRequest from the ServletContext?
No.
The ServletContext represents the application. The application can cover many sessions and requests. But you can't get the "currently running" request or session via the ServletContext. Detail on how servlets and scopes work can be found in this related answer: How do servlets work? Instantiation, sessions, shared variables and multithreading.
You're unfortunately not clear on the concrete functional requirement where you need this solution. You apparently have a ServletContext at hands somehow in an instance of the class of interest, but not a HttpServletRequest. It's hard to propose an answer showing the right way how to grab the HttpServletRequest in an instance of such class anyway. Decent MVC frameworks like JSF and Spring MVC have ways to grab the HttpServletRequest associated with the current thread in any class you want.
In case you're not using a MVC framework and thus can't use its facilities, then you can achieve this manually by storing the request (and response) as a ThreadLocal<T> in the current thread via a servlet filter.
Here's a kickoff example how such a thread local context class can look like:
public final class YourContext implements AutoCloseable {
private static ThreadLocal<YourContext> instance = new ThreadLocal<>();
private HttpServletRequest request;
private HttpServletResponse response;
private YourContext(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
public static YourContext create(HttpServletRequest request, HttpServletResponse response) {
YourContext context = new YourContext(request, response);
instance.set(context);
return context;
}
public static YourContext getCurrentInstance() {
return instance.get();
}
#Override
public void close() {
instance.remove();
}
// ... (add methods here which return/delegate the request/response).
}
You can create (and close!!) it in a servlet filter as below.
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try (YourContext context = YourContext.create(request, response)) {
chain.doFilter(request, response);
}
}
Do note that closing is very important. Otherwise the thread will get polluted after it has done its job and will be recycled for a different request or even a completely different purpose. In case you aren't on Java 7 yet and thus can't use try-with-resources statement as above, then use a try-finally block.
Then, in any artifact which is invoked by the same thread/request (i.e. other filters, any servlets, any beans/classes (in)directly invoked by those artifacts, etc), you can obtain the HttpServletRequest associated with the current thread as below:
YourContext context = YourContext.getCurrentInstance();
HttpServletRequest request = context.getRequest();
// ...
Or, better create a delegate method, depending on whatever you'd like to do with the current request, such as obtaining the request locale:
YourContext context = YourContext.getCurrentInstance();
Locale requestLocale = context.getRequestLocale();
// ...
As a real world example, Java EE's MVC framework JSF offers exactly this possibility via FacesContext.
FacesContext context = FacesContext.getCurrentInstance();
Locale requestLocale = context.getExternalContext().getRequestLocale();
// ...
I used to have an Open Session In Conversation Filter based on cookies for a JSF 2 app. Now I want to build the same mechanism but technology-agnostic. Reusing some code, I have written this in a class that extends OncePerRequestFilter:
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
UUID conversationId = lookupConversationOrCreateIfNecessary(request,
response);
log.debug("Binding conversation '{}' to request '{}'", conversationId,
request);
bindConversation(conversationId, request);
try {
filterChain.doFilter(request, response);
} finally {
log.debug("Unbinding conversation '{}' from request '{}'",
conversationId, request);
unbindConversation(conversationId, request);
}
}
Now, when I reach bindConversation(conversationId, request) I just add a request attribute which points to the conversationId which is mapped to a Hibernate Session.
Anyways, in JSF I can access the current request by using FacesContext.getCurrentInstance().getExternalContext().getRequest() and implemented a CurrentSessionContext using this. But in plain servlets how can I access the current request programmatically?
Note: I have been reading the OncePerRequestFilter javadocs and I found this:
As of Servlet 3.0, a filter may be invoked as part of a REQUEST or
ASYNC dispatches that occur in separate threads. A filter can be
configured in web.xml whether it should be involved in async
dispatches. However, in some cases servlet containers assume different
default configuration. Therefore sub-classes can override the method
shouldNotFilterAsyncDispatch() to declare statically if they [sic] shouuld
indeed be invoked, once, during both types of dispatches in order to
provide thread initialization, logging, security, and so on. This
mechanism complements and does not replace the need to configure a
filter in web.xml with dispatcher types.
So, would it be dangerous to use a ThreadLocal to achieve what I want?
As you mention in your question: using a ThreadLocal seems a good option. I don't see why it would be unsafe as soon as you use your filter for both REQUEST and ASYNC.
EDIT
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
UUID conversationId = lookupConversationOrCreateIfNecessary(request,
response);
log.debug("Binding conversation '{}' to request '{}'", conversationId,
request);
ConversationHolder.setId(conversationId);
bindConversation(conversationId, request);
try {
filterChain.doFilter(request, response);
} finally {
log.debug("Unbinding conversation '{}' from request '{}'",
conversationId, request);
ConversationHolder.clear();
unbindConversation(conversationId, request);
}
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return false; //to be sure both REQUEST and ASYNC are filtered
}
And the ConversationHolder
public class ConversationHolder extends ThreadLocal<UUID>{
private static ConversationHolder INSTANCE = new ConversationHolder();
public static void setId(UUID conversationId){
INSTANCE.set(conversationId);
}
public static UUID getId(){
return INSTANCE.get();
}
public static void clear(){
INSTANCE.remove();
}
}
Since conversationId is a local variable it won't be shared between request.
Since ConversationHolder is a ThreadLocal, the value you get from it during doFilter(...) will be correct. (except if you create new Thread by hand during your request processing, but it is not a recommended design)