通过 XHR 请求 HLS/DASH URL 获取 Reddit-hosted 视频导致“CORS header ‘Access-Control-Allow-Origin’ 丢失”错误

Request to HLS/DASH URL for Reddit-hosted video through XHR results in “CORS header ‘Access-Control-Allow-Origin’ missing” error

我正在尝试使用 hls.js 库来显示来自 HLS/DASH 播放列表的 Reddit 托管视频。但是,通过 XHR 访问任何 Reddit HLS/DASH url,例如 this one 将失败,因为错误表明违反了同源策略:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://v.redd.it/5r0nz8sywgl41/DASHPlaylist.mpd. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

奇怪的是,GET 之前的请求是一个 OPTIONS 请求,它 return Access-Control-Allow-Origin header 具有正确的来源 url。我可以在开发者控制台中看到响应,但请求仍然 "fails"。如果我使用 "Allow CORS: Access-Control-Allow-Origin" 扩展,一切正常。 我做错了什么?

OPTIONS 请求 headers:

Host: v.redd.it
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0
Accept: */*
Accept-Language: lt,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Referer: http://localhost:3000/
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Save-Data: on
Pragma: no-cache
Cache-Control: no-cache
TE: Trailers

OPTIONS 响应 headers:

HTTP/2 200 OK
retry-after: 0
access-control-allow-origin: http://localhost:3000
access-control-allow-headers: authorization
access-control-allow-methods: GET
access-control-max-age: 3000
date: Sun, 08 Mar 2020 18:40:29 GMT
via: 1.1 varnish
x-served-by: cache-hhn4029-HHN
x-cache: HIT
x-cache-hits: 0
x-timer: S1583692829.044409,VS0,VE0
server: snooserv
accept-ranges: bytes
x-cdn-server-region: EU-East
x-cdn-client-region: EU
x-cdn-name: fastly
access-control-expose-headers: x-cdn-server-region, x-cdn-client-region, x-cdn-name
cache-control: public, max-age=604800, s-maxage=86400, must-revalidate
vary: Access-Control-Request-Headers, Access-Control-Request-Method,Origin
content-length: 0
X-Firefox-Spdy: h2

GET 请求 headers:

Host: v.redd.it
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0
Accept: */*
Accept-Language: lt,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:3000/
Authorization: Bearer --------------------------- (actual token here)
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Save-Data: on
Pragma: no-cache
Cache-Control: no-cache
TE: Trailers

获取响应 headers:

HTTP/2 200 OK
last-modified: Sun, 08 Mar 2020 15:34:11 GMT
etag: "0abfb243cb8d03188bf34ff29d6d4af8"
content-type: application/dash+xml
via: 1.1 varnish
date: Sun, 08 Mar 2020 18:40:29 GMT
via: 1.1 varnish
x-served-by: cache-bwi5138-BWI, cache-hhn4029-HHN
x-cache: HIT, HIT
x-cache-hits: 2, 34
x-timer: S1583692829.127808,VS0,VE0
server: snooserv
accept-ranges: bytes
x-cdn-server-region: EU-East
x-cdn-client-region: EU
x-cdn-name: fastly
access-control-expose-headers: x-cdn-server-region, x-cdn-client-region, x-cdn-name
cache-control: public, max-age=604800, s-maxage=86400, must-revalidate
vary: Origin
content-length: 1976
X-Firefox-Spdy: h2

获取响应:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<MPD mediaPresentationDuration="PT15.034S" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
    <Period duration="PT15.034S">
        <AdaptationSet segmentAlignment="true" subsegmentAlignment="true" subsegmentStartsWithSAP="1">
            <Representation bandwidth="1172841" codecs="avc1.4d401f" frameRate="30" height="480" id="VIDEO-1" mimeType="video/mp4" startWithSAP="1" width="720">
                <BaseURL>DASH_480</BaseURL>
                <SegmentBase indexRange="913-992" indexRangeExact="true">
                    <Initialization range="0-912"/>
                </SegmentBase>
            </Representation>
            <Representation bandwidth="784684" codecs="avc1.4d401e" frameRate="30" height="360" id="VIDEO-2" mimeType="video/mp4" startWithSAP="1" width="540">
                <BaseURL>DASH_360</BaseURL>
                <SegmentBase indexRange="915-994" indexRangeExact="true">
                    <Initialization range="0-914"/>
                </SegmentBase>
            </Representation>
            <Representation bandwidth="592784" codecs="avc1.4d401e" frameRate="30" height="240" id="VIDEO-3" mimeType="video/mp4" startWithSAP="1" width="360">
                <BaseURL>DASH_240</BaseURL>
                <SegmentBase indexRange="915-994" indexRangeExact="true">
                    <Initialization range="0-914"/>
                </SegmentBase>
            </Representation>
            <Representation bandwidth="91690" codecs="avc1.4d400a" frameRate="30" height="96" id="VIDEO-4" mimeType="video/mp4" startWithSAP="1" width="144">
                <BaseURL>DASH_96</BaseURL>
                <SegmentBase indexRange="912-991" indexRangeExact="true">
                    <Initialization range="0-911"/>
                </SegmentBase>
            </Representation>
        </AdaptationSet>
    </Period>
</MPD>

非常感谢您的帮助。

问题只是:https://v.redd.it/5r0nz8sywgl41/DASHPlaylist.mpd 对您代码中 GET 请求的响应不包括 Access-Control-Allow-Origin 响应 header。奇怪的是,它确实包含 Access-Control-Expose-Headers 响应 header。所以原因只是服务器配置错误;它实际上不正确 CORS-enabled.

具体来说:即使服务器发送 Access-Control-Allow-Origin header 作为对 OPTIONS 预检的响应,它本身也不足以让浏览器允许您的前端代码在您的代码中访问对实际 GET 请求的响应。为了使您的代码正常工作,服务器还必须发送 Access-Control-Allow-Origin header 以响应 GET 请求。

但您实际上可以解决 https://v.redd.it 配置错误并从前端代码访问 Reddit HLS/DASH URL,而无需浏览器扩展——通过 CORS 代理发出请求,如下例所示.

const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "https://v.redd.it/5r0nz8sywgl41/DASHPlaylist.mpd";
fetch(proxyurl + url)
.then(response => response.text())
.then(contents => console.log(contents))

有关解释,请参阅 https://whosebug.com/a/43881141/441757 处的答案。

您可以使用 https://github.com/Rob--W/cors-anywhere/ 中的代码轻松地 运行 您自己的代理,并且您可以在 2-3 分钟内使用 5 个命令快速将您自己的代理部署到 Heroku:

git clone https://github.com/Rob--W/cors-anywhere.git
cd cors-anywhere/
npm install
heroku create
git push heroku master

在 运行 执行这些命令后,您最终会得到自己的 CORS Anywhere 服务器 运行,例如 https://cryptic-headland-94862.herokuapp.com/. So then rather than prefixing your request URL with https://cors-anywhere.herokuapp.com, prefix it instead with the URL for your own instance; e.g., https://cryptic-headland-94862.herokuapp.com/https://example.com