Wait for modules to be ready before calling http handlers

This commit is contained in:
Daniel 2021-05-17 14:42:02 +02:00
parent 734c49a88a
commit 2b165c149a
5 changed files with 75 additions and 10 deletions

View file

@ -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)

View file

@ -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)

48
api/modules.go Normal file
View file

@ -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
}

View file

@ -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(

View file

@ -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()