【问题标题】:InputStream closed when using DeferredResult使用 DeferredResult 时 InputStream 关​​闭
【发布时间】: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 中设置 &lt;async-supported&gt;true&lt;/async-supported&gt;
  • 嗨,jny,我做到了。我知道这很有效,因为如果我直接返回响应,异步输入就会不断地通过输入流。只有在添加 DeferredResult 时才会出现此问题。问题是,我需要这个 DeferredResult 的想法,在我知道我已经成功备份了他们的上传之前,我不能向用户返回任何东西!
  • 我还没有完全解决它,但我已经找到(我认为)为什么 deferredResult 会立即返回。 Spring 中的DispatcherServlet 有一个检查finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } },它没有返回true,因为asyncManagerasyncWebRequest 部分是null,即使asyncManager 上的其余属性已设置。现在只是弄清楚那部分......

标签: java spring servlets asynchronous deferred


【解决方案1】:

(这是对我最初问题的一种回答,但不是对整个应用程序问题的回答)。经过数小时的调试和摸索,我找到了 deferredResult 设置不正确的原因。我们为一些 REST 实用程序包括了一个第三方库,它使应用程序使用 Spring 中已弃用的 AnnotationMethodHandlerAdapter,而不是它应该使用的 RequestMappingHandlerAdapter。一旦我覆盖了这个外部 bean,deferredResult 就会被正确处理。

唯一的问题是这个调用流程的异步性质仍然不起作用,这可以追溯到我最初的子问题之一,即是否有人让 Spring MVC / DeferredResults 和 Servlet 3.1 一起工作。我看到一些 stackoverflow 问题问同样的问题,但遗憾的是没有答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-05
    • 2018-02-25
    • 1970-01-01
    • 2023-01-25
    • 2012-03-23
    • 2017-02-25
    • 2021-02-10
    • 2018-05-02
    相关资源
    最近更新 更多