traefik/pkg/server/service/internalhandler.go
2020-07-07 14:42:03 +02:00

170 lines
4.2 KiB
Go

package service
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log"
)
type serviceManager interface {
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
LaunchHealthCheck()
}
// InternalHandlers is the internal HTTP handlers builder.
type InternalHandlers struct {
api http.Handler
dashboard http.Handler
rest http.Handler
prometheus http.Handler
ping http.Handler
serviceManager
}
// NewInternalHandlers creates a new InternalHandlers.
func NewInternalHandlers(api func(configuration *runtime.Configuration) http.Handler, configuration *runtime.Configuration, rest, metricsHandler, pingHandler, dashboard http.Handler, next serviceManager) *InternalHandlers {
var apiHandler http.Handler
if api != nil {
apiHandler = api(configuration)
}
return &InternalHandlers{
api: apiHandler,
dashboard: dashboard,
rest: rest,
prometheus: metricsHandler,
ping: pingHandler,
serviceManager: next,
}
}
type responseModifier struct {
r *http.Request
w http.ResponseWriter
headersSent bool // whether headers have already been sent
code int // status code, must default to 200
modifier func(*http.Response) error // can be nil
modified bool // whether modifier has already been called for the current request
modifierErr error // returned by modifier call
}
// modifier can be nil.
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
return &responseModifier{
r: r,
w: w,
modifier: modifier,
code: http.StatusOK,
}
}
func (w *responseModifier) WriteHeader(code int) {
if w.headersSent {
return
}
defer func() {
w.code = code
w.headersSent = true
}()
if w.modifier == nil || w.modified {
w.w.WriteHeader(code)
return
}
resp := http.Response{
Header: w.w.Header(),
Request: w.r,
}
if err := w.modifier(&resp); err != nil {
w.modifierErr = err
// we are propagating when we are called in Write, but we're logging anyway,
// because we could be called from another place which does not take care of
// checking w.modifierErr.
log.Errorf("Error when applying response modifier: %v", err)
w.w.WriteHeader(http.StatusInternalServerError)
return
}
w.modified = true
w.w.WriteHeader(code)
}
func (w *responseModifier) Header() http.Header {
return w.w.Header()
}
func (w *responseModifier) Write(b []byte) (int, error) {
w.WriteHeader(w.code)
if w.modifierErr != nil {
return 0, w.modifierErr
}
return w.w.Write(b)
}
// BuildHTTP builds an HTTP handler.
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string, respModifier func(*http.Response) error) (http.Handler, error) {
if !strings.HasSuffix(serviceName, "@internal") {
return m.serviceManager.BuildHTTP(rootCtx, serviceName, respModifier)
}
internalHandler, err := m.get(serviceName)
if err != nil {
return nil, err
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
internalHandler.ServeHTTP(newResponseModifier(w, r, respModifier), r)
}), nil
}
func (m *InternalHandlers) get(serviceName string) (http.Handler, error) {
switch serviceName {
case "noop@internal":
return http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(http.StatusTeapot)
}), nil
case "api@internal":
if m.api == nil {
return nil, errors.New("api is not enabled")
}
return m.api, nil
case "dashboard@internal":
if m.dashboard == nil {
return nil, errors.New("dashboard is not enabled")
}
return m.dashboard, nil
case "rest@internal":
if m.rest == nil {
return nil, errors.New("rest is not enabled")
}
return m.rest, nil
case "ping@internal":
if m.ping == nil {
return nil, errors.New("ping is not enabled")
}
return m.ping, nil
case "prometheus@internal":
if m.prometheus == nil {
return nil, errors.New("prometheus is not enabled")
}
return m.prometheus, nil
default:
return nil, fmt.Errorf("unknown internal service %s", serviceName)
}
}