跨域代理

問題描述

使用 videojs 播放 cctv 直播源(http://cctvcnch5c.v.wscdns.com/live/cctv13_2/index.m3u8)時,出現跨域問題,如下所示:

Access to XMLHttpRequest at 'http://cctvcnch5c.v.wscdns.com/live/cctv13_2/index.m3u8' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

解決思路

跨域限制在 cctvcnch5c.v.wscdns.com 上,無法通過配置服務器解決限制。只能通過中間層接口請求或代理進行繞過(服務器訪問不受跨域協議限制)。

解決過程

中間層使用 express 框架,代理插件可使用 express-http-proxy

m3u8 直播源解析過程爲如下:

  1. 請求 m3u8 播放源地址
  2. 服務端返回 m3u8 純文本索引文件,其中包含各個媒體段的 url
  3. 客戶端解析 m3u8 的播放列表,再按序請求每一段的url,獲取ts數據流

其中,服務端返回的媒體段地址爲相對地址,默認前綴路徑爲步驟1的 m3u8 的請求路徑,所以還需要對步驟1返回的數據做處理,即步驟1需要使用接口模式,而步驟3使用代理模式(express-http-proxy)。

具體代碼如下:

// main.js

var express = require('express');
var proxy = require('express-http-proxy');
var router = express.Router();
var urlParse = require('url').parse;
var controller = require('./controller.js');
// 代理直播源
router.get('/videos', controller.videoProxy);
router.use(
  '/tsProxy',
  proxy(
    function(req) {
      var target = urlParse(decodeURIComponent(req.query.url))
      return target.host
    },
    {
      parseReqBody: false, // 去除默認的 body,解決某些播放源 411 問題
      proxyReqPathResolver: function(req) {
        var target = urlParse(decodeURIComponent(req.query.url))
        return target.path
      },
    }
  )
)

express-http-proxy 默認自動解析並設置 req.body,這將導致 cctv 服務器識別到沒有 content-length 頭部的 request body,並返回 411 錯誤碼。所以需要在中間件配置上將 parseReqBody 設置爲false。

// controller.js

var axios = require('axios')
var controller = {};

controller.videoProxy = function(req, res) {
  try {
    var url = decodeURIComponent(req.query.url)
    var parseUrl = urlParse(url)
    var domain = parseUrl.protocol + '//' + parseUrl.host // http 域名地址
    var m3u8Path = url.match(/\S+\//)[0] // http 至最後一個 '/' 字符
    axios
      .get(url)
      .then(resp => {
        var headers = resp.headers
        var content = resp.data
        // 內容爲 ts 文件地址則使用 tsProxy 的 path
        var path = /BANDWIDTH/i.test(content)
          ? '/school/videos?url='
          : '/school/tsProxy?url='

        // 頭部信息全部返回
        for (var key in headers) {
          res.append(key, headers[key])
        }

        // 對 data 中目標文件字符串做處理
        content = content.replace(/(?:\n)([^# \n]+\.\S+)/g, function(_, match) {
          // 絕對地址,不做任何修改
          if (/^http/.test(match)) {
            return '\n' + path + encodeURIComponent(match)
          }
          // 相對地址:有文件結構的相對地址直接加域名,否則加上帶 path 的域名
          return (
            '\n' +
            path +
            encodeURIComponent((/^\//g.test(match) ? domain : m3u8Path) + match)
          )
        })
        res.send(content)
      })
      .catch(e => {
        res.json({
          code: (e.response && e.response.status) || 404,
          message: e.message || '',
          success: false
        })
      })
  } catch (e) {
    res.json({
      code: 400,
      message: 'URI must be not empty!',
      success: false
    })
  }
}
module.exports = controller;

 

發佈了30 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章