前端加载非公开第三方文件及接口数据
前言
有时候可能需要从一些非公开的接口或者地址去获取数据或者文件,这个听起来像是一个爬虫过程,不过相比单纯的脚本爬虫,前端爬虫除了单纯的HTTP
请求,也许还多了一些选择;
注:以下探讨只是针对GET请求;
可能的问题
看到非公开&第三方的字眼就马上会想到:请求地址一端完全不可控;所以很有可能会有下面的问题:
- 跨域请求
- 请求头参数匹配
解决跨域的方法
对于跨域请求,目前确实存在许多解决办法,比如:
CORS
代理fetch no-cors mode
JSONP
crossorigin
属性
上面的跨域解决办法都不是完美的,都有各自的局限性,因此需要根据具体场景选择合适的方法;
CORS代理
一般的跨域都是由于服务端的响应头设置了特定的CORS
参数,比如限定了允许跨域的域名范围已经请求参数类型等等,这种时候请求头的某些参数就不会匹配,因此被断定为跨域请求,然后被中止请求;
所谓的CORS参数就是指Access-Control-*这类;
由于第三方的服务端代码不受控,因此只能利用代理服务器来返回一个允许跨域的CORS
响应头;这里有一个比较常用的CORS
代理网站:
使用方法很简单,就是网址 + 要代理的网址:https://cors-anywhere.herokuapp.com/https://example.com/abc
一般来说,只要是由于响应头CORS
参数设置造成的跨域,这种代理都可以解决;
fetch
fetch
是新推出的http
请求api
,风格为Promise
式的;
Promise
fetch(input[, init]);[1]
其中第二个参数可以设置一些请求相关的参数,比如请求头设置和缓存策略等等;其中有个mode
的参数:
same-origin
:只允许同站的请求,其它请求一律拒绝;cors
:在same-origin
的基础上还允许拥有正确CORS
响应头的请求;no-cors
:发出的请求不会进行CORS
检测,但同时得到的响应头是不透明(opaque
)的[2],即会丢失很多响应信息。
从上面不难看出,使用fetch
的no-cors mode
可以避免跨域:
1 | fetch('https://example.com/abc', { |
但是,这种方式的跨域有个致命的副作用,那就是获取到响应体正文是空的,也就是说跨域请求成功了,但是由于得到是不透明响应体,响应正文的解析触发时机出现了问题;
1 | fetch('https://example.com/abc', { |
这个在请求媒体文件时尤为明显,跨域请求成功后,其Promise
的resolve
触发时机并不是我们所期待的资源加载完成后,而是响应报文返回后立马触发,导致得到的二进制数据字节数为0
;这一点MDN
上[3]也有提及:
因此利用fetch
的no-cors mode
进行跨域在实际应用上基本行不通;
JSONP
JSONP
跨域的原理就是script
标签天然支持跨域,然后将数据包裹在外部JS
代码中进行回调执行然后解析;因此这种跨域方法需要第三方接口本身就支持返回JSONP
的数据,否则不能使用,比如:
因此只需要配合返回的JS
代码,提供一个相应的解析函数即可;
crossorigin属性
这是video
、audio
、img
等一些元素用于设置跨域策略的属性,有以下几个值:
anonymous
:不会检测CORS
请求;use-credentials
:CORS
请求会要求带上相应的凭证;""
:空字符串,等同于anonymous
;
当然,一般来说直接给video
这些媒体元素设置第三方地址,也不会引起跨域错误(不过有些网址可能会设置了防盗功能);因此这个属性反而很多时候是来解决一些H5 API
转码的时候出现的跨域报错,比如canvas
绘制第三方来源的视频和图片时;
关于请求头参数匹配
在请求第三方地址时,可能好不容易解决了跨域问题,但是又会出现其他莫名其妙的报错,那很有可能是其请求头增加了内部专用的一些参数设置,要想完全给出匹配的请求头这时候完全看人品了……
简单的总结
解析数据的一些思路
前端らしい
既然前面说了前端面对这种类似爬虫的需求也许有更多的选择,那么我就想到了一些很前端的思路:
- Q:已知像
video/audio/img
这类标签本身就支持跨域加载,那么能不能通过创建标签加载数据,然后在onload
事件中获取标签的二进制数据呢? - A:不能。虽然这类标签可以很自然地跨域加载数据,且能够正确地触发
load/loadeddata
事件,但是目前还没发现DOM
有暴露获取这个标签对应的二进制buffer
数据的API
,或者将相应的DOM
类型转化为Blob/File
之类的API
,因此走不通;(canvas/webGL/webAudio
这类更底层的API
还没尝试过,有时间可以验证下) - Q:已知
URL.createObjectURL()
方法可以接收MediaSource
对象,而MediaSource
可以通过ArrayBuffer
进行构造,那么video/audio/img
这类标签能够转化成ArrayBuffer
吗? - A:不能。同样也没找到相应的
API
; - Q:
<script>
标签 +innerText/innerHtml
- A:不能,因为
innerText/innerHtml
方法无法获取到script
内部源码
经过一些折腾,发现通过前端直接获取媒体元素的二进制数据,从而完全绕开跨域拉取数据进而上传是很有难度的,这也许是浏览器处于安全因素的考量,也可能是对深层次的DOM API
并不熟悉;
fetch
通过fetch API
获取到的响应体是一个Response
对象,这个对象已经自带了几种常用的数据解析方法[4]:
解析成功的前提自然是响应体正文符合对应解析方法的格式;
相关文档
- CORS settings attributes - HTML(超文本标记语言) | MDN
- javascript - Trying to use fetch and pass in mode: no-cors - Stack Overflow
- 使用 Fetch - Web API 接口参考 | MDN
- MediaSource - Web API 接口参考 | MDN