package main
import (
"context"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"slices"
"strings"
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/mod/semver"
)
const (
forgeDomain = "code.fcuny.net"
forgeUser = "fcuny"
goPkgDomain = "go.fcuny.net"
)
var (
goGetReqs = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "goget_requests_total",
Help: "go get requests processed, by repository name.",
}, []string{"name"})
modules = []string{"x"}
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
metricsMux := http.NewServeMux()
metricsMux.Handle("/metrics", promhttp.Handler())
metricsServer := &http.Server{
Addr: ":9091", Handler: metricsMux,
ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
}
go func() {
log.Println("Starting metrics server on :9091")
if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("Metrics server error: %v", err)
}
}()
s := &http.Server{
Addr: ":8070",
Handler: handler(),
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 1 * time.Minute,
}
go func() {
log.Println("Starting main server on :8080")
if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("Main server error: %v", err)
}
}()
<-ctx.Done()
log.Println("Shutdown signal received, starting graceful shutdown...")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
var shutdownErr error
log.Println("Shutting down main server...")
if err := s.Shutdown(shutdownCtx); err != nil {
log.Printf("Main server shutdown error: %v", err)
shutdownErr = err
}
log.Println("Shutting down metrics server...")
if err := metricsServer.Shutdown(shutdownCtx); err != nil {
log.Printf("Metrics server shutdown error: %v", err)
if shutdownErr == nil {
shutdownErr = err
}
}
if shutdownErr != nil {
log.Printf("Shutdown completed with errors")
os.Exit(1)
}
log.Println("Shutdown completed successfully")
}
func handler() http.Handler {
mux := http.NewServeMux()
mux.Handle(goPkgDomain+"/{name}", siteHandler(modules))
mux.Handle(goPkgDomain+"/{name}/", siteHandler(modules))
goGetMux := http.NewServeMux()
for _, name := range modules {
module := goPkgDomain + "/" + name
goGetMux.Handle(
module+"/",
goImportHandler(module, "https://"+forgeDomain+"/"+forgeUser+"/"+name),
)
}
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
status := http.StatusMethodNotAllowed
http.Error(rw, http.StatusText(status), status)
return
}
if r.URL.Query().Get("go-get") == "1" {
goGetMux.ServeHTTP(rw, r)
return
}
mux.ServeHTTP(rw, r)
})
}
func goImportHandler(module, repo string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
goGetReqs.WithLabelValues(module).Inc()
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
if _, err := fmt.Fprintf(w, `<head><meta name="go-import" content="%s git %s">`, module, repo); err != nil {
log.Printf("Error writing response: %v", err)
}
})
}
func siteHandler(names []string) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
name, version, hasV := strings.Cut(name, "@")
if !hasV {
name, _, _ = strings.Cut(name, ".")
}
if hasV && !semver.IsValid(version) || !slices.Contains(names, name) {
http.NotFound(rw, r)
return
}
u := &url.URL{
Scheme: "https",
Host: forgeDomain,
Path: forgeUser + r.URL.Path,
}
if !hasV {
path, symbol, hasSymbol := strings.Cut(r.URL.Path, ".")
if hasSymbol {
u.Path = forgeUser + "/" + path
u.Fragment = symbol
}
}
http.Redirect(rw, r, u.String(), http.StatusFound)
})
}