Unable to to intercept Spring #Async - java

I have a public method annotated with #Async. This method is not in the controller. I want to intercept the thread after completion of the thread execution so that I can clear certain ThreadLocals.
I already looked into AsyncHandlerInterceptor but this gets hit when the controller receives the request and not before and after the #Async execution.
public class SampleAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
#Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("afterConcurrentHandlingStarted " + request.getRequestURI());
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle " + request.getRequestURI());
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle " + request.getRequestURI());
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion " + request.getRequestURI());
}
}
What I would like is an interceptor like CallableProcessingInterceptor, but for #Async operation.

AsyncHandlerInterceptor is for web requests, #Async is general annotation which allows to delegate method execution to thread pool. No surprise these do not work well together.
#Async allows to provide name of thread pool. You can create your own pool which wraps all submitted tasks into your class so later you will intercept all operation with task.

Related

Spring interceptor not working for partial REST endpoints

I have a Spring Boot application with REST endpoints defined like this
/usermanagement/v1/access/ldap
/usermanagement/v1/access/db
I have created a Spring Interceptor to intercept all incoming request with following pattern
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/usermanagement/v1/**");
}
RequestInterceptor
#Component
public class RequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
System.out.println("This is Post Handling the request");
}
}
This interceptor works only if client accesses the complete endpoint i.e
/usermanagement/v1/access/ldap
In case a partial endpoint is accessed,
/usermanagement/v1/access
interceptor is not called and a 404 is returned to the client.
Is there a way to change this behavior? The reason I am doing this is because I don't want to expose specific endpoints but common endpoints and make internal calls to services and return result through common endpoints.
You are using the wrong method. Try using afterCompletion instead of postHandle
new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//This is called before handeling any request
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//This is called after successfully handeling a request. It will not be called in case of an exception
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//This will always be called after a request, even in case of an exception
}
}
With a request to a undefined endpoint /usermanagement/v1/access Spring will throw an exception. Therefor it never will enter postHandle.

Spring secure filter to protect anonymous requests

One more question about spring configuration...
I have several rest methods opened to everyone and not secured. These rest methods on server #1 are used by another server #2 in the same domain to get some data. The idea is that server #2 sets my_super_secure_cookie to some secure token and server #1 decodes and verifies it. Here is the code:
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
// Some code
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/rest/public/*").permitAll()
.anyRequest().authenticated();
}
// More code
}
public class SuperSecurityFilter extends FilterSecurityInterceptor implements Filter {
public SuperSecurityFilter(String key) {
super(key);
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
Cookie[] cookies = req.getCookies();
Optional<Cookie> tokenCookie = Arrays.stream(cookies).filter(cookie -> cookie.getName().equals("my_super_secure_cookie")).findFirst();
if (tokenCookie.isPresent()) {
Cookie cookie = tokenCookie.get();
TokenCookie.create(cookie.getValue()).validate();
} else {
throw new Exception("Ooops!"));
}
chain.doFilter(req, res);
}
}
The question is how do I configure SecurityConfig to use SecurityTokenFilter on request to any of the /rest/public/* rest methods. Something like:
http
.antMatcher("/rest/public/*")
.addFilterBefore(new SuperSecurityFilter());
is not working, SuperSecurityFilter is not called on request.
p.s. I'm forced to work with this type of security model due to current business logic restrictions.
I solved (applied workaround?) the issue I have by implementing not filter, but interceptor, like this:
public class SuperSecurityInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// implementation here
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// Nothing here
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// Nothing here
}
}
And registered this interceptor in my entity extending WebMvcConfigurerAdapter. Like this:
registry.addInterceptor(new SupeSecurityInterceptor()).addPathPatterns("/rest/public/*");
Not sure if this is right thing to do though... Anyway would be glad to know about the conventional approach of implementing this type of functionality.

How to create multiple asynchronous java filters?

I'm trying to create a Java application with multiple asynchronous filters, but cannot seem to get them to work well together. I think the main issue is in the run() method I don't know what to do to pass along the request to the next filter in the chain. I've tried chain.doFilter(request, response), but that doesn't seem to work, and there are dispatch() and complete() APIs available on the AsyncContext, but those seem to close out the entire AsyncContext. It seems like there must be another way to get this to work. Below is a snippet of the filter I'm using - the second filter looks almost identical.
Note: I'm adding headers to try and figure out what is getting called.
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
final AsyncContext asyncContext = request.startAsync();
final HttpServletResponse res = (HttpServletResponse) response;
asyncContext.addListener(new AsyncListener() {
#Override
public void onComplete(AsyncEvent event) throws IOException {
res.addHeader("S-AST2", "onComplete");
}
#Override
public void onTimeout(AsyncEvent event) throws IOException {
res.addHeader("S-AST3", "onTimeout");
}
#Override
public void onError(AsyncEvent event) throws IOException {
res.addHeader("S-AST4", "onError");
}
#Override
public void onStartAsync(AsyncEvent event) throws IOException {
res.addHeader("S-AST0", "onStartAsync");
}
});
asyncContext.start(new Runnable() {
#Override
public void run() {
res.addHeader("S-AST1", "before");
// This doesn't seem to work...
asyncContext.dispatch();
// ... or this ...
asyncContext.complete();
// ... or this ...
chain.doFilter(request, response);
}
});
}
Thanks for any insight!
There are two parts to this answer.
1) The chain.doFilter(request, response); is still required.
2) The reason this was not working is that in each filter and in the servlet I was calling request.startAsync(), which started a new async process, rather than using an existing one. So if the filter started an async process, and the servlet also started one, it would overwrite/ignore the one started in the filter. To solve this you must check to see if an async process is already started, by calling request.isAsyncStarted(), and if it is, rather than starting a new async context, you should get the existing one with request.getAsyncContext(). Below is a helper class I created to do this for each servlet and filter, so that I can just call AsyncHelper.getAsyncContext(request, response) and it will either retrieve the existing AsyncContext, or create a new one.
public class AsyncHelper {
public static AsyncContext getAsyncContext(ServletRequest request, ServletResponse response) {
AsyncContext asyncContext = null;
if (request.isAsyncStarted()) {
asyncContext = request.getAsyncContext();
}
else {
asyncContext = request.startAsync(request, response);
asyncContext.setTimeout(2000);
}
return asyncContext;
}
}
I had the need to decorate the response, and I did not know whether the underlying servlet was doing async or not, or if it already had completed. On Jetty 9.1.x I solved it by expecting IllegalStateException
The following example illustrates how to wrap the response (Using the custom BufferingHttpServletResponseWrapper that buffers all that is written to the response) to intercept input so that it can be decorated.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// Buffer the output to a string in order to calculate its signature and add the signature to a header before it's sent to the client
final BufferingHttpServletResponseWrapper responseWrapper = new BufferingHttpServletResponseWrapper(httpServletResponse);
chain.doFilter(httpServletRequest, responseWrapper);
// This is the only way I can see that will safely let us know if we should treat this as an active async request or not.
try {
httpServletRequest.getAsyncContext().addListener(new AsyncListener() {
#Override
public void onComplete(AsyncEvent event) throws IOException {
LOG.debug("onComplete {}", event);
decorateResponse(responseWrapper);
}
#Override
public void onTimeout(AsyncEvent event) throws IOException {
LOG.debug("onTimeout {}", event);
}
#Override
public void onError(AsyncEvent event) throws IOException {
LOG.debug("onError {}", event);
}
#Override
public void onStartAsync(AsyncEvent event) throws IOException {
LOG.debug("onStartAsync {}", event);
event.getAsyncContext().addListener(this);
}
}
, httpServletRequest, responseWrapper);
LOG.debug("After chain.doFilter, async was started");
} catch (IllegalStateException e) {
LOG.debug("Async not active it appears... {}", e.getMessage());
decorateResponse(responseWrapper);
}
}

Access HttpServletRequest anywhere

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)

How to send response from an interceptor, and halt further execution?

I have an interceptor, and under a certain condition I want to send a string response to the browser and then halt execution completely.
How can I do this?
Override the preHandle method and return false if you want to stop execution.
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
response.getWriter().write("something");
return false;
}
You can do like this which will return a json response to the client
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
response.getWriter().write("{ \"error_description\": \"Invalid Value\"}");
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(400);
return false;
}

Categories

Resources