In order to implement long polling I've tried different solution and did not acquire any good result.
So I decide to look into asynchronous methods and DeferredResult.
Here I implemented REST constroller.
#Controller("sessionStateRest")
#RequestMapping("ui")
public class SessionStateRest extends BaseRestResource {
private final Queue<DeferredResult<ModelAndView>> mavQueue = new ConcurrentLinkedQueue<>();
/**
* Rest to check session state.
*
* #return string with session state
*/
#RequestMapping(value = "/session")
public #ResponseBody DeferredResult<ModelAndView> sessionState() {
final DeferredResult<ModelAndView> stateResult = new DeferredResult<>();
this.mavQueue.add(stateResult);
return stateResult;
}
#Scheduled(fixedDelay = 5000)
public void processQueue() {
for(DeferredResult<ModelAndView> result: mavQueue) {
if (null == SecurityHelper.getUserLogin()) {
result.setResult(createSuccessResponse("Invalidated session"));
mavQueue.remove(result);
}
}
}
}
By idea it should process queue of requests every 5 seconds and setResult if condition is true.
Synchronous version would be something like this
#RequestMapping(value = "/sync")
public ModelAndView checkState() {
if (null == SecurityHelper.getUserLogin()) {
createSuccessResponse("Invalidated session");
}
return null; // return something instead
}
But after some time I've got an exception
java.lang.IllegalStateException: Cannot forward after response has been committed
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:349) ~[tomcat-embed-core-7.0.
39.jar:7.0.39]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339) ~[tomcat-embed-core-7.0.39
.jar:7.0.39]
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:467) [tomcat-embed-core-7.0.39.jar:7.0.3
9]
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:338) [tomcat-embed-core-7.0.39.jar:7.0.3
9]
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:428) [tomcat-embed-core-7.0.39.jar:7.
0.39]
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:417) [tomcat-embed-core-7.0.39.jar:
7.0.39]
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:294) [tomcat-embed-core-7.0.39.jar:7
.0.39]
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1567) [tomcat-embed-c
ore-7.0.39.jar:7.0.39]
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:583) [tomcat-embed-cor
e-7.0.39.jar:7.0.39]
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312) [tomcat-embed-core-7.0.39.jar:7.
0.39]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_67]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_67]
at java.lang.Thread.run(Thread.java:745) [na:1.7.0_67]
What's the problem? Should I set the timeout for DeferredResult?
I think the problem comes from the #ResponseBody annotation. It tells Spring that the controller method will directly returns the body of the response. But it does not, because it returns a ModelAndView. So Spring tries to send the return of the method directly to client, (and should send and commit an empty response), then the ModelAndView handler tries to forward to a view with an already committed response causing the error.
You should at least remove the #ResponseBody annotation since it is not it what would be a synchronous equivalent.
But that's not all :
you write final DeferredResult<... - IMHO the final should not be there since you will modify later the DeferredResult
you try to test logged user in the scheduled asynchronous thread. This should not work, since common SecurityHelper use local thread storage to store this info, and actual processing will occur in another thread. Javadoc for DeferredResult even says : For example, one might want to associate the user used to create the DeferredResult by extending the class and adding an addition property for the user. In this way, the user could easily be accessed later without the need to use a data structure to do the mapping.
you do not say how you configured async support. Spring Reference manual says : The MVC Java config and the MVC namespace both provide options for configuring async request processing. WebMvcConfigurer has the method configureAsyncSupport while <mvc:annotation-driven> has an <async-support> sub-element.
Related
I use an external rest api in my spring application, I can send json post requests to create objects but when a field is incorrect or if there is a duplicate it returns a 400 bad request error, and a body saying what the problem is.
I use Spring 5 with #PostExchange in the following code:
This is used to point spring into the right direction of the external api
public interface HardwareClient {
#PostExchange("/assetmgmt/assets/templateId/C04DBCC3-5FD3-45A2-BD34-8A84CE2EAC20")
String addMonitor(#RequestBody Monitor monitor);
}
This is the helper that is autowired into the class where I have the data that needs to be sent.
#Component
public class HardwareHelper {
private Logger logger = Logger.getLogger(getClass().getName());
#Autowired
HardwareClient hardwareClient;
#Async
public Future<String> addMonitor(MonitorForm monitorForm){
try {
Monitor monitor = new Monitor(monitorForm.objectID(), monitorForm.model(), monitorForm.make(),monitorForm.serialNumber(), monitorForm.orderNumber(),monitorForm.budgetholder(),monitorForm.ownership());
hardwareClient.addMonitor(monitor);
return new AsyncResult<String>("Success");
} catch (Exception e){
logger.info("HardwareHelper.addMonitor error: " + e.getMessage());
//todo error handling
}
return null;
}
}
When an error occurs the logger will print the error but I need to be able to control what happens after based on the response. So I need to see the body of the post request that is returned after. If everything goes well an ID is returned that I can read by printing the results of the addMonitor() method, but this is obviously not possible when it throws an exception as it skips to the catch part. How do I scan the request body when an error is thrown and handle this appropriately
Hoping someone else is having the same issue as me, or has other ideas.
I'm currently running Play 1.4.x (not by choice), but also working on upgrading to play 1.5.x, though I verified the same issue happens on both versions.
I created a simple Functional Test that loads data via fixtures
My fixture for loading test data is like so
data.yml
User(testUser):
name: blah
AccessToken(accessToken):
user: testUser
token: foo
Data(testData):
user: testUser
...
I've created a controller to do something with the data like this, that has middleware for authentication check. The routes file will map something like /foo to BasicController.test
public class BasicController extends Controller{
#Before
public void doAuth(){
String token = "foo"; // Get token somehow from header
AccessToken token = AccessToken.find("token = ?", token).first(); // returns null;
// do something with the token
if(token == null){
//return 401
}
//continue to test()
}
public void test(){
User user = //assured to be logged-in user
... // other stuff not important
}
}
Finally I have my functional test like so:
public class BasicControllerTest extends FunctionalTest{
#org.junit.Before
public void loadFixtures(){
Fixtures.loadModels("data.yml");
}
#Test
public void doTest(){
Http.Request request = newRequest()
request.headers.put(...); // Add auth token to header
Http.Response response = GET(request, "/foo");
assertIsOk(response);
}
}
Now, the problem I'm running into, is that I can verify the token is still visible in the headers, but running AccessToken token = AccessToken.find("token = ?", token).first(); returns null
I verified in the functional test, before calling the GET method that the accessToken and user were created successfully from loading the fixtures. I can see the data in my, H2 in-memory database, through plays new DBBrowser Plugin in 1.5.x. But for some reason the data is not returned in the controller method.
Things I've tried
Ensuring that the fixtures are loaded only once so there is no race condition where data is cleared while reading it.
Using multiple ways of querying the database via nativeQuery jpql/hql query language and through plays native way of querying data.
Testing on different versions of play
Any help would be very much appreciated!
This issue happens on functional tests, because JPA transactions must be encapsulated in a job to ensure that the result of the transaction is visible in your method. Otherwise, since the whole functional test is run inside a transaction, the result will only visible at the end of the test (see how to setup database/fixture for functional tests in playframework for a similar case).
So you may try this:
#Test
public void doTest() {
...
AccessToken token = new Job<AccessToken>() {
#Override
public User doJobWithResult() throws Exception {
return AccessToken.find("token = ?", tokenId).first();
}
}.now().get();
....
}
Hoping it works !
I think I had a similar issue, maybe this helps someone.
There is one transaction for the functional test and a different transaction for the controller. Changes made in the test will only become visible by any further transaction if those changes were committed.
One can achieve this by closing and re-opening the transaction in the functional test like so.
// Load / Persist date here
JPA.em().getTransaction().commit(); // commit and close the transaction
JPA.em().getTransaction().begin(); // reopen (if you need it)
Now the data should be returned in the controller method.
So your test would look like this:
public class BasicControllerTest extends FunctionalTest{
#org.junit.Before
public void loadFixtures(){
Fixtures.loadModels("data.yml");
JPA.em().getTransaction().commit();
// JPA.em().getTransaction().begin(); reopen (if you need it)
}
#Test
public void doTest(){
Http.Request request = newRequest()
request.headers.put(...); // Add auth token to header
Http.Response response = GET(request, "/foo");
assertIsOk(response);
}
}
I did never try this with fixtures. But i would assume they run in the same transaction.
I need to write a "disposable file download" MVC controller on top of a "file download controller". Once a file has been transferred to the client, it must be deleted from the server.
Initially, the code was written to serve files
import org.springframework.core.io.Resource
#GetMapping("/get/{someParam}")
public ResponseEntity<Resource> downloadFile(Long someParam)
{
Long fileId = identify(someParam);
return super.downloadFile(fileId); //This uses a "File repository" service binding file IDs to physical paths
}
protected ResponseEntity<Resource> downloadFile(Long fileId){
File theFile = resolve(fileId);
return new FileSystemResource(theFile);
}
Since the ResponseEntity is some kind of "future" entity, I can't delete the file in a finally block because it won't be served yet.
So I wrote an async version of file download first, leveraging Commons IO to copy the payload. Then I leveraged the callbacks in order to dispose of the file from only my method.
protected WebAsyncTask<Void> downloadFileAsync(Long fileId,HttpResponse response){ //API method for multiple uses across the application
InputStream is = new FileInputStream(resolve(fileId));
Callable<Void> ret = () -> {
IOUtils.copy(is,response.getOutputStream());
is.close();
return null;
};
return ret;
}
#GetMapping("/get/{someParam}")
public WebAsyncTask<Void> downloadFile(Long someParam,HttpResponse response)
{
Long fileId = identify(someParam);
WebAsyncTask ret = downloadFileAsync(fileId,response);
ret.onCompletion(()-> fileService.delete(fileId)); //Here I leverage the callback because this file, in this point, is disposable
return ret;
}
When I run the second version, I get the following error. Server is Tomcat 8.0.50
10-Sep-2018 12:20:37.551 AVVERTENZA [ajp-nio-8009-exec-3] org.apache.catalina.core.AsyncContextImpl.setErrorState onError() failed for listener of type [org.apache.catalina.core.AsyncListenerWrapper]
java.lang.IllegalArgumentException: Cannot dispatch without an AsyncContext
at org.springframework.util.Assert.notNull(Assert.java:134)
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.dispatch(StandardServletAsyncWebRequest.java:128)
at org.springframework.web.context.request.async.WebAsyncManager.setConcurrentResultAndDispatch(WebAsyncManager.java:369)
at org.springframework.web.context.request.async.WebAsyncManager.access$200(WebAsyncManager.java:60)
at org.springframework.web.context.request.async.WebAsyncManager$3.handle(WebAsyncManager.java:311)
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onError(StandardServletAsyncWebRequest.java:144)
at org.apache.catalina.core.AsyncListenerWrapper.fireOnError(AsyncListenerWrapper.java:49)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:421)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:370)
at org.apache.coyote.ajp.AbstractAjpProcessor.asyncDispatch(AbstractAjpProcessor.java:745)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:666)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1539)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1495)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
I have configured all servlets and filters to support async operation in my web.xml. I did some research and this answer was no help because I am using a newer Tomcat version.
What is wrong with my code? I have not posted it entirely to keep it simply simple, but debugging I see that the write operation succeeds with correct payload.
I had the same problem. In my case, the solution was to configure an AsyncTaskExecutor:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(-1);
configurer.setTaskExecutor(asyncTaskExecutor());
}
#Bean
public AsyncTaskExecutor asyncTaskExecutor() {
// an implementaiton of AsyncTaskExecutor
return new SimpleAsyncTaskExecutor("async");
}
}
According to #MDenium's comment
Don't use WebAsyncTask that is intended for internal use. Just use a CompletableFuture or return a Callable. If you put the try/finally inside your Callable it will work
WebAsyncTask is just not an API, so Spring doesn't know how to handle it when you return from an MVC method. It's not the correct way to perform async execution. It is used only internally to carry the task and the context.
Spring MVC supports:
DeferredResult
Callable
CompletableFuture
and probably a few others
I need to figure out a way to save and log a method request and response conditionally, with the condition being the latency of the top-level method crossing the p50 latency. The call visualization is as follows:
topLevel() -> method1() -> method2() -> ... -> makeRequest()
In makeRequest is where the request and response to that request are that I need to log.
But I'll only know if I need to actually log those at some point on the way back up the call stack - if topLevel method is taking too long.
So to me, the only option is to save the request and response in makeRequest no matter what and make that available to the topLevel method. The topLevel method will check if latency is above p50 and conditionally log the request and response.
This all leads to the titular question: How to share memory over long chain of method calls?
I don't want to be passing objects back through multiple method calls, polluting function signatures.
What is the best pattern for this? Maybe using a local cache to save the request and response and then retrieving it in topLevel? Is there an aspect oriented approach to solving this?
As long as you have control of the code for the top level and down through method1 and method2, this really isn't so hard.
You just need to pass the request down through the calling chain, and pass back the response.
topLevel() -> method1(request) -> method2(request) -> ...
-> makeRequest(request) { ... return response; }
To relate this to a real code example, you can look at how the jersey framework works.
Here's an example of a method where the request is injected, and a response is returned.
#POST
#Consumes({MediaType.TEXT_XML})
#Produces({TEXT_XML_UTF_8})
public Response resource(#Context HttpServletRequest servletRequest) throws Exception {
ExternalRequest req = makeRequest(servletRequest.getInputStream());
ExternalResponse resp = externalGateway.doSomething(req);
return Response.ok(wrapResponse(resp)).build();
}
Although Jersey offers some fancy annotations (#Context and so on), there isn't really a distinguishable design pattern here of any significance - you're just passing down the request object and returning a response.
Of course you can also maintain a cache and pass that up the call stack, or really just a wrapper object for a request and a response, but it's very similar to simply passing the request.
This type of functionality is best done using ThreadLocals. Your makeRequest will add the request and response objects into ThreadLocal and then topLevel will remove them and log them if needed. Here is an example:
public class RequestResponseThreadLocal{
public static ThreadLocal<Object[]> requestResponseThreadLocal = new ThreadLocal<>();
}
public class TopLevel{
public void topLevel(){
try{
new Method1().method1();
Object[] requestResponse = RequestResponseThreadLocal.requestResponseThreadLocal.get();
System.out.println( requestResponse[0] + " : " + requestResponse[1] );
}finally{
//make sure to clean up stuff that was added to ThreadLocal otherwise you will end up with memory leak when using Thread pools
RequestResponseThreadLocal.requestResponseThreadLocal.remove();
}
}
}
public class Method1{
public void method1(){
new Method2().method2();
}
}
public class Method2{
public void method2(){
new MakeRequest().makeRequest();
}
}
public class MakeRequest{
public void makeRequest(){
Object request = new Object();
Object response = new Object();
RequestResponseThreadLocal.requestResponseThreadLocal.set( new Object[]{request, response} );
}
}
I have used spring declarative retry in my project like
#Service
class Service {
#Async #Retryable(maxAttempts=12, backoff=#Backoff(delay=100, maxDelay=500))
public service() {
// ... do something
}
}
Now, I have two questions.
Is it fine to use retry with async, I don't have any issue, just
want to be sure.
The second requirement is, if the process fails I want to log it to log file including the number of remaining retries. So, is there a way to pass, or obtain the number of remaining retries from inside the method?
There is no way around using the annotations, #Recover annotated method executes only after the last failed retry, not after each one failing.
Refer to this github documentation
An excerpt from the link above- "Call the "service" method and if it fails with a RemoteAccessException then it will retry (up to three times by default), and then execute the "recover" method if unsuccessful."
Even with using RetryTemplate the Retry callback is called only after all retries are exhausted.
Another excerpt form the same link- "When a retry is exhausted the RetryOperations can pass control to a different callback, the RecoveryCallback. To use this feature clients just pass in the callbacks together to the same method"
You should use the #Recover annotation to perform an action on each fail and keep a count inside your object outside of the methods. Make sure no other methods interact with this counter. Here is the basic premise:
#Service
class Service {
private int attemptsLeft=12;
#Retryable(maxAttempts=12, backoff=#Backoff(delay=100, maxDelay=500))
public service() {
// ... do something that throws a KnownException you create to catch later on.
}
#Recover
public void connectionException(KnownException e) {
this.attemptsLeft = this.attemptsLeft-1; //decrease your failure counter
Logger.warn("Retry attempts left:{}",attemptsLeft);
}
}
If you don't want a member variable tracking count, you might need to ditch the annotations and declare the RetryTemplate to get access to the context, with the getRetryCount() method.
public String serviceWithRetry() {
RetryTemplate retryTemplate = new RetryTemplate();
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(12);
retryTemplate.setRetryPolicy(retryPolicy);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setInterval(100L);
retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.execute(new RetryCallback<Void, RuntimeException>()
return retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
#Override
public void doWithRetry(RetryContext context) {
LOG.info("Retry of connection count: {}", context.getRetryCount());
return //something with your connection logic
}
});
}