repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / cmd / pgs / cdn
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}