Live video on the AT Protocol

api: add http metrics handlers

+78 -50
+8 -6
go.mod
··· 44 44 github.com/pion/rtcp v1.2.14 45 45 github.com/pion/webrtc/v4 v4.0.5 46 46 github.com/piprate/json-gold v0.5.0 47 - github.com/prometheus/client_golang v1.17.0 47 + github.com/prometheus/client_golang v1.20.3 48 48 github.com/rs/cors v1.7.0 49 49 github.com/samber/slog-http v1.4.0 50 50 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e 51 + github.com/slok/go-http-metrics v0.13.0 51 52 github.com/streamplace/atproto-oauth-golang v0.0.0-20250521042753-9cfa9e504155 52 53 github.com/streamplace/oatproxy v0.0.0-20250522204300-ccdf0c639572 53 54 github.com/stretchr/testify v1.10.0 ··· 262 263 github.com/kevinburke/ssh_config v1.2.0 // indirect 263 264 github.com/kisielk/errcheck v1.9.0 // indirect 264 265 github.com/kkHAIKE/contextcheck v1.1.6 // indirect 265 - github.com/klauspost/cpuid/v2 v2.2.7 // indirect 266 + github.com/klauspost/compress v1.17.9 // indirect 267 + github.com/klauspost/cpuid/v2 v2.2.8 // indirect 266 268 github.com/kulti/thelper v0.6.3 // indirect 267 269 github.com/kunwardeep/paralleltest v1.0.14 // indirect 268 270 github.com/labstack/gommon v0.4.2 // indirect ··· 289 291 github.com/mattn/go-colorable v0.1.14 // indirect 290 292 github.com/mattn/go-pointer v0.0.1 // indirect 291 293 github.com/mattn/go-runewidth v0.0.16 // indirect 292 - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 293 294 github.com/mgechev/revive v1.9.0 // indirect 294 295 github.com/miekg/pkcs11 v1.1.1 // indirect 295 296 github.com/minio/sha256-simd v1.0.1 // indirect ··· 304 305 github.com/multiformats/go-base36 v0.2.0 // indirect 305 306 github.com/multiformats/go-multibase v0.2.0 // indirect 306 307 github.com/multiformats/go-varint v0.0.7 // indirect 308 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 307 309 github.com/nakabonne/nestif v0.3.1 // indirect 308 310 github.com/nishanths/exhaustive v0.12.0 // indirect 309 311 github.com/nishanths/predeclared v0.2.2 // indirect ··· 330 332 github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 331 333 github.com/polyfloyd/go-errorlint v1.8.0 // indirect 332 334 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect 333 - github.com/prometheus/client_model v0.5.0 // indirect 334 - github.com/prometheus/common v0.45.0 // indirect 335 - github.com/prometheus/procfs v0.12.0 // indirect 335 + github.com/prometheus/client_model v0.6.1 // indirect 336 + github.com/prometheus/common v0.59.1 // indirect 337 + github.com/prometheus/procfs v0.15.1 // indirect 336 338 github.com/quasilyte/go-ruleguard v0.4.4 // indirect 337 339 github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect 338 340 github.com/quasilyte/gogrep v0.5.0 // indirect
+16 -14
go.sum
··· 583 583 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 584 584 github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= 585 585 github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= 586 - github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= 587 - github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 588 - github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 589 - github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 586 + github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 587 + github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 588 + github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= 589 + github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 590 590 github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= 591 591 github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= 592 592 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= ··· 685 685 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 686 686 github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 687 687 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 688 - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 689 - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 690 688 github.com/mgechev/revive v1.9.0 h1:8LaA62XIKrb8lM6VsBSQ92slt/o92z5+hTw3CmrvSrM= 691 689 github.com/mgechev/revive v1.9.0/go.mod h1:LAPq3+MgOf7GcL5PlWIkHb0PT7XH4NuC2LdWymhb9Mo= 692 690 github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= ··· 734 732 github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= 735 733 github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 736 734 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 735 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 736 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 737 737 github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= 738 738 github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= 739 739 github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= ··· 818 818 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 819 819 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 820 820 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 821 - github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 822 - github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 821 + github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= 822 + github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 823 823 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 824 - github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 825 - github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 826 - github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 827 - github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 828 - github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 829 - github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 824 + github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 825 + github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 826 + github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= 827 + github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= 828 + github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 829 + github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 830 830 github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ= 831 831 github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= 832 832 github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= ··· 886 886 github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 887 887 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= 888 888 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= 889 + github.com/slok/go-http-metrics v0.13.0 h1:lQDyJJx9wKhmbliyUsZ2l6peGnXRHjsjoqPt5VYzcP8= 890 + github.com/slok/go-http-metrics v0.13.0/go.mod h1:HIr7t/HbN2sJaunvnt9wKP9xoBBVZFo1/KiHU3b0w+4= 889 891 github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 890 892 github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 891 893 github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
+50 -29
pkg/api/api.go
··· 41 41 "stream.place/streamplace/pkg/spmetrics" 42 42 "stream.place/streamplace/pkg/spxrpc" 43 43 "stream.place/streamplace/pkg/streamplace" 44 + 45 + metrics "github.com/slok/go-http-metrics/metrics/prometheus" 46 + "github.com/slok/go-http-metrics/middleware" 47 + echomiddleware "github.com/slok/go-http-metrics/middleware/echo" 48 + httproutermiddleware "github.com/slok/go-http-metrics/middleware/httprouter" 49 + middlewarestd "github.com/slok/go-http-metrics/middleware/std" 44 50 ) 45 51 46 52 type StreamplaceAPI struct { ··· 128 134 129 135 func (a *StreamplaceAPI) Handler(ctx context.Context) (http.Handler, error) { 130 136 137 + mdlw := middleware.New(middleware.Config{ 138 + Recorder: metrics.NewRecorder(metrics.Config{}), 139 + }) 131 140 var xrpc http.Handler 132 - xrpc, err := spxrpc.NewServer(ctx, a.CLI, a.Model, a.op) 141 + xrpc, err := spxrpc.NewServer(ctx, a.CLI, a.Model, a.op, mdlw) 133 142 if err != nil { 134 143 return nil, err 135 144 } 136 145 router := httprouter.New() 137 146 147 + // Create our middleware factory with the default settings. 148 + 149 + a.op.Echo.Use(echomiddleware.Handler("", mdlw)) 150 + 151 + // r.GET("/test/:id", httproutermiddleware.Handler("/test/:id", h1, mdlw)) 152 + 153 + addHandle := func(router *httprouter.Router, method, path string, handler httprouter.Handle) { 154 + router.Handle(method, path, httproutermiddleware.Handler(path, handler, mdlw)) 155 + } 156 + addFunc := func(router *httprouter.Router, method, path string, handler http.HandlerFunc) { 157 + router.Handler(method, path, middlewarestd.Handler(path, mdlw, handler)) 158 + } 159 + 138 160 router.Handler("GET", "/oauth/*anything", a.op.Handler()) 139 161 router.Handler("POST", "/oauth/*anything", a.op.Handler()) 140 162 router.Handler("GET", "/.well-known/oauth-authorization-server", a.op.Handler()) 141 163 router.Handler("GET", "/.well-known/oauth-protected-resource", a.op.Handler()) 142 164 apiRouter := httprouter.New() 143 - apiRouter.HandlerFunc("POST", "/api/notification", a.HandleNotification(ctx)) 165 + addFunc(apiRouter, "POST", "/api/notification", a.HandleNotification(ctx)) 144 166 // old clients 145 - router.HandlerFunc("GET", "/app-updates", a.HandleAppUpdates(ctx)) 146 - 167 + addFunc(router, "GET", "/app-updates", a.HandleAppUpdates(ctx)) 147 168 // new ones 148 - apiRouter.HandlerFunc("GET", "/api/manifest", a.HandleAppUpdates(ctx)) 149 - apiRouter.GET("/api/desktop-updates/:platform/:architecture/:version/:buildTime/:file", a.HandleDesktopUpdates(ctx)) 150 - apiRouter.POST("/api/webrtc/:stream", a.MistProxyHandler(ctx, "/webrtc/%s")) 151 - apiRouter.OPTIONS("/api/webrtc/:stream", a.MistProxyHandler(ctx, "/webrtc/%s")) 152 - apiRouter.DELETE("/api/webrtc/:stream", a.MistProxyHandler(ctx, "/webrtc/%s")) 153 - apiRouter.Handler("POST", "/api/segment", a.HandleSegment(ctx)) 154 - apiRouter.HandlerFunc("GET", "/api/healthz", a.HandleHealthz(ctx)) 155 - apiRouter.GET("/api/playback/:user/hls/*file", a.HandleHLSPlayback(ctx)) 156 - apiRouter.GET("/api/playback/:user/stream.mp4", a.HandleMP4Playback(ctx)) 157 - apiRouter.GET("/api/playback/:user/stream.webm", a.HandleMKVPlayback(ctx)) 169 + addFunc(apiRouter, "GET", "/api/manifest", a.HandleAppUpdates(ctx)) 170 + addHandle(apiRouter, "GET", "/api/desktop-updates/:platform/:architecture/:version/:buildTime/:file", a.HandleDesktopUpdates(ctx)) 171 + addHandle(apiRouter, "POST", "/api/webrtc/:stream", a.MistProxyHandler(ctx, "/webrtc/%s")) 172 + addHandle(apiRouter, "OPTIONS", "/api/webrtc/:stream", a.MistProxyHandler(ctx, "/webrtc/%s")) 173 + addHandle(apiRouter, "DELETE", "/api/webrtc/:stream", a.MistProxyHandler(ctx, "/webrtc/%s")) 174 + addFunc(apiRouter, "POST", "/api/segment", a.HandleSegment(ctx)) 175 + addFunc(apiRouter, "GET", "/api/healthz", a.HandleHealthz(ctx)) 176 + addHandle(apiRouter, "GET", "/api/playback/:user/hls/*file", a.HandleHLSPlayback(ctx)) 177 + addHandle(apiRouter, "GET", "/api/playback/:user/stream.mp4", a.HandleMP4Playback(ctx)) 178 + addHandle(apiRouter, "GET", "/api/playback/:user/stream.webm", a.HandleMKVPlayback(ctx)) 158 179 // they're, uh, not jpegs. but we used this once and i don't wanna break backwards compatibility 159 - apiRouter.GET("/api/playback/:user/stream.jpg", a.HandleThumbnailPlayback(ctx)) 180 + addHandle(apiRouter, "GET", "/api/playback/:user/stream.jpg", a.HandleThumbnailPlayback(ctx)) 160 181 // this one is not a lie 161 - apiRouter.GET("/api/playback/:user/stream.png", a.HandleThumbnailPlayback(ctx)) 162 - apiRouter.GET("/api/app-return/*anything", a.HandleAppReturn(ctx)) 163 - apiRouter.POST("/api/playback/:user/webrtc", a.HandleWebRTCPlayback(ctx)) 164 - apiRouter.POST("/api/ingest/webrtc", a.HandleWebRTCIngest(ctx)) 165 - apiRouter.POST("/api/ingest/webrtc/:key", a.HandleWebRTCIngest(ctx)) 166 - apiRouter.POST("/api/player-event", a.HandlePlayerEvent(ctx)) 167 - apiRouter.GET("/api/chat/:repoDID", a.HandleChat(ctx)) 168 - apiRouter.GET("/api/websocket/:repoDID", a.HandleWebsocket(ctx)) 169 - apiRouter.GET("/api/livestream/:repoDID", a.HandleLivestream(ctx)) 170 - apiRouter.GET("/api/segment/recent", a.HandleRecentSegments(ctx)) 171 - apiRouter.GET("/api/segment/recent/:repoDID", a.HandleUserRecentSegments(ctx)) 172 - apiRouter.GET("/api/bluesky/resolve/:handle", a.HandleBlueskyResolve(ctx)) 173 - apiRouter.GET("/api/view-count/:user", a.HandleViewCount(ctx)) 182 + addHandle(apiRouter, "GET", "/api/playback/:user/stream.png", a.HandleThumbnailPlayback(ctx)) 183 + addHandle(apiRouter, "GET", "/api/app-return/*anything", a.HandleAppReturn(ctx)) 184 + addHandle(apiRouter, "POST", "/api/playback/:user/webrtc", a.HandleWebRTCPlayback(ctx)) 185 + addHandle(apiRouter, "POST", "/api/ingest/webrtc", a.HandleWebRTCIngest(ctx)) 186 + addHandle(apiRouter, "POST", "/api/ingest/webrtc/:key", a.HandleWebRTCIngest(ctx)) 187 + addHandle(apiRouter, "POST", "/api/player-event", a.HandlePlayerEvent(ctx)) 188 + addHandle(apiRouter, "GET", "/api/chat/:repoDID", a.HandleChat(ctx)) 189 + addHandle(apiRouter, "GET", "/api/websocket/:repoDID", a.HandleWebsocket(ctx)) 190 + addHandle(apiRouter, "GET", "/api/livestream/:repoDID", a.HandleLivestream(ctx)) 191 + addHandle(apiRouter, "GET", "/api/segment/recent", a.HandleRecentSegments(ctx)) 192 + addHandle(apiRouter, "GET", "/api/segment/recent/:repoDID", a.HandleUserRecentSegments(ctx)) 193 + addHandle(apiRouter, "GET", "/api/bluesky/resolve/:handle", a.HandleBlueskyResolve(ctx)) 194 + addHandle(apiRouter, "GET", "/api/view-count/:user", a.HandleViewCount(ctx)) 174 195 apiRouter.NotFound = a.HandleAPI404(ctx) 175 196 apiRouterHandler := a.RateLimitMiddleware(ctx)(apiRouter) 176 197 xrpcHandler := a.RateLimitMiddleware(ctx)(xrpc) ··· 232 253 if err != nil { 233 254 return nil, err 234 255 } 235 - router.NotFound = linkingHandler 256 + router.NotFound = middlewarestd.Handler("/*static", mdlw, linkingHandler) 236 257 } 237 258 // needed because the WebRTC handler issues 405s from / otherwise 238 259 router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+4 -1
pkg/spxrpc/spxrpc.go
··· 5 5 "net/http" 6 6 7 7 "github.com/labstack/echo/v4" 8 + "github.com/slok/go-http-metrics/middleware" 9 + echomiddleware "github.com/slok/go-http-metrics/middleware/echo" 8 10 "github.com/streamplace/oatproxy/pkg/oatproxy" 9 11 "stream.place/streamplace/pkg/config" 10 12 "stream.place/streamplace/pkg/log" ··· 17 19 model model.Model 18 20 } 19 21 20 - func NewServer(ctx context.Context, cli *config.CLI, model model.Model, op *oatproxy.OATProxy) (*Server, error) { 22 + func NewServer(ctx context.Context, cli *config.CLI, model model.Model, op *oatproxy.OATProxy, mdlw middleware.Middleware) (*Server, error) { 21 23 e := echo.New() 22 24 s := &Server{ 23 25 e: e, ··· 25 27 model: model, 26 28 } 27 29 e.Use(s.ErrorHandlingMiddleware()) 30 + e.Use(echomiddleware.Handler("", mdlw)) 28 31 e.Use(op.OAuthMiddleware) 29 32 err := s.RegisterHandlersPlaceStream(e) 30 33 if err != nil {