feat(api): add server-side caching for requests that could benefit (#3463)

* feat(api): add server-side caching for requests that could benefit for them

* fix(tests): do not cache responses while in tests

* fix: remove commented out leftover code

* chore(deps): update dependency html-webpack-plugin to v5.5.4

* Bundle embedded web app

* fix: remove caching for web app assets under test

* chore(tests): re-enable temporarily disabled test

* chore(deps): update dependency typescript to v5.3.3

* Bundle embedded web app

* chore(deps): update dependency npm to v10.2.5

* Bundle embedded web app

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Owncast <owncast@owncast.online>
This commit is contained in:
Gabe Kangas 2023-12-09 16:31:50 -08:00 committed by GitHub
parent b6efe49086
commit 2217f0614a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 316 additions and 119 deletions

View file

@ -30,4 +30,8 @@ var (
// PublicFilesPath is the optional directory for hosting public files.
PublicFilesPath = filepath.Join(DataDirectory, "public")
// DisableResponseCaching will disable caching of API and resource
// responses. Disable this feature to turn off the optimizations.
DisableResponseCaching = false
)

View file

@ -6,6 +6,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core"
@ -13,8 +14,19 @@ import (
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/router/middleware"
"github.com/owncast/owncast/utils"
log "github.com/sirupsen/logrus"
cache "github.com/victorspringer/http-cache"
"github.com/victorspringer/http-cache/adapter/memory"
)
type FileServerHandler struct {
HLSPath string
}
func (fsh *FileServerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
http.ServeFile(rw, r, fsh.HLSPath)
}
// HandleHLSRequest will manage all requests to HLS content.
func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
// Sanity check to limit requests to HLS file types.
@ -23,6 +35,26 @@ func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
return
}
responseCache, err := memory.NewAdapter(
memory.AdapterWithAlgorithm(memory.LRU),
memory.AdapterWithCapacity(20),
memory.AdapterWithStorageCapacity(209_715_200),
)
if err != nil {
log.Warn("unable to create web cache", err)
}
// Since HLS segments cannot be changed once they're rendered, we can cache
// individual segments for a long time.
longTermHLSSegmentCache, err := cache.NewClient(
cache.ClientWithAdapter(responseCache),
cache.ClientWithTTL(30*time.Second),
cache.ClientWithExpiresHeader(),
)
if err != nil {
log.Warn("unable to create web cache client", err)
}
requestedPath := r.URL.Path
relativePath := strings.Replace(requestedPath, "/hls/", "", 1)
fullPath := filepath.Join(config.HLSStoragePath, relativePath)
@ -48,6 +80,10 @@ func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
} else {
cacheTime := utils.GetCacheDurationSecondsForPath(relativePath)
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheTime))
fileServer := &FileServerHandler{HLSPath: fullPath}
longTermHLSSegmentCache.Middleware(fileServer).ServeHTTP(w, r)
return
}
middleware.EnableCors(w)

1
go.mod
View file

