Live video on the AT Protocol
1package spxrpc
2
3import (
4 "context"
5 "net/http"
6
7 "github.com/labstack/echo/v4"
8 "github.com/slok/go-http-metrics/middleware"
9 echomiddleware "github.com/slok/go-http-metrics/middleware/echo"
10 "github.com/streamplace/oatproxy/pkg/oatproxy"
11 "stream.place/streamplace/pkg/config"
12 "stream.place/streamplace/pkg/log"
13 "stream.place/streamplace/pkg/model"
14)
15
16type Server struct {
17 e *echo.Echo
18 cli *config.CLI
19 model model.Model
20}
21
22func NewServer(ctx context.Context, cli *config.CLI, model model.Model, op *oatproxy.OATProxy, mdlw middleware.Middleware) (*Server, error) {
23 e := echo.New()
24 s := &Server{
25 e: e,
26 cli: cli,
27 model: model,
28 }
29 e.Use(s.ErrorHandlingMiddleware())
30 e.Use(s.ContextPreservingMiddleware())
31 e.Use(echomiddleware.Handler("", mdlw))
32 e.Use(op.OAuthMiddleware)
33 err := s.RegisterHandlersPlaceStream(e)
34 if err != nil {
35 return nil, err
36 }
37 err = s.RegisterHandlersAppBsky(e)
38 if err != nil {
39 return nil, err
40 }
41 err = s.RegisterHandlersComAtproto(e)
42 if err != nil {
43 return nil, err
44 }
45 e.GET("/xrpc/_health", func(c echo.Context) error {
46 return c.JSON(http.StatusOK, map[string]string{"version": cli.Build.Version})
47 })
48 e.GET("/xrpc/com.atproto.sync.subscribeRepos", s.handleComAtprotoSyncSubscribeRepos)
49 e.GET("/xrpc/*", s.HandleWildcard)
50 e.POST("/xrpc/*", s.HandleWildcard)
51 return s, nil
52}
53
54func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
55 s.e.ServeHTTP(w, r)
56}
57
58func (s *Server) ErrorHandlingMiddleware() echo.MiddlewareFunc {
59 return func(next echo.HandlerFunc) echo.HandlerFunc {
60 return func(c echo.Context) error {
61 err := next(c)
62 if err == nil {
63 return nil
64 }
65 httpError, ok := err.(*echo.HTTPError)
66 if ok {
67 log.Error(c.Request().Context(), "http error", "code", httpError.Code, "message", httpError.Message, "internal", httpError.Internal)
68 return err
69 }
70 log.Error(c.Request().Context(), "unhandled error", "error", err)
71 return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
72 }
73 }
74}
75
76// unique type to prevent assignment.
77type echoContextKeyType struct{}
78
79// singleton value to identify our logging metadata in context
80var echoContextKey = echoContextKeyType{}
81
82func (s *Server) ContextPreservingMiddleware() echo.MiddlewareFunc {
83 return func(next echo.HandlerFunc) echo.HandlerFunc {
84 return func(c echo.Context) error {
85 ctx := c.Request().Context()
86 if ctx == nil {
87 ctx = context.Background()
88 }
89 ctx = context.WithValue(ctx, echoContextKey, c)
90 c.SetRequest(c.Request().WithContext(ctx))
91 return next(c)
92 }
93 }
94}