破损的管道将数据块发送到 chrome,仅通过范围请求流式传输 mp4

Broken pipe sending chunks of data to chrome only streaming mp4 via range requests

我有一个应用程序,我们在其中提供受自定义身份验证和授权框架保护的资源。这导致我们必须遵守范围 headers(特别是对于视频)。

我们必须更改此代码以支持 iOS 和 safari。在此过程中,我们开始从 Chrome 收到破损的管道异常,但我不明白为什么。视频按预期在 chrome、Safari 和 iOS 中播放,但我们想清除异常。

Chrome 在第一个请求中请求整个包,我们发送它。然后它回来并开始请求块(请参见下图中的 VID 请求。

什么会导致 chrome 终止此连接?是不是第一次响应返回整个资产有问题?

我也试过在第一个包裹发送所有东西时返回 200,但结果是一样的。

使用 Safari 时无异常。

最后,我们还有其他由 EAP 提供的不受保护的内容,没有报告异常。

管理响应的代码是:

protected void createResponse(HttpServletResponse resp, ResourceInterface resource,
                              ResourceInstance instance, String range) throws IOException, ServletException {

    _logger.info("Range parameter {}", range);
    int rangeStart = 0;
    int rangeEnd = -1;
    InputStream is = resource.getResourceStream(instance);
    int lengthHeader = is.available();
    if (range != null) {
        String[] rangeSplit = range.split("=");
        //String type = rangeSplit[0];
        String[] rangeVals = rangeSplit[1].split("-");
        rangeStart = Integer.parseInt(rangeVals[0]);
        resp.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
        resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        if (rangeVals.length > 1 && !StringUtils.isBlank(rangeVals[1])) {
            rangeEnd = Integer.parseInt(rangeVals[1]);
            //bufferSize = rangeEnd - rangeStart + 1;
        } else {
            rangeEnd = lengthHeader-1;
        }
        _logger.info("Start Range {} - End Range {}", rangeStart, rangeEnd);

        if (null != is) {
            resp.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + rangeStart+"-"+rangeEnd + "/" + lengthHeader);
            String lastModifiedPattern = "EEE, dd MMM yyyy HH:mm:ss zzz";
            String dateString = DateConverter.getFormattedDate(new Date(), lastModifiedPattern);
            resp.setHeader(HttpHeaders.LAST_MODIFIED, dateString);
            _logger.info("serving resource {} with length {} bytes", instance.getFileName(), lengthHeader);
        }
    } else {
        _logger.info("NULL RANGE");
    }

    resp.setHeader("Content-Disposition", "inline; filename=" + instance.getFileName());
    resp.setContentType(instance.getMimeType());

    //CDP_DW_PUB-279: modifica nome file originale risorse protected
    String fileName = resource.getMasterFileName();
    if ("Image".equals(resource.getType())) {
        int lastIndexOf = instance.getFileName().lastIndexOf("_");
        String suffix = instance.getFileName().substring(lastIndexOf + 1, lastIndexOf + 3);
        StringBuilder sb = new StringBuilder(suffix);
        sb.append("_");
        sb.append(fileName);
        fileName = sb.toString();
    }

    //CDP_DW_PUB-279


    ServletOutputStream out = resp.getOutputStream();
    try {
        is.skip(rangeStart);
        byte[] slice = new byte[rangeEnd-rangeStart+1];
        is.read(slice, 0, rangeEnd-rangeStart+1);
        resp.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(slice.length));
        _logger.info("Slice size "+slice.length);
        out.write(slice);
    } catch (Throwable t) {
        _logger.error("Error extracting protected resource", t);
        throw new ServletException("Error extracting protected resource", t);
    } finally {
        out.close();
    }
}

例外情况很一般:

    Caused by: javax.servlet.ServletException: Error extracting protected resource
    at org.entando.entando.plugins.jacms.aps.servlet.ProtectedResourceProvider.createResponse(ProtectedResourceProvider.java:192)
    at org.entando.entando.plugins.jacms.aps.servlet.ProtectedResourceProvider.provideProtectedResource(ProtectedResourceProvider.java:112)
    ... 69 more
Caused by: org.eclipse.jetty.io.EofException
    at org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:292)
    at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:429)
    at org.eclipse.jetty.io.WriteFlusher.completeWrite(WriteFlusher.java:384)
    at org.eclipse.jetty.io.ChannelEndPoint.run(ChannelEndPoint.java:139)
    ... 7 more
Caused by: java.io.IOException: Broken pipe
    at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
    at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47)
    at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
    at sun.nio.ch.IOUtil.write(IOUtil.java:65)
    at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
    at org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:270)
    ... 10 more

这是网络流量。 VID 调用是对 MP4

的调用

回复headers是:

HTTP/1.1 206 Partial Content
Date: Thu, 08 Aug 2019 07:47:10 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Accept-Ranges: bytes
Last-Modified: Thu, 08 Aug 2019 09:47:10 CEST
Content-Disposition: inline; filename=38012166d7833c09c4b8632ea186634e.mp4

HTTP/1.1 206 Partial Content
Date: Thu, 08 Aug 2019 07:47:10 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Accept-Ranges: bytes
Last-Modified: Thu, 08 Aug 2019 09:47:10 CEST
Content-Disposition: inline; filename=38012166d7833c09c4b8632ea186634e.mp4
Content-Type: video/mp4;charset=utf-8
Server: Jetty(9.4.8.v20180619)
Content-Range: bytes 2097152-2107841/2107842
Content-Length: 10690
Content-Type: video/mp4;charset=utf-8
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Server: Jetty(9.4.8.v20180619)
Content-Range: bytes 0-2107841/2107842
Content-Length: 2107842

HTTP/1.1 206 Partial Content
Date: Thu, 08 Aug 2019 07:47:10 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Accept-Ranges: bytes
Last-Modified: Thu, 08 Aug 2019 09:47:10 CEST
Content-Disposition: inline; filename=38012166d7833c09c4b8632ea186634e.mp4
Content-Type: video/mp4;charset=utf-8
Server: Jetty(9.4.8.v20180619)
Content-Range: bytes 2097152-2107841/2107842
Content-Length: 10690

HTTP/1.1 206 Partial Content
Date: Thu, 08 Aug 2019 07:47:10 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Accept-Ranges: bytes
Last-Modified: Thu, 08 Aug 2019 09:47:10 CEST
Content-Disposition: inline; filename=38012166d7833c09c4b8632ea186634e.mp4
Content-Type: video/mp4;charset=utf-8
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Server: Jetty(9.4.8.v20180619)
Content-Range: bytes 65536-2107841/2107842
Content-Length: 2042306

这最终解决了问题。

protected void createResponse(HttpServletResponse resp, ResourceInterface resource,
            ResourceInstance instance) throws IOException, ServletException {
        resp.setContentType(instance.getMimeType());
        resp.setHeader("Content-Disposition", "inline; filename=" + instance.getFileName());
        ServletOutputStream out = resp.getOutputStream();
        try {
            InputStream is = resource.getResourceStream(instance);
            if (null != is) {
                byte[] buffer = new byte[2048];
                int length = -1;
                // Transfer the data
                while ((length = is.read(buffer)) != -1) {
                    out.write(buffer, 0, length);
                    out.flush();
                }
                is.close();
            }
        } catch (Throwable t) {
            logger.error("Error extracting protected resource", t);
            throw new ServletException("Error extracting protected resource", t);
        } finally {
            out.close();
        }
    }