Repository navigation
Expand file tree
/
Copy pathmain.go
More file actions
84 lines (74 loc) · 2.47 KB
/
Copy pathmain.go
File metadata and controls
84 lines (74 loc) · 2.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main
import (
"context"
"flag"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
func splitList(s string) map[string]bool {
out := make(map[string]bool)
for _, item := range strings.Split(s, ",") {
if item = strings.TrimSpace(item); item != "" {
out[strings.ToLower(item)] = true
}
}
return out
}
// Cloud platforms hand the port over as $PORT; fall back to 8080 locally.
func defaultAddr() string {
if port := os.Getenv("PORT"); port != "" {
return ":" + port
}
return ":8080"
}
func main() {
addr := flag.String("addr", defaultAddr(), "listen address")
upstreamBase := flag.String("upstream", "https://public.api.bsky.app", "bluesky XRPC base URL")
cdnBase := flag.String("cdn", "https://cdn.bsky.app", "bluesky CDN base URL for avatars")
ttl := flag.Duration("ttl", 45*time.Second, "thread and handle cache TTL")
avatarTTL := flag.Duration("avatar-ttl", time.Hour, "avatar image cache TTL")
publicURL := flag.String("public-url", "", "external base URL for rewritten avatar links (defaults to the request host)")
origins := flag.String("origins", "", "comma-separated origins allowed for cross-origin reads")
actors := flag.String("actors", "", "comma-separated handles/DIDs whose posts may be fetched (empty allows any)")
flag.Parse()
s := &server{
up: newUpstream(*upstreamBase),
cdn: newUpstream(*cdnBase),
threads: newCache(*ttl, 1024),
avatars: newCache(*avatarTTL, 512),
actors: splitList(*actors),
publicURL: strings.TrimRight(*publicURL, "/"),
}
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("GET /xrpc/com.atproto.identity.resolveHandle", s.resolveHandle)
mux.HandleFunc("GET /xrpc/app.bsky.feed.getPostThread", s.getPostThread)
mux.HandleFunc("GET /avatar/", s.avatar)
srv := &http.Server{
Addr: *addr,
Handler: cors(splitList(*origins), mux),
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 2 * time.Minute,
}
go func() {
log.Printf("listening on %s", *addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
}