github.com/cnotch/ipchub@v1.1.0/service/streamapis.go (about)

     1  // Copyright (c) 2019,CAOHONGJU All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package service
     6  
     7  import (
     8  	"net/http"
     9  	"path"
    10  
    11  	"github.com/cnotch/ipchub/config"
    12  	"github.com/cnotch/ipchub/network/websocket"
    13  	"github.com/cnotch/ipchub/provider/auth"
    14  	"github.com/cnotch/ipchub/service/flv"
    15  	"github.com/cnotch/ipchub/service/hls"
    16  
    17  	"github.com/cnotch/apirouter"
    18  	"github.com/cnotch/ipchub/utils/scan"
    19  )
    20  
    21  // 初始化流式访问
    22  func (s *Service) initHTTPStreams(mux *http.ServeMux) {
    23  	mux.Handle("/ws/", apirouter.WrapHandler(http.HandlerFunc(s.onWebSocketRequest), apirouter.PreInterceptor(s.streamInterceptor)))
    24  	mux.Handle("/streams/", apirouter.WrapHandler(http.HandlerFunc(s.onStreamsRequest), apirouter.PreInterceptor(s.streamInterceptor)))
    25  }
    26  
    27  // websocket 请求处理
    28  func (s *Service) onWebSocketRequest(w http.ResponseWriter, r *http.Request) {
    29  	username := r.Header.Get(usernameHeaderKey)
    30  	streamPath, ext := extractStreamPathAndExt(r.URL.Path)
    31  	_ = ext
    32  
    33  	if ws, ok := websocket.TryUpgrade(w, r, streamPath, username); ok {
    34  
    35  		if ws.Subprotocol() == "rtsp" { // rtsp 直连
    36  			// rtsp接入
    37  			s.rtsp.OnAccept(ws)
    38  			return
    39  		}
    40  
    41  		if ext == ".flv" {
    42  			go flv.ConsumeByWebsocket(s.logger, streamPath, r.RemoteAddr, ws)
    43  			return
    44  		}
    45  
    46  		s.logger.Warnf("websocket sub-protocol is not supported: %s.", ws.Subprotocol())
    47  		ws.Close()
    48  	}
    49  }
    50  
    51  // streams 请求处理(flv,mu38,ts)
    52  func (s *Service) onStreamsRequest(w http.ResponseWriter, r *http.Request) {
    53  	// 获取文件后缀和流路径
    54  	streamPath, ext := extractStreamPathAndExt(r.URL.Path)
    55  
    56  	w.Header().Set("Access-Control-Allow-Origin", "*")
    57  	switch ext {
    58  	case ".flv":
    59  		flv.ConsumeByHTTP(s.logger, streamPath, r.RemoteAddr, w)
    60  	case ".m3u8":
    61  		token := r.URL.Query().Get("token")
    62  		hls.GetM3u8(s.logger, streamPath, token, r.RemoteAddr, w)
    63  	case ".ts":
    64  		hls.GetTS(s.logger, streamPath, r.RemoteAddr, w)
    65  	default:
    66  		s.logger.Warnf("request file ext is not supported: %s.", ext)
    67  		http.NotFound(w, r)
    68  	}
    69  }
    70  
    71  func (s *Service) streamInterceptor(w http.ResponseWriter, r *http.Request) bool {
    72  	if path.Base(r.URL.Path) == "crossdomain.xml" {
    73  		w.Header().Set("Content-Type", "application/xml")
    74  		w.Write(crossdomainxml)
    75  		return false
    76  	}
    77  
    78  	if !config.Auth() {
    79  		// 不启用媒体流访问验证
    80  		return true
    81  	}
    82  
    83  	if s.authInterceptor(w, r) {
    84  		return permissionInterceptor(w, r)
    85  	}
    86  
    87  	return false
    88  }
    89  
    90  // 验证用户是否有权限播放指定的流
    91  func permissionInterceptor(w http.ResponseWriter, r *http.Request) bool {
    92  	userName := r.Header.Get(usernameHeaderKey)
    93  	u := auth.Get(userName)
    94  
    95  	streamPath, _ := extractStreamPathAndExt(r.URL.Path)
    96  
    97  	if u == nil || !u.ValidatePermission(streamPath, auth.PullRight) {
    98  		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
    99  		return false
   100  	}
   101  
   102  	return true
   103  }
   104  
   105  // 提取请求路径中的流path和格式后缀
   106  func extractStreamPathAndExt(requestPath string) (streamPath, ext string) {
   107  	ext = path.Ext(requestPath)
   108  	_, token, _ := scan.NewScanner('/', nil).Scan(requestPath[1:])
   109  	streamPath = requestPath[1+len(token) : len(requestPath)-len(ext)]
   110  	return
   111  }