1
0
mirror of https://github.com/rancher/norman.git synced 2025-04-28 11:24:47 +00:00
norman/api/server.go
Craig Jellick b694ecb0eb Log panics as error
The defaul http server logs panics to the server's log handler.
That goes to debug level and thus panics are not seen when running
rancher at a normal log level.

This fixes the problem by catching the panic at the top level norman
http handler and logging them as errors there.
2019-02-11 11:59:18 -07:00

287 lines
7.7 KiB
Go

package api
import (
"net/http"
"runtime/debug"
"sync"
"github.com/rancher/norman/api/access"
"github.com/rancher/norman/api/builtin"
"github.com/rancher/norman/api/handler"
"github.com/rancher/norman/api/writer"
"github.com/rancher/norman/authorization"
"github.com/rancher/norman/httperror"
ehandler "github.com/rancher/norman/httperror/handler"
"github.com/rancher/norman/parse"
"github.com/rancher/norman/store/wrapper"
"github.com/rancher/norman/types"
"github.com/sirupsen/logrus"
)
type StoreWrapper func(types.Store) types.Store
type Parser func(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error)
type Server struct {
initBuiltin sync.Once
IgnoreBuiltin bool
Parser Parser
Resolver parse.ResolverFunc
SubContextAttributeProvider types.SubContextAttributeProvider
ResponseWriters map[string]ResponseWriter
Schemas *types.Schemas
QueryFilter types.QueryFilter
StoreWrapper StoreWrapper
URLParser parse.URLParser
Defaults Defaults
AccessControl types.AccessControl
}
type Defaults struct {
ActionHandler types.ActionHandler
ListHandler types.RequestHandler
LinkHandler types.RequestHandler
CreateHandler types.RequestHandler
DeleteHandler types.RequestHandler
UpdateHandler types.RequestHandler
Store types.Store
ErrorHandler types.ErrorHandler
}
func NewAPIServer() *Server {
s := &Server{
Schemas: types.NewSchemas(),
ResponseWriters: map[string]ResponseWriter{
"json": &writer.EncodingResponseWriter{
ContentType: "application/json",
Encoder: types.JSONEncoder,
},
"html": &writer.HTMLResponseWriter{
EncodingResponseWriter: writer.EncodingResponseWriter{
Encoder: types.JSONEncoder,
ContentType: "application/json",
},
},
"yaml": &writer.EncodingResponseWriter{
ContentType: "application/yaml",
Encoder: types.YAMLEncoder,
},
},
SubContextAttributeProvider: &parse.DefaultSubContextAttributeProvider{},
Resolver: parse.DefaultResolver,
AccessControl: &authorization.AllAccess{},
Defaults: Defaults{
CreateHandler: handler.CreateHandler,
DeleteHandler: handler.DeleteHandler,
UpdateHandler: handler.UpdateHandler,
ListHandler: handler.ListHandler,
LinkHandler: func(*types.APIContext, types.RequestHandler) error {
return httperror.NewAPIError(httperror.NotFound, "Link not found")
},
ErrorHandler: ehandler.ErrorHandler,
},
StoreWrapper: wrapper.Wrap,
URLParser: parse.DefaultURLParser,
QueryFilter: handler.QueryFilter,
}
s.Schemas.AddHook = s.setupDefaults
s.Parser = s.parser
return s
}
func (s *Server) parser(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
ctx, err := parse.Parse(rw, req, s.Schemas, s.URLParser, s.Resolver)
ctx.ResponseWriter = s.ResponseWriters[ctx.ResponseFormat]
if ctx.ResponseWriter == nil {
ctx.ResponseWriter = s.ResponseWriters["json"]
}
if ctx.QueryFilter == nil {
ctx.QueryFilter = s.QueryFilter
}
if ctx.SubContextAttributeProvider == nil {
ctx.SubContextAttributeProvider = s.SubContextAttributeProvider
}
ctx.AccessControl = s.AccessControl
return ctx, err
}
func (s *Server) AddSchemas(schemas *types.Schemas) error {
if schemas.Err() != nil {
return schemas.Err()
}
s.initBuiltin.Do(func() {
if s.IgnoreBuiltin {
return
}
for _, schema := range builtin.Schemas.Schemas() {
s.Schemas.AddSchema(*schema)
}
})
for _, schema := range schemas.Schemas() {
s.Schemas.AddSchema(*schema)
}
return s.Schemas.Err()
}
func (s *Server) setupDefaults(schema *types.Schema) {
if schema.ActionHandler == nil {
schema.ActionHandler = s.Defaults.ActionHandler
}
if schema.Store == nil {
schema.Store = s.Defaults.Store
}
if schema.ListHandler == nil {
schema.ListHandler = s.Defaults.ListHandler
}
if schema.LinkHandler == nil {
schema.LinkHandler = s.Defaults.LinkHandler
}
if schema.CreateHandler == nil {
schema.CreateHandler = s.Defaults.CreateHandler
}
if schema.UpdateHandler == nil {
schema.UpdateHandler = s.Defaults.UpdateHandler
}
if schema.DeleteHandler == nil {
schema.DeleteHandler = s.Defaults.DeleteHandler
}
if schema.ErrorHandler == nil {
schema.ErrorHandler = s.Defaults.ErrorHandler
}
if schema.Store != nil && s.StoreWrapper != nil {
schema.Store = s.StoreWrapper(schema.Store)
}
}
func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil && err != http.ErrAbortHandler {
logrus.Error("Panic serving api request: \n" + string(debug.Stack()))
rw.WriteHeader(http.StatusInternalServerError)
}
}()
if apiResponse, err := s.handle(rw, req); err != nil {
s.handleError(apiResponse, err)
}
}
func (s *Server) handle(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
apiRequest, err := s.Parser(rw, req)
if err != nil {
return apiRequest, err
}
if err := CheckCSRF(apiRequest); err != nil {
return apiRequest, err
}
action, err := ValidateAction(apiRequest)
if err != nil {
return apiRequest, err
}
if apiRequest.Schema == nil {
return apiRequest, nil
}
if action == nil && apiRequest.Type != "" {
var handler types.RequestHandler
var nextHandler types.RequestHandler
if apiRequest.Link == "" {
switch apiRequest.Method {
case http.MethodGet:
if apiRequest.ID == "" {
if err := apiRequest.AccessControl.CanList(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, err
}
} else {
if err := apiRequest.AccessControl.CanGet(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, err
}
}
handler = apiRequest.Schema.ListHandler
nextHandler = s.Defaults.ListHandler
case http.MethodPost:
if err := apiRequest.AccessControl.CanCreate(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, err
}
handler = apiRequest.Schema.CreateHandler
nextHandler = s.Defaults.CreateHandler
case http.MethodPut:
if err := apiRequest.AccessControl.CanUpdate(apiRequest, nil, apiRequest.Schema); err != nil {
return apiRequest, err
}
handler = apiRequest.Schema.UpdateHandler
nextHandler = s.Defaults.UpdateHandler
case http.MethodDelete:
if err := apiRequest.AccessControl.CanDelete(apiRequest, nil, apiRequest.Schema); err != nil {
return apiRequest, err
}
handler = apiRequest.Schema.DeleteHandler
nextHandler = s.Defaults.DeleteHandler
}
} else {
handler = apiRequest.Schema.ListHandler
nextHandler = s.Defaults.ListHandler
}
if handler == nil {
return apiRequest, httperror.NewAPIError(httperror.NotFound, "")
}
return apiRequest, handler(apiRequest, nextHandler)
} else if action != nil {
return apiRequest, handleAction(action, apiRequest)
}
return apiRequest, nil
}
func handleAction(action *types.Action, context *types.APIContext) error {
if context.ID != "" {
if err := access.ByID(context, context.Version, context.Type, context.ID, nil); err != nil {
return err
}
}
return context.Schema.ActionHandler(context.Action, action, context)
}
func (s *Server) handleError(apiRequest *types.APIContext, err error) {
if apiRequest.Schema == nil {
s.Defaults.ErrorHandler(apiRequest, err)
} else if apiRequest.Schema.ErrorHandler != nil {
apiRequest.Schema.ErrorHandler(apiRequest, err)
}
}
func (s *Server) CustomAPIUIResponseWriter(cssURL, jsURL, version writer.StringGetter) {
wi, ok := s.ResponseWriters["html"]
if !ok {
return
}
w, ok := wi.(*writer.HTMLResponseWriter)
if !ok {
return
}
w.CSSURL = cssURL
w.JSURL = jsURL
w.APIUIVersion = version
}