Eric Bower
·
2026-01-25
main.go
1package main
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "net"
8 "net/http"
9 "net/http/httputil"
10 "net/url"
11 "strings"
12
13 "github.com/darkweak/souin/pkg/middleware"
14 "github.com/hashicorp/golang-lru/v2/expirable"
15 "github.com/picosh/pico/pkg/apps/pgs"
16 "github.com/picosh/pico/pkg/shared"
17 "github.com/prometheus/client_golang/prometheus/promhttp"
18)
19
20func main() {
21 withPipe := strings.ToLower(shared.GetEnv("PICO_PIPE_ENABLED", "true")) == "true"
22 logger := shared.CreateLogger("pgs-cdn", withPipe)
23 ctx := context.Background()
24 drain := pgs.CreateSubCacheDrain(ctx, logger)
25 pubsub := pgs.NewPubsubPipe(drain)
26 defer func() {
27 _ = pubsub.Close()
28 }()
29 cfg := pgs.NewPgsConfig(logger, nil, nil, drain)
30 httpCache := pgs.SetupCache(cfg)
31 router := &pgs.WebRouter{
32 Cfg: cfg,
33 RedirectsCache: expirable.NewLRU[string, []*pgs.RedirectRule](2048, nil, shared.CacheTimeout),
34 HeadersCache: expirable.NewLRU[string, []*pgs.HeaderRule](2048, nil, shared.CacheTimeout),
35 }
36 cacher := &cachedHttp{
37 handler: httpCache,
38 routes: router,
39 }
40
41 go router.WatchCacheClear()
42 go router.CacheMgmt(ctx, httpCache, cfg.CacheClearingQueue)
43
44 portStr := fmt.Sprintf(":%s", cfg.WebPort)
45 cfg.Logger.Info(
46 "starting server on port",
47 "port", cfg.WebPort,
48 "domain", cfg.Domain,
49 )
50 err := http.ListenAndServe(portStr, cacher)
51 cfg.Logger.Error("listen and serve", "err", err)
52}
53
54type cachedHttp struct {
55 handler *middleware.SouinBaseHandler
56 routes *pgs.WebRouter
57}
58
59type CustomTransport struct {
60 *http.Transport
61 Logger *slog.Logger
62}
63
64func (t *CustomTransport) RoundTrip(request *http.Request) (*http.Response, error) {
65 // reqDump, _ := httputil.DumpRequestOut(request, false)
66 // t.Logger.Info("request", "dump", string(reqDump))
67 response, err := http.DefaultTransport.RoundTrip(request)
68
69 // body, err := httputil.DumpResponse(response, false)
70 // if err != nil {
71 // // copying the response body did not work
72 // return nil, err
73 // }
74 // t.Logger.Info("response", "dump", string(body))
75
76 return response, err
77}
78
79func (c *cachedHttp) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
80 if req.URL.Path == "/_metrics" {
81 promhttp.Handler().ServeHTTP(writer, req)
82 return
83 }
84
85 if req.URL.Path == "/check" {
86 c.routes.Cfg.Logger.Info("proxying `/check` request to ash.pgs.sh", "query", req.URL.RawQuery)
87 req, _ := http.NewRequest("GET", "https://ash.pgs.sh/check?"+req.URL.RawQuery, nil)
88 req.Host = "pgs.sh"
89 // reqDump, _ := httputil.DumpRequestOut(req, true)
90 // fmt.Printf("REQUEST:\n%s", string(reqDump))
91
92 resp, err := http.DefaultClient.Do(req)
93 if err != nil {
94 c.routes.Cfg.Logger.Error("check request", "err", err)
95 }
96 writer.WriteHeader(resp.StatusCode)
97 return
98 }
99
100 _ = c.handler.ServeHTTP(writer, req, func(w http.ResponseWriter, r *http.Request) error {
101 url, _ := url.Parse(partialURL(r))
102
103 c.routes.Cfg.Logger.Info("proxying request to ash.pgs.sh", "url", url.String())
104 defaultTransport := http.DefaultTransport.(*http.Transport)
105 oldDialContext := defaultTransport.DialContext
106 newTransport := CustomTransport{Transport: defaultTransport, Logger: c.routes.Cfg.Logger}
107 newTransport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
108 return oldDialContext(ctx, "tcp", "ash.pgs.sh:443")
109 }
110 proxy := httputil.NewSingleHostReverseProxy(url)
111 proxy.Transport = &newTransport
112 proxy.ServeHTTP(w, r)
113 return nil
114 })
115}
116
117func partialURL(r *http.Request) string {
118 builder := strings.Builder{}
119 // this service sits behind a proxy so we need to force it to https
120 builder.WriteString("https://")
121 builder.WriteString(r.Host)
122 return builder.String()
123}