Reputation: 41
I have an old application where the client uploads large files over a very slow connection (GPRS). Currently we use Spring MVC and the older servlet 2.0 standard and get the request inputStream directly which obviously leads to long running blocked threads. I've been tasked with upgrading the application to servlet 3.1 to take advantage of the new asynchronous read listener (which was thought to be a straight forward!!) however I've hit a dead end.
Doing a straight forward upgrade of the application to servlet 3.1 went fine, ignoring the fact that I was returning the response straight away as I was more concerned with testing the fact that the file itself was uploaded correctly and onDataAvailable() was being called correctly a number of times. The problem occurs when I went to use the DeferredResult object (which seems like it was more or less invented for this purpose) to return the view name to the controller as I need to obviously let the client know if the upload was successful or not and I can see no other way to do this in this asynchronous environment.
When I return DeferredResult object, it is being incorrectly returned straight away to the client and on the first onDataAvailable() call the inputStream can not be read from as I get a java.io.IOException: Stream closed exception. A call to getDispatcherType() here tells me that the type has now switched to DispatcherType.ERROR whereas when I run the code without the DeferredResult object (and respond straight away regardless) the type is still DispatcherType.REQUEST. I had thought that somewhere in the stack the inputStream was getting read early (not by me) and causing this but it doesnt seem to be and the strange thing is that inputStream.isReady() returns true and inputStream.isFinished() returns false and I can see the text off the file in the buffer on the first onDataAvailable() but I just cannot read from the inputStream directly without getting the java.io.IOException: Stream closed exception.
No amount of testing with tiny files or really large files changes the behaviour of this. Has anyone successfully used the DeferredResult within Spring MVC and the servlet 3.1? I've been stumped for few days now and have debugged more code than I care to do again!
Heres come of my code (some business stuff clipped in case you think it looks a bit anaemic)
@Controller
public class ContentController {
@RequestMapping(value = “/upload/“, method = POST)
public DeferredResult<String> upload(ServletRequest request) throws IOException, InterruptedException {
DeferredResult<String> deferredResult = new DeferredResult<String>();
final AsyncContext asyncContext = httpServletRequest.startAsync();
ServletInputStream servletInputStream = httpServletRequest.getInputStream();
NioReadListener readListener = new NioReadListener(servletInputStream, asyncContext, deferredResult, size);
servletInputStream.setReadListener(readListener);
return deferredResult
}
public class NioReadListener implements ReadListener {
private final ServletInputStream _input;
private final AsyncContext _context;
private final DeferredResult<String> deferredResult;
public NioReadListener(ServletInputStream servletInputStream, AsyncContext asyncContext, DeferredResult<String> deferredResult, Long size) throws IOException {
this._input = servletInputStream;
this._context = asyncContext;
this.deferredResult = deferredResult;
}
@Override
public void onDataAvailable() throws IOException {
try {
int bytesRead;
byte b[] = new byte[_size.intValue()];
while (_input.isReady() && (bytesRead = _input.read(b)) != -1) {
_totalBytesRead += bytesRead;
}
} catch (IOException e) {
_log.error(e);
}
}
@Override
public void onAllDataRead() throws IOException {
this._context.complete();
deferredResult.setResult("VIEW_NAME");
}
}
Upvotes: 3
Views: 910
Reputation: 41
(This is kind of answer to my original problem but just not the answer to whole application problem). After many hours of debugging and head scratching, I found the reason why my deferredResult was not getting set correctly. We were including a 3rd party library for some REST utils which was making the application use the deprecated AnnotationMethodHandlerAdapter
from Spring rather than the RequestMappingHandlerAdapter
it should be using. Once i overrode this external bean, the deferredResult was being dealt with correctly.
Only thing is that async nature of this call flow still isnt working which goes back to one of my original sub questions as to whether anyone has got Spring MVC / DeferredResults and Servlet 3.1 working together. I've seen a few stackoverflow questions ask the same thing but with no answer unfortunately.
Upvotes: 1