Umbau-Projekt (SSR und Rest)
This commit is contained in:
@@ -147,26 +147,60 @@ func lookupASN(ip net.IP) string {
|
||||
return label
|
||||
}
|
||||
|
||||
// Impressions laufen über einen einzelnen Worker statt einer Goroutine pro
|
||||
// Request. Das deckelt die Nebenläufigkeit (ein Flood erzeugt keine unbegrenzten
|
||||
// Goroutinen mit je einem ASN-Lookup) und macht den Shutdown sicher: der Worker
|
||||
// wird gestoppt, BEVOR db und geoASN geschlossen werden -- eine freilaufende
|
||||
// Goroutine könnte sonst nach closeGeoASN() auf den unmap-ten Reader zugreifen
|
||||
// (SIGSEGV; middleware.Recoverer fängt Panics in eigenen Goroutinen nicht).
|
||||
// Bei vollem Puffer wird der Zählwert verworfen -- ein Seitenaufruf darf nie
|
||||
// auf die Statistik warten.
|
||||
type impressionEvent struct {
|
||||
remote, path string
|
||||
}
|
||||
|
||||
var (
|
||||
impressionCh = make(chan impressionEvent, 256)
|
||||
impressionDone = make(chan struct{})
|
||||
)
|
||||
|
||||
// startImpressionWorker startet den Schreib-Worker (nach initDB/initGeoASN).
|
||||
func startImpressionWorker() {
|
||||
go func() {
|
||||
defer close(impressionDone)
|
||||
for ev := range impressionCh {
|
||||
netw := maskIP(clientIP(ev.remote))
|
||||
anon := ""
|
||||
if netw != nil {
|
||||
anon = netw.String()
|
||||
}
|
||||
recordImpression(ev.path, anon, lookupASN(netw))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// stopImpressionWorker lässt den Worker die gepufferten Events noch schreiben
|
||||
// und kehrt erst zurück, wenn er fertig ist (vor db.Close/closeGeoASN rufen).
|
||||
func stopImpressionWorker() {
|
||||
close(impressionCh)
|
||||
<-impressionDone
|
||||
}
|
||||
|
||||
// trackImpression zählt einen Seitenaufruf pro (Tag, anonymisiertes Netz, Pfad)
|
||||
// und vermerkt das autonome System. Die IP wird zuerst maskiert; sowohl die
|
||||
// Speicherung als auch die ASN-Auflösung arbeiten nur auf dem anonymisierten
|
||||
// Netz. Gezielt nur auf den HTML-Seiten-Routen, nicht auf API oder Assets.
|
||||
//
|
||||
// Die Erfassung läuft asynchron: der Seitenaufruf darf nicht auf den DB-Schreib
|
||||
// warten (SQLite serialisiert Schreiber, synchronous=FULL macht pro Commit ein
|
||||
// fsync). r ist nach Rückkehr des Handlers nicht mehr sicher nutzbar -> wir
|
||||
// kopieren die zwei Werte vorher heraus.
|
||||
// Die Erfassung läuft asynchron über den Impression-Worker: der Seitenaufruf
|
||||
// darf nicht auf den DB-Schreib warten (SQLite serialisiert Schreiber). r ist
|
||||
// nach Rückkehr des Handlers nicht mehr sicher nutzbar -> wir kopieren die zwei
|
||||
// Werte vorher heraus.
|
||||
func trackImpression(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
remote, path := r.RemoteAddr, r.URL.Path
|
||||
go func() {
|
||||
netw := maskIP(clientIP(remote))
|
||||
anon := ""
|
||||
if netw != nil {
|
||||
anon = netw.String()
|
||||
}
|
||||
recordImpression(path, anon, lookupASN(netw))
|
||||
}()
|
||||
select {
|
||||
case impressionCh <- impressionEvent{remote: r.RemoteAddr, path: r.URL.Path}:
|
||||
default: // Puffer voll oder Worker nicht gestartet (Tests): verwerfen
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user