From 84d492d03bdc5ff9622a258f75b7b7fc296ebc74 Mon Sep 17 00:00:00 2001 From: Mukul Mishra Date: Thu, 10 Aug 2017 13:00:54 +0530 Subject: [PATCH] Adds fetch stream logic for networking part of PDF.js --- src/display/fetch_stream.js | 200 ++++++++++++++++++++++++++++++++++++ src/pdf.js | 5 +- 2 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/display/fetch_stream.js diff --git a/src/display/fetch_stream.js b/src/display/fetch_stream.js new file mode 100644 index 00000000000000..b26df74445f445 --- /dev/null +++ b/src/display/fetch_stream.js @@ -0,0 +1,200 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { assert, createPromiseCapability } from '../shared/util'; +import { validateRangeRequestCapabilities } from './network_utils'; + +class PDFFetchStream { + constructor(options) { + this.options = options; + this.source = options.source; + this.isHttp = /^https?:/i.test(this.source.url); + this.httpHeaders = (this.isHttp && this.source.httpHeaders) || {}; + + this._fullRequestReader = null; + this._rangeRequestReaders = []; + } + + getFullReader() { + assert(!this._fullRequestReader); + this._fullRequestReader = new PDFFetchStreamReader(this); + return this._fullRequestReader; + } + + getRangeReader(begin, end) { + let reader = new PDFFetchStreamRangeReader(this, begin, end); + this._rangeRequestReaders.push(reader); + return reader; + } + + cancelAllRequests(reason) { + if (this._fullRequestReader) { + this._fullRequestReader.cancel(reason); + } + let readers = this._rangeRequestReaders.slice(0); + readers.forEach(function(reader) { + reader.cancel(reason); + }); + } +} + +class PDFFetchStreamReader { + constructor(stream) { + this._stream = stream; + this._reader = null; + this._loaded = 0; + this._contentLength = this._stream.source.length; + this._headersCapability = createPromiseCapability(); + this._disableRange = this._stream.options.disableRange; + this._rangeChunkSize = this._stream.source.rangeChunkSize; + if (!this._rangeChunkSize && !this._disableRange) { + this._disableRange = true; + } + + this._isRangeSupported = !this._stream.options.disableRange; + this._isStreamingSupported = !this._stream.source.disableStream; + + this._headers = new Headers(); + for (let property in this._stream.httpHeaders) { + let value = this._stream.httpHeaders[property]; + if (typeof value === 'undefined') { + continue; + } + this._headers.append(property, value); + } + + fetch(this._stream.source.url, { + method: 'GET', + headers: this._headers, + }).then((response) => { + this._headersCapability.resolve(); + this._reader = response.body.getReader(); + + let { allowRangeRequests, suggestedLength, } = + validateRangeRequestCapabilities({ + getResponseHeader: (name) => { + return response.headers.get(name); + }, + isHttp: this._stream.isHttp, + rangeChunkSize: this._rangeChunkSize, + disableRange: this._disableRange, + }); + + this._contentLength = suggestedLength; + this._isRangeSupported = allowRangeRequests; + }).catch(this._headersCapability.reject); + + this.onProgress = null; + } + + get headersReady() { + return this._headersCapability.promise; + } + + get contentLength() { + return this._contentLength; + } + + get isRangeSupported() { + return this._isRangeSupported; + } + + get isStreamingSupported() { + return this._isStreamingSupported; + } + + read() { + return this._headersCapability.promise.then(() => { + return this._reader.read().then(({ value, done, }) => { + if (done) { + return Promise.resolve({ value, done, }); + } + this._loaded += value.byteLength; + if (this.onProgress) { + this.onProgress({ + loaded: this._loaded, + total: this._contentLength, + }); + } + let buffer = new Uint8Array(value).buffer; + return Promise.resolve({ value: buffer, done: false, }); + }); + }); + } + + cancel(reason) { + this._reader.cancel(reason); + } +} + +class PDFFetchStreamRangeReader { + constructor(stream, begin, end) { + this._stream = stream; + this._reader = null; + this._loaded = 0; + + this._readCapability = createPromiseCapability(); + this._isStreamingSupported = !stream.source.disableStream; + + this._headers = new Headers(); + for (let property in this._stream.httpHeaders) { + let value = this._stream.httpHeaders[property]; + if (typeof value === 'undefined') { + continue; + } + this._headers.append(property, value); + } + + let rangeStr = begin + '-' + (end - 1); + this._headers.append('Range', 'bytes=' + rangeStr); + fetch(this._stream.source.url, { + method: 'GET', + headers: this._headers, + }).then((response) => { + this._readCapability.resolve(); + this._reader = response.body.getReader(); + }); + + this.onProgress = null; + } + + get isStreamingSupported() { + return this._isStreamingSupported; + } + + read() { + return this._readCapability.promise.then(() => { + return this._reader.read().then(({ value, done, }) => { + if (done) { + return Promise.resolve({ value, done, }); + } + this._loaded += value.byteLength; + if (this.onProgress) { + this.onProgress({ loaded: this._loaded, }); + } + let buffer = new Uint8Array(value).buffer; + return Promise.resolve({ value: buffer, done: false, }); + }); + }); + } + + cancel(reason) { + this._reader.cancel(reason); + } +} + +export { + PDFFetchStream, +}; diff --git a/src/pdf.js b/src/pdf.js index 9c0b1488a6986b..c2c02a06a73024 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -34,7 +34,10 @@ if (typeof PDFJSDev === 'undefined' || if (pdfjsSharedUtil.isNodeJS()) { var PDFNodeStream = require('./display/node_stream.js').PDFNodeStream; pdfjsDisplayAPI.setPDFNetworkStreamClass(PDFNodeStream); - } else { + } else if (typeof Response !== 'undefined' && 'body' in Response.prototype) { + var PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream; + pdfjsDisplayAPI.setPDFNetworkStreamClass(PDFFetchStream); + } else { var PDFNetworkStream = require('./display/network.js').PDFNetworkStream; pdfjsDisplayAPI.setPDFNetworkStreamClass(PDFNetworkStream); }