support RTSP-over-HTTP (#433) (#768) (#887)

This commit is contained in:
Alessandro Ros
2025-09-15 19:00:50 +02:00
committed by GitHub
parent 73474c7569
commit ead4471b5c
82 changed files with 1786 additions and 610 deletions

View File

@@ -1,9 +1,13 @@
package gortsplib
import (
"bufio"
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"net/http"
"sync/atomic"
"testing"
"time"
@@ -280,7 +284,7 @@ func TestServerConnClose(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
<-nconnClosed
@@ -305,7 +309,7 @@ func TestServerCSeq(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
res, err := writeReqReadRes(conn, base.Request{
Method: base.Options,
@@ -339,7 +343,7 @@ func TestServerErrorCSeqMissing(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
res, err := writeReqReadRes(conn, base.Request{
Method: base.Options,
@@ -370,7 +374,7 @@ func TestServerErrorNilURL(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
res, err := writeReqReadRes(conn, base.Request{
Method: base.Describe,
@@ -406,7 +410,7 @@ func TestServerDescribeNonNilBody(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
res, err := writeReqReadRes(conn, base.Request{
Method: base.Describe,
@@ -467,7 +471,7 @@ func TestServerErrorMethodNotImplemented(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
desc := doDescribe(t, conn, false)
@@ -563,7 +567,7 @@ func TestServerErrorTCPTwoConnOneSession(t *testing.T) {
nconn1, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn1.Close()
conn1 := conn.NewConn(nconn1)
conn1 := conn.NewConn(bufio.NewReader(nconn1), nconn1)
desc1 := doDescribe(t, conn1, false)
@@ -583,7 +587,7 @@ func TestServerErrorTCPTwoConnOneSession(t *testing.T) {
nconn2, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn2.Close()
conn2 := conn.NewConn(nconn2)
conn2 := conn.NewConn(bufio.NewReader(nconn2), nconn2)
desc2 := doDescribe(t, conn2, false)
@@ -649,7 +653,7 @@ func TestServerErrorTCPOneConnTwoSessions(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
desc := doDescribe(t, conn, false)
@@ -717,7 +721,7 @@ func TestServerSetupMultipleTransports(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
desc := doDescribe(t, conn, false)
@@ -819,7 +823,7 @@ func TestServerGetSetParameter(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
desc := doDescribe(t, conn, false)
@@ -910,7 +914,7 @@ func TestServerErrorInvalidSession(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
res, err := writeReqReadRes(conn, base.Request{
Method: method,
@@ -967,7 +971,7 @@ func TestServerAuth(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
medias := []*description.Media{testH264Media}
@@ -1030,7 +1034,7 @@ func TestServerAuthFail(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
medias := []*description.Media{testH264Media}
@@ -1108,7 +1112,7 @@ func TestServerSessionClose(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
desc := doDescribe(t, conn, false)
@@ -1187,7 +1191,7 @@ func TestServerSessionAutoClose(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
desc := doDescribe(t, conn, false)
@@ -1255,7 +1259,7 @@ func TestServerSessionTeardown(t *testing.T) {
nconn, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn.Close()
conn := conn.NewConn(nconn)
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
desc := doDescribe(t, conn, false)
@@ -1290,3 +1294,135 @@ func TestServerStreamErrorNoServer(t *testing.T) {
err := stream.Initialize()
require.Error(t, err)
}
func TestServerHTTPTunnel(t *testing.T) {
for _, ca := range []string{"http", "https"} {
t.Run(ca, func(t *testing.T) {
done := make(chan struct{})
n := new(uint64)
s := &Server{
Handler: &testServerHandler{
onConnClose: func(ctx *ServerHandlerOnConnCloseCtx) {
switch atomic.AddUint64(n, 1) {
case 1:
require.EqualError(t, ctx.Error, "upgraded to HTTP conn")
case 2:
require.EqualError(t, ctx.Error, "upgraded to HTTP conn")
close(done)
}
},
onDescribe: func(_ *ServerHandlerOnDescribeCtx) (*base.Response, *ServerStream, error) {
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, nil
},
},
RTSPAddress: "localhost:8554",
}
if ca == "https" {
cert, err := tls.X509KeyPair(serverCert, serverKey)
require.NoError(t, err)
s.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
}
err := s.Start()
require.NoError(t, err)
defer s.Close()
nconn1, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn1.Close()
if ca == "https" {
nconn1 = tls.Client(nconn1, &tls.Config{InsecureSkipVerify: true})
}
_, err = nconn1.Write([]byte(
"GET / HTTP/1.1\r\n" +
"Host: localhost:8554\r\n" +
"X-Sessioncookie: testtunid\r\n" +
"Accept: application/x-rtsp-tunnelled\r\n" +
"Content-Length: 30000\r\n" +
"\r\n",
))
require.NoError(t, err)
buf1 := bufio.NewReader(nconn1)
res, err := http.ReadResponse(buf1, nil)
require.NoError(t, err)
res.Body.Close()
require.Equal(t, &http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
ContentLength: -1,
Close: true,
Header: http.Header{
"Cache-Control": []string{"no-cache"},
"Content-Type": []string{"application/x-rtsp-tunnelled"},
"Pragma": []string{"no-cache"},
},
Body: res.Body,
}, res)
nconn2, err := net.Dial("tcp", "localhost:8554")
require.NoError(t, err)
defer nconn2.Close()
if ca == "https" {
nconn2 = tls.Client(nconn2, &tls.Config{InsecureSkipVerify: true})
}
_, err = nconn2.Write([]byte(
"POST / HTTP/1.1\r\n" +
"Host: localhost:8554\r\n" +
"X-Sessioncookie: testtunid\r\n" +
"Content-Type: application/x-rtsp-tunnelled\r\n" +
"Content-Length: 30000\r\n" +
"\r\n",
))
require.NoError(t, err)
buf2 := bufio.NewReader(nconn2)
res, err = http.ReadResponse(buf2, nil)
require.NoError(t, err)
res.Body.Close()
require.Equal(t, &http.Response{
Status: "200 OK",
StatusCode: http.StatusOK,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
ContentLength: -1,
Close: true,
Header: http.Header{
"Cache-Control": []string{"no-cache"},
"Content-Type": []string{"application/x-rtsp-tunnelled"},
"Pragma": []string{"no-cache"},
},
Body: res.Body,
}, res)
conn := conn.NewConn(bufio.NewReader(buf1), base64.NewEncoder(base64.StdEncoding, nconn2))
rres, err := writeReqReadRes(conn, base.Request{
Method: base.Describe,
URL: mustParseURL("rtsp://localhost:8554/teststream?param=value"),
Header: base.Header{
"CSeq": base.HeaderValue{"1"},
},
})
require.NoError(t, err)
require.Equal(t, base.StatusNotFound, rres.StatusCode)
<-done
})
}
}