Displaying Attachment PDF with Frontend Javascript

背景

笔者最近在参加某校在线平台前端的开发,收到了这样的要求:

将服务器传输过来的文件尽可能(图片/PDF)在网页中就显示下来,这样就不用下载了!

笔者劈里啪啦敲下了如下代码(由于渲染问题,美元符号已替换为人民币符号!!!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var xhr = new XMLHttpRequest();

¥.ajax({
url: 'download_link',
type: 'get',
data: {},
xhr: function () {
return xhr;
},
success: function (r) {
const params = new URLSearchParams(xhr.responseURL);
const para1 = params.get('filename');
console.log("Returned url:"+xhr.responseURL);
console.log(para1);

if (para1 == null) {
¥('#fast_load').text('');
return;
}

if (para1.endsWith(".png") || para1.endsWith(".jpg") || para1.endsWith(".gif") || para1.endsWith(".bmp")) {
¥('#fast_load').html(`用户上传的图片(为正常显示,已缩放。请点击下载按钮下载原图。)<br/><br/><img src="¥{xhr.responseURL}" style="max-width:600px;width:100%"/>`);
return;
}

if(para1.endsWith(".pdf")){
¥('#fast_load').html(`用户上传的PDF<br/><br/><object class="pdf" data="¥{xhr.responseURL}" width="100%" height="1500"></object>`);
return;
}

¥('#fast_load').text(`用户上传了文件:¥{para1},请按下载按钮下载。请注意:本网站不对用户上传内容进行检查,文件有可能包含恶意内容。`);
}
});

上述代码可以正确分辨并显示各种常用类型的图片文件,然而pdf文件则会直接下载,不会显示在object中,令人迷惑。

问题分析

原来,服务器返回的是Content-Type是application/octet-stream,而又是以attachment的形式送给我们的,这时候无论iframe embed 还是 object都不会尝试在浏览器中渲染该pdf,而是直接下载。我们无法改变服务器发送的数据类型,于是我们尝试在客户端层面解决问题。

解决思路

Blob类型接受response然后本地改变Content-Type到application/pdf,再用本地数据渲染。

问题解决

首先,我们需要以Blob类型处理response,然而据说jquery处理blob类型有许多问题(事实上笔者也确实碰到了问题),所以我们换用普通的Ajax:

1
2
3
4
5
6
7
8
9
10
var req = new XMLHttpRequest();
req.open("GET", "...", true);
req.responseType = "blob";

req.onload = function (event) {
var blob=req.response;
//...
};

req.send();

接下来,我们发现Blob的类型还是octet-stream,直接放入object中仍然失败,所以我们需要修改其type。然而Blob是immutable的,我们无法直接修改其参数。根据本文,我们这么修改blob的类型:

1
2
//hacky solution!!
blob = blob.slice(0, blob.size, "application/pdf")

最后我们使用非常强大的URL工具创建本地URL:

1
2
3
4
5
6
if(para1.endsWith(".pdf")){
blob=blob.slice(0,blob.size,"application/pdf");
const url=URL.createObjectURL(blob);
¥('#fast_load').html(`用户上传的PDF<br/><br/><object class="pdf" data="¥{url}" width="100%" height="1500"></object>`);
return;
}

该URL会随着document卸载而卸载,本场景下不用手动卸载。

(欸,我的代码中的美元都到哪去了?)

重点

  • 如何获取服务器上的Blob数据
  • 如何修改Blob的类型
  • 如何用Blob创建本地URL