Browse Source

webrtc: return detailed errors in responses (#2594)

pull/2595/head
Alessandro Ros 2 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 { @@ -40,6 +40,12 @@ func relativeLocation(u *url.URL) string {
return p
}
func webrtcWriteError(ctx *gin.Context, statusCode int, err error) {
ctx.JSON(statusCode, &apiError{
Error: err.Error(),
})
}
type webRTCHTTPServerParent interface {
logger.Writer
generateICEServers() ([]pwebrtc.ICEServer, error)
@ -143,11 +149,11 @@ func (s *webRTCHTTPServer) checkAuthOutsideSession(ctx *gin.Context, path string @@ -143,11 +149,11 @@ func (s *webRTCHTTPServer) checkAuthOutsideSession(ctx *gin.Context, path string
// wait some seconds to stop brute force attacks
<-time.After(webrtcPauseAfterAuthError)
ctx.Writer.WriteHeader(http.StatusUnauthorized)
webrtcWriteError(ctx, http.StatusUnauthorized, terr)
return false
}
ctx.Writer.WriteHeader(http.StatusNotFound)
webrtcWriteError(ctx, http.StatusInternalServerError, res.err)
return false
}
@ -161,7 +167,7 @@ func (s *webRTCHTTPServer) onWHIPOptions(ctx *gin.Context, path string, publish @@ -161,7 +167,7 @@ func (s *webRTCHTTPServer) onWHIPOptions(ctx *gin.Context, path string, publish
servers, err := s.parent.generateICEServers()
if err != nil {
ctx.Writer.WriteHeader(http.StatusInternalServerError)
webrtcWriteError(ctx, http.StatusInternalServerError, err)
return
}
@ -174,7 +180,7 @@ func (s *webRTCHTTPServer) onWHIPOptions(ctx *gin.Context, path string, publish @@ -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) {
if ctx.Request.Header.Get("Content-Type") != "application/sdp" {
ctx.Writer.WriteHeader(http.StatusBadRequest)
webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid Content-Type"))
return
}
@ -198,18 +204,18 @@ func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish boo @@ -198,18 +204,18 @@ func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish boo
publish: publish,
})
if res.err != nil {
ctx.Writer.WriteHeader(res.errStatusCode)
webrtcWriteError(ctx, res.errStatusCode, res.err)
return
}
servers, err := s.parent.generateICEServers()
if err != nil {
ctx.Writer.WriteHeader(http.StatusInternalServerError)
webrtcWriteError(ctx, http.StatusInternalServerError, err)
return
}
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("ID", res.sx.uuid.String())
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 @@ -223,12 +229,12 @@ func (s *webRTCHTTPServer) onWHIPPost(ctx *gin.Context, path string, publish boo
func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
secret, err := uuid.Parse(rawSecret)
if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest)
webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
return
}
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
}
@ -239,7 +245,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) { @@ -239,7 +245,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
candidates, err := webrtc.ICEFragmentUnmarshal(byts)
if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest)
webrtcWriteError(ctx, http.StatusBadRequest, err)
return
}
@ -248,7 +254,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) { @@ -248,7 +254,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
candidates: candidates,
})
if res.err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest)
webrtcWriteError(ctx, http.StatusInternalServerError, err)
return
}
@ -258,7 +264,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) { @@ -258,7 +264,7 @@ func (s *webRTCHTTPServer) onWHIPPatch(ctx *gin.Context, rawSecret string) {
func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) {
secret, err := uuid.Parse(rawSecret)
if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest)
webrtcWriteError(ctx, http.StatusBadRequest, fmt.Errorf("invalid secret"))
return
}
@ -266,7 +272,7 @@ func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) { @@ -266,7 +272,7 @@ func (s *webRTCHTTPServer) onWHIPDelete(ctx *gin.Context, rawSecret string) {
secret: secret,
})
if err != nil {
ctx.Writer.WriteHeader(http.StatusBadRequest)
webrtcWriteError(ctx, http.StatusInternalServerError, err)
return
}
@ -302,7 +308,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) { @@ -302,7 +308,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
return
}
// WHIP, outside session
// WHIP/WHEP, outside session
if m := reWHIPWHEPNoID.FindStringSubmatch(ctx.Request.URL.Path); m != nil {
switch ctx.Request.Method {
case http.MethodOptions:
@ -315,12 +321,12 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) { @@ -315,12 +321,12 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
// RFC draft-ietf-whip-09
// The WHIP endpoints MUST return an "405 Method Not Allowed" response
// for any HTTP GET, HEAD or PUT requests
ctx.Writer.WriteHeader(http.StatusMethodNotAllowed)
webrtcWriteError(ctx, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed"))
}
return
}
// WHIP, inside session
// WHIP/WHEP, inside session
if m := reWHIPWHEPWithID.FindStringSubmatch(ctx.Request.URL.Path); m != nil {
switch ctx.Request.Method {
case http.MethodPatch:
@ -337,7 +343,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) { @@ -337,7 +343,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
switch {
case ctx.Request.URL.Path == "/favicon.ico":
case len(ctx.Request.URL.Path) >= 3:
case len(ctx.Request.URL.Path) >= 2:
switch {
case strings.HasSuffix(ctx.Request.URL.Path, "/publish"):
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 { @@ -114,8 +114,8 @@ type webRTCManagerAPISessionsKickReq struct {
type webRTCNewSessionRes struct {
sx *webRTCSession
answer []byte
err error
errStatusCode int
err error
}
type webRTCNewSessionReq struct {
@ -470,7 +470,10 @@ func (m *webRTCManager) newSession(req webRTCNewSessionReq) webRTCNewSessionRes @@ -470,7 +470,10 @@ func (m *webRTCManager) newSession(req webRTCNewSessionReq) webRTCNewSessionRes
return res.sx.new(req)
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 ( @@ -19,6 +19,28 @@ import (
"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) {
for _, auth := range []string{
"none",
@ -169,7 +191,7 @@ func TestWebRTCReadNotFound(t *testing.T) { @@ -169,7 +191,7 @@ func TestWebRTCReadNotFound(t *testing.T) {
offer, err := pc.CreateOffer(nil)
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)
req.Header.Set("Content-Type", "application/sdp")
@ -220,7 +242,7 @@ func TestWebRTCPublish(t *testing.T) { @@ -220,7 +242,7 @@ func TestWebRTCPublish(t *testing.T) {
// preflight requests must always work, without authentication
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)
req.Header.Set("Access-Control-Request-Method", "OPTIONS")

9
internal/core/webrtc_session.go

@ -395,8 +395,8 @@ func (s *webRTCSession) runInner() error { @@ -395,8 +395,8 @@ func (s *webRTCSession) runInner() error {
if errStatusCode != 0 {
s.req.res <- webRTCNewSessionRes{
err: err,
errStatusCode: errStatusCode,
err: err,
}
}
@ -466,7 +466,12 @@ func (s *webRTCSession) runPublish() (int, error) { @@ -466,7 +466,12 @@ func (s *webRTCSession) runPublish() (int, error) {
trackCount, err := webrtc.TrackCount(sdp.MediaDescriptions)
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)

Loading…
Cancel
Save