package main import ( "context" "embed" "fmt" "html/template" "io/fs" "log" "net/http" "os" "os/signal" "path" "runtime" "syscall" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "rsc.io/markdown" ) //go:embed content/* var contentFiles embed.FS //go:embed static/* var staticFiles embed.FS //go:embed templates/* var templateFiles embed.FS const ( domain = "fcuny.net" forgeDomain = "code.fcuny.net" forgeUser = "fcuny" ) type PageData struct { Title string Content template.HTML } var ( goGetReqs = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "goget_requests_total", Help: "go get requests processed, by repository name.", }, []string{"name"}) staticReqs = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "static_requests_total", Help: "HTTP requests served from the FS.", }, []string{"path"}) 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()) metricsMux.Handle("/.info", info()) 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 :8070") 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() staticFS, _ := fs.Sub(staticFiles, "static") fileServer := http.FileServer(http.FS(staticFS)) mux.Handle("/static/", http.StripPrefix("/static/", fileServer)) mux.HandleFunc("/", homeHandler) mux.HandleFunc("/resume", resumeHandler) for _, name := range modules { module := "/" + name mux.Handle( module+"/", goImportHandler(module, "https://"+forgeDomain+"/"+forgeUser+"/"+name), ) } return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodHead { status := http.StatusMethodNotAllowed http.Error(rw, http.StatusText(status), status) return } mux.ServeHTTP(rw, r) }) } func homeHandler(w http.ResponseWriter, r *http.Request) { markdownContent, err := contentFiles.ReadFile("content/home.md") if err != nil { log.Printf("Error reading home markdown: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } parser := markdown.Parser{} doc := parser.Parse(string(markdownContent)) htmlContent := markdown.ToHTML(doc) tmplContent, err := templateFiles.ReadFile("templates/home.html") if err != nil { log.Printf("Error reading template: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } tmpl, err := template.New("layout").Parse(string(tmplContent)) if err != nil { log.Printf("Error parsing template: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } data := PageData{ Title: "Franck Cuny", Content: template.HTML(htmlContent), } w.Header().Set("Content-Type", "text/html; charset=UTF-8") if err := tmpl.Execute(w, data); err != nil { log.Printf("Error executing template: %v", err) } staticReqs.WithLabelValues(path.Clean(r.URL.Path)).Inc() } func resumeHandler(w http.ResponseWriter, r *http.Request) { markdownContent, err := contentFiles.ReadFile("content/resume.md") if err != nil { log.Printf("Error reading resume markdown: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } parser := markdown.Parser{} doc := parser.Parse(string(markdownContent)) htmlContent := markdown.ToHTML(doc) tmplContent, err := templateFiles.ReadFile("templates/resume.html") if err != nil { log.Printf("Error reading template: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } tmpl, err := template.New("layout").Parse(string(tmplContent)) if err != nil { log.Printf("Error parsing template: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } data := PageData{ Title: "Work Experience", Content: template.HTML(htmlContent), } w.Header().Set("Content-Type", "text/html; charset=UTF-8") if err := tmpl.Execute(w, data); err != nil { log.Printf("Error executing template: %v", err) } staticReqs.WithLabelValues(path.Clean(r.URL.Path)).Inc() } func goImportHandler(module, repo string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { goGetReqs.WithLabelValues(module).Inc() if r.URL.Query().Get("go-get") == "1" { w.Header().Set("Content-Type", "text/html; charset=UTF-8") importPath := domain + module if _, err := fmt.Fprintf(w, ``, importPath, repo); err != nil { log.Printf("Error writing response: %v", err) } return } http.Redirect(w, r, repo, http.StatusFound) }) } func info() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, "Go version: %s\n", runtime.Version()) }) }