stephanrauh / ngx-extended-pdf-viewer

A full-blown PDF viewer for Angular 16, 17, and beyond
https://pdfviewer.net
Apache License 2.0
449 stars 167 forks source link

Unexpected behavior in range request #2353

Closed scry-UrjitDesai closed 3 weeks ago

scry-UrjitDesai commented 3 weeks ago

Description I have an angular front-end and an express backend. In my backend, I have a route which reads a pdf file and sends it to the frontend. Since the pdf files can be huge (>50mb) I am making use of range-requests as mentioned in the documentation.

In my browser developer tools, I see an initial request and then several other requests being made to the route. The initial request has a response status of 200 and all the others have a response status of 206 which is the expected behavior. However, I notice that the entire pdf file is loaded all at once only when the first request is finished and the response size of the first request is the total file size which I feel shouldn't be the case.

I checked out the following link-https://pdfviewer.net/extended-pdf-viewer/server-side-rendering In the above link, in the networks tab, the pages start to load after the initial few requests. Moreover, the first request has a status code of 200 and the response size is very small.

Here is the code snippet- Angular

<ngx-extended-pdf-viewer
    style="position: relative"
    [(src)]="src"
    [(page)]="page"
    (pageRendered)="onPageRendered($event)"
    (pdfLoaded)="onPdfLoaded()"
    (textLayerRendered)="onTextLayerRendered($event)"
    (pdfLoadingFailed)="onPdfLoadingFailed($event)"
    #pdfViewer
  ></ngx-extended-pdf-viewer>
pdfDefaultOptions.rangeChunkSize = 500 * 1024; // set chunk size to 500kb
this.src = `${this.appConfigService.config.SERVER_URL}/documents/${this.documentId}/stream-pdf`;

Express


const streamPDFService = async (req, res, pdfPath) => {
        newFilePath = path.join(__dirname, '500-pages.pdf');
        /** Calculate Size of file */
        const { size } = await fileInfo(newFilePath);
        const range = req.headers.range;
        /** Check for Range header */
        if (range) {
            /** Extracting Start and End value from Range Header */
            let [start, end] = range.replace(/bytes=/, '').split('-');
            start = parseInt(start, 10);
            end = end ? parseInt(end, 10) : size - 1;
            if (!isNaN(start) && isNaN(end)) {
                end = size - 1;
            }
            if (isNaN(start) && !isNaN(end)) {
                start = size - end;
                end = size - 1;
            }

            // Handle unavailable range request
            if (start >= size || end >= size) {
                // Return the 416 Range Not Satisfiable.
                res.writeHead(416, {
                    'Content-Range': `bytes */${size}`,
                });
                return res.end();
            }

            /** Sending Partial Content With HTTP Code 206 */
            res.writeHead(206, {
                'Content-Range': `bytes ${start}-${end}/${size}`,
                'Accept-Ranges': 'bytes',
                'Content-Length': end - start + 1,
                'Content-Type': 'application/pdf',
            });

            const readable = createReadStream(newFilePath, { start, end });
            pipeline(readable, res, (err) => {
                console.log('err=', err);
            });
        } else {
            res.writeHead(200, {
                'Access-Control-Expose-Headers': 'Accept-Ranges',
                'Access-Control-Allow-Headers': 'Accept-Ranges,range',
                'Accept-Ranges': 'bytes',
                'Content-Length': size,
                'Content-Type': 'application/pdf',
                'Access-Control-Allow-Origin': 'http://localhost:4200',
            });

            if (req.method === 'GET') {
                const readable = createReadStream(newFilePath);
                pipeline(readable, res, (err) => {
                    console.log(err);
                });
            } else {
                res.end();
            }
        }
    } else {
        // throw error
    }
};

I have attached a video link for a smaller file which shows the requests- https://drive.google.com/file/d/1w9FybqJNOJiFAbiZMzUU03V8aiue7sQZ/view?usp=sharing

For context, my backend is being served via nginx.

Versions

"ngx-extended-pdf-viewer": "18.1.14",

stephanrauh commented 3 weeks ago

I don't know why your server doesn't work, but I've pushed a working example to https://github.com/stephanrauh/ngx-extended-pdf-viewer-issues/tree/main/issue2353. Clone the repository, put your large PDF file into the assets/pdf folder, and run npm i express && ng b && node simple-express-server.js.

If you need a more sophisticated approach, have a look at the file express-server.js. However, the simple solution already did the trick for me.

Your code snippet looks almost identical to my solution. Maybe it's really nginx that ruins the show. However, that sounds unlikely to me.