diff options
Diffstat (limited to 'tvix/nar-bridge-go/pkg/http/server.go')
-rw-r--r-- | tvix/nar-bridge-go/pkg/http/server.go | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/tvix/nar-bridge-go/pkg/http/server.go b/tvix/nar-bridge-go/pkg/http/server.go new file mode 100644 index 000000000000..fbcb20be18b7 --- /dev/null +++ b/tvix/nar-bridge-go/pkg/http/server.go @@ -0,0 +1,119 @@ +package http + +import ( + "context" + "fmt" + "net" + "net/http" + "strings" + "sync" + "time" + + castorev1pb "code.tvl.fyi/tvix/castore-go" + storev1pb "code.tvl.fyi/tvix/store-go" + "github.com/go-chi/chi/middleware" + "github.com/go-chi/chi/v5" + log "github.com/sirupsen/logrus" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +type Server struct { + srv *http.Server + handler chi.Router + + directoryServiceClient castorev1pb.DirectoryServiceClient + blobServiceClient castorev1pb.BlobServiceClient + pathInfoServiceClient storev1pb.PathInfoServiceClient + + // When uploading NAR files to a HTTP binary cache, the .nar + // files are uploaded before the .narinfo files. + // We need *both* to be able to fully construct a PathInfo object. + // Keep a in-memory map of narhash(es) (in SRI) to (unnamed) root node and nar + // size. + // This is necessary until we can ask a PathInfoService for a node with a given + // narSha256. + narDbMu sync.Mutex + narDb map[string]*narData +} + +type narData struct { + rootNode *castorev1pb.Node + narSize uint64 +} + +func New( + directoryServiceClient castorev1pb.DirectoryServiceClient, + blobServiceClient castorev1pb.BlobServiceClient, + pathInfoServiceClient storev1pb.PathInfoServiceClient, + enableAccessLog bool, + priority int, +) *Server { + r := chi.NewRouter() + r.Use(func(h http.Handler) http.Handler { + return otelhttp.NewHandler(h, "http.request") + }) + + if enableAccessLog { + r.Use(middleware.Logger) + } + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("nar-bridge")) + if err != nil { + log.Errorf("Unable to write response: %v", err) + } + }) + + r.Get("/nix-cache-info", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(fmt.Sprintf("StoreDir: /nix/store\nWantMassQuery: 1\nPriority: %d\n", priority))) + if err != nil { + log.Errorf("Unable to write response: %v", err) + } + }) + + s := &Server{ + handler: r, + directoryServiceClient: directoryServiceClient, + blobServiceClient: blobServiceClient, + pathInfoServiceClient: pathInfoServiceClient, + narDb: make(map[string]*narData), + } + + registerNarPut(s) + registerNarinfoPut(s) + + registerNarinfoGet(s) + registerNarGet(s) + + return s +} + +func (s *Server) Shutdown(ctx context.Context) error { + return s.srv.Shutdown(ctx) +} + +// ListenAndServer starts the webserver, and waits for it being closed or +// shutdown, after which it'll return ErrServerClosed. +func (s *Server) ListenAndServe(addr string) error { + s.srv = &http.Server{ + Handler: s.handler, + ReadTimeout: 500 * time.Second, + WriteTimeout: 500 * time.Second, + IdleTimeout: 500 * time.Second, + } + + var listener net.Listener + var err error + + // check addr. If it contains slashes, assume it's a unix domain socket. + if strings.Contains(addr, "/") { + listener, err = net.Listen("unix", addr) + } else { + listener, err = net.Listen("tcp", addr) + } + if err != nil { + return fmt.Errorf("unable to listen on %v: %w", addr, err) + } + + return s.srv.Serve(listener) +} |