repos / pico

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

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