From 823217cfc2be1e9cdd74af1460cf0349a1924ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Einarson?= Date: Tue, 22 Aug 2023 15:53:20 +0200 Subject: [PATCH 1/2] feat: add support for utc_head --- cmd/livesim2/app/configurl.go | 3 ++- cmd/livesim2/app/configurl_test.go | 19 +++---------------- cmd/livesim2/app/handler_livesim.go | 15 ++++++++++++--- cmd/livesim2/app/livempd.go | 11 ++++++++--- cmd/livesim2/app/livempd_test.go | 18 +++++++++--------- cmd/livesim2/app/routes.go | 1 + cmd/livesim2/app/static/time.txt | 1 + cmd/livesim2/app/strconv.go | 4 +--- 8 files changed, 37 insertions(+), 35 deletions(-) create mode 100644 cmd/livesim2/app/static/time.txt diff --git a/cmd/livesim2/app/configurl.go b/cmd/livesim2/app/configurl.go index cd1abaf..66235f1 100644 --- a/cmd/livesim2/app/configurl.go +++ b/cmd/livesim2/app/configurl.go @@ -29,7 +29,7 @@ type UTCTimingMethod string const ( UtcTimingDirect UTCTimingMethod = "direct" - UtcTimingHead UTCTimingMethod = "head" // Note, not supported + UtcTimingHead UTCTimingMethod = "head" UtcTimingNtp UTCTimingMethod = "ntp" UtcTimingSntp UTCTimingMethod = "sntp" UtcTimingHttpXSDate UTCTimingMethod = "httpxsdate" @@ -42,6 +42,7 @@ const ( UtcTimingSntpServer = "time.kfki.hu" UtcTimingHttpServer = "http://time.akamai.com/?iso" UtcTimingHttpServerMS = "http://time.akamai.com/?isoms" + UtcTimingHeadAsset = "/static/time.txt" ) type ResponseConfig struct { diff --git a/cmd/livesim2/app/configurl_test.go b/cmd/livesim2/app/configurl_test.go index 0cc498d..3a1d8b6 100644 --- a/cmd/livesim2/app/configurl_test.go +++ b/cmd/livesim2/app/configurl_test.go @@ -20,18 +20,18 @@ func TestProcessURLCfg(t *testing.T) { err string }{ { - url: "/livesim2/utc_direct-ntp-sntp-httpxsdate-httpiso/asset/x.mpd", + url: "/livesim2/utc_direct-ntp-sntp-httpxsdate-httpiso-head/asset/x.mpd", nowMS: 0, contentPart: "asset/x.mpd", wantedCfg: &ResponseConfig{ - URLParts: []string{"", "livesim2", "utc_direct-ntp-sntp-httpxsdate-httpiso", "asset", "x.mpd"}, + URLParts: []string{"", "livesim2", "utc_direct-ntp-sntp-httpxsdate-httpiso-head", "asset", "x.mpd"}, URLContentIdx: 3, StartTimeS: 0, TimeShiftBufferDepthS: Ptr(defaultTimeShiftBufferDepthS), StartNr: Ptr(0), AvailabilityTimeCompleteFlag: true, TimeSubsDurMS: defaultTimeSubsDurMS, - UTCTimingMethods: []UTCTimingMethod{"direct", "ntp", "sntp", "httpxsdate", "httpiso"}, + UTCTimingMethods: []UTCTimingMethod{"direct", "ntp", "sntp", "httpxsdate", "httpiso", "head"}, }, err: "", }, @@ -48,19 +48,6 @@ func TestProcessURLCfg(t *testing.T) { }, err: `key="utc", val="unknown" is not a valid UTC timing method`, }, - { - url: "/livesim/utc_head/asset.mpd", - nowMS: 0, - contentPart: "asset.mpd", - wantedCfg: &ResponseConfig{ - StartTimeS: 0, - TimeShiftBufferDepthS: Ptr(defaultTimeShiftBufferDepthS), - StartNr: Ptr(0), - AvailabilityTimeCompleteFlag: true, - TimeSubsDurMS: defaultTimeSubsDurMS, - }, - err: `key="utc", val="head", UTC timing method "head" not supported`, - }, { url: "/livesim2/utc_none/asset.mpd", nowMS: 0, diff --git a/cmd/livesim2/app/handler_livesim.go b/cmd/livesim2/app/handler_livesim.go index fb39753..dc767e7 100644 --- a/cmd/livesim2/app/handler_livesim.go +++ b/cmd/livesim2/app/handler_livesim.go @@ -36,6 +36,7 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) { http.Error(w, msg, http.StatusInternalServerError) return } + fullHost := getSchemeAndHost(r) var nowMS int // Set from query string or from wall-clock q := r.URL.Query() @@ -80,7 +81,7 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) { _, mpdName := path.Split(contentPart) cfg.SetScheme(s.Cfg.Scheme, r) cfg.SetHost(s.Cfg.Host, r) - err := writeLiveMPD(log, w, cfg, a, mpdName, nowMS) + err := writeLiveMPD(log, w, cfg, a, mpdName, fullHost, nowMS) if err != nil { // TODO. Add more granular errors like 404 not found msg := fmt.Sprintf("liveMPD: %s", err) @@ -112,10 +113,18 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) { } } -func writeLiveMPD(log *zerolog.Logger, w http.ResponseWriter, cfg *ResponseConfig, a *asset, mpdName string, nowMS int) error { +func getSchemeAndHost(r *http.Request) string { + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + return fmt.Sprintf("%s://%s", scheme, r.Host) +} + +func writeLiveMPD(log *zerolog.Logger, w http.ResponseWriter, cfg *ResponseConfig, a *asset, mpdName, host string, nowMS int) error { work := make([]byte, 0, 1024) buf := bytes.NewBuffer(work) - lMPD, err := LiveMPD(a, mpdName, cfg, nowMS) + lMPD, err := LiveMPD(a, mpdName, cfg, host, nowMS) if err != nil { return fmt.Errorf("convertToLive: %w", err) } diff --git a/cmd/livesim2/app/livempd.go b/cmd/livesim2/app/livempd.go index 0c63406..15fad60 100644 --- a/cmd/livesim2/app/livempd.go +++ b/cmd/livesim2/app/livempd.go @@ -43,7 +43,7 @@ func calcWrapTimes(a *asset, cfg *ResponseConfig, nowMS int, tsbd m.Duration) wr } // LiveMPD generates a dynamic configured MPD for a VoD asset. -func LiveMPD(a *asset, mpdName string, cfg *ResponseConfig, nowMS int) (*m.MPD, error) { +func LiveMPD(a *asset, mpdName string, cfg *ResponseConfig, host string, nowMS int) (*m.MPD, error) { mpd, err := a.getVodMPD(mpdName) if err != nil { return nil, err @@ -90,7 +90,7 @@ func LiveMPD(a *asset, mpdName string, cfg *ResponseConfig, nowMS int) (*m.MPD, } } - addUTCTimings(mpd, cfg) + addUTCTimings(mpd, cfg, host) afterStop := false endTimeMS := nowMS @@ -525,7 +525,7 @@ func lastSegAvailTimeS(cfg *ResponseConfig, lsi lastSegInfo) float64 { } // addUTCTimings adds the UTCTiming elements to the MPD. -func addUTCTimings(mpd *m.MPD, cfg *ResponseConfig) { +func addUTCTimings(mpd *m.MPD, cfg *ResponseConfig, host string) { if len(cfg.UTCTimingMethods) == 0 { // default if none is set. Use HTTP with ms precision. mpd.UTCTimings = []*m.DescriptorType{ @@ -564,6 +564,11 @@ func addUTCTimings(mpd *m.MPD, cfg *ResponseConfig) { SchemeIdUri: "urn:mpeg:dash:utc:http-iso:2014", Value: UtcTimingHttpServerMS, } + case UtcTimingHead: + ut = &m.DescriptorType{ + SchemeIdUri: "urn:mpeg:dash:utc:http-head:2014", + Value: fmt.Sprintf("%s%s", host, UtcTimingHeadAsset), + } case UtcTimingNone: cfg.UTCTimingMethods = nil return // no UTCTiming elements diff --git a/cmd/livesim2/app/livempd_test.go b/cmd/livesim2/app/livempd_test.go index c0163ae..201deaf 100644 --- a/cmd/livesim2/app/livempd_test.go +++ b/cmd/livesim2/app/livempd_test.go @@ -62,7 +62,7 @@ func TestLiveMPD(t *testing.T) { cfg := NewResponseConfig() nowMS := 100_000 // Number template - liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, nowMS) + liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", nowMS) assert.NoError(t, err) assert.Equal(t, "dynamic", *liveMPD.Type) assert.Equal(t, m.DateTime("1970-01-01T00:00:00Z"), liveMPD.AvailabilityStartTime) @@ -83,7 +83,7 @@ func TestLiveMPD(t *testing.T) { } // SegmentTimeline with $Time$ cfg.SegTimelineFlag = true - liveMPD, err = LiveMPD(asset, tc.mpdName, cfg, nowMS) + liveMPD, err = LiveMPD(asset, tc.mpdName, cfg, "", nowMS) assert.NoError(t, err) assert.Equal(t, "dynamic", *liveMPD.Type) assert.Equal(t, m.DateTime("1970-01-01T00:00:00Z"), liveMPD.AvailabilityStartTime) @@ -133,7 +133,7 @@ func TestLiveMPDWithTimeSubs(t *testing.T) { cfg.TimeSubsStpp = []string{"en", "sv"} nowMS := 100_000 // Number template - liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, nowMS) + liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", nowMS) assert.NoError(t, err) assert.Equal(t, "dynamic", *liveMPD.Type) aSets := liveMPD.Periods[0].AdaptationSets @@ -208,7 +208,7 @@ func TestSegmentTimes(t *testing.T) { } for nowS := tc.startTimeS; nowS < tc.endTimeS; nowS++ { nowMS := nowS * 1000 - liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, nowMS) + liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", nowMS) wantedStartNr := (nowS - 62) / 2 // Sliding window of 60s + one segment assert.NoError(t, err) for _, as := range liveMPD.Periods[0].AdaptationSets { @@ -460,7 +460,7 @@ func TestPublishTime(t *testing.T) { } err := verifyAndFillConfig(cfg, tc.nowMS) require.NoError(t, err) - liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS) + liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS) assert.NoError(t, err) assert.Equal(t, m.ConvertToDateTimeS(int64(tc.availabilityStartTime)), liveMPD.AvailabilityStartTime) assert.Equal(t, m.DateTime(tc.wantedPublishTime), liveMPD.PublishTime) @@ -532,7 +532,7 @@ func TestNormalAvailabilityTimeOffset(t *testing.T) { cfg.SegTimelineFlag = tc.segTimelineTime sc := strConvAccErr{} cfg.AvailabilityTimeOffsetS = sc.AtofInf("ato", tc.ato) - liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS) + liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS) if tc.wantedErr != "" { assert.EqualError(t, err, tc.wantedErr) return @@ -602,7 +602,7 @@ func TestUTCTiming(t *testing.T) { } err := verifyAndFillConfig(cfg, tc.nowMS) require.NoError(t, err) - liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS) + liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS) assert.NoError(t, err) assert.Equal(t, m.DateTime(tc.wantedPublishTime), liveMPD.PublishTime) assert.Equal(t, tc.wantedUTCTimings, len(liveMPD.UTCTimings)) @@ -700,7 +700,7 @@ func TestMultiPeriod(t *testing.T) { default: // $Number$ // no flag } - liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS) + liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS) if tc.wantedErr != "" { assert.EqualError(t, err, tc.wantedErr) return @@ -754,7 +754,7 @@ func TestRelStartStopTimeIntoLocation(t *testing.T) { asset, ok := am.findAsset(contentPart) require.True(t, ok) _, mpdName := path.Split(contentPart) - liveMPD, err := LiveMPD(asset, mpdName, cfg, c.nowMS) + liveMPD, err := LiveMPD(asset, mpdName, cfg, "", c.nowMS) require.NoError(t, err) require.Equal(t, c.wantedLocation, string(liveMPD.Location[0]), "the right location element is not inserted") } diff --git a/cmd/livesim2/app/routes.go b/cmd/livesim2/app/routes.go index 2da5971..5f3e977 100644 --- a/cmd/livesim2/app/routes.go +++ b/cmd/livesim2/app/routes.go @@ -30,6 +30,7 @@ func (s *Server) Routes(ctx context.Context) error { s.Router.MethodFunc("GET", "/config", s.configHandlerFunc) s.Router.MethodFunc("GET", "/assets", s.assetsHandlerFunc) s.Router.MethodFunc("GET", "/static/*", s.embeddedStaticHandlerFunc) + s.Router.MethodFunc("HEAD", "/static/*", s.embeddedStaticHandlerFunc) // LiveRouter is mounted at /livesim2 s.LiveRouter.MethodFunc("GET", "/*", s.livesimHandlerFunc) s.LiveRouter.MethodFunc("HEAD", "/*", s.livesimHandlerFunc) diff --git a/cmd/livesim2/app/static/time.txt b/cmd/livesim2/app/static/time.txt new file mode 100644 index 0000000..3646e72 --- /dev/null +++ b/cmd/livesim2/app/static/time.txt @@ -0,0 +1 @@ +Used for UTCTiming in head mode. \ No newline at end of file diff --git a/cmd/livesim2/app/strconv.go b/cmd/livesim2/app/strconv.go index cbf014a..ca35cf9 100644 --- a/cmd/livesim2/app/strconv.go +++ b/cmd/livesim2/app/strconv.go @@ -80,10 +80,8 @@ func (s *strConvAccErr) SplitUTCTimings(key, val string) []UTCTimingMethod { utcVal := UTCTimingMethod(val) switch utcVal { case UtcTimingDirect, UtcTimingNtp, UtcTimingSntp, UtcTimingHttpXSDate, UtcTimingHttpISO, - UtcTimingNone: + UtcTimingNone, UtcTimingHead: utcTimingMethods[i] = utcVal - case UtcTimingHead: - s.err = fmt.Errorf("key=%q, val=%q, UTC timing method %q not supported", key, val, UtcTimingHead) default: s.err = fmt.Errorf("key=%q, val=%q is not a valid UTC timing method", key, val) } From 560902fc731a653c9d0653f8410d63c9259df62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Einarson?= Date: Tue, 22 Aug 2023 16:35:17 +0200 Subject: [PATCH 2/2] fix: reduce configuration parameters for mounting service at non-standard location --- CHANGELOG.md | 1 + cmd/livesim2/app/config.go | 9 ++------- cmd/livesim2/app/handler_index.go | 3 ++- cmd/livesim2/app/handler_index_test.go | 11 ++++++----- cmd/livesim2/app/handler_livesim.go | 8 +++++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 116fe8d..a99a212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - moved list URL parameters to [livesim2 wiki](https://github.com/Dash-Industry-Forum/livesim2/wiki/URL-Parameters) +- removed `scheme` and `urlprefix` configuration. Now replaced with `host` which overrides `scheme://host` in all generated URLs ### Added diff --git a/cmd/livesim2/app/config.go b/cmd/livesim2/app/config.go index 5115a58..ef87e20 100644 --- a/cmd/livesim2/app/config.go +++ b/cmd/livesim2/app/config.go @@ -50,12 +50,8 @@ type ServerConfig struct { CertPath string `json:"certpath"` // KeyPath is a path to a valid private TLS key KeyPath string `json:"keypath"` - // Scheme should be http or https if set. Otherwise the scheme is auto-detected as far as possible. - Scheme string `json:"scheme"` - // If Host is set, it will be used instead of autodetected value. + // If Host is set, it will be used instead of autodetected value scheme://host. Host string `json:"host"` - // URLPrefix is used for paths generated by livesim2, such as links in welcome page - URLPrefix string `json:"urlprefix"` } var DefaultConfig = ServerConfig{ @@ -113,8 +109,7 @@ func LoadConfig(args []string, cwd string) (*ServerConfig, error) { f.String("certpath", k.String("certpath"), "path to TLS certificate file (for HTTPS). Use domains instead if possible") f.String("keypath", k.String("keypath"), "path to TLS private key file (for HTTPS). Use domains instead if possible.") f.String("scheme", k.String("scheme"), "scheme used in Location and BaseURL elements. If empty, it is attempted to be auto-detected") - f.String("host", k.String("host"), "host used in Location and BaseURL elements. If empty, it is attempted to be auto-detected") - f.String("urlprefix", k.String("urlprefix"), "prefix when paths are not mounted at root") + f.String("host", k.String("host"), "host (and possible prefix) used in MPD elements. Overrides auto-detected full scheme://host") if err := f.Parse(args[1:]); err != nil { return nil, fmt.Errorf("command line parse: %w", err) } diff --git a/cmd/livesim2/app/handler_index.go b/cmd/livesim2/app/handler_index.go index 36a76a5..fecc072 100644 --- a/cmd/livesim2/app/handler_index.go +++ b/cmd/livesim2/app/handler_index.go @@ -13,7 +13,8 @@ import ( // indexHandlerFunc handles access to /. func (s *Server) indexHandlerFunc(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") - err := s.htmlTemplates.ExecuteTemplate(w, "welcome.html", s.Cfg.URLPrefix) + fullHost := getSchemeAndHost(r, s.Cfg) + err := s.htmlTemplates.ExecuteTemplate(w, "welcome.html", fullHost) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/cmd/livesim2/app/handler_index_test.go b/cmd/livesim2/app/handler_index_test.go index 512afe0..0815f8d 100644 --- a/cmd/livesim2/app/handler_index_test.go +++ b/cmd/livesim2/app/handler_index_test.go @@ -6,6 +6,7 @@ package app import ( "context" + "fmt" "net/http" "net/http/httptest" "strings" @@ -15,12 +16,12 @@ import ( "github.com/stretchr/testify/require" ) -func TestIndexPageWithPrefix(t *testing.T) { +func TestIndexPageWithHost(t *testing.T) { cfg := ServerConfig{ VodRoot: "testdata/assets", TimeoutS: 0, LogFormat: logging.LogDiscard, - URLPrefix: "/livesim2", + Host: "https://example.com/subfolder", } _, err := logging.InitZerolog(cfg.LogLevel, cfg.LogFormat) require.NoError(t, err) @@ -31,10 +32,10 @@ func TestIndexPageWithPrefix(t *testing.T) { resp, body := testFullRequest(t, ts, "GET", "/", nil) require.Equal(t, http.StatusOK, resp.StatusCode) - require.Greater(t, strings.Index(string(body), `href="/livesim2/assets"`), 0) + require.Greater(t, strings.Index(string(body), `href="https://example.com/subfolder/assets"`), 0) } -func TestIndexPageWithoutPrefix(t *testing.T) { +func TestIndexPageWithoutHost(t *testing.T) { cfg := ServerConfig{ VodRoot: "testdata/assets", TimeoutS: 0, @@ -49,5 +50,5 @@ func TestIndexPageWithoutPrefix(t *testing.T) { resp, body := testFullRequest(t, ts, "GET", "/", nil) require.Equal(t, http.StatusOK, resp.StatusCode) - require.Greater(t, strings.Index(string(body), `href="/assets"`), 0) + require.Greater(t, strings.Index(string(body), fmt.Sprintf(`href="%s/assets"`, ts.URL)), 0) } diff --git a/cmd/livesim2/app/handler_livesim.go b/cmd/livesim2/app/handler_livesim.go index dc767e7..9bcc3b1 100644 --- a/cmd/livesim2/app/handler_livesim.go +++ b/cmd/livesim2/app/handler_livesim.go @@ -36,7 +36,7 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) { http.Error(w, msg, http.StatusInternalServerError) return } - fullHost := getSchemeAndHost(r) + fullHost := getSchemeAndHost(r, s.Cfg) var nowMS int // Set from query string or from wall-clock q := r.URL.Query() @@ -79,7 +79,6 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) { switch ext { case ".mpd": _, mpdName := path.Split(contentPart) - cfg.SetScheme(s.Cfg.Scheme, r) cfg.SetHost(s.Cfg.Host, r) err := writeLiveMPD(log, w, cfg, a, mpdName, fullHost, nowMS) if err != nil { @@ -113,7 +112,10 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) { } } -func getSchemeAndHost(r *http.Request) string { +func getSchemeAndHost(r *http.Request, cfg *ServerConfig) string { + if cfg.Host != "" { + return cfg.Host + } scheme := "http" if r.TLS != nil { scheme = "https"