【发布时间】:2015-09-21 16:53:34
【问题描述】:
我有一个旧应用程序,客户端通过非常慢的连接 (GPRS) 上传大文件。目前我们使用 Spring MVC 和较旧的 servlet 2.0 标准并直接获取请求 inputStream,这显然会导致长时间运行的阻塞线程。我的任务是将应用程序升级到 servlet 3.1 以利用新的异步读取侦听器(这被认为是直截了当的!!)但是我已经走到了死胡同。
将应用程序直接升级到 servlet 3.1 很顺利,忽略了我立即返回响应的事实,因为我更关心测试文件本身是否正确上传并且 onDataAvailable() 正在执行的事实多次正确调用。当我去使用 DeferredResult 对象(这似乎或多或少是为此目的而发明的)将视图名称返回给控制器时,就会出现问题,因为我显然需要让客户端知道上传是否成功并且在这种异步环境中,我看不出有其他方法可以做到这一点。
当我返回 DeferredResult 对象时,它被错误地直接返回给客户端,并且在第一次 onDataAvailable() 调用时,无法读取 inputStream,因为我收到 java.io.IOException: Stream closed 异常。此处对 getDispatcherType() 的调用告诉我,该类型现在已切换为 DispatcherType.ERROR,而当我在没有 DeferredResult 对象的情况下运行代码时(并且无论如何都会立即响应),该类型仍然是 DispatcherType.REQUEST。我曾认为在堆栈中的某个地方,inputStream 被提前读取(不是我)并导致了这个,但它似乎不是,奇怪的是 inputStream.isReady() 返回 true 并且 inputStream.isFinished() 返回 false 并且我可以在第一个 onDataAvailable() 的缓冲区中看到文件中的文本,但如果没有得到 java.io.IOException: Stream closed exception,我就无法直接从 inputStream 中读取。
对小文件或非常大的文件进行的任何测试都不会改变这种行为。有没有人在 Spring MVC 和 servlet 3.1 中成功使用了 DeferredResult?我已经被难住了好几天了,调试的代码比我想再做的还要多!
这是我的代码(一些业务内容被剪掉,以防你觉得它看起来有点乏味)
@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");
}
}
【问题讨论】:
-
您是否将 servlet 配置为异步的?即在 web.xml 中设置
<async-supported>true</async-supported> -
嗨,jny,我做到了。我知道这很有效,因为如果我直接返回响应,异步输入就会不断地通过输入流。只有在添加 DeferredResult 时才会出现此问题。问题是,我需要这个 DeferredResult 的想法,在我知道我已经成功备份了他们的上传之前,我不能向用户返回任何东西!
-
我还没有完全解决它,但我已经找到(我认为)为什么 deferredResult 会立即返回。 Spring 中的
DispatcherServlet有一个检查finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } },它没有返回true,因为asyncManager的asyncWebRequest部分是null,即使asyncManager上的其余属性已设置。现在只是弄清楚那部分......
标签: java spring servlets asynchronous deferred