Model: opus-5-5
This commit is contained in:
+571
-520
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ import (
|
|||||||
// path and would panic if called.
|
// path and would panic if called.
|
||||||
type blockingStatsDB struct {
|
type blockingStatsDB struct {
|
||||||
database.Store
|
database.Store
|
||||||
|
|
||||||
release chan struct{}
|
release chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ func (d blockingStatsDB) GetStatsContext(_ context.Context) (database.Stats, err
|
|||||||
// value. Before the fix the per-request goroutine sent on an unbuffered channel
|
// value. Before the fix the per-request goroutine sent on an unbuffered channel
|
||||||
// that nothing received once the timeout won, so it blocked forever and every
|
// that nothing received once the timeout won, so it blocked forever and every
|
||||||
// poll leaked one goroutine.
|
// poll leaked one goroutine.
|
||||||
|
//
|
||||||
|
//nolint:paralleltest // counts goroutines, so no other test may run alongside
|
||||||
func TestStatsHandlersDoNotLeakOnTimeout(t *testing.T) {
|
func TestStatsHandlersDoNotLeakOnTimeout(t *testing.T) {
|
||||||
release := make(chan struct{})
|
release := make(chan struct{})
|
||||||
db := blockingStatsDB{release: release}
|
db := blockingStatsDB{release: release}
|
||||||
@@ -58,7 +61,7 @@ func TestStatsHandlersDoNotLeakOnTimeout(t *testing.T) {
|
|||||||
for _, handler := range handlers {
|
for _, handler := range handlers {
|
||||||
for range iterations {
|
for range iterations {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||||
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
|
req := httptest.NewRequestWithContext(ctx, http.MethodGet, "/", nil)
|
||||||
handler(httptest.NewRecorder(), req)
|
handler(httptest.NewRecorder(), req)
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
@@ -83,27 +86,33 @@ func TestStatsHandlersDoNotLeakOnTimeout(t *testing.T) {
|
|||||||
// The oldest and newest route times still come from one-row lookups at the ends
|
// The oldest and newest route times still come from one-row lookups at the ends
|
||||||
// of an index; with the database closed they are left out of the answer.
|
// of an index; with the database closed they are left out of the answer.
|
||||||
func TestStatsHandlersAnswerFromMemory(t *testing.T) {
|
func TestStatsHandlersAnswerFromMemory(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
db, err := database.New(&config.Config{StateDir: t.TempDir()}, logger.New())
|
db, err := database.New(&config.Config{StateDir: t.TempDir()}, logger.New())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("database.New: %v", err)
|
t.Fatalf("database.New: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||||
if err := db.UpsertLiveRouteBatch([]*database.LiveRoute{
|
|
||||||
|
err = db.UpsertLiveRouteBatch([]*database.LiveRoute{
|
||||||
{
|
{
|
||||||
ID: uuid.New(), Prefix: "198.51.100.0/24", MaskLength: 24, IPVersion: 4,
|
ID: uuid.New(), Prefix: "198.51.100.0/24", MaskLength: 24, IPVersion: 4,
|
||||||
OriginASN: 64500, PeerIP: "192.0.2.1", ASPath: []int{64500}, NextHop: "192.0.2.1",
|
OriginASN: 64500, PeerIP: "192.0.2.1", ASPath: []int{64500},
|
||||||
LastUpdated: ts,
|
NextHop: "192.0.2.1", LastUpdated: ts,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: uuid.New(), Prefix: "2001:db8::/32", MaskLength: 32, IPVersion: 6,
|
ID: uuid.New(), Prefix: "2001:db8::/32", MaskLength: 32, IPVersion: 6,
|
||||||
OriginASN: 64501, PeerIP: "2001:db8::1", ASPath: []int{64501}, NextHop: "2001:db8::1",
|
OriginASN: 64501, PeerIP: "2001:db8::1", ASPath: []int{64501},
|
||||||
LastUpdated: ts,
|
NextHop: "2001:db8::1", LastUpdated: ts,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
})
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("UpsertLiveRouteBatch: %v", err)
|
t.Fatalf("UpsertLiveRouteBatch: %v", err)
|
||||||
}
|
}
|
||||||
if err := db.Close(); err != nil {
|
|
||||||
|
err = db.Close()
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("Close: %v", err)
|
t.Fatalf("Close: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,35 +124,45 @@ func TestStatsHandlersAnswerFromMemory(t *testing.T) {
|
|||||||
|
|
||||||
for name, handler := range handlers {
|
for name, handler := range handlers {
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
handler(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
handler(rec, httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil))
|
||||||
|
|
||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Errorf("%s: status %d, want %d; body %s", name, rec.Code, http.StatusOK, rec.Body)
|
t.Errorf("%s: status %d, want %d; body %s",
|
||||||
|
name, rec.Code, http.StatusOK, rec.Body)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:tagliatelle // field names are the existing JSON API
|
||||||
var body struct {
|
var body struct {
|
||||||
Data struct {
|
Data struct {
|
||||||
IPv4Routes int `json:"ipv4_routes"`
|
IPv4Routes int `json:"ipv4_routes"`
|
||||||
IPv6Routes int `json:"ipv6_routes"`
|
IPv6Routes int `json:"ipv6_routes"`
|
||||||
IPv4PrefixDistribution []database.PrefixDistribution `json:"ipv4_prefix_distribution"`
|
IPv4Distribution []database.PrefixDistribution `json:"ipv4_prefix_distribution"`
|
||||||
IPv6PrefixDistribution []database.PrefixDistribution `json:"ipv6_prefix_distribution"`
|
IPv6Distribution []database.PrefixDistribution `json:"ipv6_prefix_distribution"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
||||||
|
err := json.Unmarshal(rec.Body.Bytes(), &body)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("%s: decoding the answer: %v", name, err)
|
t.Fatalf("%s: decoding the answer: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if body.Data.IPv4Routes != 1 || body.Data.IPv6Routes != 1 {
|
if body.Data.IPv4Routes != 1 || body.Data.IPv6Routes != 1 {
|
||||||
t.Errorf("%s: routes = (v4 %d, v6 %d), want (1, 1)", name, body.Data.IPv4Routes, body.Data.IPv6Routes)
|
t.Errorf("%s: routes = (v4 %d, v6 %d), want (1, 1)",
|
||||||
|
name, body.Data.IPv4Routes, body.Data.IPv6Routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantV4 := []database.PrefixDistribution{{MaskLength: 24, Count: 1}}
|
wantV4 := []database.PrefixDistribution{{MaskLength: 24, Count: 1}}
|
||||||
if !slices.Equal(body.Data.IPv4PrefixDistribution, wantV4) {
|
if !slices.Equal(body.Data.IPv4Distribution, wantV4) {
|
||||||
t.Errorf("%s: IPv4 distribution = %v, want %v", name, body.Data.IPv4PrefixDistribution, wantV4)
|
t.Errorf("%s: IPv4 distribution = %v, want %v",
|
||||||
|
name, body.Data.IPv4Distribution, wantV4)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantV6 := []database.PrefixDistribution{{MaskLength: 32, Count: 1}}
|
wantV6 := []database.PrefixDistribution{{MaskLength: 32, Count: 1}}
|
||||||
if !slices.Equal(body.Data.IPv6PrefixDistribution, wantV6) {
|
if !slices.Equal(body.Data.IPv6Distribution, wantV6) {
|
||||||
t.Errorf("%s: IPv6 distribution = %v, want %v", name, body.Data.IPv6PrefixDistribution, wantV6)
|
t.Errorf("%s: IPv6 distribution = %v, want %v",
|
||||||
|
name, body.Data.IPv6Distribution, wantV6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,12 +171,15 @@ func TestStatsHandlersAnswerFromMemory(t *testing.T) {
|
|||||||
// current count.
|
// current count.
|
||||||
func settledGoroutineCount() int {
|
func settledGoroutineCount() int {
|
||||||
prev := runtime.NumGoroutine()
|
prev := runtime.NumGoroutine()
|
||||||
|
|
||||||
for range 20 {
|
for range 20 {
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
cur := runtime.NumGoroutine()
|
cur := runtime.NumGoroutine()
|
||||||
if cur == prev {
|
if cur == prev {
|
||||||
return cur
|
return cur
|
||||||
}
|
}
|
||||||
|
|
||||||
prev = cur
|
prev = cur
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +192,7 @@ func waitForGoroutines(target int) bool {
|
|||||||
if runtime.NumGoroutine() <= target {
|
if runtime.NumGoroutine() <= target {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
// responseWriter wraps http.ResponseWriter to capture the response
|
// responseWriter wraps http.ResponseWriter to capture the response
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
||||||
body *bytes.Buffer
|
body *bytes.Buffer
|
||||||
statusCode int
|
statusCode int
|
||||||
written bool
|
written bool
|
||||||
@@ -86,8 +87,10 @@ func JSONResponseMiddleware(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse the original response
|
// Parse the original response
|
||||||
var originalResponse map[string]interface{}
|
var originalResponse map[string]any
|
||||||
if err := json.Unmarshal(rw.body.Bytes(), &originalResponse); err != nil {
|
|
||||||
|
err := json.Unmarshal(rw.body.Bytes(), &originalResponse)
|
||||||
|
if err != nil {
|
||||||
// If we can't parse it, just write original
|
// If we can't parse it, just write original
|
||||||
w.WriteHeader(rw.statusCode)
|
w.WriteHeader(rw.statusCode)
|
||||||
_, _ = w.Write(rw.body.Bytes())
|
_, _ = w.Write(rw.body.Bytes())
|
||||||
@@ -96,7 +99,7 @@ func JSONResponseMiddleware(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add @meta field
|
// Add @meta field
|
||||||
originalResponse["@meta"] = map[string]interface{}{
|
originalResponse["@meta"] = map[string]any{
|
||||||
"time_zone": "UTC",
|
"time_zone": "UTC",
|
||||||
"api_version": 1,
|
"api_version": 1,
|
||||||
"execution_time": fmt.Sprintf("%d ms", execTime.Milliseconds()),
|
"execution_time": fmt.Sprintf("%d ms", execTime.Milliseconds()),
|
||||||
@@ -112,6 +115,7 @@ func JSONResponseMiddleware(next http.Handler) http.Handler {
|
|||||||
// timeoutWriter wraps ResponseWriter to prevent concurrent writes after timeout
|
// timeoutWriter wraps ResponseWriter to prevent concurrent writes after timeout
|
||||||
type timeoutWriter struct {
|
type timeoutWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
written bool
|
written bool
|
||||||
header http.Header // cached header to prevent concurrent access
|
header http.Header // cached header to prevent concurrent access
|
||||||
@@ -158,6 +162,7 @@ func (tw *timeoutWriter) Header() http.Header {
|
|||||||
func (tw *timeoutWriter) markWritten() {
|
func (tw *timeoutWriter) markWritten() {
|
||||||
tw.mu.Lock()
|
tw.mu.Lock()
|
||||||
defer tw.mu.Unlock()
|
defer tw.mu.Unlock()
|
||||||
|
|
||||||
tw.written = true
|
tw.written = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +173,10 @@ func (tw *timeoutWriter) markWritten() {
|
|||||||
// The timeout parameter specifies the maximum duration allowed for request processing.
|
// The timeout parameter specifies the maximum duration allowed for request processing.
|
||||||
// The returned middleware handles panics from the wrapped handler by re-panicking
|
// The returned middleware handles panics from the wrapped handler by re-panicking
|
||||||
// after cleanup, and prevents concurrent writes to the response after timeout occurs.
|
// after cleanup, and prevents concurrent writes to the response after timeout occurs.
|
||||||
func TimeoutMiddleware(timeout time.Duration, logger *slog.Logger) func(http.Handler) http.Handler {
|
func TimeoutMiddleware(
|
||||||
|
timeout time.Duration,
|
||||||
|
logger *slog.Logger,
|
||||||
|
) func(http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
@@ -182,7 +190,7 @@ func TimeoutMiddleware(timeout time.Duration, logger *slog.Logger) func(http.Han
|
|||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
panicChan := make(chan interface{}, 1)
|
panicChan := make(chan any, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -202,6 +210,7 @@ func TimeoutMiddleware(timeout time.Duration, logger *slog.Logger) func(http.Han
|
|||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
tw.markWritten() // Prevent the handler from writing after timeout
|
tw.markWritten() // Prevent the handler from writing after timeout
|
||||||
|
|
||||||
execTime := time.Since(startTime)
|
execTime := time.Since(startTime)
|
||||||
|
|
||||||
// Log the timeout as a warning
|
// Log the timeout as a warning
|
||||||
@@ -219,13 +228,13 @@ func TimeoutMiddleware(timeout time.Duration, logger *slog.Logger) func(http.Han
|
|||||||
w.WriteHeader(http.StatusRequestTimeout)
|
w.WriteHeader(http.StatusRequestTimeout)
|
||||||
tw.mu.Unlock()
|
tw.mu.Unlock()
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]any{
|
||||||
"status": "error",
|
jsonKeyStatus: jsonStatusError,
|
||||||
"error": map[string]interface{}{
|
jsonKeyError: map[string]any{
|
||||||
"msg": "Request timeout",
|
jsonKeyMsg: "Request timeout",
|
||||||
"code": http.StatusRequestTimeout,
|
jsonKeyCode: http.StatusRequestTimeout,
|
||||||
},
|
},
|
||||||
"@meta": map[string]interface{}{
|
"@meta": map[string]any{
|
||||||
"time_zone": "UTC",
|
"time_zone": "UTC",
|
||||||
"api_version": 1,
|
"api_version": 1,
|
||||||
"execution_time": fmt.Sprintf("%d ms", execTime.Milliseconds()),
|
"execution_time": fmt.Sprintf("%d ms", execTime.Milliseconds()),
|
||||||
@@ -273,8 +282,10 @@ func JSONValidationMiddleware(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
// For JSON responses, validate the JSON
|
// For JSON responses, validate the JSON
|
||||||
if rw.body.Len() > 0 {
|
if rw.body.Len() > 0 {
|
||||||
var testParse interface{}
|
var testParse any
|
||||||
if err := json.Unmarshal(rw.body.Bytes(), &testParse); err == nil {
|
|
||||||
|
err := json.Unmarshal(rw.body.Bytes(), &testParse)
|
||||||
|
if err == nil {
|
||||||
// Valid JSON, write it out
|
// Valid JSON, write it out
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(rw.statusCode)
|
w.WriteHeader(rw.statusCode)
|
||||||
@@ -293,6 +304,7 @@ func JSONValidationMiddleware(next http.Handler) http.Handler {
|
|||||||
if statusCode == http.StatusOK {
|
if statusCode == http.StatusOK {
|
||||||
statusCode = http.StatusInternalServerError
|
statusCode = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
|
|
||||||
errorMsg := "Internal server error"
|
errorMsg := "Internal server error"
|
||||||
@@ -300,11 +312,11 @@ func JSONValidationMiddleware(next http.Handler) http.Handler {
|
|||||||
errorMsg = "Request timeout"
|
errorMsg = "Request timeout"
|
||||||
}
|
}
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]any{
|
||||||
"status": "error",
|
jsonKeyStatus: jsonStatusError,
|
||||||
"error": map[string]interface{}{
|
jsonKeyError: map[string]any{
|
||||||
"msg": errorMsg,
|
jsonKeyMsg: errorMsg,
|
||||||
"code": statusCode,
|
jsonKeyCode: statusCode,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_ = json.NewEncoder(w).Encode(response)
|
_ = json.NewEncoder(w).Encode(response)
|
||||||
@@ -314,6 +326,7 @@ func JSONValidationMiddleware(next http.Handler) http.Handler {
|
|||||||
// statusWriter wraps http.ResponseWriter to capture the status code
|
// statusWriter wraps http.ResponseWriter to capture the status code
|
||||||
type statusWriter struct {
|
type statusWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
||||||
statusCode int
|
statusCode int
|
||||||
written bool
|
written bool
|
||||||
}
|
}
|
||||||
@@ -323,6 +336,7 @@ func (sw *statusWriter) WriteHeader(statusCode int) {
|
|||||||
sw.statusCode = statusCode
|
sw.statusCode = statusCode
|
||||||
sw.written = true
|
sw.written = true
|
||||||
}
|
}
|
||||||
|
|
||||||
sw.ResponseWriter.WriteHeader(statusCode)
|
sw.ResponseWriter.WriteHeader(statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,11 +376,12 @@ func RequestLoggerMiddleware(logger *slog.Logger) func(http.Handler) http.Handle
|
|||||||
// Slow query threshold (1 second)
|
// Slow query threshold (1 second)
|
||||||
const slowQueryThreshold = 1 * time.Second
|
const slowQueryThreshold = 1 * time.Second
|
||||||
|
|
||||||
if sw.statusCode >= http.StatusInternalServerError {
|
switch {
|
||||||
|
case sw.statusCode >= http.StatusInternalServerError:
|
||||||
logLevel = slog.LevelError
|
logLevel = slog.LevelError
|
||||||
} else if sw.statusCode >= http.StatusBadRequest {
|
case sw.statusCode >= http.StatusBadRequest:
|
||||||
logLevel = slog.LevelWarn
|
logLevel = slog.LevelWarn
|
||||||
} else if duration >= slowQueryThreshold {
|
case duration >= slowQueryThreshold:
|
||||||
logLevel = slog.LevelWarn
|
logLevel = slog.LevelWarn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ func (s *Server) setupRoutes() {
|
|||||||
r.Use(middleware.RealIP)
|
r.Use(middleware.RealIP)
|
||||||
r.Use(RequestLoggerMiddleware(s.logger.Logger)) // Structured request logging
|
r.Use(RequestLoggerMiddleware(s.logger.Logger)) // Structured request logging
|
||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
|
|
||||||
const requestTimeout = 30 * time.Second // Increased from 8s for slow queries
|
const requestTimeout = 30 * time.Second // Increased from 8s for slow queries
|
||||||
|
|
||||||
r.Use(TimeoutMiddleware(requestTimeout, s.logger.Logger))
|
r.Use(TimeoutMiddleware(requestTimeout, s.logger.Logger))
|
||||||
r.Use(JSONResponseMiddleware)
|
r.Use(JSONResponseMiddleware)
|
||||||
|
|
||||||
@@ -24,7 +26,10 @@ func (s *Server) setupRoutes() {
|
|||||||
r.Get("/", s.handleIndex())
|
r.Get("/", s.handleIndex())
|
||||||
r.Get("/status", s.handleStatusHTML())
|
r.Get("/status", s.handleStatusHTML())
|
||||||
r.Get("/status.json", JSONValidationMiddleware(s.handleStatusJSON()).ServeHTTP)
|
r.Get("/status.json", JSONValidationMiddleware(s.handleStatusJSON()).ServeHTTP)
|
||||||
r.Get("/.well-known/healthcheck.json", JSONValidationMiddleware(s.handleHealthCheck()).ServeHTTP)
|
r.Get(
|
||||||
|
"/.well-known/healthcheck.json",
|
||||||
|
JSONValidationMiddleware(s.handleHealthCheck()).ServeHTTP,
|
||||||
|
)
|
||||||
|
|
||||||
// AS and prefix detail pages
|
// AS and prefix detail pages
|
||||||
r.Get("/as/{asn}", s.handleASDetail())
|
r.Get("/as/{asn}", s.handleASDetail())
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -40,7 +41,12 @@ type Server struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new HTTP server
|
// New creates a new HTTP server
|
||||||
func New(db database.Store, streamer *streamer.Streamer, logger *logger.Logger, cfg *config.Config) *Server {
|
func New(
|
||||||
|
db database.Store,
|
||||||
|
streamer *streamer.Streamer,
|
||||||
|
logger *logger.Logger,
|
||||||
|
cfg *config.Config,
|
||||||
|
) *Server {
|
||||||
s := &Server{
|
s := &Server{
|
||||||
db: db,
|
db: db,
|
||||||
streamer: streamer,
|
streamer: streamer,
|
||||||
@@ -76,7 +82,9 @@ func (s *Server) Start() error {
|
|||||||
// Start in goroutine but log when actually listening
|
// Start in goroutine but log when actually listening
|
||||||
go func() {
|
go func() {
|
||||||
s.logger.Info("HTTP server listening", "addr", s.srv.Addr)
|
s.logger.Info("HTTP server listening", "addr", s.srv.Addr)
|
||||||
if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
|
err := s.srv.ListenAndServe()
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
s.logger.Error("HTTP server error", "error", err)
|
s.logger.Error("HTTP server error", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
Reference in New Issue
Block a user