-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.go
More file actions
151 lines (134 loc) · 3.88 KB
/
server.go
File metadata and controls
151 lines (134 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package httpok
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/candango/httpok/logger"
"github.com/candango/httpok/session"
)
// newSignalChan creates a channel that listens for specified signals or
// default signals if none are provided.
// It returns a channel that receives these signals. This function is used
// internally by [GracefulServer.Run]
func newSignalChan(sig ...os.Signal) chan os.Signal {
if len(sig) == 0 {
sig = []os.Signal{
syscall.SIGINT,
syscall.SIGHUP,
syscall.SIGQUIT,
syscall.SIGTERM,
syscall.SIGUSR1,
syscall.SIGUSR2,
}
}
c := make(chan os.Signal, 1)
signal.Notify(c, sig...)
return c
}
// GracefulCancelFunc defines a user-provided function that is called during
// shutdown for custom cleanup or cancellation logic. It receives a context and
// returns an error if the cancellation fails.
type GracefulCancelFunc func(context.Context) error
// GracefulServer combines an HTTP server with a context for graceful shutdown
// handling.
type GracefulServer struct {
Name string
*http.Server
context.Context
logger.Logger
SessionEngine session.Engine
ShutdownTimeout float64
cancel context.CancelFunc
CancelFunc GracefulCancelFunc
cancelMutex sync.Mutex
sigChan chan os.Signal
}
// NewGracefulServer creates a new GracefulServer wrapping the given http.Server.
func NewGracefulServer(s *http.Server, name string) *GracefulServer {
gs := &GracefulServer{
Name: name,
Server: s,
Context: context.Background(),
}
return gs
}
// WithShutdownTimeout sets the shutdown timeout (in seconds) for the server.
// Returns the server for method chaining.
func (s *GracefulServer) WithShutdownTimeout(timeout float64) *GracefulServer {
s.ShutdownTimeout = timeout
return s
}
// WithCancelFunc sets a custom cancellation function to be called before
// shutdown. Returns the server for method chaining.
func (s *GracefulServer) WithCancelFunc(cancelFunc GracefulCancelFunc) *GracefulServer {
s.CancelFunc = cancelFunc
return s
}
// TriggerShutdown programmatically requests a graceful shutdown of the server.
// Returns an error if the shutdown cancel function is not available (e.g., Run
// has not been called).
func (s *GracefulServer) TriggerShutdown() error {
s.cancelMutex.Lock()
defer s.cancelMutex.Unlock()
if s.cancel == nil {
return fmt.Errorf("no shutdown cancel function available")
}
s.cancel()
return nil
}
// Run starts the HTTP server in a goroutine and listens for termination
// signals to gracefully shut down.
// It takes optional signals to listen for; if none are provided, it uses
// default signals.
func (s *GracefulServer) Run(sig ...os.Signal) {
l := s.Logger
if l == nil {
l = &logger.StandardLogger{}
}
go func() {
err := s.ListenAndServe()
if err != http.ErrServerClosed {
l.Fatalf("server %s HTTP ListenAndServe error: %v", s.Name, err)
}
}()
l.Printf("server %s started at %s", s.Name, s.Addr)
s.sigChan = newSignalChan(sig...)
done := make(chan struct{})
ctx, cancel := context.WithCancel(s.Context)
s.cancelMutex.Lock()
s.cancel = cancel
s.cancelMutex.Unlock()
go func() {
select {
case sig := <-s.sigChan:
l.Printf("shutting down %s due to signal %s", s.Name, sig)
case <-ctx.Done():
l.Printf("shutting down %s cancellation triggered", s.Name)
ctx, cancel = context.WithCancel(context.Background())
}
if s.ShutdownTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx,
time.Duration(s.ShutdownTimeout)*time.Second)
}
defer func() {
signal.Stop(s.sigChan)
cancel()
close(done)
}()
if s.CancelFunc != nil {
if err := s.CancelFunc(ctx); err != nil {
l.Fatalf("Server %s cancellation function failed: %v", s.Name, err)
}
}
if err := s.Server.Shutdown(ctx); err != nil {
l.Fatalf("Server %s shutdown failed: %v", s.Name, err)
}
l.Printf("%s shutdown gracefully", s.Name)
}()
<-done
}