@@ -46,7 +46,7 @@ const APIServerMetricsSubSystemName = "api_server_rest"
46
46
type APIServer interface {
47
47
Serve (ctx context.Context ) error
48
48
Started () <- chan struct {}
49
- MuxRouter (ctx context.Context ) * mux.Router
49
+ MuxRouter (ctx context.Context ) ( * mux.Router , error )
50
50
APIPublicURL () string // valid to call after server is successfully started
51
51
}
52
52
@@ -67,6 +67,7 @@ type apiServer[T any] struct {
67
67
livenessPath string
68
68
monitoringPublicURL string
69
69
mux * mux.Router
70
+ oah * OpenAPIHandlerFactory
70
71
71
72
APIServerOptions [T ]
72
73
}
@@ -97,6 +98,14 @@ type APIServerRouteExt[T any] struct {
97
98
// NewAPIServer makes a new server, with the specified configuration, and
98
99
// the supplied wrapper function - which will inject
99
100
func NewAPIServer [T any ](ctx context.Context , options APIServerOptions [T ]) APIServer {
101
+ if options .APIConfig == nil {
102
+ panic ("APIConfig is required" )
103
+ }
104
+
105
+ if options .MonitoringConfig == nil {
106
+ panic ("MonitoringConfig is required" )
107
+ }
108
+
100
109
as := & apiServer [T ]{
101
110
defaultFilterLimit : options .APIConfig .GetUint64 (ConfAPIDefaultFilterLimit ),
102
111
maxFilterLimit : options .APIConfig .GetUint64 (ConfAPIMaxFilterLimit ),
@@ -119,27 +128,34 @@ func NewAPIServer[T any](ctx context.Context, options APIServerOptions[T]) APISe
119
128
as .FavIcon32 = ffLogo16
120
129
}
121
130
122
- metricsSubsystemName := APIServerMetricsSubSystemName
123
- if options .MetricsSubsystemName != "" {
124
- metricsSubsystemName = options .MetricsSubsystemName
125
- }
126
-
127
131
_ = as .MetricsRegistry .NewHTTPMetricsInstrumentationsForSubsystem (
128
132
ctx ,
129
- metricsSubsystemName ,
133
+ as . metricsSubsystemName () ,
130
134
true ,
131
135
prometheus .DefBuckets ,
132
136
map [string ]string {},
133
137
)
134
138
return as
135
139
}
136
140
141
+ func (as * apiServer [T ]) metricsSubsystemName () string {
142
+ metricsSubsystemName := APIServerMetricsSubSystemName
143
+ if as .MetricsSubsystemName != "" {
144
+ metricsSubsystemName = as .MetricsSubsystemName
145
+ }
146
+ return metricsSubsystemName
147
+ }
148
+
137
149
// Can be called before Serve, but MUST use the background context if so
138
- func (as * apiServer [T ]) MuxRouter (ctx context.Context ) * mux.Router {
150
+ func (as * apiServer [T ]) MuxRouter (ctx context.Context ) ( * mux.Router , error ) {
139
151
if as .mux == nil {
140
- as .mux = as .createMuxRouter (ctx )
152
+ var err error
153
+ if as .mux , err = as .createMuxRouter (ctx ); err != nil {
154
+ return nil , err
155
+ }
156
+
141
157
}
142
- return as .mux
158
+ return as .mux , nil
143
159
}
144
160
145
161
// Serve is the main entry point for the API Server
@@ -155,17 +171,26 @@ func (as *apiServer[T]) Serve(ctx context.Context) (err error) {
155
171
httpErrChan := make (chan error )
156
172
monitoringErrChan := make (chan error )
157
173
158
- apiHTTPServer , err := httpserver .NewHTTPServer (ctx , "api" , as .MuxRouter (ctx ), httpErrChan , as .APIConfig , as .CORSConfig , & httpserver.ServerOptions {
174
+ apiMux , err := as .MuxRouter (ctx )
175
+ if err != nil {
176
+ return err
177
+ }
178
+ apiHTTPServer , err := httpserver .NewHTTPServer (ctx , "api" , apiMux , httpErrChan , as .APIConfig , as .CORSConfig , & httpserver.ServerOptions {
159
179
MaximumRequestTimeout : as .requestMaxTimeout ,
160
180
})
161
181
if err != nil {
162
182
return err
163
183
}
164
184
as .apiPublicURL = buildPublicURL (as .APIConfig , apiHTTPServer .Addr ())
185
+ as .oah .StaticPublicURL = as .apiPublicURL
165
186
go apiHTTPServer .ServeHTTP (ctx )
166
187
167
188
if as .monitoringEnabled {
168
- monitoringHTTPServer , err := httpserver .NewHTTPServer (ctx , "monitoring" , as .createMonitoringMuxRouter (ctx ), monitoringErrChan , as .MonitoringConfig , as .CORSConfig , & httpserver.ServerOptions {
189
+ monitoringMux , err := as .createMonitoringMuxRouter (ctx )
190
+ if err != nil {
191
+ return err
192
+ }
193
+ monitoringHTTPServer , err := httpserver .NewHTTPServer (ctx , "monitoring" , monitoringMux , monitoringErrChan , as .MonitoringConfig , as .CORSConfig , & httpserver.ServerOptions {
169
194
MaximumRequestTimeout : as .requestMaxTimeout ,
170
195
})
171
196
if err != nil {
@@ -249,12 +274,12 @@ func (as *apiServer[T]) handlerFactory() *HandlerFactory {
249
274
}
250
275
}
251
276
252
- func (as * apiServer [T ]) createMuxRouter (ctx context.Context ) * mux.Router {
277
+ func (as * apiServer [T ]) createMuxRouter (ctx context.Context ) ( * mux.Router , error ) {
253
278
r := mux .NewRouter ().UseEncodedPath ()
254
279
hf := as .handlerFactory ()
255
280
256
281
if as .monitoringEnabled {
257
- h , _ := as .MetricsRegistry .GetHTTPMetricsInstrumentationsMiddlewareForSubsystem (ctx , APIServerMetricsSubSystemName )
282
+ h , _ := as .MetricsRegistry .GetHTTPMetricsInstrumentationsMiddlewareForSubsystem (ctx , as . metricsSubsystemName () )
258
283
r .Use (h )
259
284
}
260
285
@@ -273,30 +298,33 @@ func (as *apiServer[T]) createMuxRouter(ctx context.Context) *mux.Router {
273
298
}
274
299
}
275
300
if ce .JSONHandler != nil || ce .UploadHandler != nil || ce .StreamHandler != nil {
301
+ if strings .HasPrefix (route .Path , "/" ) {
302
+ return nil , fmt .Errorf ("route path '%s' must not start with '/'" , route .Path )
303
+ }
276
304
r .HandleFunc (fmt .Sprintf ("/api/v1/%s" , route .Path ), as .routeHandler (hf , route )).
277
305
Methods (route .Method )
278
306
}
279
307
}
280
308
281
- oah : = & OpenAPIHandlerFactory {
309
+ as . oah = & OpenAPIHandlerFactory {
282
310
BaseSwaggerGenOptions : SwaggerGenOptions {
283
311
Title : as .Description ,
284
312
Version : "1.0" ,
285
313
PanicOnMissingDescription : as .PanicOnMissingDescription ,
286
314
DefaultRequestTimeout : as .requestTimeout ,
287
315
SupportFieldRedaction : as .SupportFieldRedaction ,
288
316
},
289
- StaticPublicURL : as .apiPublicURL ,
317
+ StaticPublicURL : as .apiPublicURL , // this is most likely not yet set, we'll ensure its set later on
290
318
}
291
- r .HandleFunc (`/api/swagger.yaml` , hf .APIWrapper (oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatYAML , as .Routes )))
292
- r .HandleFunc (`/api/swagger.json` , hf .APIWrapper (oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatJSON , as .Routes )))
293
- r .HandleFunc (`/api/openapi.yaml` , hf .APIWrapper (oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatYAML , as .Routes )))
294
- r .HandleFunc (`/api/openapi.json` , hf .APIWrapper (oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatJSON , as .Routes )))
295
- r .HandleFunc (`/api` , hf .APIWrapper (oah .SwaggerUIHandler (`/api/openapi.yaml` )))
319
+ r .HandleFunc (`/api/swagger.yaml` , hf .APIWrapper (as . oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatYAML , as .Routes )))
320
+ r .HandleFunc (`/api/swagger.json` , hf .APIWrapper (as . oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatJSON , as .Routes )))
321
+ r .HandleFunc (`/api/openapi.yaml` , hf .APIWrapper (as . oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatYAML , as .Routes )))
322
+ r .HandleFunc (`/api/openapi.json` , hf .APIWrapper (as . oah .OpenAPIHandler (`/api/v1` , OpenAPIFormatJSON , as .Routes )))
323
+ r .HandleFunc (`/api` , hf .APIWrapper (as . oah .SwaggerUIHandler (`/api/openapi.yaml` )))
296
324
r .HandleFunc (`/favicon{any:.*}.png` , favIconsHandler (as .FavIcon16 , as .FavIcon32 ))
297
325
298
326
r .NotFoundHandler = hf .APIWrapper (as .notFoundHandler )
299
- return r
327
+ return r , nil
300
328
}
301
329
302
330
func (as * apiServer [T ]) notFoundHandler (res http.ResponseWriter , req * http.Request ) (status int , err error ) {
@@ -309,7 +337,7 @@ func (as *apiServer[T]) emptyJSONHandler(res http.ResponseWriter, _ *http.Reques
309
337
return 200 , nil
310
338
}
311
339
312
- func (as * apiServer [T ]) createMonitoringMuxRouter (ctx context.Context ) * mux.Router {
340
+ func (as * apiServer [T ]) createMonitoringMuxRouter (ctx context.Context ) ( * mux.Router , error ) {
313
341
r := mux .NewRouter ().UseEncodedPath ()
314
342
hf := as .handlerFactory () // TODO separate factory for monitoring ??
315
343
@@ -322,12 +350,12 @@ func (as *apiServer[T]) createMonitoringMuxRouter(ctx context.Context) *mux.Rout
322
350
323
351
for _ , route := range as .MonitoringRoutes {
324
352
path := route .Path
325
- if ! strings .HasPrefix (route .Path , "/" ) {
326
- path = fmt .Sprintf ( "/%s " , route .Path )
353
+ if strings .HasPrefix (route .Path , "/" ) {
354
+ return nil , fmt .Errorf ( "route path '%s' must not start with '/' " , route .Path )
327
355
}
328
- r .HandleFunc (path , as .routeHandler (hf , route )).Methods (route .Method )
356
+ r .HandleFunc ("/" + path , as .routeHandler (hf , route )).Methods (route .Method )
329
357
}
330
358
331
359
r .NotFoundHandler = hf .APIWrapper (as .notFoundHandler )
332
- return r
360
+ return r , nil
333
361
}
0 commit comments