update: HTTP API

This commit is contained in:
Oarkflow
2025-03-30 16:55:32 +05:45
parent ba75adc7d6
commit 31a9fb8ba7
11 changed files with 340 additions and 85 deletions

View File

@@ -3,8 +3,10 @@ package mq
import (
"context"
"fmt"
"io"
"log"
"net"
"net/http"
"strings"
"time"
@@ -253,6 +255,14 @@ func (c *Consumer) Consume(ctx context.Context) error {
return fmt.Errorf("failed to connect to server for queue %s: %v", c.queue, err)
}
c.pool.Start(c.opts.numOfWorkers)
if c.opts.enableHTTPApi {
go func() {
_, err := c.StartHTTPAPI()
if err != nil {
log.Println(fmt.Sprintf("Error on running HTTP API %s", err.Error()))
}
}()
}
// Infinite loop to continuously read messages and reconnect if needed.
for {
select {
@@ -341,3 +351,134 @@ func (c *Consumer) sendOpsMessage(ctx context.Context, cmd consts.CMD) error {
func (c *Consumer) Conn() net.Conn {
return c.conn
}
// StartHTTPAPI starts an HTTP server on a random available port and registers API endpoints.
// It returns the port number the server is listening on.
func (c *Consumer) StartHTTPAPI() (int, error) {
// Listen on a random port.
ln, err := net.Listen("tcp", ":0")
if err != nil {
return 0, fmt.Errorf("failed to start listener: %w", err)
}
port := ln.Addr().(*net.TCPAddr).Port
// Create a new HTTP mux and register endpoints.
mux := http.NewServeMux()
mux.HandleFunc("/stats", c.handleStats)
mux.HandleFunc("/update", c.handleUpdate)
mux.HandleFunc("/pause", c.handlePause)
mux.HandleFunc("/resume", c.handleResume)
mux.HandleFunc("/stop", c.handleStop)
// Start the server in a new goroutine.
go func() {
// Log errors if the HTTP server stops.
if err := http.Serve(ln, mux); err != nil {
log.Printf("HTTP server error on port %d: %v", port, err)
}
}()
log.Printf("HTTP API for consumer %s started on port %d", c.id, port)
return port, nil
}
// handleStats responds with JSON containing consumer and pool metrics.
func (c *Consumer) handleStats(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// Gather consumer and pool stats using formatted metrics.
stats := map[string]interface{}{
"consumer_id": c.id,
"queue": c.queue,
"pool_metrics": c.pool.FormattedMetrics(),
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(stats); err != nil {
http.Error(w, fmt.Sprintf("failed to encode stats: %v", err), http.StatusInternalServerError)
}
}
// handleUpdate accepts a POST request with a JSON payload to update the consumer's pool configuration.
// It reuses the consumer's Update method which updates the pool configuration.
func (c *Consumer) handleUpdate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// Read the request body.
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("failed to read request body: %v", err), http.StatusBadRequest)
return
}
defer r.Body.Close()
// Call the Update method on the consumer (which in turn updates the pool configuration).
if err := c.Update(r.Context(), body); err != nil {
http.Error(w, fmt.Sprintf("failed to update configuration: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
resp := map[string]string{"status": "configuration updated"}
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, fmt.Sprintf("failed to encode response: %v", err), http.StatusInternalServerError)
}
}
// handlePause pauses the consumer's pool.
func (c *Consumer) handlePause(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
if err := c.Pause(r.Context()); err != nil {
http.Error(w, fmt.Sprintf("failed to pause consumer: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
resp := map[string]string{"status": "consumer paused"}
json.NewEncoder(w).Encode(resp)
}
// handleResume resumes the consumer's pool.
func (c *Consumer) handleResume(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
if err := c.Resume(r.Context()); err != nil {
http.Error(w, fmt.Sprintf("failed to resume consumer: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
resp := map[string]string{"status": "consumer resumed"}
json.NewEncoder(w).Encode(resp)
}
// handleStop stops the consumer's pool.
func (c *Consumer) handleStop(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// Stop the consumer.
if err := c.Stop(r.Context()); err != nil {
http.Error(w, fmt.Sprintf("failed to stop consumer: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
resp := map[string]string{"status": "consumer stopped"}
json.NewEncoder(w).Encode(resp)
}