Monorepo for Tangled tangled.org
5

Configure Feed

Select the types of activity you want to include in your feed.

1package microvm 2 3import ( 4 "context" 5 "errors" 6 "io" 7 "log/slog" 8 "net/http" 9 "net/http/httputil" 10 "net/url" 11 "strings" 12) 13 14// httpUploadBackend reverse-proxies guest binary-cache upload traffic to an 15// http(s) upload cache such as ncps. 16type httpUploadBackend struct { 17 handler http.Handler 18} 19 20func newHTTPUploadProxyBackend(target *url.URL, readUpstreams []CacheUpstream, logger *slog.Logger) *httpUploadBackend { 21 return &httpUploadBackend{handler: uploadProxyHandler(target, readUpstreams, logger)} 22} 23 24func (b *httpUploadBackend) ServeHTTP(w http.ResponseWriter, r *http.Request) { 25 b.handler.ServeHTTP(w, r) 26} 27 28func (b *httpUploadBackend) Close() error { return nil } 29 30func uploadProxyHandler(target *url.URL, readUpstreams []CacheUpstream, logger *slog.Logger) http.Handler { 31 rp := httputil.NewSingleHostReverseProxy(target) 32 rp.ErrorLog = slog.NewLogLogger(logger.Handler(), slog.LevelError) 33 34 origDirector := rp.Director 35 rp.Director = func(req *http.Request) { 36 origDirector(req) 37 // ensure host matches target 38 req.Host = target.Host 39 // the transport doesn't turn URL userinfo into basic auth, only 40 // http.Client does, so do it ourselves 41 if user := target.User; user != nil { 42 password, _ := user.Password() 43 req.SetBasicAuth(user.Username(), password) 44 } 45 } 46 47 // before uploading, nix copy asks the destination whether it already has each 48 // path by GET/HEAD-ing <hash>.narinfo and skips the ones it does. we answer 49 // that check across the upload target *and* the read caches: if any of them 50 // already serves the path there is no point uploading it (the guest would 51 // just substitute it from there anyway). 52 narinfoUpstreams := append([]CacheUpstream{{url: target}}, readUpstreams...) 53 exists := newNarinfoExistenceTransport(narinfoUpstreams, logger) 54 55 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 if isNarinfoExistenceCheck(r) { 57 serveNarinfoExistence(w, r, exists, logger) 58 return 59 } 60 rp.ServeHTTP(w, r) 61 }) 62} 63 64func newNarinfoExistenceTransport(upstreams []CacheUpstream, logger *slog.Logger) http.RoundTripper { 65 return &parallelRacingTransport{ 66 upstreams: upstreams, 67 underlying: proxyTransport, 68 guardedUnderlying: guardedProxyTransport, 69 logger: logger, 70 } 71} 72 73func isNarinfoExistenceCheck(r *http.Request) bool { 74 if r.Method != http.MethodGet && r.Method != http.MethodHead { 75 return false 76 } 77 return strings.HasSuffix(r.URL.Path, ".narinfo") 78} 79 80func serveNarinfoExistence(w http.ResponseWriter, r *http.Request, exists http.RoundTripper, logger *slog.Logger) { 81 probe := r.Clone(r.Context()) 82 probe.RequestURI = "" 83 84 resp, err := exists.RoundTrip(probe) 85 if err != nil { 86 logger.Warn("upload proxy narinfo check failed, treating as not present", "path", r.URL.Path, "error", err) 87 w.WriteHeader(http.StatusNotFound) 88 return 89 } 90 defer resp.Body.Close() 91 92 for key, values := range resp.Header { 93 for _, value := range values { 94 w.Header().Add(key, value) 95 } 96 } 97 w.WriteHeader(resp.StatusCode) 98 if _, err := io.Copy(w, resp.Body); err != nil && !errors.Is(err, context.Canceled) { 99 logger.Warn("upload proxy narinfo copy failed", "path", r.URL.Path, "error", err) 100 } 101}