• Failed to load PDF in chrome/Firefox/IE


     笔者的公司搭建了一个Nexus服务器,用来管理我们自己的项目Release构件和Site文档.

    今天的问题是当用户访问一个Site里的PDF文件的时候,报错说“detected that the network has been closed.”

    但是用右键点击,然后另存为,却每次都能成功下载下来.本来这几天公司的网络确实是不稳定,刚开始还真的以为是网络问题.

    后来仔细观察了http请求后发现,每次报错后浏览器都还在不停的发送 http range 请求. 我们用的是Adbobe的PDF插件.

     

    • Chrome问题

    Chrome里有一个自带的plugin,可以通过 chrome://plugins 进入plugin配置页面,找到自带的Chrome PDF viewer.

    这个Chrome插件发现提供PDF的Nexus服务器支持range请求后,在读取完response的header后会主动abort response.

    然后改为通过XHR分批发送Http Range 请求,此时Adbobe的PDF插件就报network已经关闭了。这里的主要原因是两个插件互相冲突,

    而且chrome自带的PDF viewer有bug,不能正确组装返回的range response. 解决办法可以是禁用Chrome PDF viewer。

     

    • Firefox问题/IE问题

    老版本的IE和Firefox都没问题,有问题的是IE10,Firefox25. 原因是老版本的IE/Firefox里的adobe插件不会发生http range 请求.

    新版的IE/Firefox发生了range请求后,服务器也正常的给了response,结果可笑的是adobe 插件不能正确组装,悲剧。

    最后想到的就是禁用range请求.但是在流浪器上是做不到的,因为是插件自己发送的.而Nexus服务器2.7.1是hard code支持这个特性的。

    range request是http1.1标准里的特性,支持是理所应当的。 下面是NexusContextServlet.Java的部分代码.红色部分显示了这个特性。

    protected void doGetFile(final HttpServletRequest request, final HttpServletResponse response,
          final StorageFileItem file) throws IOException
      {
        // ETag, in "shaved" form of {SHA1{e5c244520e897865709c730433f8b0c44ef271f1}} (without quotes)
        // or null if file does not have SHA1 (like Virtual) or generated items (as their SHA1 would correspond to template,
        // not to actual generated content).
        final String etag;
        if (!file.isContentGenerated() && !file.isVirtual()
            && file.getRepositoryItemAttributes().containsKey(StorageFileItem.DIGEST_SHA1_KEY)) {
          etag = "{SHA1{" + file.getRepositoryItemAttributes().get(StorageFileItem.DIGEST_SHA1_KEY) + "}}";
          // tag header ETag: "{SHA1{e5c244520e897865709c730433f8b0c44ef271f1}}", quotes are must by RFC
          response.setHeader("ETag", """ + etag + """);
        }
        else {
          etag = null;
        }
        // content-type
        response.setHeader("Content-Type", file.getMimeType());
    
        // last-modified
        response.setDateHeader("Last-Modified", file.getModified());
    
        // content-length, if known
        if (file.getLength() != ContentLocator.UNKNOWN_LENGTH) {
          // Note: response.setContentLength Servlet API method uses ints (max 2GB file)!
          // TODO: apparently, some Servlet containers follow serlvet API and assume
          // contents can have 2GB max, so even this workaround below in inherently unsafe.
          // Jetty is checked, and supports this (uses long internally), but unsure for other containers
          response.setHeader("Content-Length", String.valueOf(file.getLength()));
        }
    
        // handle conditional GETs only for "static" content, actual content stored, not generated
        if (!file.isContentGenerated() && file.getResourceStoreRequest().getIfModifiedSince() != 0
            && file.getModified() <= file.getResourceStoreRequest().getIfModifiedSince()) {
          // this is a conditional GET using time-stamp
          response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        }
        else if (!file.isContentGenerated() && file.getResourceStoreRequest().getIfNoneMatch() != null && etag != null
            && file.getResourceStoreRequest().getIfNoneMatch().equals(etag)) {
          // this is a conditional GET using ETag
          response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        }
        else {
          // NEXUS-5023 disable IE for sniffing into response content
          response.setHeader("X-Content-Type-Options", "nosniff");
    
          final List<Range<Long>> ranges = getRequestedRanges(request, file.getLength());
    
          // pour the content, but only if needed (this method will be called even for HEAD reqs, but with content tossed
          // away), so be conservative as getting input stream involves locking etc, is expensive
          final boolean contentNeeded = "GET".equalsIgnoreCase(request.getMethod());
          if (ranges.isEmpty()) {
            if (contentNeeded) {
              try (final InputStream in = file.getInputStream()) {
                sendContent(in, response);
              }
            }
          }
          else if (ranges.size() > 1) {
            response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
            renderer.renderErrorPage(request, response, file.getResourceStoreRequest(), new UnsupportedOperationException(
                "Multiple ranges not yet supported!"));
          }
          else {
            final Range<Long> range = ranges.get(0);
            if (!isRequestedRangeSatisfiable(file, range)) {
              response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
              response.setHeader("Content-Length", "0");
              response.setHeader("Content-Range", "bytes */" + file.getLength());
              return;
            }
            final long bodySize = range.upperEndpoint() - range.lowerEndpoint();
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            response.setHeader("Content-Length", String.valueOf(bodySize));
            response.setHeader("Content-Range",
                range.lowerEndpoint() + "-" + range.upperEndpoint() + "/" + file.getLength());
            if (contentNeeded) {
              try (final InputStream in = file.getInputStream()) {
                in.skip(range.lowerEndpoint());
                sendContent(limit(in, bodySize), response);
              }
            }
          }
        }
      }

    最后幸好我们的请求在到Nexus之前有一个Apache proxy,根据http1.1标准,range request必须是client先发送range 请求,如果服务器支持

    那么才返回状态嘛206和部分数据。所以最后我在proxy中把range header 强行去掉。这样服务器就不会返回206,而是返回200了.

    也没有content-range header返回了,所以插件就会按照老实的方式,等待下载完成,然后渲染打开PDF文件.

    最后的解决方案就是在Apache中增加如下配置.

    LoadModule headers_module modules/mod_headers.so

    RequestHeader unset Range

  • 相关阅读:
    Spring 09 : AOP实例
    Spring08 AOP概念
    Spring 07 : 动态代理
    Spring06 Spring+Junit
    Spring05 : 基于注解的IOC
    Spring03 : 依赖注入
    jupyter修改python核(使用不同的python虚拟环境)
    线性代数的本质——引入几何视角
    图像的去雾与加雾
    从MATLAB看一个IDE应该具有的素质
  • 原文地址:https://www.cnblogs.com/princessd8251/p/3853323.html
Copyright © 2020-2023  润新知