Monorepo for Tangled tangled.org
803
fork

Configure Feed

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

at master 146 lines 4.1 kB view raw
1package xrpc 2 3import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "strings" 10 11 "github.com/bluesky-social/indigo/api/atproto" 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 14 "tangled.org/core/api/tangled" 15 "tangled.org/core/knotmirror/db" 16) 17 18var mirrorToKnotNSID = map[string]string{ 19 tangled.GitTempListBranchesNSID: tangled.RepoBranchesNSID, 20 tangled.GitTempListTagsNSID: tangled.RepoTagsNSID, 21 tangled.GitTempListCommitsNSID: tangled.RepoLogNSID, 22 tangled.GitTempGetTreeNSID: tangled.RepoTreeNSID, 23 tangled.GitTempGetBranchNSID: tangled.RepoBranchNSID, 24 tangled.GitTempGetBlobNSID: tangled.RepoBlobNSID, 25 tangled.GitTempGetTagNSID: tangled.RepoTagNSID, 26 tangled.GitTempGetArchiveNSID: tangled.RepoArchiveNSID, 27 tangled.GitTempListLanguagesNSID: tangled.RepoLanguagesNSID, 28} 29 30var hopByHopHeaders = map[string]bool{ 31 "Connection": true, 32 "Keep-Alive": true, 33 "Transfer-Encoding": true, 34 "Te": true, 35 "Trailer": true, 36 "Upgrade": true, 37 "Proxy-Authorization": true, 38 "Proxy-Authenticate": true, 39} 40 41type knotInfo struct { 42 baseURL string 43 didSlashRepo string 44} 45 46func (x *Xrpc) resolveKnot(ctx context.Context, repoAt syntax.ATURI) (*knotInfo, error) { 47 repo, err := db.GetRepoByAtUri(ctx, x.db, repoAt) 48 if err == nil && repo != nil { 49 knotURL := repo.KnotDomain 50 if !strings.Contains(repo.KnotDomain, "://") { 51 if host, _ := db.GetHost(ctx, x.db, repo.KnotDomain); host != nil { 52 knotURL = host.URL() 53 } else { 54 x.logger.Warn("repo is from unknown knot") 55 if x.cfg.KnotUseSSL { 56 knotURL = "https://" + knotURL 57 } else { 58 knotURL = "http://" + knotURL 59 } 60 } 61 } 62 return &knotInfo{baseURL: knotURL, didSlashRepo: repo.DidSlashRepo()}, nil 63 } 64 65 owner, err := x.resolver.ResolveIdent(ctx, repoAt.Authority().String()) 66 if err != nil { 67 return nil, fmt.Errorf("resolving repo owner: %w", err) 68 } 69 70 xrpcc := indigoxrpc.Client{Host: owner.PDSEndpoint()} 71 out, err := atproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 72 if err != nil { 73 return nil, fmt.Errorf("fetching repo record from PDS: %w", err) 74 } 75 76 record := out.Value.Val.(*tangled.Repo) 77 knotURL := record.Knot 78 if !strings.Contains(record.Knot, "://") { 79 if host, _ := db.GetHost(ctx, x.db, record.Knot); host != nil { 80 knotURL = host.URL() 81 } else { 82 x.logger.Warn("repo is from unknown knot") 83 if x.cfg.KnotUseSSL { 84 knotURL = "https://" + knotURL 85 } else { 86 knotURL = "http://" + knotURL 87 } 88 } 89 } 90 91 return &knotInfo{ 92 baseURL: knotURL, 93 didSlashRepo: fmt.Sprintf("%s/%s", owner.DID, record.Name), 94 }, nil 95} 96 97func (x *Xrpc) proxyToKnot(w http.ResponseWriter, r *http.Request, repoAt syntax.ATURI) bool { 98 mirrorNSID := strings.TrimPrefix(r.URL.Path, "/xrpc/") 99 knotNSID, ok := mirrorToKnotNSID[mirrorNSID] 100 if !ok { 101 return false 102 } 103 104 knot, err := x.resolveKnot(r.Context(), repoAt) 105 if err != nil { 106 x.logger.Warn("proxy: failed to resolve knot", "repo", repoAt, "err", err) 107 return false 108 } 109 110 params := make(url.Values) 111 for k, v := range r.URL.Query() { 112 params[k] = v 113 } 114 params.Set("repo", knot.didSlashRepo) 115 116 target := fmt.Sprintf("%s/xrpc/%s?%s", knot.baseURL, knotNSID, params.Encode()) 117 118 req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, target, nil) 119 if err != nil { 120 x.logger.Warn("proxy: failed to build request", "target", target, "err", err) 121 return false 122 } 123 124 resp, err := x.httpClient.Do(req) 125 if err != nil { 126 x.logger.Warn("proxy: knot request failed", "target", target, "err", err) 127 return false 128 } 129 defer resp.Body.Close() 130 131 for k, vv := range resp.Header { 132 if hopByHopHeaders[k] { 133 continue 134 } 135 for _, v := range vv { 136 w.Header().Add(k, v) 137 } 138 } 139 w.WriteHeader(resp.StatusCode) 140 if _, err := io.Copy(w, resp.Body); err != nil { 141 x.logger.Warn("proxy: response copy interrupted", "target", target, "err", err) 142 } 143 144 x.logger.Info("proxy: served from knot", "repo", repoAt, "knot", knot.baseURL, "status", resp.StatusCode) 145 return true 146}