如何用PHP将本地文件服务器的PDF作为块元素加载到内网Web页面
背景
我正在搭建一个内网网页,用户可从列表中选择条目,页面会展示对应条目信息以及作为块元素的PDF文件。
这些PDF需要由特定人员更新替换,因此存储在内部网络的独立文件服务器上。
HTML方案尝试
我尝试使用如下代码:
<embed src="file:///myFileServer/PDFs/filename.pdf" width="1000" height="600" type="application/pdf">
但触发了本地资源错误:
- Firefox报错:"Security Error: Content at myWebServer/file_read.php may not load or link to file:///myFileServer/PDFs/filename.pdf."
- Edge/Chrome报错:"Not allowed to load local resource: file:///myFileServer/PDFs/filename.pdf"
我知道报错原因是PDF路径由浏览器而非Web服务器加载,即使允许该操作也会有问题,因为并非所有用户都有权限访问该文件服务器。因此我接下来尝试用PHP渲染该元素,让Web服务器加载PDF,浏览器无需接触文件路径。
PHP方案尝试
之后我尝试使用如下代码:
<?php $filename = "file:///myFileServer/PDFs/filename.pdf"; header("Content-type: application/pdf"); header("Content-Length: " . filesize($filename)); readfile($filename); ?>
这触发了如下警告,原因和加载页面导航栏的PHP代码有关:
"Cannot modify header information - headers already sent by (output started at C:\Apache24\htdocs\nav.php:66) in C:\Apache24\htdocs\file_read.php on line 81"
在file_read.php中移除<?php include 'nav.php' ?>后上述PHP代码可运行,但不符合预期:它直接加载了全屏PDF视图,就像你在浏览器中选择打开一个PDF文件一样,而非将PDF作为页面元素展示。
PHP PDF转Base64方案
上述方案无效后我继续寻找解决方案,虽然有点取巧,但我找到了一个可行方案:
<?php $fileLocation = "//myFileServer/PDFs/filename.pdf"; $pdf = chunk_split(base64_encode(file_get_contents($fileLocation))); //Convert to Base64 echo '<embed src="data:application/pdf;base64,' . $pdf . '" style="height:500px;width:80%;" title="test"></embed>'; ?>
该方案确实可以运行且代码比较简洁,但速度偏慢,我认为肯定有更高效的实现方式。
我的问题
除了将PDF转换为Base64之外,有没有更好的方式可以解决从其他内部服务器加载本地资源的限制问题?
解决方案
你可以用独立的PHP代理接口实现,性能远高于Base64方案,也不会出现头部冲突、全屏跳转的问题:
第一步:编写独立的PDF代理文件
新建一个pdf_proxy.php文件,不要引入nav.php这类会提前输出内容的文件,完整代码如下:
<?php // 白名单校验,防止目录遍历、任意文件读取漏洞 $allowed_pdfs = [ 'filename.pdf' => '//myFileServer/PDFs/filename.pdf', // 按需补充其他允许访问的PDF映射关系 ]; $req_file = $_GET['filename'] ?? ''; // 校验请求的文件是否在白名单内 if (!isset($allowed_pdfs[$req_file])) { http_response_code(403); exit('非法请求'); } $real_path = $allowed_pdfs[$req_file]; // 校验物理文件是否存在 if (!file_exists($real_path)) { http_response_code(404); exit('文件不存在'); } // 输出PDF响应头 header('Content-Type: application/pdf'); header('Content-Length: ' . filesize($real_path)); // inline参数控制浏览器内嵌展示,不会触发跳转或下载 header('Content-Disposition: inline; filename="'.$req_file.'"'); // 直接输出文件内容 readfile($real_path); exit; ?>
第二步:在业务页面嵌入PDF
在你原来带导航栏的页面中,直接把embed的src指向这个代理接口即可:
<embed src="pdf_proxy.php?filename=filename.pdf" style="height:500px;width:80%;" title="test" type="application/pdf">
方案优势
- 没有Base64编码带来的33%体积额外开销,加载速度提升明显,大体积PDF的优化效果更突出
- 代理接口独立运行,不会和页面其他代码产生头部输出冲突
- 可以灵活叠加权限控制,比如校验用户登录状态再返回文件,安全性更高
- 浏览器全程不直接访问文件服务器,完全由Web服务器转发请求,符合内网权限控制要求
如果PDF数量较多,不需要手动维护白名单,也可以做路径校验:限定请求的文件后缀必须为pdf,且解析后的真实路径必须落在你指定的共享目录范围内即可。
内容的提问来源于stack exchange,提问作者VeryColdAir

