Browse Source

webrtc: return detailed errors in responses (#2594)

pull/2595/head
Alessandro Ros 3 years ago committed by GitHub
parent
commit
64eb90738a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      internal/core/webrtc_http_server.go
  2. 7
      internal/core/webrtc_manager.go
  3. 26
      internal/core/webrtc_manager_test.go
  4. 9
      internal/core/webrtc_session.go

40
internal/core/webrtc_http_server.go

@ -40,6 +40,12 @@ func relativeLocation(u *url.URL) string {
return p return p
} }
func webrtcWriteError(ctx *gin.Context, statusCode int, err error) {
ctx.JSON(statusCode, &apiError{
Error: err.Error(),
})
}
type webRTCHTTPServerParent interface { type webRTCHTTPServerParent interface {
logger.Writer logger.Writer
generateICEServers() ([]pwebrtc.ICEServer, error) generateICEServers() ([]pwebrtc.ICEServer, error)
@ -143,11 +149,11 @@ func (s *webRTCHTTPServer) checkAuthOutsideSession(ctx *gin.Context, path string
// wait some seconds to stop brute force attacks // wait some seconds to stop brute force attacks
<-time.After(webrtcPauseAfterAuthError) <-time.After(webrtcPauseAfterAuthError)
ctx.Writer.WriteHeader(http.StatusUnauthorized) webrtcWriteError(ctx, http.StatusUnauthorized, terr)
return false return false
} }
ctx.Writer.WriteHeader(http.StatusNotFound) webrtcWriteError(ctx, http.StatusInternalServerError, res.err)
return false return false
} }
@ -161,7 +167,7 @@ func (s *webRTCHTTPServer) onWHIPOptions(ctx *gin.Context, path string, publish
servers, err := s.parent.generateICEServers() servers, err := s.parent.generateICEServers()
if err != nil { if err != nil {
ctx.Writer.WriteHeader(http.StatusInternalServerError) webrtcWriteError(ctx, http.StatusInternalServerError, err)
return return
} }
@ -174,7 +180,7 @@ func (s *webRTCHTTPServer) onWHIPOptions(ctx *gin.Context, path string, publish
func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish bool) { func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish bool) {
if ctx.Request.Header.Get("Content-Type") != "application/sdp" { if ctx.Request.Header.Get("Content-Type") != "application/sdp" {
ctx.Writer.WriteHeader(http.StatusBadRequest) webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid Content-Type"))
return return
} }
@ -198,18 +204,18 @@ func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish boo
publish: publish, publish: publish,
}) })
if res.err != nil { if res.err != nil {
ctx.Writer.WriteHeader(res.errStatusCode) webrtcWriteError(ctx, res.errStatusCode, res.err)
return return
} }
servers, err := s.parent.generateICEServers() servers, err := s.parent.generateICEServers()
if err != nil { if err != nil {
ctx.Writer.WriteHeader(http.StatusInternalServerError) webrtcWriteError(ctx, http.StatusInternalServerError, err)
return return
} }
ctx.Writer.Header().Set("Content-Type", "application/sdp") ctx.Writer.Header().Set("Content-Type", "application/sdp")
ctx.Writer.Header().Set("Access-Control-Expose-Headers", "ETag, Accept-Patch, Link, Location") ctx.Writer.Header().Set("Access-Control-Expose-Headers", "ETag, ID, Accept-Patch, Link, Location")
ctx.Writer.Header().Set("ETag", "*") ctx.Writer.Header().Set("ETag", "*")
ctx.Writer.Header().Set("ID", res.sx.uuid.String()) ctx.Writer.Header().Set("ID", res.sx.uuid.String())
ctx.Writer.Header().Set("Accept-Patch", "application/trickle-ice-sdpfrag") ctx.Writer.Header().Set("Accept-Patch", "application/trickle-ice-sdpfrag")
@ -223,12 +229,12 @@ func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish boo
func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) { func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
secret, err := uuid.Parse(rawSecret) secret, err := uuid.Parse(rawSecret)
if err != nil { if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest) webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
return return
} }
if ctx.Request.Header.Get("Content-Type") != "application/trickle-ice-sdpfrag" { if ctx.Request.Header.Get("Content-Type") != "application/trickle-ice-sdpfrag" {
ctx.Writer.WriteHeader(http.StatusBadRequest) webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid Content-Type"))
return return
} }
@ -239,7 +245,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
candidates, err := webrtc.ICEFragmentUnmarshal(byts) candidates, err := webrtc.ICEFragmentUnmarshal(byts)
if err != nil { if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest) webrtcWriteError(ctx, http.StatusBadRequest, err)
return return
} }
@ -248,7 +254,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
candidates: candidates, candidates: candidates,
}) })
if res.err != nil { if res.err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest) webrtcWriteError(ctx, http.StatusInternalServerError, err)
return return
} }
@ -258,7 +264,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) { func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) {
secret, err := uuid.Parse(rawSecret) secret, err := uuid.Parse(rawSecret)
if err != nil { if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest) webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
return return
} }
@ -266,7 +272,7 @@ func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) {
secret: secret, secret: secret,
}) })
if err != nil { if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest) webrtcWriteError(ctx, http.StatusInternalServerError, err)
return return
} }
@ -302,7 +308,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
return return
} }
// WHIP, outside session // WHIP/WHEP, outside session
if m := reWHIPWHEPNoID.FindStringSubmatch(ctx.Request.URL.Path); m != nil { if m := reWHIPWHEPNoID.FindStringSubmatch(ctx.Request.URL.Path); m != nil {
switch ctx.Request.Method { switch ctx.Request.Method {
case http.MethodOptions: case http.MethodOptions:
@ -315,12 +321,12 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
// RFC draft-ietf-whip-09 // RFC draft-ietf-whip-09
// The WHIP endpoints MUST return an "405 Method Not Allowed" response // The WHIP endpoints MUST return an "405 Method Not Allowed" response
// for any HTTP GET, HEAD or PUT requests // for any HTTP GET, HEAD or PUT requests
ctx.Writer.WriteHeader(http.StatusMethodNotAllowed) webrtcWriteError(ctx, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed"))
} }
return return
} }
// WHIP, inside session // WHIP/WHEP, inside session
if m := reWHIPWHEPWithID.FindStringSubmatch(ctx.Request.URL.Path); m != nil { if m := reWHIPWHEPWithID.FindStringSubmatch(ctx.Request.URL.Path); m != nil {
switch ctx.Request.Method { switch ctx.Request.Method {
case http.MethodPatch: case http.MethodPatch:
@ -337,7 +343,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
switch { switch {
case ctx.Request.URL.Path == "/favicon.ico": case ctx.Request.URL.Path == "/favicon.ico":
case len(ctx.Request.URL.Path) >= 3: case len(ctx.Request.URL.Path) >= 2:
switch { switch {
case strings.HasSuffix(ctx.Request.URL.Path, "/publish"): case strings.HasSuffix(ctx.Request.URL.Path, "/publish"):
s.onPage(ctx, ctx.Request.URL.Path[1:len(ctx.Request.URL.Path)-len("/publish")], true) s.onPage(ctx, ctx.Request.URL.Path[1:len(ctx.Request.URL.Path)-len("/publish")], true)

7
internal/core/webrtc_manager.go

@ -114,8 +114,8 @@ type webRTCManagerAPISessionsKickReq struct {
type webRTCNewSessionRes struct { type webRTCNewSessionRes struct {
sx *webRTCSession sx *webRTCSession
answer []byte answer []byte
err error
errStatusCode int errStatusCode int
err error
} }
type webRTCNewSessionReq struct { type webRTCNewSessionReq struct {
@ -470,7 +470,10 @@ func (m *webRTCManager) newSession(req webRTCNewSessionReq) webRTCNewSessionRes
return res.sx.new(req) return res.sx.new(req)
case <-m.ctx.Done(): case <-m.ctx.Done():
return webRTCNewSessionRes{err: fmt.Errorf("terminated"), errStatusCode: http.StatusInternalServerError} return webRTCNewSessionRes{
errStatusCode: http.StatusInternalServerError,
err: fmt.Errorf("terminated"),
}
} }
} }

26
internal/core/webrtc_manager_test.go

@ -19,6 +19,28 @@ import (
"github.com/bluenviron/mediamtx/internal/protocols/webrtc" "github.com/bluenviron/mediamtx/internal/protocols/webrtc"
) )
func TestWebRTCPages(t *testing.T) {
p, ok := newInstance("paths:\n" +
" stream:\n")
require.Equal(t, true, ok)
defer p.Close()
hc := &http.Client{Transport: &http.Transport{}}
for _, path := range []string{"/stream", "/stream/publish"} {
func() {
req, err := http.NewRequest(http.MethodGet, "http://localhost:8889"+path, nil)
require.NoError(t, err)
res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
}()
}
}
func TestWebRTCRead(t *testing.T) { func TestWebRTCRead(t *testing.T) {
for _, auth := range []string{ for _, auth := range []string{
"none", "none",
@ -169,7 +191,7 @@ func TestWebRTCReadNotFound(t *testing.T) {
offer, err := pc.CreateOffer(nil) offer, err := pc.CreateOffer(nil)
require.NoError(t, err) require.NoError(t, err)
req, err := http.NewRequest("POST", "http://localhost:8889/stream/whep", bytes.NewReader([]byte(offer.SDP))) req, err := http.NewRequest(http.MethodPost, "http://localhost:8889/stream/whep", bytes.NewReader([]byte(offer.SDP)))
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("Content-Type", "application/sdp") req.Header.Set("Content-Type", "application/sdp")
@ -220,7 +242,7 @@ func TestWebRTCPublish(t *testing.T) {
// preflight requests must always work, without authentication // preflight requests must always work, without authentication
func() { func() {
req, err := http.NewRequest("OPTIONS", "http://localhost:8889/teststream/whip", nil) req, err := http.NewRequest(http.MethodOptions, "http://localhost:8889/teststream/whip", nil)
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("Access-Control-Request-Method", "OPTIONS") req.Header.Set("Access-Control-Request-Method", "OPTIONS")

9
internal/core/webrtc_session.go

@ -395,8 +395,8 @@ func (s *webRTCSession) runInner() error {
if errStatusCode != 0 { if errStatusCode != 0 {
s.req.res <- webRTCNewSessionRes{ s.req.res <- webRTCNewSessionRes{
err: err,
errStatusCode: errStatusCode, errStatusCode: errStatusCode,
err: err,
} }
} }
@ -466,7 +466,12 @@ func (s *webRTCSession) runPublish() (int, error) {
trackCount, err := webrtc.TrackCount(sdp.MediaDescriptions) trackCount, err := webrtc.TrackCount(sdp.MediaDescriptions)
if err != nil { if err != nil {
return http.StatusBadRequest, err // RFC draft-ietf-wish-whip
// if the number of audio and or video
// tracks or number streams is not supported by the WHIP Endpoint, it
// MUST reject the HTTP POST request with a "406 Not Acceptable" error
// response.
return http.StatusNotAcceptable, err
} }
answer, err := pc.CreateFullAnswer(s.ctx, offer) answer, err := pc.CreateFullAnswer(s.ctx, offer)

Loading…
Cancel
Save