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