Skip to content

Commit

Permalink
add stats
Browse files Browse the repository at this point in the history
  • Loading branch information
dhcgn committed Jun 21, 2024
1 parent d59a781 commit 5db7ea6
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 59 deletions.
1 change: 1 addition & 0 deletions httphandler/deleteHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func (c Config) DeleteHandler(w http.ResponseWriter, r *http.Request) {

downloadKey, err := domain.DeriveDownloadKey(uploadKey)
if err != nil {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Error deriving download key", http.StatusInternalServerError)
return
}
Expand Down
9 changes: 9 additions & 0 deletions httphandler/downloadHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ func (c Config) PlainDownloadHandler(w http.ResponseWriter, r *http.Request) {

jsonData, err := c.StorageInstance.GetJSON(downloadKey)
if err != nil {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Invalid download key or database error", http.StatusNotFound)
return
}

// Parse the JSON data to retrieve the specific parameter
paramMap := make(map[string]interface{})
if err := json.Unmarshal(jsonData, &paramMap); err != nil {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Error decoding JSON", http.StatusInternalServerError)
return
}
Expand All @@ -36,15 +38,19 @@ func (c Config) PlainDownloadHandler(w http.ResponseWriter, r *http.Request) {
if m, ok := value.(map[string]interface{}); ok {
value, ok = m[key]
if !ok {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Parameter not found", http.StatusNotFound)
return
}
} else {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Invalid parameter path", http.StatusBadRequest)
return
}
}

c.StatsInstance.IncrementDownloads()

// Return the value as plain text
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintln(w, value)
Expand All @@ -56,10 +62,13 @@ func (c Config) DownloadHandler(w http.ResponseWriter, r *http.Request) {

jsonData, err := c.StorageInstance.GetJSON(downloadKey)
if err != nil {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Invalid download key or database error", http.StatusNotFound)
return
}

c.StatsInstance.IncrementDownloads()

// Set header and write the JSON data to the response writer
w.Header().Set("Content-Type", "application/json")
w.Write(jsonData)
Expand Down
1 change: 1 addition & 0 deletions httphandler/keyPairHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ func (c Config) KeyPairHandler(w http.ResponseWriter, r *http.Request) {
uploadKey := domain.GenerateRandomKey()
downloadKey, err := domain.DeriveDownloadKey(uploadKey)
if err != nil {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Error deriving download key", http.StatusInternalServerError)
return
}
Expand Down
3 changes: 2 additions & 1 deletion httphandler/uploadHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func (c Config) handleUpload(w http.ResponseWriter, r *http.Request, uploadKey,
// Derive download key
downloadKey, err := domain.DeriveDownloadKey(uploadKey)
if err != nil {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Error deriving download key", http.StatusInternalServerError)
return
}
Expand All @@ -51,7 +52,7 @@ func (c Config) handleUpload(w http.ResponseWriter, r *http.Request, uploadKey,
}
c.StorageInstance.Store(downloadKey, data)

c.StatsInstance.IncrementUpload()
c.StatsInstance.IncrementUploads()

// Construct and return response
constructAndReturnResponse(w, r, downloadKey, paramMap)
Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (
const (
// Server configuration
MaxRequestSize = 1024 * 10 // 10 KB for request size limit
RateLimitPerSecond = 10 // Requests per second
RateLimitBurst = 5 // Burst capability
RateLimitPerSecond = 100 // Requests per second
RateLimitBurst = 10 // Burst capability

// Database and server paths
DefaultStorePath = "./data"
Expand Down
1 change: 1 addition & 0 deletions middleware/limitRequestSize.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ func (c Config) LimitRequestSize(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if the request size is too large
if r.ContentLength > c.MaxRequestSize {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Request size is too large", http.StatusRequestEntityTooLarge)
return
}
Expand Down
11 changes: 7 additions & 4 deletions middleware/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@ func (c Config) RateLimit(next http.Handler) http.Handler {

ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
c.StatsInstance.IncrementHTTPErrors()
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}

// Exclude local IP addresses from rate limiting
if ip == "127.0.0.1" || ip == "::1" {
next.ServeHTTP(w, r)
return
}
// if ip == "127.0.0.1" || ip == "::1" {
// next.ServeHTTP(w, r)
// return
// }

limiter := c.getLimiter(ip)
if !limiter.Allow() {
c.StatsInstance.IncrementHTTPErrors()
c.StatsInstance.RecordRateLimitHit(ip)
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
Expand Down
40 changes: 22 additions & 18 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,26 +105,30 @@ <h2>Server Settings</h2>
<h2>Server Stats</h2>
<ul>
<li><strong>Uptime:</strong> {{.Uptime}}</li>
<li><strong>Count Download/Uploads since Start:</strong> {{.StateData.CountDownload}}/{{.StateData.CountUpload}}</li>
<li><strong>Count Download/Uploads last 24:</strong> {{.StateData.CountLast24hDownload}}/{{.StateData.CountLast24hUpload}}</li>
<li><strong>Count HTTP Error since Start:</strong> {{.StateData.HTTPError}}</li>
<li><strong>Count HTTP Error last 24:</strong> {{.StateData.HTTPLast24hError}}</li>
<li><strong>Count Rate Limit Counts:</strong> {{.StateData.CountRateLimitHits}}</li>
<li><strong>Download/Upload Count since Start:</strong> {{.StateData.DownloadCount}}/{{.StateData.UploadCount}}</li>
<li><strong>Download/Upload Count last 24h:</strong> {{.StateData.Last24hDownloadCount}}/{{.StateData.Last24hUploadCount}}</li>
<li><strong>HTTP Error Count since Start:</strong> {{.StateData.HTTPErrorCount}}</li>
<li><strong>HTTP Error Count last 24h:</strong> {{.StateData.Last24hHTTPErrorCount}}</li>
<li><strong>Rate Limit Hit Count:</strong> {{.StateData.RateLimitHitCount}}</li>
</ul>

<!-- Table of affected IPs of rate limit -->

<h2>Rate Limit Stats</h2>
<table>
<tr>
<th>IP</th>
<th>Count</th>
</tr>
{{range $ip, $count := .StateData.RateLimitIPs}}
<tr>
<td>{{$ip}}</td>
<td>{{$count}}</td>
</tr>
{{end}}
{{if len .StateData.RateLimitedIPs}}
<table>
<tr>
<th>IP</th>
<th>Request Count</th>
</tr>
{{range .StateData.RateLimitedIPs}}
<tr>
<td>{{.IP}}</td>
<td>{{.RequestCount}}</td>
</tr>
{{end}}
</table>
{{else}}
<p>No records</p>
{{end}}

<h2>API Usage</h2>
<ul>
Expand Down
65 changes: 31 additions & 34 deletions stats/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//
// Note: This package stores all data in memory. For high-load or long-running
// applications, consider persisting data to a database or using a more
// sophisticated time-series data structure.package stats
// sophisticated time-series data structure.
package stats

import (
Expand All @@ -22,22 +22,19 @@ type Stats struct {
}

type StatsData struct {
CountDownload int
CountUpload int

CountLast24hDownload int
CountLast24hUpload int

HTTPError int
HTTPLast24hError int

CountRateLimitHits int
RateLimitIPs []RateLimitIP
DownloadCount int
UploadCount int
Last24hDownloadCount int
Last24hUploadCount int
HTTPErrorCount int
Last24hHTTPErrorCount int
RateLimitHitCount int
RateLimitedIPs []RateLimitedIP
}

type RateLimitIP struct {
IP string
Requests int
type RateLimitedIP struct {
IP string
RequestCount int
}

func NewStats() *Stats {
Expand All @@ -54,51 +51,51 @@ func (s *Stats) GetCurrentStats() StatsData {
return s.data
}

func (s *Stats) IncrementDownload() {
func (s *Stats) IncrementDownloads() {
s.mu.Lock()
defer s.mu.Unlock()

s.data.CountDownload++
s.data.DownloadCount++
s.addToLast24h(func(sd *StatsData) {
sd.CountDownload++
sd.DownloadCount++
})
}

func (s *Stats) IncrementUpload() {
func (s *Stats) IncrementUploads() {
s.mu.Lock()
defer s.mu.Unlock()

s.data.CountUpload++
s.data.UploadCount++
s.addToLast24h(func(sd *StatsData) {
sd.CountUpload++
sd.UploadCount++
})
}

func (s *Stats) IncrementHTTPError() {
func (s *Stats) IncrementHTTPErrors() {
s.mu.Lock()
defer s.mu.Unlock()

s.data.HTTPError++
s.data.HTTPErrorCount++
s.addToLast24h(func(sd *StatsData) {
sd.HTTPError++
sd.HTTPErrorCount++
})
}

func (s *Stats) RecordRateLimitHit(ip string) {
s.mu.Lock()
defer s.mu.Unlock()

s.data.CountRateLimitHits++
s.data.RateLimitHitCount++
found := false
for i, rlIP := range s.data.RateLimitIPs {
for i, rlIP := range s.data.RateLimitedIPs {
if rlIP.IP == ip {
s.data.RateLimitIPs[i].Requests++
s.data.RateLimitedIPs[i].RequestCount++
found = true
break
}
}
if !found {
s.data.RateLimitIPs = append(s.data.RateLimitIPs, RateLimitIP{IP: ip, Requests: 1})
s.data.RateLimitedIPs = append(s.data.RateLimitedIPs, RateLimitedIP{IP: ip, RequestCount: 1})
}
}

Expand All @@ -116,17 +113,17 @@ func (s *Stats) updateLast24hStats() {
now := time.Now()
cutoff := now.Add(-24 * time.Hour)

s.data.CountLast24hDownload = 0
s.data.CountLast24hUpload = 0
s.data.HTTPLast24hError = 0
s.data.Last24hDownloadCount = 0
s.data.Last24hUploadCount = 0
s.data.Last24hHTTPErrorCount = 0

for t, sd := range s.last24h {
if t.Before(cutoff) {
delete(s.last24h, t)
} else {
s.data.CountLast24hDownload += sd.CountDownload
s.data.CountLast24hUpload += sd.CountUpload
s.data.HTTPLast24hError += sd.HTTPError
s.data.Last24hDownloadCount += sd.DownloadCount
s.data.Last24hUploadCount += sd.UploadCount
s.data.Last24hHTTPErrorCount += sd.HTTPErrorCount
}
}
}

0 comments on commit 5db7ea6

Please sign in to comment.