为什么在通过 Firebase 中的 Express 应用程序从 Google Cloud Storage 提供 PDF 时下载在 Chrome 中挂起?

问题描述

我创建了一个 express 应用程序来向客户端提供存储在 Google 云存储中的 PDF 文件

当点击文件的端点时,下载在 Chrome 中间歇性地挂起,下载的 KB 数是随机的,通常在 1000 KB 到 1300 KB 的范围内。但是我通常可以在 Firefox、Safari 和 Postman 中成功下载文件

downloading network failures

我还注意到,如果我省略了强制浏览器尝试呈现 PDF 的“Content-disposition”标头,浏览器会开始在 Chrome 中呈现它,但会以某个随机加载百分比挂起。在 Firefox/Safari 中,它会在第一次加载时以随机百分比挂起,但通常会在立即刷新 URL 时呈现。

我不确定我是否错误地使用了流/缓冲区,或者我是否错误地返回了从 Google Storage 收到的缓冲区。我已经在谷歌上搜索了几个小时,但我的想法已经用完了,如果有人提供任何帮助,我将不胜感激。

是否有更好的方式来提供文件,或者我是否需要以不同于谷歌云存储的方式下载文件

感谢您看到这里!

// FileService for fetching from Google Storage
export class FilesService {

    pdfBucket: Bucket

    constructor() {
        this.pdfBucket = admin.storage().bucket(config.firebase_ppe_product_pdfs_bucket)
    }

    getPdfFile(filePath: string) : File {
        return this.pdfBucket.file(filePath);
    }

    async getBufferOfPdfFile(filePath: string) {
        const file = await this.getPdfFile(filePath)

        // Download the file to a buffer
        // Omit the "destination" option from download method to receive a buffer
        const [ buffer ] = await file.download()

        return buffer
    }
}
/* index.ts - main express server */ 

import * as functions from "firebase-functions";
import logger from "morgan";
import cookieParser from "cookie-parser";
import express from "express";
import cors from "cors";

import files from './routers/files'

const app = express();

app.use(cors());
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());

app.use('/files',files);

export const server = functions.https.onRequest(app)
/* files.ts - files router for express server */ 

import express,{Express} from "express";
import {FilesService} from "../../services/files";
import logger from "../../services/rollbar";

const router = express.Router();

const filesService = new FilesService()

/* GET pdf */
router.get('/product/:slug',async (req,res) => {
    const slug = req.params.slug

    try {
        // Get the file buffer
        const data = await filesService.getBufferOfPdfFile(slug)

        res.writeHead(200,{
            'Content-Type': 'application/pdf','Content-disposition': `attachment; filename=${slug}`,'Content-Length': data.length
        })
        res.end(data,'binary')
    } catch (err) {
        res.status(500).send("Looks like there was a problem...")
        logger.error(err)
    }
});

export default router;

解决方法

终于想通了...似乎我应该直接使用流并将其通过管道传输到 Express 响应中。

这为我修好了

// in the files.ts express handler
// ...

// Get the file stream
const file = await filesService.getPdfFile(`${hostname}/${slug}`)
const stream = file.createReadStream()

res.on('error',err => logger.error(err))
res.set('Content-Type','application/pdf')

if (download) {
    res.set('Content-Disposition',`attachment; filename=${slug}`)
}

// Pipe file read stream to the response (write stream)
stream.pipe(res)
stream.on('error',err => logger.error(err))

// ...