Browse Source

implement paths with regular expressions (#54) (#59)

pull/101/head
aler9 5 years ago
parent
commit
6679b02a39
  1. 2
      README.md
  2. 50
      client.go
  3. 125
      conf.go
  4. 23
      main.go
  5. 2
      main_test.go
  6. 26
      path.go
  7. 7
      rtsp-simple-server.yml
  8. 26
      source.go
  9. 8
      utils.go

2
README.md

@ -201,7 +201,7 @@ There are multiple ways to monitor the server usage over time:
``` ```
2020/01/01 00:00:00 [2/1/1] [client 127.0.0.1:44428] OPTION 2020/01/01 00:00:00 [2/1/1] [client 127.0.0.1:44428] OPTION
``` ```
means that there are 2 clients, 1 publisher and 1 receiver. means that there are 2 clients, 1 publisher and 1 reader.
* A metrics exporter, compatible with Prometheus, can be enabled with the option `metrics: yes`; then the server can be queried for metrics with Prometheus or with a simple HTTP request: * A metrics exporter, compatible with Prometheus, can be enabled with the option `metrics: yes`; then the server can be queried for metrics with Prometheus or with a simple HTTP request:
``` ```

50
client.go

@ -27,12 +27,14 @@ const (
type clientDescribeReq struct { type clientDescribeReq struct {
client *client client *client
pathName string pathName string
pathConf *pathConf
} }
type clientAnnounceReq struct { type clientAnnounceReq struct {
res chan error res chan error
client *client client *client
pathName string pathName string
pathConf *pathConf
trackCount int trackCount int
sdp []byte sdp []byte
} }
@ -369,14 +371,13 @@ func (c *client) handleRequest(req *gortsplib.Request) error {
pathName = removeQueryFromPath(pathName) pathName = removeQueryFromPath(pathName)
confp := c.p.findConfForPathName(pathName) pathConf, err := c.p.conf.checkPathNameAndFindConf(pathName)
if confp == nil { if err != nil {
c.writeResError(cseq, gortsplib.StatusBadRequest, c.writeResError(cseq, gortsplib.StatusBadRequest, err)
fmt.Errorf("unable to find a valid configuration for path '%s'", pathName))
return errRunTerminate return errRunTerminate
} }
err := c.authenticate(confp.readIpsParsed, confp.ReadUser, confp.ReadPass, req) err = c.authenticate(pathConf.readIpsParsed, pathConf.ReadUser, pathConf.ReadPass, req)
if err != nil { if err != nil {
if err == errAuthCritical { if err == errAuthCritical {
return errRunTerminate return errRunTerminate
@ -384,7 +385,7 @@ func (c *client) handleRequest(req *gortsplib.Request) error {
return nil return nil
} }
c.p.clientDescribe <- clientDescribeReq{c, pathName} c.p.clientDescribe <- clientDescribeReq{c, pathName, pathConf}
c.describeCSeq = cseq c.describeCSeq = cseq
c.describeUrl = req.Url.String() c.describeUrl = req.Url.String()
@ -400,25 +401,13 @@ func (c *client) handleRequest(req *gortsplib.Request) error {
pathName = removeQueryFromPath(pathName) pathName = removeQueryFromPath(pathName)
if len(pathName) == 0 { pathConf, err := c.p.conf.checkPathNameAndFindConf(pathName)
c.writeResError(cseq, gortsplib.StatusBadRequest, fmt.Errorf("empty base path"))
return errRunTerminate
}
err := checkPathName(pathName)
if err != nil { if err != nil {
c.writeResError(cseq, gortsplib.StatusBadRequest, fmt.Errorf("invalid path name: %s (%s)", err, pathName)) c.writeResError(cseq, gortsplib.StatusBadRequest, err)
return errRunTerminate
}
confp := c.p.findConfForPathName(pathName)
if confp == nil {
c.writeResError(cseq, gortsplib.StatusBadRequest,
fmt.Errorf("unable to find a valid configuration for path '%s'", pathName))
return errRunTerminate return errRunTerminate
} }
err = c.authenticate(confp.publishIpsParsed, confp.PublishUser, confp.PublishPass, req) err = c.authenticate(pathConf.publishIpsParsed, pathConf.PublishUser, pathConf.PublishPass, req)
if err != nil { if err != nil {
if err == errAuthCritical { if err == errAuthCritical {
return errRunTerminate return errRunTerminate
@ -451,7 +440,7 @@ func (c *client) handleRequest(req *gortsplib.Request) error {
sdp := tracks.Write() sdp := tracks.Write()
res := make(chan error) res := make(chan error)
c.p.clientAnnounce <- clientAnnounceReq{res, c, pathName, len(tracks), sdp} c.p.clientAnnounce <- clientAnnounceReq{res, c, pathName, pathConf, len(tracks), sdp}
err = <-res err = <-res
if err != nil { if err != nil {
c.writeResError(cseq, gortsplib.StatusBadRequest, err) c.writeResError(cseq, gortsplib.StatusBadRequest, err)
@ -489,14 +478,13 @@ func (c *client) handleRequest(req *gortsplib.Request) error {
switch c.state { switch c.state {
// play // play
case clientStateInitial, clientStatePrePlay: case clientStateInitial, clientStatePrePlay:
confp := c.p.findConfForPathName(basePath) pathConf, err := c.p.conf.checkPathNameAndFindConf(basePath)
if confp == nil { if err != nil {
c.writeResError(cseq, gortsplib.StatusBadRequest, c.writeResError(cseq, gortsplib.StatusBadRequest, err)
fmt.Errorf("unable to find a valid configuration for path '%s'", basePath))
return errRunTerminate return errRunTerminate
} }
err := c.authenticate(confp.readIpsParsed, confp.ReadUser, confp.ReadPass, req) err = c.authenticate(pathConf.readIpsParsed, pathConf.ReadUser, pathConf.ReadPass, req)
if err != nil { if err != nil {
if err == errAuthCritical { if err == errAuthCritical {
return errRunTerminate return errRunTerminate
@ -899,9 +887,9 @@ func (c *client) runPlay() bool {
}(), c.streamProtocol) }(), c.streamProtocol)
var onReadCmd *exec.Cmd var onReadCmd *exec.Cmd
if c.path.confp.RunOnRead != "" { if c.path.conf.RunOnRead != "" {
var err error var err error
onReadCmd, err = startExternalCommand(c.path.confp.RunOnRead, c.path.name) onReadCmd, err = startExternalCommand(c.path.conf.RunOnRead, c.path.name)
if err != nil { if err != nil {
c.log("ERR: %s", err) c.log("ERR: %s", err)
} }
@ -1050,9 +1038,9 @@ func (c *client) runRecord() bool {
}(), c.streamProtocol) }(), c.streamProtocol)
var onPublishCmd *exec.Cmd var onPublishCmd *exec.Cmd
if c.path.confp.RunOnPublish != "" { if c.path.conf.RunOnPublish != "" {
var err error var err error
onPublishCmd, err = startExternalCommand(c.path.confp.RunOnPublish, c.path.name) onPublishCmd, err = startExternalCommand(c.path.conf.RunOnPublish, c.path.name)
if err != nil { if err != nil {
c.log("ERR: %s", err) c.log("ERR: %s", err)
} }

125
conf.go

@ -12,7 +12,8 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
type confPath struct { type pathConf struct {
regexp *regexp.Regexp
Source string `yaml:"source"` Source string `yaml:"source"`
sourceUrl *url.URL `` sourceUrl *url.URL ``
SourceProtocol string `yaml:"sourceProtocol"` SourceProtocol string `yaml:"sourceProtocol"`
@ -48,7 +49,7 @@ type conf struct {
LogDestinations []string `yaml:"logDestinations"` LogDestinations []string `yaml:"logDestinations"`
logDestinationsParsed map[logDestination]struct{} `` logDestinationsParsed map[logDestination]struct{} ``
LogFile string `yaml:"logFile"` LogFile string `yaml:"logFile"`
Paths map[string]*confPath `yaml:"paths"` Paths map[string]*pathConf `yaml:"paths"`
} }
func loadConf(fpath string, stdin io.Reader) (*conf, error) { func loadConf(fpath string, stdin io.Reader) (*conf, error) {
@ -172,105 +173,147 @@ func loadConf(fpath string, stdin io.Reader) (*conf, error) {
} }
if len(conf.Paths) == 0 { if len(conf.Paths) == 0 {
conf.Paths = map[string]*confPath{ conf.Paths = map[string]*pathConf{
"all": {}, "all": {},
} }
} }
for name, confp := range conf.Paths { // "all" is an alias for "~^.*$"
if confp == nil { if _, ok := conf.Paths["all"]; ok {
conf.Paths[name] = &confPath{} conf.Paths["~^.*$"] = conf.Paths["all"]
confp = conf.Paths[name] delete(conf.Paths, "all")
} }
for name, pconf := range conf.Paths {
if pconf == nil {
conf.Paths[name] = &pathConf{}
pconf = conf.Paths[name]
}
if name == "" {
return nil, fmt.Errorf("path name can not be empty")
}
// normal path
if name[0] != '~' {
err := checkPathName(name) err := checkPathName(name)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid path name: %s (%s)", err, name) return nil, fmt.Errorf("invalid path name: %s (%s)", err, name)
} }
if confp.Source == "" { // regular expression path
confp.Source = "record" } else {
pathRegexp, err := regexp.Compile(name[1:])
if err != nil {
return nil, fmt.Errorf("invalid regular expression: %s", name[1:])
}
pconf.regexp = pathRegexp
}
if pconf.Source == "" {
pconf.Source = "record"
} }
if confp.Source != "record" { if pconf.Source != "record" {
if name == "all" { if pconf.regexp != nil {
return nil, fmt.Errorf("path 'all' cannot have a RTSP source; use another path") return nil, fmt.Errorf("a path with a regular expression cannot have a RTSP source; use another path")
} }
confp.sourceUrl, err = url.Parse(confp.Source) pconf.sourceUrl, err = url.Parse(pconf.Source)
if err != nil { if err != nil {
return nil, fmt.Errorf("'%s' is not a valid RTSP url", confp.Source) return nil, fmt.Errorf("'%s' is not a valid RTSP url", pconf.Source)
} }
if confp.sourceUrl.Scheme != "rtsp" { if pconf.sourceUrl.Scheme != "rtsp" {
return nil, fmt.Errorf("'%s' is not a valid RTSP url", confp.Source) return nil, fmt.Errorf("'%s' is not a valid RTSP url", pconf.Source)
} }
if confp.sourceUrl.Port() == "" { if pconf.sourceUrl.Port() == "" {
confp.sourceUrl.Host += ":554" pconf.sourceUrl.Host += ":554"
} }
if confp.sourceUrl.User != nil { if pconf.sourceUrl.User != nil {
pass, _ := confp.sourceUrl.User.Password() pass, _ := pconf.sourceUrl.User.Password()
user := confp.sourceUrl.User.Username() user := pconf.sourceUrl.User.Username()
if user != "" && pass == "" || if user != "" && pass == "" ||
user == "" && pass != "" { user == "" && pass != "" {
fmt.Errorf("username and password must be both provided") fmt.Errorf("username and password must be both provided")
} }
} }
if confp.SourceProtocol == "" { if pconf.SourceProtocol == "" {
confp.SourceProtocol = "udp" pconf.SourceProtocol = "udp"
} }
switch confp.SourceProtocol { switch pconf.SourceProtocol {
case "udp": case "udp":
confp.sourceProtocolParsed = gortsplib.StreamProtocolUDP pconf.sourceProtocolParsed = gortsplib.StreamProtocolUDP
case "tcp": case "tcp":
confp.sourceProtocolParsed = gortsplib.StreamProtocolTCP pconf.sourceProtocolParsed = gortsplib.StreamProtocolTCP
default: default:
return nil, fmt.Errorf("unsupported protocol '%s'", confp.SourceProtocol) return nil, fmt.Errorf("unsupported protocol '%s'", pconf.SourceProtocol)
} }
} }
if confp.PublishUser != "" { if pconf.PublishUser != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(confp.PublishUser) { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishUser) {
return nil, fmt.Errorf("publish username must be alphanumeric") return nil, fmt.Errorf("publish username must be alphanumeric")
} }
} }
if confp.PublishPass != "" { if pconf.PublishPass != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(confp.PublishPass) { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishPass) {
return nil, fmt.Errorf("publish password must be alphanumeric") return nil, fmt.Errorf("publish password must be alphanumeric")
} }
} }
confp.publishIpsParsed, err = parseIpCidrList(confp.PublishIps) pconf.publishIpsParsed, err = parseIpCidrList(pconf.PublishIps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if confp.ReadUser != "" && confp.ReadPass == "" || confp.ReadUser == "" && confp.ReadPass != "" { if pconf.ReadUser != "" && pconf.ReadPass == "" || pconf.ReadUser == "" && pconf.ReadPass != "" {
return nil, fmt.Errorf("read username and password must be both filled") return nil, fmt.Errorf("read username and password must be both filled")
} }
if confp.ReadUser != "" { if pconf.ReadUser != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(confp.ReadUser) { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.ReadUser) {
return nil, fmt.Errorf("read username must be alphanumeric") return nil, fmt.Errorf("read username must be alphanumeric")
} }
} }
if confp.ReadPass != "" { if pconf.ReadPass != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(confp.ReadPass) { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.ReadPass) {
return nil, fmt.Errorf("read password must be alphanumeric") return nil, fmt.Errorf("read password must be alphanumeric")
} }
} }
if confp.ReadUser != "" && confp.ReadPass == "" || confp.ReadUser == "" && confp.ReadPass != "" { if pconf.ReadUser != "" && pconf.ReadPass == "" || pconf.ReadUser == "" && pconf.ReadPass != "" {
return nil, fmt.Errorf("read username and password must be both filled") return nil, fmt.Errorf("read username and password must be both filled")
} }
confp.readIpsParsed, err = parseIpCidrList(confp.ReadIps) pconf.readIpsParsed, err = parseIpCidrList(pconf.ReadIps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if name == "all" && confp.RunOnInit != "" { if pconf.regexp != nil && pconf.RunOnInit != "" {
return nil, fmt.Errorf("path 'all' does not support option 'runOnInit'; use another path") return nil, fmt.Errorf("a path with a regular expression does not support option 'runOnInit'; use another path")
} }
} }
return conf, nil return conf, nil
} }
func (conf *conf) checkPathNameAndFindConf(name string) (*pathConf, error) {
err := checkPathName(name)
if err != nil {
return nil, fmt.Errorf("invalid path name: %s (%s)", err, name)
}
// normal path
if pconf, ok := conf.Paths[name]; ok {
return pconf, nil
}
// regular expression path
for _, pconf := range conf.Paths {
if pconf.regexp != nil && pconf.regexp.MatchString(name) {
return pconf, nil
}
}
return nil, fmt.Errorf("unable to find a valid configuration for path '%s'", name)
}

23
main.go

@ -110,11 +110,10 @@ func newProgram(args []string, stdin io.Reader) (*program, error) {
} }
} }
for name, confp := range conf.Paths { for name, pathConf := range conf.Paths {
if name == "all" { if pathConf.regexp == nil {
continue p.paths[name] = newPath(p, name, pathConf)
} }
p.paths[name] = newPath(p, name, confp, true)
} }
if _, ok := conf.protocolsParsed[gortsplib.StreamProtocolUDP]; ok { if _, ok := conf.protocolsParsed[gortsplib.StreamProtocolUDP]; ok {
@ -204,7 +203,7 @@ outer:
case req := <-p.clientDescribe: case req := <-p.clientDescribe:
// create path if not exist // create path if not exist
if _, ok := p.paths[req.pathName]; !ok { if _, ok := p.paths[req.pathName]; !ok {
p.paths[req.pathName] = newPath(p, req.pathName, p.findConfForPathName(req.pathName), false) p.paths[req.pathName] = newPath(p, req.pathName, req.pathConf)
} }
p.paths[req.pathName].onDescribe(req.client) p.paths[req.pathName].onDescribe(req.client)
@ -212,7 +211,7 @@ outer:
case req := <-p.clientAnnounce: case req := <-p.clientAnnounce:
// create path if not exist // create path if not exist
if path, ok := p.paths[req.pathName]; !ok { if path, ok := p.paths[req.pathName]; !ok {
p.paths[req.pathName] = newPath(p, req.pathName, p.findConfForPathName(req.pathName), false) p.paths[req.pathName] = newPath(p, req.pathName, req.pathConf)
} else { } else {
if path.publisher != nil { if path.publisher != nil {
@ -364,18 +363,6 @@ func (p *program) close() {
<-p.done <-p.done
} }
func (p *program) findConfForPathName(name string) *confPath {
if confp, ok := p.conf.Paths[name]; ok {
return confp
}
if confp, ok := p.conf.Paths["all"]; ok {
return confp
}
return nil
}
func main() { func main() {
_, err := newProgram(os.Args[1:], os.Stdin) _, err := newProgram(os.Args[1:], os.Stdin)
if err != nil { if err != nil {

2
main_test.go

@ -480,7 +480,7 @@ func TestProxy(t *testing.T) {
func TestRunOnDemand(t *testing.T) { func TestRunOnDemand(t *testing.T) {
stdin := []byte("\n" + stdin := []byte("\n" +
"paths:\n" + "paths:\n" +
" ondemand:\n" + " all:\n" +
" runOnDemand: ffmpeg -hide_banner -loglevel error -re -i test-images/ffmpeg/emptyvideo.ts -c copy -f rtsp rtsp://localhost:8554/$RTSP_SERVER_PATH\n") " runOnDemand: ffmpeg -hide_banner -loglevel error -re -i test-images/ffmpeg/emptyvideo.ts -c copy -f rtsp rtsp://localhost:8554/$RTSP_SERVER_PATH\n")
p1, err := newProgram([]string{"stdin"}, bytes.NewBuffer(stdin)) p1, err := newProgram([]string{"stdin"}, bytes.NewBuffer(stdin))
require.NoError(t, err) require.NoError(t, err)

26
path.go

@ -21,8 +21,7 @@ type publisher interface {
type path struct { type path struct {
p *program p *program
name string name string
confp *confPath conf *pathConf
permanent bool
source *source source *source
publisher publisher publisher publisher
publisherReady bool publisherReady bool
@ -34,16 +33,15 @@ type path struct {
onDemandCmd *exec.Cmd onDemandCmd *exec.Cmd
} }
func newPath(p *program, name string, confp *confPath, permanent bool) *path { func newPath(p *program, name string, conf *pathConf) *path {
pa := &path{ pa := &path{
p: p, p: p,
name: name, name: name,
confp: confp, conf: conf,
permanent: permanent,
} }
if confp.Source != "record" { if conf.Source != "record" {
s := newSource(p, pa, confp) s := newSource(p, pa, conf)
pa.source = s pa.source = s
pa.publisher = s pa.publisher = s
} }
@ -60,11 +58,11 @@ func (pa *path) onInit() {
go pa.source.run(pa.source.state) go pa.source.run(pa.source.state)
} }
if pa.confp.RunOnInit != "" { if pa.conf.RunOnInit != "" {
pa.log("starting on init command") pa.log("starting on init command")
var err error var err error
pa.onInitCmd, err = startExternalCommand(pa.confp.RunOnInit, pa.name) pa.onInitCmd, err = startExternalCommand(pa.conf.RunOnInit, pa.name)
if err != nil { if err != nil {
pa.log("ERR: %s", err) pa.log("ERR: %s", err)
} }
@ -149,7 +147,7 @@ func (pa *path) onCheck() {
// stop on demand source if needed // stop on demand source if needed
if pa.source != nil && if pa.source != nil &&
pa.confp.SourceOnDemand && pa.conf.SourceOnDemand &&
pa.source.state == sourceStateRunning && pa.source.state == sourceStateRunning &&
!pa.hasClients() && !pa.hasClients() &&
time.Since(pa.lastDescribeReq) >= sourceStopAfterDescribeSecs { time.Since(pa.lastDescribeReq) >= sourceStopAfterDescribeSecs {
@ -168,8 +166,8 @@ func (pa *path) onCheck() {
pa.onDemandCmd = nil pa.onDemandCmd = nil
} }
// remove non-permanent paths // remove regular expression paths
if !pa.permanent && if pa.conf.regexp != nil &&
pa.publisher == nil && pa.publisher == nil &&
!pa.hasClients() { !pa.hasClients() {
pa.onClose(false) pa.onClose(false)
@ -223,13 +221,13 @@ func (pa *path) onDescribe(client *client) {
// publisher not found // publisher not found
if pa.publisher == nil { if pa.publisher == nil {
// on demand command is available: put the client on hold // on demand command is available: put the client on hold
if pa.confp.RunOnDemand != "" { if pa.conf.RunOnDemand != "" {
if pa.onDemandCmd == nil { // start if needed if pa.onDemandCmd == nil { // start if needed
pa.log("starting on demand command") pa.log("starting on demand command")
pa.lastDescribeActivation = time.Now() pa.lastDescribeActivation = time.Now()
var err error var err error
pa.onDemandCmd, err = startExternalCommand(pa.confp.RunOnDemand, pa.name) pa.onDemandCmd, err = startExternalCommand(pa.conf.RunOnDemand, pa.name)
if err != nil { if err != nil {
pa.log("ERR: %s", err) pa.log("ERR: %s", err)
} }

7
rtsp-simple-server.yml

@ -29,8 +29,11 @@ logDestinations: [stdout]
# if 'file' is in logDestinations, this is the file that will receive the logs # if 'file' is in logDestinations, this is the file that will receive the logs
logFile: rtsp-simple-server.log logFile: rtsp-simple-server.log
# these settings are path-dependent. The settings under the path 'all' are # these settings are path-dependent.
# applied to all paths that do not match a specific entry. # The settings under the path 'all' are applied to all paths that do not match
# another entry.
# It's possible to use regular expressions by using a tilde as prefix,
# for instance, '~^(test1|test2)$' will match both test1 and test2.
paths: paths:
all: all:
# source of the stream - this can be: # source of the stream - this can be:

26
source.go

@ -24,7 +24,7 @@ const (
type source struct { type source struct {
p *program p *program
path *path path *path
confp *confPath pathConf *pathConf
state sourceState state sourceState
tracks []*gortsplib.Track tracks []*gortsplib.Track
innerRunning bool innerRunning bool
@ -36,17 +36,17 @@ type source struct {
done chan struct{} done chan struct{}
} }
func newSource(p *program, path *path, confp *confPath) *source { func newSource(p *program, path *path, pathConf *pathConf) *source {
s := &source{ s := &source{
p: p, p: p,
path: path, path: path,
confp: confp, pathConf: pathConf,
setState: make(chan sourceState), setState: make(chan sourceState),
terminate: make(chan struct{}), terminate: make(chan struct{}),
done: make(chan struct{}), done: make(chan struct{}),
} }
if confp.SourceOnDemand { if pathConf.SourceOnDemand {
s.state = sourceStateStopped s.state = sourceStateStopped
} else { } else {
s.state = sourceStateRunning s.state = sourceStateRunning
@ -134,7 +134,7 @@ func (s *source) runInnerInner() bool {
dialDone := make(chan struct{}) dialDone := make(chan struct{})
go func() { go func() {
conn, err = gortsplib.NewConnClient(gortsplib.ConnClientConf{ conn, err = gortsplib.NewConnClient(gortsplib.ConnClientConf{
Host: s.confp.sourceUrl.Host, Host: s.pathConf.sourceUrl.Host,
ReadTimeout: s.p.conf.ReadTimeout, ReadTimeout: s.p.conf.ReadTimeout,
WriteTimeout: s.p.conf.WriteTimeout, WriteTimeout: s.p.conf.WriteTimeout,
}) })
@ -152,14 +152,14 @@ func (s *source) runInnerInner() bool {
return true return true
} }
_, err = conn.Options(s.confp.sourceUrl) _, err = conn.Options(s.pathConf.sourceUrl)
if err != nil { if err != nil {
conn.Close() conn.Close()
s.path.log("source ERR: %s", err) s.path.log("source ERR: %s", err)
return true return true
} }
tracks, _, err := conn.Describe(s.confp.sourceUrl) tracks, _, err := conn.Describe(s.pathConf.sourceUrl)
if err != nil { if err != nil {
conn.Close() conn.Close()
s.path.log("source ERR: %s", err) s.path.log("source ERR: %s", err)
@ -173,7 +173,7 @@ func (s *source) runInnerInner() bool {
s.path.publisherTrackCount = len(tracks) s.path.publisherTrackCount = len(tracks)
s.path.publisherSdp = serverSdp s.path.publisherSdp = serverSdp
if s.confp.sourceProtocolParsed == gortsplib.StreamProtocolUDP { if s.pathConf.sourceProtocolParsed == gortsplib.StreamProtocolUDP {
return s.runUDP(conn) return s.runUDP(conn)
} else { } else {
return s.runTCP(conn) return s.runTCP(conn)
@ -191,7 +191,7 @@ func (s *source) runUDP(conn *gortsplib.ConnClient) bool {
rtpPort := (rand.Intn((65535-10000)/2) * 2) + 10000 rtpPort := (rand.Intn((65535-10000)/2) * 2) + 10000
rtcpPort := rtpPort + 1 rtcpPort := rtpPort + 1
rtpRead, rtcpRead, _, err := conn.SetupUDP(s.confp.sourceUrl, track, rtpPort, rtcpPort) rtpRead, rtcpRead, _, err := conn.SetupUDP(s.pathConf.sourceUrl, track, rtpPort, rtcpPort)
if err != nil { if err != nil {
if isBindError(err) { if isBindError(err) {
continue // retry continue // retry
@ -208,7 +208,7 @@ func (s *source) runUDP(conn *gortsplib.ConnClient) bool {
} }
} }
_, err := conn.Play(s.confp.sourceUrl) _, err := conn.Play(s.pathConf.sourceUrl)
if err != nil { if err != nil {
conn.Close() conn.Close()
s.path.log("source ERR: %s", err) s.path.log("source ERR: %s", err)
@ -263,7 +263,7 @@ func (s *source) runUDP(conn *gortsplib.ConnClient) bool {
tcpConnDone := make(chan error) tcpConnDone := make(chan error)
go func() { go func() {
tcpConnDone <- conn.LoopUDP(s.confp.sourceUrl) tcpConnDone <- conn.LoopUDP(s.pathConf.sourceUrl)
}() }()
var ret bool var ret bool
@ -294,7 +294,7 @@ outer:
func (s *source) runTCP(conn *gortsplib.ConnClient) bool { func (s *source) runTCP(conn *gortsplib.ConnClient) bool {
for _, track := range s.tracks { for _, track := range s.tracks {
_, err := conn.SetupTCP(s.confp.sourceUrl, track) _, err := conn.SetupTCP(s.pathConf.sourceUrl, track)
if err != nil { if err != nil {
conn.Close() conn.Close()
s.path.log("source ERR: %s", err) s.path.log("source ERR: %s", err)
@ -302,7 +302,7 @@ func (s *source) runTCP(conn *gortsplib.ConnClient) bool {
} }
} }
_, err := conn.Play(s.confp.sourceUrl) _, err := conn.Play(s.pathConf.sourceUrl)
if err != nil { if err != nil {
conn.Close() conn.Close()
s.path.log("source ERR: %s", err) s.path.log("source ERR: %s", err)

8
utils.go

@ -118,8 +118,8 @@ func removeQueryFromPath(path string) string {
var rePathName = regexp.MustCompile("^[0-9a-zA-Z_\\-/]+$") var rePathName = regexp.MustCompile("^[0-9a-zA-Z_\\-/]+$")
func checkPathName(name string) error { func checkPathName(name string) error {
if !rePathName.MatchString(name) { if name == "" {
return fmt.Errorf("can contain only alfanumeric characters, underscore, minus or slash") return fmt.Errorf("cannot be empty")
} }
if name[0] == '/' { if name[0] == '/' {
@ -130,6 +130,10 @@ func checkPathName(name string) error {
return fmt.Errorf("can't end with a slash") return fmt.Errorf("can't end with a slash")
} }
if !rePathName.MatchString(name) {
return fmt.Errorf("can contain only alfanumeric characters, underscore, minus or slash")
}
return nil return nil
} }

Loading…
Cancel
Save