1
+ // Package queuemanager implements a Traefik middleware plugin that manages
2
+ // access to services by implementing a queue when capacity is reached.
3
+ // It functions similar to a virtual waiting room, allowing a controlled number
4
+ // of users to access the service while placing others in a structured queue.
1
5
package queuemanager
2
6
3
7
import (
@@ -134,47 +138,64 @@ func (qm *QueueManager) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
134
138
log .Printf ("[Queue Manager] Active sessions: %d, Max entries: %d" , len (qm .activeSessionIDs ), qm .config .MaxEntries )
135
139
}
136
140
137
- // Check if client is already in active sessions
138
- if qm .activeSessionIDs [clientID ] {
139
- // Update last seen timestamp
140
- if session , found := qm .cache .Get (clientID ); found {
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
- }
150
- }
151
-
152
- // Allow access
141
+ // Check if the client can proceed directly
142
+ if qm .canClientProceed (clientID ) {
153
143
qm .next .ServeHTTP (rw , req )
154
144
return
155
145
}
156
146
157
- // If there's room for more active sessions, add this client
147
+ // Handle client that needs to wait
148
+ position := qm .placeClientInQueue (clientID )
149
+
150
+ // Serve queue page
151
+ qm .serveQueuePage (rw , position )
152
+ }
153
+
154
+ // canClientProceed checks if a client can proceed without waiting.
155
+ func (qm * QueueManager ) canClientProceed (clientID string ) bool {
156
+ // If client is in active sessions, update timestamp and proceed
157
+ if qm .activeSessionIDs [clientID ] {
158
+ qm .updateClientTimestamp (clientID )
159
+ return true
160
+ }
161
+
162
+ // If there's room for more active sessions, add client and proceed
158
163
if len (qm .activeSessionIDs ) < qm .config .MaxEntries {
159
- // Create new session
160
164
session := Session {
161
165
ID : clientID ,
162
166
CreatedAt : time .Now (),
163
167
LastSeen : time .Now (),
164
168
Position : 0 ,
165
169
}
166
170
167
- // Add to active sessions
168
171
qm .activeSessionIDs [clientID ] = true
169
172
qm .cache .Set (clientID , session , cache .DefaultExpiration )
173
+ return true
174
+ }
175
+
176
+ // No capacity available
177
+ return false
178
+ }
179
+
180
+ // updateClientTimestamp updates the last seen timestamp for a client.
181
+ func (qm * QueueManager ) updateClientTimestamp (clientID string ) {
182
+ if session , found := qm .cache .Get (clientID ); found {
183
+ sessionData , ok := session .(Session )
184
+ if ! ok {
185
+ if qm .config .Debug {
186
+ log .Printf ("[Queue Manager] Error: Failed to convert session to Session type" )
187
+ }
188
+ return
189
+ }
170
190
171
- // Allow access
172
- qm .next .ServeHTTP (rw , req )
173
- return
191
+ sessionData .LastSeen = time .Now ()
192
+ qm .cache .Set (clientID , sessionData , cache .DefaultExpiration )
174
193
}
194
+ }
175
195
176
- // At this point, we know the client needs to wait
177
- // Let's check if they're already in the queue
196
+ // placeClientInQueue places a client in the waiting queue and returns their position.
197
+ func (qm * QueueManager ) placeClientInQueue (clientID string ) int {
198
+ // Check if they're already in the queue
178
199
position := - 1
179
200
for i , session := range qm .queue {
180
201
if session .ID == clientID {
@@ -197,20 +218,9 @@ func (qm *QueueManager) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
197
218
}
198
219
199
220
// Update last seen timestamp
200
- if session , found := qm .cache .Get (clientID ); found {
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
- }
210
- }
211
-
212
- // Serve queue page
213
- qm .serveQueuePage (rw , position )
221
+ qm .updateClientTimestamp (clientID )
222
+
223
+ return position
214
224
}
215
225
216
226
// getClientID generates or retrieves a unique client identifier.
@@ -310,6 +320,20 @@ func getClientIP(req *http.Request) string {
310
320
311
321
// serveQueuePage serves the queue page HTML.
312
322
func (qm * QueueManager ) serveQueuePage (rw http.ResponseWriter , position int ) {
323
+ // Prepare template data
324
+ data := qm .prepareQueuePageData (position )
325
+
326
+ // Try to use the template file
327
+ if qm .serveCustomTemplate (rw , data ) {
328
+ return
329
+ }
330
+
331
+ // Fall back to the default template
332
+ qm .serveFallbackTemplate (rw , data )
333
+ }
334
+
335
+ // prepareQueuePageData creates the data structure for the queue page template.
336
+ func (qm * QueueManager ) prepareQueuePageData (position int ) QueuePageData {
313
337
// Calculate estimated wait time (rough estimate: 30 seconds per position)
314
338
estimatedWaitTime := int (math .Ceil (float64 (position ) * 0.5 )) // in minutes
315
339
@@ -319,40 +343,54 @@ func (qm *QueueManager) serveQueuePage(rw http.ResponseWriter, position int) {
319
343
progressPercentage = int (100 - (float64 (position ) / float64 (len (qm .queue )) * 100 ))
320
344
}
321
345
322
- // Prepare template data
323
- data := QueuePageData {
346
+ return QueuePageData {
324
347
Position : position + 1 , // 1-based position for users
325
348
QueueSize : len (qm .queue ),
326
349
EstimatedWaitTime : estimatedWaitTime ,
327
350
RefreshInterval : qm .config .RefreshInterval ,
328
351
ProgressPercentage : progressPercentage ,
329
352
Message : "Please wait while we process your request." ,
330
353
}
354
+ }
355
+
356
+ // serveCustomTemplate attempts to serve the custom queue page template.
357
+ // Returns true if successful, false if the template could not be served.
358
+ func (qm * QueueManager ) serveCustomTemplate (rw http.ResponseWriter , data QueuePageData ) bool {
359
+ if ! fileExists (qm .config .QueuePageFile ) {
360
+ return false
361
+ }
331
362
332
- // Try to use the template file
333
- if fileExists (qm .config .QueuePageFile ) {
334
- content , err := os .ReadFile (qm .config .QueuePageFile )
335
- if err == nil {
336
- queueTemplate , parseErr := template .New ("QueuePage" ).Delims ("[[" , "]]" ).Parse (string (content ))
337
- if parseErr == nil {
338
- // Set content type
339
- rw .Header ().Set ("Content-Type" , qm .config .HTTPContentType )
340
- rw .WriteHeader (qm .config .HTTPResponseCode )
341
-
342
- // Execute template
343
- if execErr := queueTemplate .Execute (rw , data ); execErr != nil && qm .config .Debug {
344
- log .Printf ("[Queue Manager] Error executing template: %v" , execErr )
345
- }
346
- return
347
- } else if qm .config .Debug {
348
- log .Printf ("[Queue Manager] Error parsing template: %v" , parseErr )
349
- }
350
- } else if qm .config .Debug {
363
+ content , err := os .ReadFile (qm .config .QueuePageFile )
364
+ if err != nil {
365
+ if qm .config .Debug {
351
366
log .Printf ("[Queue Manager] Error reading template file: %v" , err )
352
367
}
368
+ return false
369
+ }
370
+
371
+ queueTemplate , parseErr := template .New ("QueuePage" ).Delims ("[[" , "]]" ).Parse (string (content ))
372
+ if parseErr != nil {
373
+ if qm .config .Debug {
374
+ log .Printf ("[Queue Manager] Error parsing template: %v" , parseErr )
375
+ }
376
+ return false
377
+ }
378
+
379
+ // Set content type
380
+ rw .Header ().Set ("Content-Type" , qm .config .HTTPContentType )
381
+ rw .WriteHeader (qm .config .HTTPResponseCode )
382
+
383
+ // Execute template
384
+ if execErr := queueTemplate .Execute (rw , data ); execErr != nil && qm .config .Debug {
385
+ log .Printf ("[Queue Manager] Error executing template: %v" , execErr )
386
+ return false
353
387
}
354
388
355
- // Fall back to a simple built-in template if there's an issue with the file
389
+ return true
390
+ }
391
+
392
+ // serveFallbackTemplate serves the built-in default queue page template.
393
+ func (qm * QueueManager ) serveFallbackTemplate (rw http.ResponseWriter , data QueuePageData ) {
356
394
fallbackTemplate := `<!DOCTYPE html>
357
395
<html>
358
396
<head>
0 commit comments