-
Notifications
You must be signed in to change notification settings - Fork 0
Add ServeHTTP methods to Ticker and Tickers for REST API access #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
4471b7d
e30f1f0
535b367
68ef7c1
54795e4
94dadfe
aae33f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,17 +1,25 @@ | ||||||||||||||||||||||||||||||
| package utils | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import "time" | ||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||
| "log/slog" | ||||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||||
| "sync" | ||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Ticker is a wrapper around time.Ticker it is given a name, it hold | ||||||||||||||||||||||||||||||
| // Ticker is a wrapper around time.Ticker it is given a name, it holds | ||||||||||||||||||||||||||||||
| // the duration and kept in a map indexed by name such that it is easy | ||||||||||||||||||||||||||||||
| // to lookup to shutdown or reset | ||||||||||||||||||||||||||||||
| type Ticker struct { | ||||||||||||||||||||||||||||||
| Name string | ||||||||||||||||||||||||||||||
| *time.Ticker | ||||||||||||||||||||||||||||||
| Func func(t time.Time) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| mu sync.RWMutex | ||||||||||||||||||||||||||||||
| lastTick time.Time | ||||||||||||||||||||||||||||||
| ticks int | ||||||||||||||||||||||||||||||
| active bool | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| var ( | ||||||||||||||||||||||||||||||
|
|
@@ -34,22 +42,28 @@ func Timestamp() time.Duration { | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // NewTicker creates a time.Ticker with the name n that will fire | ||||||||||||||||||||||||||||||
| // every d time.Duration. The function f will be called every time | ||||||||||||||||||||||||||||||
| // ticker goes off. The ticker can be stoped, restarted and reset | ||||||||||||||||||||||||||||||
| // ticker goes off. The ticker can be stopped, restarted and reset | ||||||||||||||||||||||||||||||
| // with a different duration | ||||||||||||||||||||||||||||||
| func NewTicker(n string, d time.Duration, f func(t time.Time)) *Ticker { | ||||||||||||||||||||||||||||||
| t := &Ticker{ | ||||||||||||||||||||||||||||||
| Name: n, | ||||||||||||||||||||||||||||||
| Ticker: time.NewTicker(d), | ||||||||||||||||||||||||||||||
| Func: f, | ||||||||||||||||||||||||||||||
| active: true, | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| tickers[n] = t | ||||||||||||||||||||||||||||||
| go func() { | ||||||||||||||||||||||||||||||
| for tick := range t.Ticker.C { | ||||||||||||||||||||||||||||||
| t.mu.Lock() | ||||||||||||||||||||||||||||||
| t.lastTick = time.Now() | ||||||||||||||||||||||||||||||
| t.ticks++ | ||||||||||||||||||||||||||||||
| t.mu.Unlock() | ||||||||||||||||||||||||||||||
| f(tick) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| t.mu.Lock() | ||||||||||||||||||||||||||||||
| t.active = false | ||||||||||||||||||||||||||||||
| t.mu.Unlock() | ||||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||||
| return t | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
@@ -64,3 +78,36 @@ func GetTicker(n string) *Ticker { | |||||||||||||||||||||||||||||
| t, _ := tickers[n] | ||||||||||||||||||||||||||||||
| return t | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // TickerInfo holds the JSON-serializable ticker information | ||||||||||||||||||||||||||||||
| type TickerInfo struct { | ||||||||||||||||||||||||||||||
| Name string `json:"name"` | ||||||||||||||||||||||||||||||
| LastTick time.Time `json:"last_tick"` | ||||||||||||||||||||||||||||||
| Ticks int `json:"ticks"` | ||||||||||||||||||||||||||||||
| Active bool `json:"active"` | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // ServeHTTP implements http.Handler to return ticker information as JSON. | ||||||||||||||||||||||||||||||
| // It returns the ticker's name, last tick time, total tick count, and active status. | ||||||||||||||||||||||||||||||
| func (t *Ticker) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||||||||||||||||||||||||||
| if r.Method != http.MethodGet { | ||||||||||||||||||||||||||||||
| http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | ||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| t.mu.RLock() | ||||||||||||||||||||||||||||||
| info := TickerInfo{ | ||||||||||||||||||||||||||||||
| Name: t.Name, | ||||||||||||||||||||||||||||||
| LastTick: t.lastTick, | ||||||||||||||||||||||||||||||
| Ticks: t.ticks, | ||||||||||||||||||||||||||||||
| Active: t.active, | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| t.mu.RUnlock() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| w.Header().Set("Content-Type", "application/json") | ||||||||||||||||||||||||||||||
| w.WriteHeader(http.StatusOK) | ||||||||||||||||||||||||||||||
| if err := json.NewEncoder(w).Encode(info); err != nil { | ||||||||||||||||||||||||||||||
| slog.Error("Failed to encode ticker info", "error", err, "ticker", t.Name) | ||||||||||||||||||||||||||||||
|
Comment on lines
+111
to
+113
|
||||||||||||||||||||||||||||||
| w.WriteHeader(http.StatusOK) | |
| if err := json.NewEncoder(w).Encode(info); err != nil { | |
| slog.Error("Failed to encode ticker info", "error", err, "ticker", t.Name) | |
| data, err := json.Marshal(info) | |
| if err != nil { | |
| slog.Error("Failed to marshal ticker info", "error", err, "ticker", t.Name) | |
| http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | |
| return | |
| } | |
| w.WriteHeader(http.StatusOK) | |
| if _, err := w.Write(data); err != nil { | |
| slog.Error("Failed to write ticker info response", "error", err, "ticker", t.Name) |
Copilot
AI
Jan 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling after json.NewEncoder().Encode() fails is incomplete. The error is logged but no HTTP error response is sent to the client. The client will receive a 200 OK status with an incomplete or malformed JSON body. Consider using http.Error() to send a proper error response with status 500 after logging the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test logic is incorrect. The
stationsvariable is obtained before the goroutine waits and stops station-009. The assertion on line 68 checks the length ofstationswhich was captured earlier and won't reflect the state after the station is stopped. The assertion should check the length of a freshly fetched stations list after the goroutine completes, not the stalestationsvariable from line 48.