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, `<html><head><meta name="go-import" content="%s git %s"></head></html>`, 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())
})
}