如何通过Cloudflare Worker从R2获取PDF并在React SPA中正常展示?
我正使用Cloudflare官方推出的Vite Cloudflare插件,将React SPA从传统Nginx主机迁移到Cloudflare Worker。该SPA需要从R2存储桶获取图片和PDF文件,目前图片获取功能正常,客户端代码如下:
const getR2Data = (r2Url) => { const url = "/api?r2Image=" + r2Url; fetch(url) .then((res) => res.body) .then((rs) => { const reader = rs.getReader(); return new ReadableStream({ async start(controller) { while (true) { const { done, value } = await reader.read(); if (done) { break; } controller.enqueue(value); } controller.close(); reader.releaseLock(); } }) }) .then(rs => new Response(rs)) .then(response => response.blob()) .then(blob => URL.createObjectURL(blob)) .then(binaryData => setBinaryData(binaryData)) .catch((err) => { console.log("R2 fetch error:") console.log(err); }) }
在/worker/index.js中使用了稍作修改的默认fetch()代码,setBinaryData()用于设置<img>标签的状态变量。但使用同一函数获取PDF并尝试在<object>标签中展示时(该方式在Nginx环境下正常):
<object data={pdfBinary} type="application/pdf" width="100%" height="100%" > <p>text</p> </object>
却无法正常工作,PDF源码可见且渲染耗时极长。尝试<embed>和<iframe>标签也仅显示PDF源码,推测问题出在R2文件获取方式上。此外,将PDF转为下载功能时可正常运行:
.then( blob => { var fileURL = URL.createObjectURL(blob); var fileLink = document.createElement('a'); fileLink.href = fileURL; fileLink.download = `whatever.pdf`; fileLink.click(); })
困惑于为何与Nginx环境下从/pdfs/data.pdf读取文件的表现不同,请问需如何修改才能让PDF在<object>标签中正常展示?
问题核心出在响应的Content-Type头部以及手动处理ReadableStream的冗余逻辑上,以下是具体修复步骤:
1. 简化客户端fetch逻辑,保留原始响应元数据
当前代码手动重新封装ReadableStream时丢失了原始响应的Content-Type等关键头部,而<object>渲染PDF需要明确的application/pdf类型标识。直接使用原始响应转换blob即可:
const getR2Data = (r2Url) => { const url = "/api?r2Image=" + r2Url; fetch(url) .then(response => response.blob()) .then(blob => { // 兜底处理:若后端未返回Content-Type,根据文件名后缀指定 if (!blob.type && r2Url.endsWith('.pdf')) { blob = new Blob([blob], { type: 'application/pdf' }); } return URL.createObjectURL(blob); }) .then(binaryData => setBinaryData(binaryData)) .catch(err => { console.log("R2 fetch error:"); console.log(err); }); };
2. 确保Worker端返回正确的响应头部
在/worker/index.js中,从R2获取文件后必须传递正确的Content-Type,这是浏览器识别文件类型的关键:
// Worker中处理R2请求的核心代码示例 async function handleR2Request(request) { const url = new URL(request.url); const r2Key = url.searchParams.get('r2Image'); if (!r2Key) return new Response('Missing key', { status: 400 }); const object = await R2_BUCKET.get(r2Key); if (!object) return new Response('Not found', { status: 404 }); // 优先使用R2对象自带的元数据,否则根据后缀推断 const contentType = object.httpMetadata?.contentType || (r2Key.endsWith('.pdf') ? 'application/pdf' : 'image/jpeg'); return new Response(object.body, { headers: { 'Content-Type': contentType, 'Cache-Control': 'public, max-age=31536000' } }); }
3. 验证<object>标签的属性一致性
确保pdfBinary状态更新正确,且标签的type属性与blob的Content-Type严格匹配:
<object data={pdfBinary} type="application/pdf" width="100%" height="100%" > <p>无法加载PDF,请<a href={pdfBinary} target="_blank">点击查看</a></p> </object>
差异原因说明
Nginx会自动根据文件后缀设置正确的Content-Type响应头,而你之前的Worker代码未正确传递或设置该头部,导致浏览器无法识别PDF的MIME类型,只能以纯文本形式解析展示。
内容的提问来源于stack exchange,提问作者Omortis

