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 }