package main import ( "context" "encoding/json" "fmt" "io" "log" "net" "net/http" "net/http/fcgi" "time" "blitiri.com.ar/go/systemd" "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/pgxpool" ) type Event struct { Created_ts int64 Kind string Details string } func getListener() (net.Listener, error) { listeners, err := systemd.Listeners() if err != nil { return nil, fmt.Errorf("unable to get systemd listeners: %w", err) } count := 0 for _, listeners := range listeners { count += len(listeners) if count == 1 { return listeners[0], nil } } return nil, fmt.Errorf("expected 1 systemd fd passed, got %v", count) } func httpEnd(w http.ResponseWriter, r *http.Request, content string, code int) { log.Printf("%v %d %d %s", r.RemoteAddr, r.ContentLength, code, content) http.Error(w, content, code) } func main() { conn, err := pgxpool.Connect(context.Background(), "") if err != nil { log.Fatal("Unable to connect to database: ", err) } defer conn.Close() http.HandleFunc("/analytics", func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { if url, ok := r.URL.Query()["url"]; ok { if len(url) > 1 { httpEnd(w, r, "too many urls", 400) } else { http.Redirect(w, r, url[0], 303) _, err = conn.Exec(context.Background(), "insert into events(created_ts, ip_addr, kind, details) values ($1, $2, $3, $4)", time.Now(), r.RemoteAddr, "r", url[0]) if err != nil { log.Printf("%v 0 303 db error: %s", r.RemoteAddr, err) return } log.Printf("%v 0 303 ok", r.RemoteAddr) } } else { httpEnd(w, r, "missing url for GET", 400) } return } if r.Method != "POST" { httpEnd(w, r, "method not allowed: "+r.Method, 405) return } if r.ContentLength <= 0 { httpEnd(w, r, "length required", 411) return } if r.ContentLength > 1048576 { httpEnd(w, r, "too much data", 413) return } data, err := io.ReadAll(r.Body) if err != nil { httpEnd(w, r, "read error", 400) return } var events []Event if err := json.Unmarshal(data, &events); err != nil { log.Print(err) httpEnd(w, r, "bad json", 400) return } _, err = conn.CopyFrom( r.Context(), pgx.Identifier{"events"}, []string{"created_ts", "ip_addr", "kind", "details"}, pgx.CopyFromSlice(len(events), func(i int) ([]interface{}, error) { return []interface{}{time.Unix(events[i].Created_ts/1000, events[i].Created_ts%1000*1000000), r.RemoteAddr, events[i].Kind, events[i].Details}, nil }), ) if err != nil { log.Printf("%v %d 500 db error: %s", r.RemoteAddr, r.ContentLength, err) http.Error(w, "db error", 500) } else { httpEnd(w, r, "ok", 200) } }) l, err := getListener() if err != nil { log.Fatal(err) } fcgi.Serve(l, nil) }