Skip to content

Commit

Permalink
Adds fetch stream logic for networking part of PDF.js
Browse files Browse the repository at this point in the history
  • Loading branch information
mukulmishra18 committed Aug 29, 2017
1 parent f54dfc6 commit 3516a59
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 20 deletions.
222 changes: 222 additions & 0 deletions src/display/fetch_stream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/* 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 {
createResponseStatusError, validateRangeRequestCapabilities,
validateResponseStatus
} from './network_utils';

function createFetchOptions(headers, withCredentials) {
return {
method: 'GET',
headers,
mode: 'cors',
credentials: withCredentials ? 'omit' : 'include',
redirect: 'follow',
};
}

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._withCredentials = stream.source.withCredentials;
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);
}

let url = this._stream.source.url;
fetch(url, createFetchOptions(this._headers, this._withCredentials)).
then((response) => {
if (!validateResponseStatus(response.status, this._stream.isHttp)) {
throw createResponseStatusError(response.status, url);
}
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) {
if (this._reader) {
this._reader.cancel(reason);
}
}
}

class PDFFetchStreamRangeReader {
constructor(stream, begin, end) {
this._stream = stream;
this._reader = null;
this._loaded = 0;
this._withCredentials = stream.source.withCredentials;
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);
let url = this._stream.source.url;
fetch(url, createFetchOptions(this._headers, this._withCredentials)).
then((response) => {
if (!validateResponseStatus(response.status, this._stream.isHttp)) {
throw createResponseStatusError(response.status, url);
}
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) {
if (this._reader) {
this._reader.cancel(reason);
}
}
}

export {
PDFFetchStream,
};
16 changes: 4 additions & 12 deletions src/display/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
* limitations under the License.
*/

import { assert, createPromiseCapability } from '../shared/util';
import {
assert, createPromiseCapability, MissingPDFException,
UnexpectedResponseException
} from '../shared/util';
createResponseStatusError, validateRangeRequestCapabilities
} from './network_utils';
import globalScope from '../shared/global_scope';
import { validateRangeRequestCapabilities } from './network_utils';

if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
throw new Error('Module "./network" shall not ' +
Expand Down Expand Up @@ -417,14 +416,7 @@ PDFNetworkStreamFullRequestReader.prototype = {

_onError: function PDFNetworkStreamFullRequestReader_onError(status) {
var url = this._url;
var exception;
if (status === 404 || status === 0 && /^file:/.test(url)) {
exception = new MissingPDFException('Missing PDF "' + url + '".');
} else {
exception = new UnexpectedResponseException(
'Unexpected server response (' + status +
') while retrieving PDF "' + url + '".', status);
}
var exception = createResponseStatusError(status, url);
this._storedError = exception;
this._headersReceivedCapability.reject(exception);
this._requests.forEach(function (requestCapability) {
Expand Down
21 changes: 20 additions & 1 deletion src/display/network_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
* limitations under the License.
*/

import { assert, isInt } from '../shared/util';
import { assert, isInt, MissingPDFException, UnexpectedResponseException
} from '../shared/util';

function validateRangeRequestCapabilities({ getResponseHeader, isHttp,
rangeChunkSize, disableRange, }) {
Expand Down Expand Up @@ -52,6 +53,24 @@ function validateRangeRequestCapabilities({ getResponseHeader, isHttp,
return returnValues;
}

function createResponseStatusError(status, url) {
if (status === 404 || status === 0 && /^file:/.test(url)) {
return new MissingPDFException('Missing PDF "' + url + '".');
}
return new UnexpectedResponseException(
'Unexpected server response (' + status +
') while retrieving PDF "' + url + '".', status);
}

function validateResponseStatus(status, isHttp) {
if (!isHttp) {
return status === 0;
}
return status === 200 || status === 206;
}

export {
createResponseStatusError,
validateRangeRequestCapabilities,
validateResponseStatus,
};
5 changes: 4 additions & 1 deletion src/pdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
17 changes: 12 additions & 5 deletions src/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,17 @@ function wrapReason(reason) {
}
}

function makeReasonSerializable(reason) {
if (!(reason instanceof Error) ||
reason instanceof AbortException ||
reason instanceof MissingPDFException ||
reason instanceof UnexpectedResponseException ||
reason instanceof UnknownErrorException) {
return reason;
}
return new UnknownErrorException(reason.message, reason.toString());
}

function resolveOrReject(capability, success, reason) {
if (success) {
capability.resolve();
Expand Down Expand Up @@ -1307,16 +1318,12 @@ function MessageHandler(sourceName, targetName, comObj) {
data: result,
});
}, (reason) => {
if (reason instanceof Error) {
// Serialize error to avoid "DataCloneError"
reason = reason + '';
}
comObj.postMessage({
sourceName,
targetName,
isReply: true,
callbackId: data.callbackId,
error: reason,
error: makeReasonSerializable(reason),
});
});
} else if (data.streamId) {
Expand Down
9 changes: 8 additions & 1 deletion test/unit/jasmine-boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function initializePDFJS(callback) {
'pdfjs/display/global',
'pdfjs/display/api',
'pdfjs/display/network',
'pdfjs/display/fetch_stream',
'pdfjs-test/unit/annotation_spec',
'pdfjs-test/unit/api_spec',
'pdfjs-test/unit/bidi_spec',
Expand Down Expand Up @@ -76,9 +77,15 @@ function initializePDFJS(callback) {
var displayGlobal = modules[0];
var displayApi = modules[1];
var PDFNetworkStream = modules[2].PDFNetworkStream;
var PDFFetchStream = modules[3].PDFFetchStream;

// Set network stream class for unit tests.
displayApi.setPDFNetworkStreamClass(PDFNetworkStream);
if (typeof Response !== 'undefined' && 'body' in Response.prototype) {
displayApi.setPDFNetworkStreamClass(PDFFetchStream);
} else {
displayApi.setPDFNetworkStreamClass(PDFNetworkStream);
}

// Configure the worker.
displayGlobal.PDFJS.workerSrc = '../../build/generic/build/pdf.worker.js';
// Opt-in to using the latest API.
Expand Down

0 comments on commit 3516a59

Please sign in to comment.