diff --git a/docker-compose.yml b/docker-compose.yml index f3c9b50b..7e973aa2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -282,6 +282,7 @@ services: MIRROR_RESYNC_PARALLELISM: "4" MIRROR_APPVIEW_URL: http://appview:3000 MIRROR_SEARCH_ZOEKT_URL: https://zoekt.tngl.boltless.dev/indexserver + MIRROR_V2_HOST: http://gitmirror:9001 volumes: - knotmirror-data:/data - ./localinfra/certs/root.crt:/usr/local/share/ca-certificates/caddy.crt:ro @@ -454,7 +455,7 @@ services: BOBBIN_SLINGSHOT_URL: http://hydrant:3000 BOBBIN_KNOT_ALLOW_PRIVATE: "true" BOBBIN_KNOT_REQUIRE_HTTPS: "false" - BOBBIN_MIRROR_V2_URL: http://gitmirror:9001 + BOBBIN_MIRROR_V2_URL: http://knotmirror:7000 BOBBIN_LOG: info volumes: - .:/src:cached diff --git a/knotmirror/config/config.go b/knotmirror/config/config.go index bc076292..6344079f 100644 --- a/knotmirror/config/config.go +++ b/knotmirror/config/config.go @@ -25,6 +25,7 @@ type Config struct { Listen string `env:"MIRROR_LISTEN, default=:7000"` MetricsListen string `env:"MIRROR_METRICS_LISTEN, default=127.0.0.1:7100"` AdminListen string `env:"MIRROR_ADMIN_LISTEN, default=127.0.0.1:7200"` + V2Host string `env:"MIRROR_V2_HOST"` } func (c *Config) BaseUrl() string { diff --git a/knotmirror/xrpc/xrpc.go b/knotmirror/xrpc/xrpc.go index b94b015e..1de8c9fa 100644 --- a/knotmirror/xrpc/xrpc.go +++ b/knotmirror/xrpc/xrpc.go @@ -4,8 +4,10 @@ import ( "database/sql" "encoding/json" "errors" + "io" "log/slog" "net/http" + "net/url" "time" "github.com/bluesky-social/indigo/atproto/atclient" @@ -29,6 +31,7 @@ type Xrpc struct { ks *knotstream.KnotStream logger *slog.Logger httpClient *http.Client + v2Client *http.Client inflight *inflightTracker } @@ -48,6 +51,7 @@ func New(logger *slog.Logger, cfg *config.Config, db *sql.DB, rdb *redis.Client, ks: ks, logger: log.SubLogger(logger, "xrpc"), httpClient: httpClient, + v2Client: &http.Client{Timeout: 10 * time.Second}, inflight: newInflightTracker(), } } @@ -76,11 +80,72 @@ func (x *Xrpc) Router() http.Handler { r.Get("/"+tangled.GitTempListLanguagesNSID, x.ListLanguages) r.Get("/"+tangled.GitTempListTagsNSID, x.ListTags) r.Post("/"+tangled.SyncRequestCrawlNSID, x.RequestCrawl) + + r.Get("/sh.tangled.git.temp2.getBlob", x.proxyV2) + r.Get("/sh.tangled.git.temp2.getDiff", x.proxyV2) + r.Get("/sh.tangled.git.temp2.getInterdiff", x.proxyV2) + r.Get("/sh.tangled.git.temp2.listCommits", x.proxyV2) + r.Get("/sh.tangled.git.temp2.mergeCheck", x.proxyV2) }) return r } +func (x *Xrpc) proxyV2(w http.ResponseWriter, r *http.Request) { + x.logger.Debug("v2", "host", x.cfg.V2Host) + u, err := url.Parse(x.cfg.V2Host) + if err != nil { + writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalError", Message: "failed to proxy to v2 mirror"}) + return + } + x.proxyRequest(w, r, u.Host, u.Scheme) +} + +func (x *Xrpc) proxyRequest(w http.ResponseWriter, r *http.Request, hostname, scheme string) { + req := r + respWriter := w + + u := req.URL + u.Scheme = scheme + u.Host = hostname + upstreamReq, err := http.NewRequest(req.Method, u.String(), req.Body) + if err != nil { + x.logger.Warn("proxy request failed", "err", err) + writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "failed to proxy to v2 mirror"}) + return + } + + // copy subset of request headers + for _, hdr := range []string{"Accept", "User-Agent", "Authorization", "Via", "Content-Type", "Content-Length"} { + val := req.Header.Get(hdr) + if val != "" { + upstreamReq.Header.Set(hdr, val) + } + } + + upstreamResp, err := x.v2Client.Do(upstreamReq) + if err != nil { + x.logger.Warn("proxy request failed", "err", err) + writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "failed to proxy to v2 mirror"}) + return + } + defer upstreamResp.Body.Close() + + // copy a subset of response headers + for _, hdr := range []string{"Content-Type", "Content-Length", "Location"} { + val := upstreamResp.Header.Get(hdr) + if val != "" { + respWriter.Header().Set(hdr, val) + } + } + respWriter.WriteHeader(upstreamResp.StatusCode) + + _, err = io.Copy(respWriter, upstreamResp.Body) + if err != nil { + x.logger.Error("error copying proxy body", "err", err) + } +} + func writeJson(w http.ResponseWriter, status int, response any) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status)