diff --git a/api/api_bridge.go b/api/api_bridge.go index 905c201..c1385ca 100644 --- a/api/api_bridge.go +++ b/api/api_bridge.go @@ -161,7 +161,7 @@ func callAPI(ebr *EndpointBridgeRequest) (record.Record, error) { } response := &EndpointBridgeResponse{ - MimeType: w.Result().Header.Get("Content-Type"), + MimeType: w.Header().Get("Content-Type"), Body: w.Body.String(), } response.SetKey(apiDatabaseName + ":" + ebr.Path) diff --git a/api/endpoints.go b/api/endpoints.go index c02b9d0..7934dcd 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -15,16 +15,18 @@ import ( "github.com/safing/portbase/database/record" "github.com/safing/portbase/log" + "github.com/safing/portbase/modules" ) // Endpoint describes an API Endpoint. // Path and at least one permission are required. // As is exactly one function. type Endpoint struct { - Path string - MimeType string - Read Permission - Write Permission + Path string + MimeType string + Read Permission + Write Permission + BelongsTo *modules.Module // ActionFunc is for simple actions with a return message for the user. ActionFunc ActionFunc `json:"-"` @@ -268,6 +270,12 @@ func (e *Endpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + // Wait for the owning module to be ready. + if !moduleIsReady(e.BelongsTo) { + http.Error(w, "The API endpoint not ready yet. Please try again later.", http.StatusServiceUnavailable) + return + } + switch r.Method { case http.MethodHead: w.WriteHeader(http.StatusOK) diff --git a/api/modules.go b/api/modules.go new file mode 100644 index 0000000..94b7594 --- /dev/null +++ b/api/modules.go @@ -0,0 +1,48 @@ +package api + +import ( + "time" + + "github.com/safing/portbase/modules" +) + +type ModuleHandler interface { + BelongsTo() *modules.Module +} + +const ( + moduleCheckMaxWait = 10 * time.Second + moduleCheckTickDuration = 500 * time.Millisecond +) + +// moduleIsReady checks if the given module is online and http requests can be +// sent its way. If the module is not online already, it will wait for a short +// duration for it to come online. +func moduleIsReady(m *modules.Module) (ok bool) { + // Check if we are given a module. + if m == nil { + // If no module is given, we assume that the handler has not been linked to + // a module, and we can safely continue with the request. + return true + } + + // Check if the module is online. + if m.Online() { + return true + } + + // Check if the module will come online. + if m.OnlineSoon() { + var i time.Duration + for i = 0; i < moduleCheckMaxWait; i += moduleCheckTickDuration { + // Wait a little. + time.Sleep(moduleCheckTickDuration) + // Check if module is now online. + if m.Online() { + return true + } + } + } + + return false +} diff --git a/api/router.go b/api/router.go index 5bde7b0..6fbcacd 100644 --- a/api/router.go +++ b/api/router.go @@ -86,12 +86,12 @@ func (mh *mainHandler) handle(w http.ResponseWriter, r *http.Request) error { r = r.WithContext(ctx) lrw := NewLoggingResponseWriter(w, r) - tracer.Tracef("api request: %s ___ %s", r.RemoteAddr, r.RequestURI) + tracer.Tracef("api request: %s ___ %s %s", r.RemoteAddr, lrw.Request.Method, r.RequestURI) defer func() { // Log request status. if lrw.Status != 0 { // If lrw.Status is 0, the request may have been hijacked. - tracer.Debugf("api request: %s %d %s", lrw.Request.RemoteAddr, lrw.Status, lrw.Request.RequestURI) + tracer.Debugf("api request: %s %d %s %s", lrw.Request.RemoteAddr, lrw.Status, lrw.Request.Method, lrw.Request.RequestURI) } tracer.Submit() }() @@ -130,6 +130,14 @@ func (mh *mainHandler) handle(w http.ResponseWriter, r *http.Request) error { return nil } + // Wait for the owning module to be ready. + if moduleHandler, ok := handler.(ModuleHandler); ok { + if !moduleIsReady(moduleHandler.BelongsTo()) { + http.Error(lrw, "The API endpoint not ready yet. Please try again later.", http.StatusServiceUnavailable) + return nil + } + } + // Add security headers. if !devMode() { w.Header().Set( diff --git a/metrics/api.go b/metrics/api.go index e206416..bbf4335 100644 --- a/metrics/api.go +++ b/metrics/api.go @@ -19,9 +19,10 @@ func registerAPI() error { api.RegisterHandler("/metrics", &metricsAPI{}) return api.RegisterEndpoint(api.Endpoint{ - Path: "metrics/list", - Read: api.PermitAnyone, - MimeType: api.MimeTypeJSON, + Path: "metrics/list", + Read: api.PermitAnyone, + MimeType: api.MimeTypeJSON, + BelongsTo: module, DataFunc: func(*api.Request) ([]byte, error) { registryLock.RLock() defer registryLock.RUnlock()