@@ -31,28 +31,57 @@ func New(name string, opts ...Option) (*Server, error) {
31
31
cfg : cfg ,
32
32
}
33
33
34
- if initErr := server .initializeHTTP (); initErr != nil {
35
- return nil , fmt .Errorf ("failed to initialize http server: %w" , initErr )
34
+ if initErr := server .initializeDebug (); initErr != nil {
35
+ return nil , fmt .Errorf ("failed to initialize debug server: %w" , initErr )
36
36
}
37
- if initErr := server .initializeGRPC (); initErr != nil {
38
- return nil , fmt .Errorf ("failed to initialize grpc server: %w" , initErr )
37
+
38
+ if server .isHTTPEnabled () {
39
+ if initErr := server .initializeHTTP (); initErr != nil {
40
+ return nil , fmt .Errorf ("failed to initialize http server: %w" , initErr )
41
+ }
42
+ }
43
+
44
+ if server .isGRPCEnabled () {
45
+ if initErr := server .initializeGRPC (); initErr != nil {
46
+ return nil , fmt .Errorf ("failed to initialize grpc server: %w" , initErr )
47
+ }
39
48
}
40
49
41
50
return server , nil
42
51
}
43
52
53
+ // Server is a packaged server with batteries included. It is designed to be standard across components where it makes sense.
54
+ // Server implements graceful shutdown making it suitable for usage in integration tests. See server_test.go.
55
+ //
56
+ // Server is composed of the following:
57
+ // * Debug server which serves observability and debug endpoints
58
+ // - /metrics for Prometheus metrics
59
+ // - /pprof for Golang profiler
60
+ // - /ready for kubernetes readiness check
61
+ // - /live for kubernetes liveness check
62
+ // * (optional) gRPC server with standard interceptors and configuration
63
+ // - Started when baseserver is configured WithGRPCPort (port is non-negative)
64
+ // - Use Server.GRPC() to get access to the underlying grpc.Server and register services
65
+ // * (optional) HTTP server
66
+ // - Currently does not come with any standard HTTP middlewares
67
+ // - Started when baseserver is configured WithHTTPPort (port is non-negative)
68
+ // - Use Server.HTTPMux() to get access to the root handler and register your endpoints
44
69
type Server struct {
45
70
// Name is the name of this server, used for logging context
46
71
Name string
47
72
48
73
cfg * config
49
74
50
- // http is an http Server
75
+ // debug is an HTTP server for debug endpoints - metrics, pprof, readiness & liveness.
76
+ debug * http.Server
77
+ debugListener net.Listener
78
+
79
+ // http is an http Server, only used when port is specified in cfg
51
80
http * http.Server
52
81
httpMux * http.ServeMux
53
82
httpListener net.Listener
54
83
55
- // grpc is a grpc Server
84
+ // grpc is a grpc Server, only used when port is specified in cfg
56
85
grpc * grpc.Server
57
86
grpcListener net.Listener
58
87
@@ -62,25 +91,34 @@ type Server struct {
62
91
63
92
func (s * Server ) ListenAndServe () error {
64
93
var err error
65
- s .grpcListener , err = net .Listen ("tcp" , fmt .Sprintf (":%d" , s .cfg .grpcPort ))
94
+
95
+ s .debugListener , err = net .Listen ("tcp" , fmt .Sprintf (":%d" , s .cfg .debugPort ))
66
96
if err != nil {
67
- return fmt .Errorf ("failed to acquire port %d" , s .cfg .grpcPort )
97
+ return fmt .Errorf ("failed to acquire port %d" , s .cfg .debugPort )
68
98
}
69
99
70
- s .httpListener , err = net .Listen ("tcp" , fmt .Sprintf (":%d" , s .cfg .httpPort ))
71
- if err != nil {
72
- return fmt .Errorf ("failed to acquire port %d" , s .cfg .httpPort )
100
+ if s .isGRPCEnabled () {
101
+ s .grpcListener , err = net .Listen ("tcp" , fmt .Sprintf (":%d" , s .cfg .grpcPort ))
102
+ if err != nil {
103
+ return fmt .Errorf ("failed to acquire port %d" , s .cfg .grpcPort )
104
+ }
105
+ }
106
+
107
+ if s .isHTTPEnabled () {
108
+ s .httpListener , err = net .Listen ("tcp" , fmt .Sprintf (":%d" , s .cfg .httpPort ))
109
+ if err != nil {
110
+ return fmt .Errorf ("failed to acquire port %d" , s .cfg .httpPort )
111
+ }
73
112
}
74
113
75
114
errors := make (chan error )
76
115
defer close (errors )
77
116
s .listening = make (chan struct {})
78
117
118
+ // Always start the debug server, we should always have metrics and other debug information.
79
119
go func () {
80
- s .Logger ().
81
- WithField ("protocol" , "grpc" ).
82
- Infof ("Serving gRPC on %s" , s .grpcListener .Addr ().String ())
83
- if serveErr := s .grpc .Serve (s .grpcListener ); serveErr != nil {
120
+ s .Logger ().WithField ("protocol" , "http" ).Infof ("Serving debug server on %s" , s .debugListener .Addr ().String ())
121
+ if serveErr := s .debug .Serve (s .debugListener ); serveErr != nil {
84
122
if s .isClosing () {
85
123
return
86
124
}
@@ -89,18 +127,31 @@ func (s *Server) ListenAndServe() error {
89
127
}
90
128
}()
91
129
92
- go func () {
93
- s .Logger ().
94
- WithField ("protocol" , "http" ).
95
- Infof ("Serving http on %s" , s .httpListener .Addr ().String ())
96
- if serveErr := s .http .Serve (s .httpListener ); serveErr != nil {
97
- if s .isClosing () {
98
- return
130
+ if s .isGRPCEnabled () {
131
+ go func () {
132
+ s .Logger ().WithField ("protocol" , "grpc" ).Infof ("Serving gRPC on %s" , s .grpcListener .Addr ().String ())
133
+ if serveErr := s .grpc .Serve (s .grpcListener ); serveErr != nil {
134
+ if s .isClosing () {
135
+ return
136
+ }
137
+
138
+ errors <- serveErr
99
139
}
140
+ }()
141
+ }
100
142
101
- errors <- serveErr
102
- }
103
- }()
143
+ if s .isHTTPEnabled () {
144
+ go func () {
145
+ s .Logger ().WithField ("protocol" , "http" ).Infof ("Serving http on %s" , s .httpListener .Addr ().String ())
146
+ if serveErr := s .http .Serve (s .httpListener ); serveErr != nil {
147
+ if s .isClosing () {
148
+ return
149
+ }
150
+
151
+ errors <- serveErr
152
+ }
153
+ }()
154
+ }
104
155
105
156
signals := make (chan os.Signal , 1 )
106
157
signal .Notify (signals , syscall .SIGINT , syscall .SIGTERM )
@@ -157,6 +208,15 @@ func (s *Server) GRPCAddress() string {
157
208
return fmt .Sprintf ("%s:%d" , s .cfg .hostname , addr .Port )
158
209
}
159
210
211
+ func (s * Server ) DebugAddress () string {
212
+ if s .debugListener == nil {
213
+ return ""
214
+ }
215
+ protocol := "http"
216
+ addr := s .debugListener .Addr ().(* net.TCPAddr )
217
+ return fmt .Sprintf ("%s://%s:%d" , protocol , s .cfg .hostname , addr .Port )
218
+ }
219
+
160
220
func (s * Server ) HTTPMux () * http.ServeMux {
161
221
return s .httpMux
162
222
}
@@ -178,17 +238,29 @@ func (s *Server) close(ctx context.Context) error {
178
238
s .Logger ().Info ("Received graceful shutdown request." )
179
239
close (s .listening )
180
240
181
- s .grpc .GracefulStop ()
182
- // s.grpc.GracefulStop() also closes the underlying net.Listener, we just release the reference.
183
- s .grpcListener = nil
184
- s .Logger ().Info ("GRPC server terminated." )
241
+ if s .isGRPCEnabled () {
242
+ s .grpc .GracefulStop ()
243
+ // s.grpc.GracefulStop() also closes the underlying net.Listener, we just release the reference.
244
+ s .grpcListener = nil
245
+ s .Logger ().Info ("GRPC server terminated." )
246
+ }
185
247
186
- if err := s .http .Shutdown (ctx ); err != nil {
187
- return fmt .Errorf ("failed to close http server: %w" , err )
248
+ if s .isHTTPEnabled () {
249
+ if err := s .http .Shutdown (ctx ); err != nil {
250
+ return fmt .Errorf ("failed to close http server: %w" , err )
251
+ }
252
+ // s.http.Shutdown() also closes the underlying net.Listener, we just release the reference.
253
+ s .httpListener = nil
254
+ s .Logger ().Info ("HTTP server terminated." )
255
+ }
256
+
257
+ // Always terminate debug server last, we want to keep it running for as long as possible
258
+ if err := s .debug .Shutdown (ctx ); err != nil {
259
+ return fmt .Errorf ("failed to close debug server: %w" , err )
188
260
}
189
261
// s.http.Shutdown() also closes the underlying net.Listener, we just release the reference.
190
- s .httpListener = nil
191
- s .Logger ().Info ("HTTP server terminated." )
262
+ s .debugListener = nil
263
+ s .Logger ().Info ("Debug server terminated." )
192
264
193
265
return nil
194
266
}
@@ -204,7 +276,7 @@ func (s *Server) isClosing() bool {
204
276
}
205
277
206
278
func (s * Server ) initializeHTTP () error {
207
- s .httpMux = s . newHTTPMux ()
279
+ s .httpMux = http . NewServeMux ()
208
280
s .http = & http.Server {
209
281
Addr : fmt .Sprintf (":%d" , s .cfg .httpPort ),
210
282
Handler : s .httpMux ,
@@ -213,13 +285,16 @@ func (s *Server) initializeHTTP() error {
213
285
return nil
214
286
}
215
287
216
- func (s * Server ) newHTTPMux () * http.ServeMux {
288
+ func (s * Server ) initializeDebug () error {
289
+ logger := s .Logger ().WithField ("protocol" , "debug" )
290
+
217
291
mux := http .NewServeMux ()
292
+
218
293
mux .HandleFunc ("/ready" , s .cfg .healthHandler .ReadyEndpoint )
219
- s . Logger (). WithField ( "protocol" , "http" ) .Debug ("Serving readiness handler on /ready" )
294
+ logger .Debug ("Serving readiness handler on /ready" )
220
295
221
296
mux .HandleFunc ("/live" , s .cfg .healthHandler .LiveEndpoint )
222
- s . Logger (). WithField ( "protocol" , "http" ) .Debug ("Serving liveliness handler on /live" )
297
+ logger .Debug ("Serving liveliness handler on /live" )
223
298
224
299
// Metrics endpoint
225
300
metricsHandler := promhttp .Handler ()
@@ -229,12 +304,17 @@ func (s *Server) newHTTPMux() *http.ServeMux {
229
304
)
230
305
}
231
306
mux .Handle ("/metrics" , metricsHandler )
232
- s . Logger (). WithField ( "protocol" , "http" ) .Debug ("Serving metrics on /metrics" )
307
+ logger .Debug ("Serving metrics on /metrics" )
233
308
234
309
mux .Handle (pprof .Path , pprof .Handler ())
235
- s .Logger ().WithField ("protocol" , "http" ).Debug ("Serving profiler on /debug/pprof" )
310
+ logger .Debug ("Serving profiler on /debug/pprof" )
311
+
312
+ s .debug = & http.Server {
313
+ Addr : fmt .Sprintf (":%d" , s .cfg .debugPort ),
314
+ Handler : mux ,
315
+ }
236
316
237
- return mux
317
+ return nil
238
318
}
239
319
240
320
func (s * Server ) initializeGRPC () error {
@@ -251,3 +331,11 @@ func (s *Server) initializeGRPC() error {
251
331
252
332
return nil
253
333
}
334
+
335
+ func (s * Server ) isGRPCEnabled () bool {
336
+ return s .cfg .grpcPort >= 0
337
+ }
338
+
339
+ func (s * Server ) isHTTPEnabled () bool {
340
+ return s .cfg .httpPort >= 0
341
+ }
0 commit comments