Failed to load PDF in chrome/Firefox/IE

 筆者的公司搭建了一個Nexus服務器,用來管理咱們本身的項目Release構件和Site文檔.chrome

今天的問題是當用戶訪問一個Site裏的PDF文件的時候,報錯說「detected that the network has been closed.」瀏覽器

可是用右鍵點擊,而後另存爲,卻每次都能成功下載下來.原本這幾天公司的網絡確實是不穩定,剛開始還真的覺得是網絡問題.服務器

後來仔細觀察了http請求後發現,每次報錯後瀏覽器都還在不停的發送 http range 請求. 咱們用的是Adbobe的PDF插件.網絡

 

  • Chrome問題

Chrome裏有一個自帶的plugin,能夠經過 chrome://plugins 進入plugin配置頁面,找到自帶的Chrome PDF viewer.app

這個Chrome插件發現提供PDF的Nexus服務器支持range請求後,在讀取完response的header後會主動abort response.this

而後改成經過XHR分批發送Http Range 請求,此時Adbobe的PDF插件就報network已經關閉了。這裏的主要緣由是兩個插件互相沖突,spa

並且chrome自帶的PDF viewer有bug,不能正確組裝返回的range response. 解決辦法能夠是禁用Chrome PDF viewer。插件

 

  • Firefox問題/IE問題

老版本的IE和Firefox都沒問題,有問題的是IE10,Firefox25. 緣由是老版本的IE/Firefox裏的adobe插件不會發生http range 請求.code

新版的IE/Firefox發生了range請求後,服務器也正常的給了response,結果好笑的是adobe 插件不能正確組裝,悲劇。orm

 

最後想到的就是禁用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

相關文章
相關標籤/搜索