[agent/codex:gpt-5.4-mini] Read docs/RFC.md fully. Find ONE feature described in the sp... #22
2 changed files with 131 additions and 0 deletions
85
http_server.go
Normal file
85
http_server.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPServer owns the health endpoint listener and server.
|
||||
type HTTPServer struct {
|
||||
listener net.Listener
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func (server *HTTPServer) Address() string {
|
||||
if server == nil || server.listener == nil {
|
||||
return ""
|
||||
}
|
||||
return server.listener.Addr().String()
|
||||
}
|
||||
|
||||
func (server *HTTPServer) Close() error {
|
||||
if server == nil {
|
||||
return nil
|
||||
}
|
||||
shutdownContext, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if server.server != nil {
|
||||
if err := server.server.Shutdown(shutdownContext); err != nil {
|
||||
_ = server.server.Close()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if server.listener != nil {
|
||||
return server.listener.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeHTTPHealth starts a minimal HTTP server exposing GET /health.
|
||||
//
|
||||
// service.ServeHTTPHealth("127.0.0.1", 5554)
|
||||
func (service *Service) ServeHTTPHealth(bind string, port int) (*HTTPServer, error) {
|
||||
if bind == "" {
|
||||
bind = "127.0.0.1"
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(bind, strconv.Itoa(port))
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/health", func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
if request.Method != http.MethodGet {
|
||||
responseWriter.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
responseWriter.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(responseWriter).Encode(service.Health())
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
httpServer := &HTTPServer{
|
||||
listener: listener,
|
||||
server: server,
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = server.Serve(listener)
|
||||
}()
|
||||
|
||||
return httpServer, nil
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
|
@ -300,6 +301,51 @@ func TestServiceHealthUsesChainTreeRootAfterDiscovery(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServiceServeHTTPHealthReturnsJSON(t *testing.T) {
|
||||
service := NewService(ServiceOptions{
|
||||
Records: map[string]NameRecords{
|
||||
"gateway.charon.lthn": {
|
||||
A: []string{"10.10.10.10"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
httpServer, err := service.ServeHTTPHealth("127.0.0.1", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected health HTTP server to start: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = httpServer.Close()
|
||||
}()
|
||||
|
||||
response, err := http.Get("http://" + httpServer.Address() + "/health")
|
||||
if err != nil {
|
||||
t.Fatalf("expected health endpoint to respond: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = response.Body.Close()
|
||||
}()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Fatalf("unexpected health status: %d", response.StatusCode)
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("expected health payload: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
t.Fatalf("expected health JSON: %v", err)
|
||||
}
|
||||
if payload["status"] != "ready" {
|
||||
t.Fatalf("expected ready health status, got %#v", payload["status"])
|
||||
}
|
||||
if payload["names_cached"] != float64(1) {
|
||||
t.Fatalf("expected one cached name, got %#v", payload["names_cached"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceDiscoverReplacesRecordsFromDiscoverer(t *testing.T) {
|
||||
records := []map[string]NameRecords{
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue