init visit counter

This commit is contained in:
Travis Shears 2025-10-07 23:40:00 +02:00
parent a5e480746e
commit 07aae31703
4 changed files with 187 additions and 31 deletions

78
main.go
View file

@ -2,7 +2,6 @@ package main
import (
"database/sql"
"errors"
"flag"
"fmt"
"log/slog"
@ -26,11 +25,17 @@ type MainHandler struct {
blog microblog.Handler
gemlog gemlog.Handler
guestbook guestbook.Handler
db *sql.DB
counter *RequestCounter
}
func (h MainHandler) ServeGemini(w gemini.ResponseWriter, req *gemini.Request) {
// Increment request counter
count := h.counter.Increment(req.URL.Path)
slog.Info("gemini request",
"path", req.URL.Path,
"count", count,
"user", strings.Join(userName(req), " "))
// Check if this is a blog request
@ -52,37 +57,40 @@ func (h MainHandler) ServeGemini(w gemini.ResponseWriter, req *gemini.Request) {
switch req.URL.Path {
case "/":
gemini.ServeFileName("pages/home.gmi", "text/gemini")(w, req)
// err := w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
// requireNoError(err)
// _, err = w.WriteBody([]byte("Hello, world!"))
// requireNoError(err)
case "/user":
if req.Certificate() == nil {
w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication Required")
return
}
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
w.WriteBody([]byte(req.Certificate().Subject.CommonName))
case "/die":
requireNoError(errors.New("must die"))
case "/file":
gemini.ServeFileName("cmd/example/hello.gmi", "text/gemini")(w, req)
case "/post":
if req.URL.Scheme != gemini.SchemaTitan {
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
w.WriteBody([]byte("Use titan scheme to upload data"))
return
}
payload, err := req.ReadTitanPayload()
data, err := os.ReadFile("pages/home.gmi")
requireNoError(err)
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
w.WriteBody([]byte("Titan Upload Parameters\r\n"))
w.WriteBody([]byte(fmt.Sprintf("Upload MIME Type: %s\r\n", req.Titan.Mime)))
w.WriteBody([]byte(fmt.Sprintf("Token: %s\r\n", req.Titan.Token)))
w.WriteBody([]byte(fmt.Sprintf("Size: %v\r\n", req.Titan.Size)))
w.WriteBody([]byte("Payload:\r\n"))
w.WriteBody(payload)
page := string(data)
var content strings.Builder
content.WriteString(page)
content.WriteString(fmt.Sprintf("\n\n------ stats: total requests served %d, this page %d ------", h.counter.GetTotal(), h.counter.Get(req.URL.Path)))
w.WriteBody([]byte(content.String()))
// case "/user":
// if req.Certificate() == nil {
// w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication Required")
// return
// }
// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
// w.WriteBody([]byte(req.Certificate().Subject.CommonName))
// case "/die":
// requireNoError(errors.New("must die"))
// case "/file":
// gemini.ServeFileName("cmd/example/hello.gmi", "text/gemini")(w, req)
// case "/post":
// if req.URL.Scheme != gemini.SchemaTitan {
// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
// w.WriteBody([]byte("Use titan scheme to upload data"))
// return
// }
// payload, err := req.ReadTitanPayload()
// requireNoError(err)
// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
// w.WriteBody([]byte("Titan Upload Parameters\r\n"))
// w.WriteBody([]byte(fmt.Sprintf("Upload MIME Type: %s\r\n", req.Titan.Mime)))
// w.WriteBody([]byte(fmt.Sprintf("Token: %s\r\n", req.Titan.Token)))
// w.WriteBody([]byte(fmt.Sprintf("Size: %v\r\n", req.Titan.Size)))
// w.WriteBody([]byte("Payload:\r\n"))
// w.WriteBody(payload)
default:
w.WriteStatusMsg(gemini.StatusNotFound, req.URL.Path)
@ -132,11 +140,21 @@ func main() {
flag.StringVar(&key, "key", "server.key.pem", "private key associated with certificate file")
flag.Parse()
// Initialize request counter with 30-second snapshot interval
counter, err := NewRequestCounter("request_counts.json", 30*time.Second)
if err != nil {
slog.Error("failed to initialize request counter", "error", err)
os.Exit(1)
}
defer counter.Close()
pbClient := pocketbase.NewPocketBaseClient()
handler := MainHandler{
blog: microblog.NewHandler(pbClient),
gemlog: gemlog.NewHandler(),
guestbook: guestbook.NewGuestBook(db),
db: db,
counter: counter,
}
err = gemini.ListenAndServe(host, cert, key, gemini.TrapPanic(handler.ServeGemini))