traefik/pkg/api/handler.go
mpl 429b1d8574 API: new contract
Co-authored-by: Ludovic Fernandez <ldez@users.noreply.github.com>
2019-06-19 18:34:04 +02:00

468 lines
13 KiB
Go

package api
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"github.com/containous/mux"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/types"
"github.com/containous/traefik/pkg/version"
assetfs "github.com/elazarl/go-bindata-assetfs"
)
const (
defaultPerPage = 100
defaultPage = 1
)
const nextPageHeader = "X-Next-Page"
type serviceInfoRepresentation struct {
*config.ServiceInfo
ServerStatus map[string]string `json:"serverStatus,omitempty"`
}
// RunTimeRepresentation is the configuration information exposed by the API handler.
type RunTimeRepresentation struct {
Routers map[string]*config.RouterInfo `json:"routers,omitempty"`
Middlewares map[string]*config.MiddlewareInfo `json:"middlewares,omitempty"`
Services map[string]*serviceInfoRepresentation `json:"services,omitempty"`
TCPRouters map[string]*config.TCPRouterInfo `json:"tcpRouters,omitempty"`
TCPServices map[string]*config.TCPServiceInfo `json:"tcpServices,omitempty"`
}
type routerRepresentation struct {
*config.RouterInfo
Name string `json:"name,omitempty"`
Provider string `json:"provider,omitempty"`
}
type serviceRepresentation struct {
*config.ServiceInfo
ServerStatus map[string]string `json:"serverStatus,omitempty"`
Name string `json:"name,omitempty"`
Provider string `json:"provider,omitempty"`
}
type middlewareRepresentation struct {
*config.MiddlewareInfo
Name string `json:"name,omitempty"`
Provider string `json:"provider,omitempty"`
}
type tcpRouterRepresentation struct {
*config.TCPRouterInfo
Name string `json:"name,omitempty"`
Provider string `json:"provider,omitempty"`
}
type tcpServiceRepresentation struct {
*config.TCPServiceInfo
Name string `json:"name,omitempty"`
Provider string `json:"provider,omitempty"`
}
type pageInfo struct {
startIndex int
endIndex int
nextPage int
}
// Handler serves the configuration and status of Traefik on API endpoints.
type Handler struct {
dashboard bool
debug bool
// runtimeConfiguration is the data set used to create all the data representations exposed by the API.
runtimeConfiguration *config.RuntimeConfiguration
statistics *types.Statistics
// stats *thoasstats.Stats // FIXME stats
// StatsRecorder *middlewares.StatsRecorder // FIXME stats
dashboardAssets *assetfs.AssetFS
}
// New returns a Handler defined by staticConfig, and if provided, by runtimeConfig.
// It finishes populating the information provided in the runtimeConfig.
func New(staticConfig static.Configuration, runtimeConfig *config.RuntimeConfiguration) *Handler {
rConfig := runtimeConfig
if rConfig == nil {
rConfig = &config.RuntimeConfiguration{}
}
return &Handler{
dashboard: staticConfig.API.Dashboard,
statistics: staticConfig.API.Statistics,
dashboardAssets: staticConfig.API.DashboardAssets,
runtimeConfiguration: rConfig,
debug: staticConfig.API.Debug,
}
}
// Append add api routes on a router
func (h Handler) Append(router *mux.Router) {
if h.debug {
DebugHandler{}.Append(router)
}
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
router.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
router.Methods(http.MethodGet).Path("/api/http/middlewares").HandlerFunc(h.getMiddlewares)
router.Methods(http.MethodGet).Path("/api/http/middlewares/{middlewareID}").HandlerFunc(h.getMiddleware)
router.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
router.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
router.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
router.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
// FIXME stats
// health route
// router.Methods(http.MethodGet).Path("/health").HandlerFunc(p.getHealthHandler)
version.Handler{}.Append(router)
if h.dashboard {
DashboardHandler{Assets: h.dashboardAssets}.Append(router)
}
}
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
for name, rt := range h.runtimeConfiguration.Routers {
results = append(results, routerRepresentation{
RouterInfo: rt,
Name: name,
Provider: getProviderName(name),
})
}
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
pageInfo, err := pagination(request, len(results))
if err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
routerID := mux.Vars(request)["routerID"]
router, ok := h.runtimeConfiguration.Routers[routerID]
if !ok {
http.NotFound(rw, request)
return
}
result := routerRepresentation{
RouterInfo: router,
Name: routerID,
Provider: getProviderName(routerID),
}
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
for name, si := range h.runtimeConfiguration.Services {
results = append(results, serviceRepresentation{
ServiceInfo: si,
Name: name,
Provider: getProviderName(name),
ServerStatus: si.GetAllStatus(),
})
}
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
pageInfo, err := pagination(request, len(results))
if err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
serviceID := mux.Vars(request)["serviceID"]
service, ok := h.runtimeConfiguration.Services[serviceID]
if !ok {
http.NotFound(rw, request)
return
}
result := serviceRepresentation{
ServiceInfo: service,
Name: serviceID,
Provider: getProviderName(serviceID),
ServerStatus: service.GetAllStatus(),
}
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
for name, mi := range h.runtimeConfiguration.Middlewares {
results = append(results, middlewareRepresentation{
MiddlewareInfo: mi,
Name: name,
Provider: getProviderName(name),
})
}
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
pageInfo, err := pagination(request, len(results))
if err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
middlewareID := mux.Vars(request)["middlewareID"]
middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID]
if !ok {
http.NotFound(rw, request)
return
}
result := middlewareRepresentation{
MiddlewareInfo: middleware,
Name: middlewareID,
Provider: getProviderName(middlewareID),
}
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
for name, rt := range h.runtimeConfiguration.TCPRouters {
results = append(results, tcpRouterRepresentation{
TCPRouterInfo: rt,
Name: name,
Provider: getProviderName(name),
})
}
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
pageInfo, err := pagination(request, len(results))
if err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
routerID := mux.Vars(request)["routerID"]
router, ok := h.runtimeConfiguration.TCPRouters[routerID]
if !ok {
http.NotFound(rw, request)
return
}
result := tcpRouterRepresentation{
TCPRouterInfo: router,
Name: routerID,
Provider: getProviderName(routerID),
}
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
for name, si := range h.runtimeConfiguration.TCPServices {
results = append(results, tcpServiceRepresentation{
TCPServiceInfo: si,
Name: name,
Provider: getProviderName(name),
})
}
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
pageInfo, err := pagination(request, len(results))
if err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
serviceID := mux.Vars(request)["serviceID"]
service, ok := h.runtimeConfiguration.TCPServices[serviceID]
if !ok {
http.NotFound(rw, request)
return
}
result := tcpServiceRepresentation{
TCPServiceInfo: service,
Name: serviceID,
Provider: getProviderName(serviceID),
}
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {
siRepr := make(map[string]*serviceInfoRepresentation, len(h.runtimeConfiguration.Services))
for k, v := range h.runtimeConfiguration.Services {
siRepr[k] = &serviceInfoRepresentation{
ServiceInfo: v,
ServerStatus: v.GetAllStatus(),
}
}
result := RunTimeRepresentation{
Routers: h.runtimeConfiguration.Routers,
Middlewares: h.runtimeConfiguration.Middlewares,
Services: siRepr,
TCPRouters: h.runtimeConfiguration.TCPRouters,
TCPServices: h.runtimeConfiguration.TCPServices,
}
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func pagination(request *http.Request, max int) (pageInfo, error) {
perPage, err := getIntParam(request, "per_page", defaultPerPage)
if err != nil {
return pageInfo{}, err
}
page, err := getIntParam(request, "page", defaultPage)
if err != nil {
return pageInfo{}, err
}
startIndex := (page - 1) * perPage
if startIndex != 0 && startIndex >= max {
return pageInfo{}, fmt.Errorf("invalid request: page: %d, per_page: %d", page, perPage)
}
endIndex := startIndex + perPage
if endIndex >= max {
endIndex = max
}
nextPage := 1
if page*perPage < max {
nextPage = page + 1
}
return pageInfo{startIndex: startIndex, endIndex: endIndex, nextPage: nextPage}, nil
}
func getIntParam(request *http.Request, key string, defaultValue int) (int, error) {
raw := request.URL.Query().Get(key)
if raw == "" {
return defaultValue, nil
}
value, err := strconv.Atoi(raw)
if err != nil || value <= 0 {
return 0, fmt.Errorf("invalid request: %s: %d", key, value)
}
return value, nil
}
func getProviderName(id string) string {
return strings.SplitN(id, ".", 2)[0]
}