Skip to content

Commit f4f1929

Browse files
committed
update
1 parent cae0c4a commit f4f1929

File tree

2 files changed

+128
-87
lines changed

2 files changed

+128
-87
lines changed

queue-manager.go

Lines changed: 82 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
package traefik_queue_manager
1+
package queuemanager
22

33
import (
44
"context"
5-
"crypto/md5"
5+
"crypto/rand"
6+
"crypto/sha256"
67
"encoding/hex"
78
"fmt"
89
"html/template"
910
"log"
1011
"math"
1112
"net/http"
1213
"os"
14+
"strconv"
1315
"strings"
1416
"time"
1517

1618
"github.com/patrickmn/go-cache"
1719
)
1820

19-
// Config holds the plugin configuration
21+
// Config holds the plugin configuration.
2022
type Config struct {
2123
Enabled bool `json:"enabled"` // Enable/disable the queue manager
2224
QueuePageFile string `json:"queuePageFile"` // Path to queue page HTML template
2325
SessionTime time.Duration `json:"sessionTime"` // How long a session is valid for
2426
PurgeTime time.Duration `json:"purgeTime"` // How often to purge expired sessions
2527
MaxEntries int `json:"maxEntries"` // Maximum concurrent users
26-
HttpResponseCode int `json:"httpResponseCode"` // HTTP response code for queue page
27-
HttpContentType string `json:"httpContentType"` // Content type of queue page
28+
HTTPResponseCode int `json:"httpResponseCode"` // HTTP response code for queue page
29+
HTTPContentType string `json:"httpContentType"` // Content type of queue page
2830
UseCookies bool `json:"useCookies"` // Use cookies or IP+UserAgent hash
2931
CookieName string `json:"cookieName"` // Name of the cookie
3032
CookieMaxAge int `json:"cookieMaxAge"` // Max age of the cookie in seconds
@@ -33,16 +35,16 @@ type Config struct {
3335
Debug bool `json:"debug"` // Enable debug logging
3436
}
3537

36-
// CreateConfig creates the default plugin configuration
38+
// CreateConfig creates the default plugin configuration.
3739
func CreateConfig() *Config {
3840
return &Config{
3941
Enabled: true,
4042
QueuePageFile: "queue-page.html",
4143
SessionTime: 1 * time.Minute,
4244
PurgeTime: 5 * time.Minute,
4345
MaxEntries: 100,
44-
HttpResponseCode: http.StatusTooManyRequests,
45-
HttpContentType: "text/html; charset=utf-8",
46+
HTTPResponseCode: http.StatusTooManyRequests,
47+
HTTPContentType: "text/html; charset=utf-8",
4648
UseCookies: true,
4749
CookieName: "queue-manager-id",
4850
CookieMaxAge: 3600,
@@ -52,15 +54,15 @@ func CreateConfig() *Config {
5254
}
5355
}
5456

55-
// Session represents a visitor session
57+
// Session represents a visitor session.
5658
type Session struct {
5759
ID string `json:"id"` // Unique client identifier
5860
CreatedAt time.Time `json:"createdAt"` // When the session was created
5961
LastSeen time.Time `json:"lastSeen"` // When the client was last seen
6062
Position int `json:"position"` // Position in the queue
6163
}
6264

63-
// QueuePageData contains data to be passed to the HTML template
65+
// QueuePageData contains data to be passed to the HTML template.
6466
type QueuePageData struct {
6567
Position int `json:"position"` // Position in queue
6668
QueueSize int `json:"queueSize"` // Total queue size
@@ -70,7 +72,7 @@ type QueuePageData struct {
7072
Message string `json:"message"` // Custom message
7173
}
7274

73-
// QueueManager is the middleware handler
75+
// QueueManager is the middleware handler.
7476
type QueueManager struct {
7577
next http.Handler
7678
name string
@@ -81,7 +83,7 @@ type QueueManager struct {
8183
activeSessionIDs map[string]bool
8284
}
8385

84-
// New creates a new queue manager middleware
86+
// New creates a new queue manager middleware.
8587
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
8688
// Validate configuration
8789
if config.MaxEntries <= 0 {
@@ -115,7 +117,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
115117
}, nil
116118
}
117119

118-
// ServeHTTP implements the http.Handler interface
120+
// ServeHTTP implements the http.Handler interface.
119121
func (qm *QueueManager) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
120122
// Skip if disabled
121123
if !qm.config.Enabled {
@@ -136,9 +138,15 @@ func (qm *QueueManager) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
136138
if qm.activeSessionIDs[clientID] {
137139
// Update last seen timestamp
138140
if session, found := qm.cache.Get(clientID); found {
139-
sessionData := session.(Session)
140-
sessionData.LastSeen = time.Now()
141-
qm.cache.Set(clientID, sessionData, cache.DefaultExpiration)
141+
sessionData, ok := session.(Session)
142+
if !ok {
143+
if qm.config.Debug {
144+
log.Printf("[Queue Manager] Error: Failed to convert session to Session type")
145+
}
146+
} else {
147+
sessionData.LastSeen = time.Now()
148+
qm.cache.Set(clientID, sessionData, cache.DefaultExpiration)
149+
}
142150
}
143151

144152
// Allow access
@@ -190,16 +198,22 @@ func (qm *QueueManager) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
190198

191199
// Update last seen timestamp
192200
if session, found := qm.cache.Get(clientID); found {
193-
sessionData := session.(Session)
194-
sessionData.LastSeen = time.Now()
195-
qm.cache.Set(clientID, sessionData, cache.DefaultExpiration)
201+
sessionData, ok := session.(Session)
202+
if !ok {
203+
if qm.config.Debug {
204+
log.Printf("[Queue Manager] Error: Failed to convert session to Session type")
205+
}
206+
} else {
207+
sessionData.LastSeen = time.Now()
208+
qm.cache.Set(clientID, sessionData, cache.DefaultExpiration)
209+
}
196210
}
197211

198212
// Serve queue page
199-
qm.serveQueuePage(rw, req, position)
213+
qm.serveQueuePage(rw, position)
200214
}
201215

202-
// getClientID generates or retrieves a unique client identifier
216+
// getClientID generates or retrieves a unique client identifier.
203217
func (qm *QueueManager) getClientID(rw http.ResponseWriter, req *http.Request) (string, bool) {
204218
if qm.config.UseCookies {
205219
// Try to get existing cookie
@@ -220,36 +234,41 @@ func (qm *QueueManager) getClientID(rw http.ResponseWriter, req *http.Request) (
220234
SameSite: http.SameSiteLaxMode,
221235
})
222236
return newID, true
223-
} else {
224-
// Use IP + UserAgent hash
225-
return generateClientHash(req), false
226237
}
238+
239+
// Use IP + UserAgent hash
240+
return generateClientHash(req), false
227241
}
228242

229-
// generateUniqueID creates a unique identifier for a client
243+
// generateUniqueID creates a unique identifier for a client.
230244
func generateUniqueID(req *http.Request) string {
231-
// Create unique ID based on current time, remote IP, and cryptographic randomness
232-
timestamp := time.Now().UnixNano()
233-
clientIP := getClientIP(req)
234-
235-
// Add randomness to ensure uniqueness
236-
randBytes := make([]byte, 8)
237-
for i := range randBytes {
238-
randBytes[i] = byte(timestamp % 256)
239-
timestamp /= 256
245+
// Create a buffer for true randomness
246+
randBytes := make([]byte, 16)
247+
_, err := rand.Read(randBytes)
248+
if err != nil {
249+
// If crypto/rand fails, use a fallback method
250+
timestamp := time.Now().UnixNano()
251+
for i := range randBytes {
252+
randBytes[i] = byte((timestamp + int64(i)) % 256)
253+
}
240254
}
241255

242-
// Create a hash from the random bytes
243-
hasher := md5.New()
256+
// Add client IP to the randomness
257+
clientIP := getClientIP(req)
258+
259+
// Create a hash of the random bytes + IP
260+
hasher := sha256.New()
244261
hasher.Write(randBytes)
245262
hasher.Write([]byte(clientIP))
246-
randHash := hex.EncodeToString(hasher.Sum(nil))[:12]
263+
hasher.Write([]byte(strconv.FormatInt(time.Now().UnixNano(), 10)))
264+
265+
randHash := hex.EncodeToString(hasher.Sum(nil))[:16]
247266

248267
// Format: timestamp-ip-randomhash
249268
return fmt.Sprintf("%d-%s-%s", time.Now().UnixNano(), clientIP, randHash)
250269
}
251270

252-
// generateClientHash creates a hash from client attributes
271+
// generateClientHash creates a hash from client attributes.
253272
func generateClientHash(req *http.Request) string {
254273
// Get client IP
255274
clientIP := getClientIP(req)
@@ -258,12 +277,12 @@ func generateClientHash(req *http.Request) string {
258277
userAgent := req.UserAgent()
259278

260279
// Create hash
261-
hasher := md5.New()
280+
hasher := sha256.New()
262281
hasher.Write([]byte(clientIP + "|" + userAgent))
263-
return hex.EncodeToString(hasher.Sum(nil))
282+
return hex.EncodeToString(hasher.Sum(nil))[:32]
264283
}
265284

266-
// getClientIP extracts the client's real IP address
285+
// getClientIP extracts the client's real IP address.
267286
func getClientIP(req *http.Request) string {
268287
// Check for X-Forwarded-For header
269288
if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
@@ -289,8 +308,8 @@ func getClientIP(req *http.Request) string {
289308
return remoteAddr
290309
}
291310

292-
// serveQueuePage serves the queue page HTML
293-
func (qm *QueueManager) serveQueuePage(rw http.ResponseWriter, req *http.Request, position int) {
311+
// serveQueuePage serves the queue page HTML.
312+
func (qm *QueueManager) serveQueuePage(rw http.ResponseWriter, position int) {
294313
// Calculate estimated wait time (rough estimate: 30 seconds per position)
295314
estimatedWaitTime := int(math.Ceil(float64(position) * 0.5)) // in minutes
296315

@@ -310,26 +329,23 @@ func (qm *QueueManager) serveQueuePage(rw http.ResponseWriter, req *http.Request
310329
Message: "Please wait while we process your request.",
311330
}
312331

313-
// Check if we have a template file
332+
// Try to use the template file
314333
if fileExists(qm.config.QueuePageFile) {
315-
// Read the file content
316334
content, err := os.ReadFile(qm.config.QueuePageFile)
317335
if err == nil {
318-
// Create a new template from the file content
319-
tmpl, err := template.New("QueuePage").Delims("[[", "]]").Parse(string(content))
320-
if err == nil {
336+
queueTemplate, parseErr := template.New("QueuePage").Delims("[[", "]]").Parse(string(content))
337+
if parseErr == nil {
321338
// Set content type
322-
rw.Header().Set("Content-Type", qm.config.HttpContentType)
323-
rw.WriteHeader(qm.config.HttpResponseCode)
339+
rw.Header().Set("Content-Type", qm.config.HTTPContentType)
340+
rw.WriteHeader(qm.config.HTTPResponseCode)
324341

325342
// Execute template
326-
err = tmpl.Execute(rw, data)
327-
if err != nil && qm.config.Debug {
328-
log.Printf("[Queue Manager] Error executing template: %v", err)
343+
if execErr := queueTemplate.Execute(rw, data); execErr != nil && qm.config.Debug {
344+
log.Printf("[Queue Manager] Error executing template: %v", execErr)
329345
}
330346
return
331347
} else if qm.config.Debug {
332-
log.Printf("[Queue Manager] Error parsing template: %v", err)
348+
log.Printf("[Queue Manager] Error parsing template: %v", parseErr)
333349
}
334350
} else if qm.config.Debug {
335351
log.Printf("[Queue Manager] Error reading template file: %v", err)
@@ -391,16 +407,16 @@ func (qm *QueueManager) serveQueuePage(rw http.ResponseWriter, req *http.Request
391407

392408
// Create and execute the fallback template
393409
tmpl, _ := template.New("FallbackQueuePage").Delims("[[", "]]").Parse(fallbackTemplate)
394-
rw.Header().Set("Content-Type", qm.config.HttpContentType)
395-
rw.WriteHeader(qm.config.HttpResponseCode)
396-
err := tmpl.Execute(rw, data)
397-
if err != nil && qm.config.Debug {
410+
rw.Header().Set("Content-Type", qm.config.HTTPContentType)
411+
rw.WriteHeader(qm.config.HTTPResponseCode)
412+
if err := tmpl.Execute(rw, data); err != nil && qm.config.Debug {
398413
log.Printf("[Queue Manager] Error executing fallback template: %v", err)
399414
}
400415
}
401416

402-
// Periodically check and clean up expired sessions
403-
func (qm *QueueManager) cleanupExpiredSessions() {
417+
// CleanupExpiredSessions periodically checks and removes expired sessions.
418+
// This should be called periodically, e.g., using a background goroutine.
419+
func (qm *QueueManager) CleanupExpiredSessions() {
404420
// Check if any active sessions have expired
405421
for id := range qm.activeSessionIDs {
406422
if _, found := qm.cache.Get(id); !found {
@@ -430,14 +446,16 @@ func (qm *QueueManager) cleanupExpiredSessions() {
430446
// Update positions in queue
431447
for i := range qm.queue {
432448
if session, found := qm.cache.Get(qm.queue[i].ID); found {
433-
sessionData := session.(Session)
434-
sessionData.Position = i
435-
qm.cache.Set(qm.queue[i].ID, sessionData, cache.DefaultExpiration)
449+
sessionData, ok := session.(Session)
450+
if ok {
451+
sessionData.Position = i
452+
qm.cache.Set(qm.queue[i].ID, sessionData, cache.DefaultExpiration)
453+
}
436454
}
437455
}
438456
}
439457

440-
// Helper function to check if a file exists
458+
// fileExists checks if a file exists.
441459
func fileExists(path string) bool {
442460
_, err := os.Stat(path)
443461
return err == nil

0 commit comments

Comments
 (0)