@ -69,6 +69,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/oschwald/maxminddb-golang v1.11.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/victorspringer/http-cache v0.0.0-20231006141456-6446fe59efba // indirect
)
require (

2
go.sum
View file

@ -138,6 +138,8 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
github.com/victorspringer/http-cache v0.0.0-20231006141456-6446fe59efba h1:+oqDKQIOdkkvro1psUKtI4oH9WBeKkGY2S8h9/lo288=
github.com/victorspringer/http-cache v0.0.0-20231006141456-6446fe59efba/go.mod h1:D1AD6nlXv7HkIfTVd8ZWK1KQEiXYNy/LbLkx8H9tIQw=
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=

27
main.go
View file

@ -17,17 +17,18 @@ import (
)
var (
dbFile = flag.String("database", "", "Path to the database file.")
logDirectory = flag.String("logdir", "", "Directory where logs will be written to")
backupDirectory = flag.String("backupdir", "", "Directory where backups will be written to")
enableDebugOptions = flag.Bool("enableDebugFeatures", false, "Enable additional debugging options.")
enableVerboseLogging = flag.Bool("enableVerboseLogging", false, "Enable additional logging.")
restoreDatabaseFile = flag.String("restoreDatabase", "", "Restore an Owncast database backup")
newAdminPassword = flag.String("adminpassword", "", "Set your admin password")
newStreamKey = flag.String("streamkey", "", "Set a temporary stream key for this session")
webServerPortOverride = flag.String("webserverport", "", "Force the web server to listen on a specific port")
webServerIPOverride = flag.String("webserverip", "", "Force web server to listen on this IP address")
rtmpPortOverride = flag.Int("rtmpport", 0, "Set listen port for the RTMP server")
dbFile = flag.String("database", "", "Path to the database file.")
logDirectory = flag.String("logdir", "", "Directory where logs will be written to")
backupDirectory = flag.String("backupdir", "", "Directory where backups will be written to")
enableDebugOptions = flag.Bool("enableDebugFeatures", false, "Enable additional debugging options.")
enableVerboseLogging = flag.Bool("enableVerboseLogging", false, "Enable additional logging.")
restoreDatabaseFile = flag.String("restoreDatabase", "", "Restore an Owncast database backup")
newAdminPassword = flag.String("adminpassword", "", "Set your admin password")
newStreamKey = flag.String("streamkey", "", "Set a temporary stream key for this session")
webServerPortOverride = flag.String("webserverport", "", "Force the web server to listen on a specific port")
webServerIPOverride = flag.String("webserverip", "", "Force web server to listen on this IP address")
rtmpPortOverride = flag.Int("rtmpport", 0, "Set listen port for the RTMP server")
disableResponseCaching = flag.Bool("disableResponseCaching", false, "Do not optimize performance by caching of web responses")
)
// nolint:cyclop
@ -42,6 +43,10 @@ func main() {
config.BackupDirectory = *backupDirectory
}
if *disableResponseCaching {
config.DisableResponseCaching = *disableResponseCaching
}
// Create the data directory if needed
if !utils.DoesFileExists("data") {
if err := os.Mkdir("./data", 0o700); err != nil {

View file

@ -24,52 +24,131 @@ import (
"github.com/owncast/owncast/router/middleware"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/yp"
cache "github.com/victorspringer/http-cache"
"github.com/victorspringer/http-cache/adapter/memory"
)
// Start starts the router for the http, ws, and rtmp.
func Start() error {
// Setup a web response cache
enableCache := !config.DisableResponseCaching
responseCache, err := memory.NewAdapter(
memory.AdapterWithAlgorithm(memory.LRU),
memory.AdapterWithCapacity(50),
)
if err != nil {
log.Warn("unable to create web cache", err)
}
superShortCacheClient, err := cache.NewClient(
cache.ClientWithAdapter(responseCache),
cache.ClientWithTTL(3*time.Second),
)
if err != nil {
log.Warn("unable to create web cache client", err)
}
reasonableDurationCacheClient, err := cache.NewClient(
cache.ClientWithAdapter(responseCache),
cache.ClientWithTTL(8*time.Second),
)
if err != nil {
log.Warn("unable to create web cache client", err)
}
longerDurationCacheClient, err := cache.NewClient(
cache.ClientWithAdapter(responseCache),
cache.ClientWithTTL(3*time.Minute),
)
if err != nil {
log.Warn("unable to create web cache client", err)
}
// The primary web app.
http.HandleFunc("/", controllers.IndexHandler)
if enableCache {
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.IndexHandler)).ServeHTTP(rw, r)
})
} else {
http.HandleFunc("/", controllers.IndexHandler)
}
// The admin web app.
http.HandleFunc("/admin/", middleware.RequireAdminAuth(controllers.IndexHandler))
// Images
http.HandleFunc("/thumbnail.jpg", controllers.GetThumbnail)
http.HandleFunc("/preview.gif", controllers.GetPreview)
http.HandleFunc("/logo", controllers.GetLogo)
http.HandleFunc("/thumbnail.jpg", func(rw http.ResponseWriter, r *http.Request) {
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetThumbnail)).ServeHTTP(rw, r)
})
http.HandleFunc("/preview.gif", func(rw http.ResponseWriter, r *http.Request) {
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetPreview)).ServeHTTP(rw, r)
})
http.HandleFunc("/logo", func(rw http.ResponseWriter, r *http.Request) {
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetLogo)).ServeHTTP(rw, r)
})
// Custom Javascript
http.HandleFunc("/customjavascript", controllers.ServeCustomJavascript)
http.HandleFunc("/customjavascript", func(rw http.ResponseWriter, r *http.Request) {
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.ServeCustomJavascript)).ServeHTTP(rw, r)
})
// Return a single emoji image.
http.HandleFunc(config.EmojiDir, controllers.GetCustomEmojiImage)
http.HandleFunc(config.EmojiDir, func(rw http.ResponseWriter, r *http.Request) {
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetCustomEmojiImage)).ServeHTTP(rw, r)
})
// return the logo
// return a logo that's compatible with external social networks
http.HandleFunc("/logo/external", controllers.GetCompatibleLogo)
http.HandleFunc("/logo/external", func(rw http.ResponseWriter, r *http.Request) {
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetCompatibleLogo)).ServeHTTP(rw, r)
})
// robots.txt
http.HandleFunc("/robots.txt", controllers.GetRobotsDotTxt)
http.HandleFunc("/robots.txt", func(rw http.ResponseWriter, r *http.Request) {
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetRobotsDotTxt)).ServeHTTP(rw, r)
})
// status of the system
http.HandleFunc("/api/status", controllers.GetStatus)
if enableCache {
http.HandleFunc("/api/status", func(rw http.ResponseWriter, r *http.Request) {
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetStatus)).ServeHTTP(rw, r)
})
} else {
http.HandleFunc("/api/status", controllers.GetStatus)
}
// custom emoji supported in the chat
http.HandleFunc("/api/emoji", controllers.GetCustomEmojiList)
http.HandleFunc("/api/emoji", func(rw http.ResponseWriter, r *http.Request) {
reasonableDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetCustomEmojiList)).ServeHTTP(rw, r)
})
// chat rest api
http.HandleFunc("/api/chat", middleware.RequireUserAccessToken(controllers.GetChatMessages))
if enableCache {
http.HandleFunc("/api/chat", func(rw http.ResponseWriter, r *http.Request) {
superShortCacheClient.Middleware(middleware.RequireUserAccessToken(controllers.GetChatMessages))
})
} else {
http.HandleFunc("/api/chat", middleware.RequireUserAccessToken(controllers.GetChatMessages))
}
// web config api
http.HandleFunc("/api/config", controllers.GetWebConfig)
if enableCache {
http.HandleFunc("/api/config", func(rw http.ResponseWriter, r *http.Request) {
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetWebConfig)).ServeHTTP(rw, r)
})
} else {
http.HandleFunc("/api/config", controllers.GetWebConfig)
}
// return the YP protocol data
http.HandleFunc("/api/yp", yp.GetYPResponse)
// list of all social platforms
http.HandleFunc("/api/socialplatforms", controllers.GetAllSocialPlatforms)
http.HandleFunc("/api/socialplatforms", func(rw http.ResponseWriter, r *http.Request) {
reasonableDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetAllSocialPlatforms)).ServeHTTP(rw, r)
})
// return the list of video variants available
http.HandleFunc("/api/video/variants", controllers.GetVideoStreamOutputVariants)
@ -84,7 +163,9 @@ func Start() error {
http.HandleFunc("/api/remotefollow", controllers.RemoteFollow)
// return followers
http.HandleFunc("/api/followers", middleware.HandlePagination(controllers.GetFollowers))
http.HandleFunc("/api/followers", func(rw http.ResponseWriter, r *http.Request) {
reasonableDurationCacheClient.Middleware(middleware.HandlePagination(controllers.GetFollowers)).ServeHTTP(rw, r)
})
// save client video playback metrics
http.HandleFunc("/api/metrics/playback", controllers.ReportPlaybackMetrics)

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
self.__SSG_MANIFEST=new Set,self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[5584],{15584:function(e,t,n){n.r(t);var r,a,s,o,i,l=n(67294);function _extends(){return(_extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}t.default=function(e){return l.createElement("svg",_extends({xmlns:"http://www.w3.org/2000/svg",width:500,height:500,viewBox:"0 0 132.292 132.292"},e),r||(r=l.createElement("linearGradient",{id:"like_svg__a",x1:432.851,x2:464.644,y1:49.977,y2:49.977,gradientUnits:"userSpaceOnUse"},l.createElement("stop",{offset:0,stopColor:"#2087e2"}),l.createElement("stop",{offset:1,stopColor:"#b63fff"}))),a||(a=l.createElement("path",{fill:"url(#like_svg__a)",d:"M438.672 34.08h20.151a5.82 5.82 45 0 1 5.82 5.821v20.151a5.82 5.82 135 0 1-5.82 5.821h-20.15a5.82 5.82 45 0 1-5.822-5.82V39.9a5.82 5.82 135 0 1 5.821-5.82z",transform:"matrix(4.1611 0 0 4.1611 -1801.14 -141.813)"})),s||(s=l.createElement("path",{fill:"#853dd0",d:"M106.243 25.198 110 33.435l5.378 24.12-20.557 29.696-28.676 20.66-35.66-24.468 49.536 48.849h28.048a24.221 24.221 0 0 0 24.222-24.222V44.165z",opacity:.75})),o||(o=l.createElement("path",{fill:"#8392ee",d:"M51.275 39.14s-36.386-7.356-17.999 25.83c13.869 25.032 29.59 23.091 29.59 23.091S47.512 65.822 51.275 39.14"})),i||(i=l.createElement("path",{fill:"none",stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:7.865,d:"M674.216 70.254c-21.056-22.863-45.943 2.215-45.943 2.215s-24.888-25.078-45.943-2.216c-21.056 22.863 16.89 64.133 45.943 78.023 29.053-13.89 66.998-55.16 45.943-78.022",transform:"matrix(.95455 0 0 .95455 -533.57 -33.626)"})))}}}]);

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[7268],{97268:function(e,t,r){r.r(t);var a,n,s,o,l=r(67294);function _extends(){return(_extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e}).apply(this,arguments)}t.default=function(e){return l.createElement("svg",_extends({xmlns:"http://www.w3.org/2000/svg",width:500,height:500,viewBox:"0 0 132.292 132.292"},e),a||(a=l.createElement("linearGradient",{id:"repost_svg__a",x1:432.851,x2:464.644,y1:49.977,y2:49.977,gradientUnits:"userSpaceOnUse"},l.createElement("stop",{offset:0,stopColor:"#2087e2"}),l.createElement("stop",{offset:1,stopColor:"#b63fff"}))),n||(n=l.createElement("path",{fill:"url(#repost_svg__a)",d:"M438.672 34.08h20.151a5.82 5.82 45 0 1 5.82 5.821v20.151a5.82 5.82 135 0 1-5.82 5.821h-20.15a5.82 5.82 45 0 1-5.822-5.82V39.9a5.82 5.82 135 0 1 5.821-5.82z",transform:"matrix(4.16112 0 0 4.1611 -1801.146 -141.813)"})),s||(s=l.createElement("path",{fill:"#7f40cf",d:"m103.028 50.073-.794 41.033-10.18 12.882-49.412 3.477 26.027 24.827h39.4c13.378 0 24.223-10.845 24.222-24.222V68.265l-9.86-12.31z",opacity:.75})),o||(o=l.createElement("g",{fill:"none",stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:7.865},l.createElement("path",{d:"M741.453 94.965h-41.418a14.744 14.744 0 0 0-14.777 14.777v41.418m14.777 14.777h41.418c8.187 0 14.777-6.59 14.777-14.777v-41.418",transform:"translate(-696.642 -71.915)scale(1.05833)"}),l.createElement("path",{d:"m670.076 143.371 15.182 13.79 15.5-13.6",transform:"translate(-696.642 -71.915)scale(1.05833)"}),l.createElement("path",{d:"m670.076 143.371 15.182 13.79 15.5-13.6",transform:"rotate(180 414.466 105.278)scale(1.05833)"}))))}}}]);

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9069],{49069:function(e,t,r){r.r(t);var a,n,o,l,s=r(67294);function _extends(){return(_extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e}).apply(this,arguments)}t.default=function(e){return s.createElement("svg",_extends({xmlns:"http://www.w3.org/2000/svg",width:500,height:500,viewBox:"0 0 132.292 132.292"},e),a||(a=s.createElement("linearGradient",{id:"follow_svg__a",x1:432.851,x2:464.644,y1:49.977,y2:49.977,gradientUnits:"userSpaceOnUse"},s.createElement("stop",{offset:0,stopColor:"#2087e2"}),s.createElement("stop",{offset:1,stopColor:"#b63fff"}))),n||(n=s.createElement("path",{fill:"url(#follow_svg__a)",d:"M438.672 34.08h20.151a5.82 5.82 45 0 1 5.82 5.821v20.151a5.82 5.82 135 0 1-5.82 5.821h-20.15a5.82 5.82 45 0 1-5.822-5.82V39.9a5.82 5.82 135 0 1 5.821-5.82z",transform:"matrix(4.16112 0 0 4.1611 -1801.146 -141.813)"})),o||(o=s.createElement("path",{fill:"#8842da",d:"m99.29 73.002-1.238 22.769-22.423.995 25.259 35.526h7.183c13.377-.001 24.22-10.845 24.22-24.222V89.683z",opacity:.85})),l||(l=s.createElement("g",{stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round"},s.createElement("circle",{cx:876.218,cy:118.03,r:21.554,fill:"none",strokeWidth:8.788,transform:"matrix(.90817 0 0 .9124 -737.017 -65.428)"}),s.createElement("path",{fill:"none",strokeWidth:6.641,d:"M845.107 163.996c0-16.543 13.41-29.953 29.953-29.953a29.953 29.953 0 0 1 19.632 7.331",transform:"matrix(1.14743 0 0 1.26483 -944.188 -103.004)"}),s.createElement("g",{fill:"#fff",strokeWidth:7.559},s.createElement("path",{d:"m881.641 159.874 34.92.28",transform:"translate(-853.609 -74.031)scale(1.05833)"}),s.createElement("path",{d:"m881.641 159.874 34.92.28",transform:"rotate(90 561.76 -294.47)scale(1.05833)"})))))}}}]);

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9262],{70918:function(e,t,a){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/upgrade",function(){return a(7494)}])},7494:function(e,t,a){"use strict";a.r(t);var n=a(85893),s=a(67294),l=a(59899),r=a(2307),d=a(53740),u=a(92863),i=a(15578);let{Title:o}=d.default,AssetTable=e=>{let t=Object.values(e);return(0,n.jsx)(r.Z,{dataSource:t,columns:[{title:"Name",dataIndex:"name",key:"name",render:(e,t)=>(0,n.jsx)("a",{href:t.browser_download_url,children:e})},{title:"Size",dataIndex:"size",key:"size",render:e=>"".concat((e/1024/1024).toFixed(2)," MB")}],rowKey:e=>e.id,size:"large",pagination:!1})},Logs=()=>{let[e,t]=(0,s.useState)({html_url:"",name:"",created_at:null,body:"",assets:[]}),getRelease=async()=>{try{let e=await (0,u.Kt)();t(e)}catch(e){console.log("==== error",e)}};return((0,s.useEffect)(()=>{getRelease()},[]),e)?(0,n.jsxs)("div",{className:"upgrade-page",children:[(0,n.jsx)(o,{level:2,children:(0,n.jsx)("a",{href:e.html_url,children:e.name})}),(0,n.jsx)(o,{level:5,children:new Date(e.created_at).toDateString()}),(0,n.jsx)(l.U,{children:e.body}),(0,n.jsx)("h3",{children:"Downloads"}),(0,n.jsx)(AssetTable,{...e.assets})]}):null};Logs.getLayout=function(e){return(0,n.jsx)(i.l,{page:e})},t.default=Logs}},function(e){e.O(0,[5596,1130,4104,9403,1024,3942,971,6697,1664,1749,1700,2122,7752,5891,2891,4749,6627,8966,7521,5578,9774,2888,179],function(){return e(e.s=70918)}),_N_E=e.O()}]);

File diff suppressed because one or more lines are too long

View file

@ -9,9 +9,9 @@ request = request('http://127.0.0.1:8080');
var ajv = new Ajv();
var nodeInfoSchema = jsonfile.readFileSync('schema/nodeinfo_2.0.json');
const serverName = 'owncast.server.test'
const serverURL = 'http://' + serverName
const fediUsername = 'streamer'
const serverName = 'owncast.server.test';
const serverURL = 'http://' + serverName;
const fediUsername = 'streamer';
test('disable federation', async (done) => {
const res = await sendAdminRequest('config/federation/enable', false);
@ -59,10 +59,7 @@ test('verify responses of /federation/ when federation is disabled', async (done
});
test('set required parameters and enable federation', async (done) => {
const res1 = await sendAdminRequest(
'config/serverurl',
serverURL
);
const res1 = await sendAdminRequest('config/serverurl', serverURL);
const res2 = await sendAdminRequest(
'config/federation/username',
fediUsername
@ -73,28 +70,47 @@ test('set required parameters and enable federation', async (done) => {
test('verify responses of /.well-known/webfinger when federation is enabled', async (done) => {
const resNoResource = request.get('/.well-known/webfinger').expect(400);
const resBadResource = request.get(
'/.well-known/webfinger?resource=' + fediUsername + '@' + serverName
).expect(400);
const resBadResource2 = request.get(
'/.well-known/webfinger?resource=notacct:' + fediUsername + '@' + serverName
).expect(400);
const resBadServer = request.get(
'/.well-known/webfinger?resource=acct:' + fediUsername + '@not' + serverName
).expect(404);
const resBadUser = request.get(
'/.well-known/webfinger?resource=acct:not' + fediUsername + '@' + serverName
).expect(404);
const resNoAccept = request.get(
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
).expect(200)
const resBadResource = request
.get('/.well-known/webfinger?resource=' + fediUsername + '@' + serverName)
.expect(400);
const resBadResource2 = request
.get(
'/.well-known/webfinger?resource=notacct:' +
fediUsername +
'@' +
serverName
)
.expect(400);
const resBadServer = request
.get(
'/.well-known/webfinger?resource=acct:' +
fediUsername +
'@not' +
serverName
)
.expect(404);
const resBadUser = request
.get(
'/.well-known/webfinger?resource=acct:not' +
fediUsername +
'@' +
serverName
)
.expect(404);
const resNoAccept = request
.get(
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
)
.expect(200)
.expect('Content-Type', /json/)
.then((res) => {
parseJson(res.text);
});
const resWithAccept = request.get(
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
).expect(200)
const resWithAccept = request
.get(
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
)
.expect(200)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.then((res) => {
@ -104,14 +120,16 @@ test('verify responses of /.well-known/webfinger when federation is enabled', as
});
test('verify responses of /.well-known/host-meta when federation is enabled', async (done) => {
const res = request.get('/.well-known/host-meta')
const res = request
.get('/.well-known/host-meta')
.expect(200)
.expect('Content-Type', /xml/);
done();
});
test('verify responses of /.well-known/nodeinfo when federation is enabled', async (done) => {
const res = request.get('/.well-known/nodeinfo')
const res = request
.get('/.well-known/nodeinfo')
.expect(200)
.expect('Content-Type', /json/)
.then((res) => {
@ -121,7 +139,8 @@ test('verify responses of /.well-known/nodeinfo when federation is enabled', asy
});
test('verify responses of /.well-known/x-nodeinfo2 when federation is enabled', async (done) => {
const res = request.get('/.well-known/x-nodeinfo2')
const res = request
.get('/.well-known/x-nodeinfo2')
.expect(200)
.expect('Content-Type', /json/)
.then((res) => {
@ -143,7 +162,8 @@ test('verify responses of /nodeinfo/2.0 when federation is enabled', async (done
});
test('verify responses of /api/v1/instance when federation is enabled', async (done) => {
const res = request.get('/api/v1/instance')
const res = request
.get('/api/v1/instance')
.expect(200)
.expect('Content-Type', /json/)
.then((res) => {
@ -153,15 +173,17 @@ test('verify responses of /api/v1/instance when federation is enabled', async (d
});
test('verify responses of /federation/user/ when federation is enabled', async (done) => {
const resNoAccept = request.get('/federation/user/')
.expect(307);
const resWithAccept = request.get('/federation/user/')
const resNoAccept = request.get('/federation/user/').expect(307);
const resWithAccept = request
.get('/federation/user/')
.set('Accept', 'application/json')
.expect(404);
const resWithAcceptWrongUsername = request.get('/federation/user/not' + fediUsername)
const resWithAcceptWrongUsername = request
.get('/federation/user/not' + fediUsername)
.set('Accept', 'application/json')
.expect(404);
const resWithAcceptUsername = request.get('/federation/user/' + fediUsername)
const resWithAcceptUsername = request
.get('/federation/user/' + fediUsername)
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/)
@ -172,9 +194,9 @@ test('verify responses of /federation/user/ when federation is enabled', async (
});
test('verify responses of /federation/ when federation is enabled', async (done) => {
const resNoAccept = request.get('/federation/')
.expect(307);
const resWithAccept = request.get('/federation/')
const resNoAccept = request.get('/federation/').expect(307);
const resWithAccept = request
.get('/federation/')
.set('Accept', 'application/json')
.expect(404);
done();

View file

@ -3,91 +3,91 @@
set -e
function install_ffmpeg() {
# install a specific version of ffmpeg
# install a specific version of ffmpeg
FFMPEG_VER="4.4.1"
FFMPEG_PATH="$(pwd)/ffmpeg-$FFMPEG_VER"
PATH=$FFMPEG_PATH:$PATH
FFMPEG_VER="4.4.1"
FFMPEG_PATH="$(pwd)/ffmpeg-$FFMPEG_VER"
PATH=$FFMPEG_PATH:$PATH
if ! [[ -d "$FFMPEG_PATH" ]]; then
mkdir "$FFMPEG_PATH"
fi
if ! [[ -d "$FFMPEG_PATH" ]]; then
mkdir "$FFMPEG_PATH"
fi
pushd "$FFMPEG_PATH" >/dev/null
pushd "$FFMPEG_PATH" >/dev/null
if [[ -x "$FFMPEG_PATH/ffmpeg" ]]; then
ffmpeg_version=$("$FFMPEG_PATH/ffmpeg" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}')
if [[ -x "$FFMPEG_PATH/ffmpeg" ]]; then
if [[ "$ffmpeg_version" == "$FFMPEG_VER-static" ]]; then
popd >/dev/null
return 0
else
mv "$FFMPEG_PATH/ffmpeg" "$FFMPEG_PATH/ffmpeg.bk" || rm -f "$FFMPEG_PATH/ffmpeg"
fi
fi
ffmpeg_version=$("$FFMPEG_PATH/ffmpeg" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}')
rm -f ffmpeg.zip
curl -sL --fail https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v${FFMPEG_VER}/ffmpeg-${FFMPEG_VER}-linux-64.zip --output ffmpeg.zip >/dev/null
unzip -o ffmpeg.zip >/dev/null && rm -f ffmpeg.zip
chmod +x ffmpeg
PATH=$FFMPEG_PATH:$PATH
if [[ "$ffmpeg_version" == "$FFMPEG_VER-static" ]]; then
popd >/dev/null
return 0
else
mv "$FFMPEG_PATH/ffmpeg" "$FFMPEG_PATH/ffmpeg.bk" || rm -f "$FFMPEG_PATH/ffmpeg"
fi
fi
popd >/dev/null
rm -f ffmpeg.zip
curl -sL --fail https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v${FFMPEG_VER}/ffmpeg-${FFMPEG_VER}-linux-64.zip --output ffmpeg.zip >/dev/null
unzip -o ffmpeg.zip >/dev/null && rm -f ffmpeg.zip
chmod +x ffmpeg
PATH=$FFMPEG_PATH:$PATH
popd >/dev/null
}
function start_owncast() {
# Build and run owncast from source
echo "Building owncast..."
pushd "$(git rev-parse --show-toplevel)" >/dev/null
go build -o owncast main.go
# Build and run owncast from source
echo "Building owncast..."
pushd "$(git rev-parse --show-toplevel)" >/dev/null
go build -o owncast main.go
echo "Running owncast..."
./owncast -database "$TEMP_DB" &
SERVER_PID=$!
popd >/dev/null
echo "Running owncast..."
./owncast -disableResponseCaching -database "$TEMP_DB" &
SERVER_PID=$!
popd >/dev/null
sleep 5
sleep 5
}
function start_stream() {
# Start streaming the test file over RTMP to the local owncast instance.
../../ocTestStream.sh &
STREAM_PID=$!
# Start streaming the test file over RTMP to the local owncast instance.
../../ocTestStream.sh &
STREAM_PID=$!
echo "Waiting for stream to start..."
sleep 12
echo "Waiting for stream to start..."
sleep 12
}
function update_storage_config() {
echo "Configuring external storage to use ${S3_BUCKET}..."
echo "Configuring external storage to use ${S3_BUCKET}..."
# Hard-coded to admin:abc123 for auth
curl --fail 'http://localhost:8080/api/admin/config/s3' \
-H 'Authorization: Basic YWRtaW46YWJjMTIz' \
--data-raw "{\"value\":{\"accessKey\":\"${S3_ACCESS_KEY}\",\"acl\":\"\",\"bucket\":\"${S3_BUCKET}\",\"enabled\":true,\"endpoint\":\"${S3_ENDPOINT}\",\"region\":\"${S3_REGION}\",\"secret\":\"${S3_SECRET}\",\"servingEndpoint\":\"\"}}"
# Hard-coded to admin:abc123 for auth
curl --fail 'http://localhost:8080/api/admin/config/s3' \
-H 'Authorization: Basic YWRtaW46YWJjMTIz' \
--data-raw "{\"value\":{\"accessKey\":\"${S3_ACCESS_KEY}\",\"acl\":\"\",\"bucket\":\"${S3_BUCKET}\",\"enabled\":true,\"endpoint\":\"${S3_ENDPOINT}\",\"region\":\"${S3_REGION}\",\"secret\":\"${S3_SECRET}\",\"servingEndpoint\":\"\"}}"
}
function kill_with_kids() {
# kill a process and all its children (by pid)! return no error.
# kill a process and all its children (by pid)! return no error.
if [[ -n $1 ]]; then
mapfile -t CHILDREN_PID_LIST < <(ps --ppid "$1" -o pid= &>/dev/null || true)
for child_pid in "${CHILDREN_PID_LIST[@]}"; do
kill "$child_pid" &>/dev/null || true
wait "$child_pid" &>/dev/null || true
done
kill "$1" &>/dev/null || true
wait "$1" &>/dev/null || true
fi
if [[ -n $1 ]]; then
mapfile -t CHILDREN_PID_LIST < <(ps --ppid "$1" -o pid= &>/dev/null || true)
for child_pid in "${CHILDREN_PID_LIST[@]}"; do
kill "$child_pid" &>/dev/null || true
wait "$child_pid" &>/dev/null || true
done
kill "$1" &>/dev/null || true
wait "$1" &>/dev/null || true
fi
}
function finish() {
echo "Cleaning up..."
kill_with_kids "$STREAM_PID"
kill "$SERVER_PID" &>/dev/null || true
wait "$SERVER_PID" &>/dev/null || true
echo "Cleaning up..."
kill_with_kids "$STREAM_PID"
kill "$SERVER_PID" &>/dev/null || true
wait "$SERVER_PID" &>/dev/null || true
rm -fr "$TEMP_DB"
}