使用DeferredResult时InputStream被关闭

3
我有一个老的应用程序,客户端通过非常缓慢的连接(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()中看到文件缓冲区中的文本,但我无法直接从inputStream中读取,而不会得到java.io.IOException: Stream closed异常。
无论使用小文件还是大文件进行多少次测试都无法改变此行为。有没有人成功地在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
嗨jny,我昨天进行了测试。我知道它是有效的,因为如果我立即返回响应,异步输入将继续通过inputstream到达。只有当我添加DeferredResult时,这个问题才会出现。问题在于,我需要DeferredResult的这个想法,除非我知道已经成功备份了他们的上传内容,否则我不能向用户返回任何东西! - Jonathan Keenan
我还没有完全解决它,但我已经找到了(我认为)为什么deferredResult会立即返回的原因。Spring中的DispatcherServlet有一个检查finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } },由于我的asyncWebRequest部分在asyncManager中是null,即使asyncManager的其余属性已设置,它也不会返回true。现在只需要弄清楚那一部分... - Jonathan Keenan
1个回答

1

这个回答解决了我的问题的一部分,但并不完全。经过多个小时的调试和思考,我找到了我的deferredResult未能正确设置的原因。我们包含了一个第三方库来使用一些REST工具,导致应用程序使用了Spring中已废弃的AnnotationMethodHandlerAdapter而不是应该使用的RequestMappingHandlerAdapter。一旦我重写了这个外部bean,deferredResult就可以被正确处理。

唯一的问题是这个调用流程仍然没有实现异步,这又回到了我最初提出的一个子问题,即是否有人成功地将Spring MVC / DeferredResults和Servlet 3.1结合使用。我看到了一些StackOverflow的问题也在询问同样的问题,但遗憾的是没有得到答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接