我們在對EasyDSS做功能測試的時候,講過在EasyDSS演示平臺上,為了節省資源佔用設定的自動停播問題。基於EasyDSS的成功經驗,我們在EasyNVR的官網也做了同樣一套機制。
在EasyNVR的演示平臺內設定自動斷流機制,限制幾分鐘後流自動斷開,這樣客戶在瀏覽的時候就算看了忘了關,系統也會在幾分鐘就自動斷開,耗費流量就會少很多。
在獲取通過直播連結的時候,在直播連結後面新增一個校驗的流的字串。
func wrapURLWithLiveToken(rawURL string, c *gin.Context) (wrapURL string) {
wrapURL = rawURL
demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
if !demo {
return
}
if rawURL == "" {
return
}
_url, err := url.Parse(rawURL)
if err != nil {
return
}
q := _url.Query()
//token := utils.MD5(sessions.Default(c).ID() + rawURL)
token := createRandomString(8)
q.Set("token", token)
_url.RawQuery = q.Encode()
wrapURL = _url.String()
liveTokenCache.SetDefault(token, wrapURL)
return
}
func createRandomString(len int) string {
var container string
var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
b := bytes.NewBufferString(str)
length := b.Len()
bigInt := big.NewInt(int64(length))
for i := 0; i < len; i++ {
randomInt, _ := rand.Int(rand.Reader, bigInt)
container += string(str[randomInt.Int64()])
}
return container
}
這樣,直播連結就會有一個校驗引數token。
針對不同流的特點來進行不同的限制:
func WSFlvHandler() gin.HandlerFunc {
return func(c *gin.Context) {
demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
demoDuration := utils.Conf().Section("base_config").Key("demo_duration").MustInt(180)
flag := false
path := c.Param("path")
if strings.HasSuffix(path, ".flv") {
target := fmt.Sprintf("127.0.0.1:%v", dss.GetHTTPPort())
//獲取nginx裡面的真實流地址
flvUrl := "http://" + target + path
upGrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
return true
}}
//websocket長連線
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer func() {
//fmt.Println("關閉ws-flv長連線")
ws.Close()
}()
if demo {
go func() {
time.Sleep(time.Duration(demoDuration) * time.Second)
flag = true
}()
}
//傳送http請求,將視訊流資料迴圈寫入到websocket
req, _ := http.NewRequest("GET", flvUrl, nil)
res, _ := http.DefaultClient.Do(req)
reader := bufio.NewReader(res.Body)
defer res.Body.Close()
//迴圈遍歷
for {
line, err := reader.ReadBytes(' ')
if err != nil {
return
}
ws.WriteMessage(websocket.BinaryMessage, line)
if flag {
return
}
}
}
c.Next()
}
}
至此,ws-flv流就限制成功了。
//可關閉 Transport
type ShutDownTransport struct {
Trans *http.Transport
response *http.Response
}
//覆蓋上層Transport
func (t *ShutDownTransport) RoundTrip(req *http.Request) (*http.Response, error) {
res, err := t.Trans.RoundTrip(req)
t.response = res
return res, err
}
//實現關閉方法
func (t *ShutDownTransport) ShutDown(d time.Duration) {
time.AfterFunc(d, func() {
res := t.response
if res != nil {
if res.Body != nil {
res.Body.Close()
}
}
})
}
// FlvHandler flv request handler
func FlvHandler() gin.HandlerFunc {
return func(c *gin.Context) {
demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
demoDuration := utils.Conf().Section("base_config").Key("demo_duration").MustInt(180)
path := c.Param("path")
if strings.HasSuffix(path, ".flv") {
target := fmt.Sprintf("127.0.0.1:%v", dss.GetHTTPPort())
director := func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = target
req.URL.Path = path
}
modifyRes := func(res *http.Response) (err error) {
res.Header.Del("Access-Control-Allow-Credentials")
res.Header.Del("Access-Control-Allow-Headers")
res.Header.Del("Access-Control-Allow-Methods")
res.Header.Del("Access-Control-Allow-Origin")
res.Header.Del("Vary")
res.Header.Del("Server")
return
}
transport := &ShutDownTransport{
Trans: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
},
}
if demo {
transport.ShutDown(time.Duration(demoDuration) * time.Second)
}
proxy := &httputil.ReverseProxy{
Director: director,
Transport: transport,
ModifyResponse: modifyRes,
}
defer func() {
if p := recover(); p != nil {
log.Println(p)
}
}()
proxy.ServeHTTP(c.Writer, c.Request)
return
}
c.Next()
}
}
至此,http-flv限流就完成了。
因為hls流和ws-flv、http-flv流不同,前端會一直請求這個連結,所以就不用向上面一樣限流。
// 檢查斷流
func checkTime() gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Param("path")
demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
isTS := strings.HasSuffix(path, ".ts")
if demo && !isTS {
token := c.Query("token")
if token == "" {
c.IndentedJSON(401, "Token Not Found")
return
}
if _, ok := liveTokenCache.Get(token); !ok {
c.IndentedJSON(401, "Invalid Token")
return
}
}
c.Next()
}
}
因為播放器又斷流自動重連機制,所以在請求的流連結時先要判斷一下,請求的這個流地址是不是系統快取中,不在系統快取中就不讓播放流了。
只要有演示平臺需求的專案都可以通過該呼叫方法節省公網的瀏覽和頻寬,大家可以參考《EasyGBS平臺如何開啟「演示」模式》一文了解一下演示平臺的機制和作用。