podman
635 строк · 19.6 Кб
1// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package middleware16
17import (18stdContext "context"19"fmt"20"net/http"21"strings"22"sync"23
24"github.com/go-openapi/analysis"25"github.com/go-openapi/errors"26"github.com/go-openapi/loads"27"github.com/go-openapi/spec"28"github.com/go-openapi/strfmt"29
30"github.com/go-openapi/runtime"31"github.com/go-openapi/runtime/logger"32"github.com/go-openapi/runtime/middleware/untyped"33"github.com/go-openapi/runtime/security"34)
35
36// Debug when true turns on verbose logging
37var Debug = logger.DebugEnabled()38var Logger logger.Logger = logger.StandardLogger{}39
40func debugLog(format string, args ...interface{}) {41if Debug {42Logger.Printf(format, args...)43}44}
45
46// A Builder can create middlewares
47type Builder func(http.Handler) http.Handler48
49// PassthroughBuilder returns the handler, aka the builder identity function
50func PassthroughBuilder(handler http.Handler) http.Handler { return handler }51
52// RequestBinder is an interface for types to implement
53// when they want to be able to bind from a request
54type RequestBinder interface {55BindRequest(*http.Request, *MatchedRoute) error56}
57
58// Responder is an interface for types to implement
59// when they want to be considered for writing HTTP responses
60type Responder interface {61WriteResponse(http.ResponseWriter, runtime.Producer)62}
63
64// ResponderFunc wraps a func as a Responder interface
65type ResponderFunc func(http.ResponseWriter, runtime.Producer)66
67// WriteResponse writes to the response
68func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Producer) {69fn(rw, pr)70}
71
72// Context is a type safe wrapper around an untyped request context
73// used throughout to store request context with the standard context attached
74// to the http.Request
75type Context struct {76spec *loads.Document77analyzer *analysis.Spec78api RoutableAPI
79router Router
80}
81
82type routableUntypedAPI struct {83api *untyped.API84hlock *sync.Mutex85handlers map[string]map[string]http.Handler86defaultConsumes string87defaultProduces string88}
89
90func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI {91var handlers map[string]map[string]http.Handler92if spec == nil || api == nil {93return nil94}95analyzer := analysis.New(spec.Spec())96for method, hls := range analyzer.Operations() {97um := strings.ToUpper(method)98for path, op := range hls {99schemes := analyzer.SecurityRequirementsFor(op)100
101if oh, ok := api.OperationHandlerFor(method, path); ok {102if handlers == nil {103handlers = make(map[string]map[string]http.Handler)104}105if b, ok := handlers[um]; !ok || b == nil {106handlers[um] = make(map[string]http.Handler)107}108
109var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {110// lookup route info in the context111route, rCtx, _ := context.RouteInfo(r)112if rCtx != nil {113r = rCtx114}115
116// bind and validate the request using reflection117var bound interface{}118var validation error119bound, r, validation = context.BindAndValidate(r, route)120if validation != nil {121context.Respond(w, r, route.Produces, route, validation)122return123}124
125// actually handle the request126result, err := oh.Handle(bound)127if err != nil {128// respond with failure129context.Respond(w, r, route.Produces, route, err)130return131}132
133// respond with success134context.Respond(w, r, route.Produces, route, result)135})136
137if len(schemes) > 0 {138handler = newSecureAPI(context, handler)139}140handlers[um][path] = handler141}142}143}144
145return &routableUntypedAPI{146api: api,147hlock: new(sync.Mutex),148handlers: handlers,149defaultProduces: api.DefaultProduces,150defaultConsumes: api.DefaultConsumes,151}152}
153
154func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {155r.hlock.Lock()156paths, ok := r.handlers[strings.ToUpper(method)]157if !ok {158r.hlock.Unlock()159return nil, false160}161handler, ok := paths[path]162r.hlock.Unlock()163return handler, ok164}
165func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {166return r.api.ServeError167}
168func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {169return r.api.ConsumersFor(mediaTypes)170}
171func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {172return r.api.ProducersFor(mediaTypes)173}
174func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {175return r.api.AuthenticatorsFor(schemes)176}
177func (r *routableUntypedAPI) Authorizer() runtime.Authorizer {178return r.api.Authorizer()179}
180func (r *routableUntypedAPI) Formats() strfmt.Registry {181return r.api.Formats()182}
183
184func (r *routableUntypedAPI) DefaultProduces() string {185return r.defaultProduces186}
187
188func (r *routableUntypedAPI) DefaultConsumes() string {189return r.defaultConsumes190}
191
192// NewRoutableContext creates a new context for a routable API
193func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {194var an *analysis.Spec195if spec != nil {196an = analysis.New(spec.Spec())197}198
199return NewRoutableContextWithAnalyzedSpec(spec, an, routableAPI, routes)200}
201
202// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes in input the analysed spec too
203func NewRoutableContextWithAnalyzedSpec(spec *loads.Document, an *analysis.Spec, routableAPI RoutableAPI, routes Router) *Context {204// Either there are no spec doc and analysis, or both of them.205if !((spec == nil && an == nil) || (spec != nil && an != nil)) {206panic(errors.New(http.StatusInternalServerError, "routable context requires either both spec doc and analysis, or none of them"))207}208
209ctx := &Context{spec: spec, api: routableAPI, analyzer: an, router: routes}210return ctx211}
212
213// NewContext creates a new context wrapper
214func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context {215var an *analysis.Spec216if spec != nil {217an = analysis.New(spec.Spec())218}219ctx := &Context{spec: spec, analyzer: an}220ctx.api = newRoutableUntypedAPI(spec, api, ctx)221ctx.router = routes222return ctx223}
224
225// Serve serves the specified spec with the specified api registrations as a http.Handler
226func Serve(spec *loads.Document, api *untyped.API) http.Handler {227return ServeWithBuilder(spec, api, PassthroughBuilder)228}
229
230// ServeWithBuilder serves the specified spec with the specified api registrations as a http.Handler that is decorated
231// by the Builder
232func ServeWithBuilder(spec *loads.Document, api *untyped.API, builder Builder) http.Handler {233context := NewContext(spec, api, nil)234return context.APIHandler(builder)235}
236
237type contextKey int8238
239const (240_ contextKey = iota241ctxContentType
242ctxResponseFormat
243ctxMatchedRoute
244ctxBoundParams
245ctxSecurityPrincipal
246ctxSecurityScopes
247)
248
249// MatchedRouteFrom request context value.
250func MatchedRouteFrom(req *http.Request) *MatchedRoute {251mr := req.Context().Value(ctxMatchedRoute)252if mr == nil {253return nil254}255if res, ok := mr.(*MatchedRoute); ok {256return res257}258return nil259}
260
261// SecurityPrincipalFrom request context value.
262func SecurityPrincipalFrom(req *http.Request) interface{} {263return req.Context().Value(ctxSecurityPrincipal)264}
265
266// SecurityScopesFrom request context value.
267func SecurityScopesFrom(req *http.Request) []string {268rs := req.Context().Value(ctxSecurityScopes)269if res, ok := rs.([]string); ok {270return res271}272return nil273}
274
275type contentTypeValue struct {276MediaType string277Charset string278}
279
280// BasePath returns the base path for this API
281func (c *Context) BasePath() string {282return c.spec.BasePath()283}
284
285// RequiredProduces returns the accepted content types for responses
286func (c *Context) RequiredProduces() []string {287return c.analyzer.RequiredProduces()288}
289
290// BindValidRequest binds a params object to a request but only when the request is valid
291// if the request is not valid an error will be returned
292func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {293var res []error294var requestContentType string295
296// check and validate content type, select consumer297if runtime.HasBody(request) {298ct, _, err := runtime.ContentType(request.Header)299if err != nil {300res = append(res, err)301} else {302if err := validateContentType(route.Consumes, ct); err != nil {303res = append(res, err)304}305if len(res) == 0 {306cons, ok := route.Consumers[ct]307if !ok {308res = append(res, errors.New(500, "no consumer registered for %s", ct))309} else {310route.Consumer = cons311requestContentType = ct312}313}314}315}316
317// check and validate the response format318if len(res) == 0 {319// if the route does not provide Produces and a default contentType could not be identified320// based on a body, typical for GET and DELETE requests, then default contentType to.321if len(route.Produces) == 0 && requestContentType == "" {322requestContentType = "*/*"323}324
325if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {326res = append(res, errors.InvalidResponseFormat(request.Header.Get(runtime.HeaderAccept), route.Produces))327}328}329
330// now bind the request with the provided binder331// it's assumed the binder will also validate the request and return an error if the332// request is invalid333if binder != nil && len(res) == 0 {334if err := binder.BindRequest(request, route); err != nil {335return err336}337}338
339if len(res) > 0 {340return errors.CompositeValidationError(res...)341}342return nil343}
344
345// ContentType gets the parsed value of a content type
346// Returns the media type, its charset and a shallow copy of the request
347// when its context doesn't contain the content type value, otherwise it returns
348// the same request
349// Returns the error that runtime.ContentType may retunrs.
350func (c *Context) ContentType(request *http.Request) (string, string, *http.Request, error) {351var rCtx = request.Context()352
353if v, ok := rCtx.Value(ctxContentType).(*contentTypeValue); ok {354return v.MediaType, v.Charset, request, nil355}356
357mt, cs, err := runtime.ContentType(request.Header)358if err != nil {359return "", "", nil, err360}361rCtx = stdContext.WithValue(rCtx, ctxContentType, &contentTypeValue{mt, cs})362return mt, cs, request.WithContext(rCtx), nil363}
364
365// LookupRoute looks a route up and returns true when it is found
366func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {367if route, ok := c.router.Lookup(request.Method, request.URL.EscapedPath()); ok {368return route, ok369}370return nil, false371}
372
373// RouteInfo tries to match a route for this request
374// Returns the matched route, a shallow copy of the request if its context
375// contains the matched router, otherwise the same request, and a bool to
376// indicate if it the request matches one of the routes, if it doesn't
377// then it returns false and nil for the other two return values
378func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, *http.Request, bool) {379var rCtx = request.Context()380
381if v, ok := rCtx.Value(ctxMatchedRoute).(*MatchedRoute); ok {382return v, request, ok383}384
385if route, ok := c.LookupRoute(request); ok {386rCtx = stdContext.WithValue(rCtx, ctxMatchedRoute, route)387return route, request.WithContext(rCtx), ok388}389
390return nil, nil, false391}
392
393// ResponseFormat negotiates the response content type
394// Returns the response format and a shallow copy of the request if its context
395// doesn't contain the response format, otherwise the same request
396func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *http.Request) {397var rCtx = r.Context()398
399if v, ok := rCtx.Value(ctxResponseFormat).(string); ok {400debugLog("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)401return v, r402}403
404format := NegotiateContentType(r, offers, "")405if format != "" {406debugLog("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)407r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))408}409debugLog("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)410return format, r411}
412
413// AllowedMethods gets the allowed methods for the path of this request
414func (c *Context) AllowedMethods(request *http.Request) []string {415return c.router.OtherMethods(request.Method, request.URL.EscapedPath())416}
417
418// ResetAuth removes the current principal from the request context
419func (c *Context) ResetAuth(request *http.Request) *http.Request {420rctx := request.Context()421rctx = stdContext.WithValue(rctx, ctxSecurityPrincipal, nil)422rctx = stdContext.WithValue(rctx, ctxSecurityScopes, nil)423return request.WithContext(rctx)424}
425
426// Authorize authorizes the request
427// Returns the principal object and a shallow copy of the request when its
428// context doesn't contain the principal, otherwise the same request or an error
429// (the last) if one of the authenticators returns one or an Unauthenticated error
430func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, *http.Request, error) {431if route == nil || !route.HasAuth() {432return nil, nil, nil433}434
435var rCtx = request.Context()436if v := rCtx.Value(ctxSecurityPrincipal); v != nil {437return v, request, nil438}439
440applies, usr, err := route.Authenticators.Authenticate(request, route)441if !applies || err != nil || !route.Authenticators.AllowsAnonymous() && usr == nil {442if err != nil {443return nil, nil, err444}445return nil, nil, errors.Unauthenticated("invalid credentials")446}447if route.Authorizer != nil {448if err := route.Authorizer.Authorize(request, usr); err != nil {449if _, ok := err.(errors.Error); ok {450return nil, nil, err451}452
453return nil, nil, errors.New(http.StatusForbidden, err.Error())454}455}456
457rCtx = request.Context()458
459rCtx = stdContext.WithValue(rCtx, ctxSecurityPrincipal, usr)460rCtx = stdContext.WithValue(rCtx, ctxSecurityScopes, route.Authenticator.AllScopes())461return usr, request.WithContext(rCtx), nil462}
463
464// BindAndValidate binds and validates the request
465// Returns the validation map and a shallow copy of the request when its context
466// doesn't contain the validation, otherwise it returns the same request or an
467// CompositeValidationError error
468func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, *http.Request, error) {469var rCtx = request.Context()470
471if v, ok := rCtx.Value(ctxBoundParams).(*validation); ok {472debugLog("got cached validation (valid: %t)", len(v.result) == 0)473if len(v.result) > 0 {474return v.bound, request, errors.CompositeValidationError(v.result...)475}476return v.bound, request, nil477}478result := validateRequest(c, request, matched)479rCtx = stdContext.WithValue(rCtx, ctxBoundParams, result)480request = request.WithContext(rCtx)481if len(result.result) > 0 {482return result.bound, request, errors.CompositeValidationError(result.result...)483}484debugLog("no validation errors found")485return result.bound, request, nil486}
487
488// NotFound the default not found responder for when no route has been matched yet
489func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {490c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))491}
492
493// Respond renders the response after doing some content negotiation
494func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {495debugLog("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)496offers := []string{}497for _, mt := range produces {498if mt != c.api.DefaultProduces() {499offers = append(offers, mt)500}501}502// the default producer is last so more specific producers take precedence503offers = append(offers, c.api.DefaultProduces())504debugLog("offers: %v", offers)505
506var format string507format, r = c.ResponseFormat(r, offers)508rw.Header().Set(runtime.HeaderContentType, format)509
510if resp, ok := data.(Responder); ok {511producers := route.Producers512// producers contains keys with normalized format, if a format has MIME type parameter such as `text/plain; charset=utf-8`513// then you must provide `text/plain` to get the correct producer. HOWEVER, format here is not normalized.514prod, ok := producers[normalizeOffer(format)]515if !ok {516prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))517pr, ok := prods[c.api.DefaultProduces()]518if !ok {519panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))520}521prod = pr522}523resp.WriteResponse(rw, prod)524return525}526
527if err, ok := data.(error); ok {528if format == "" {529rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)530}531
532if realm := security.FailedBasicAuth(r); realm != "" {533rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))534}535
536if route == nil || route.Operation == nil {537c.api.ServeErrorFor("")(rw, r, err)538return539}540c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)541return542}543
544if route == nil || route.Operation == nil {545rw.WriteHeader(200)546if r.Method == "HEAD" {547return548}549producers := c.api.ProducersFor(normalizeOffers(offers))550prod, ok := producers[format]551if !ok {552panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))553}554if err := prod.Produce(rw, data); err != nil {555panic(err) // let the recovery middleware deal with this556}557return558}559
560if _, code, ok := route.Operation.SuccessResponse(); ok {561rw.WriteHeader(code)562if code == 204 || r.Method == "HEAD" {563return564}565
566producers := route.Producers567prod, ok := producers[format]568if !ok {569if !ok {570prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))571pr, ok := prods[c.api.DefaultProduces()]572if !ok {573panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))574}575prod = pr576}577}578if err := prod.Produce(rw, data); err != nil {579panic(err) // let the recovery middleware deal with this580}581return582}583
584c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))585}
586
587func (c *Context) APIHandlerSwaggerUI(builder Builder) http.Handler {588b := builder589if b == nil {590b = PassthroughBuilder591}592
593var title string594sp := c.spec.Spec()595if sp != nil && sp.Info != nil && sp.Info.Title != "" {596title = sp.Info.Title597}598
599swaggerUIOpts := SwaggerUIOpts{600BasePath: c.BasePath(),601Title: title,602}603
604return Spec("", c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)))605}
606
607// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec
608func (c *Context) APIHandler(builder Builder) http.Handler {609b := builder610if b == nil {611b = PassthroughBuilder612}613
614var title string615sp := c.spec.Spec()616if sp != nil && sp.Info != nil && sp.Info.Title != "" {617title = sp.Info.Title618}619
620redocOpts := RedocOpts{621BasePath: c.BasePath(),622Title: title,623}624
625return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)))626}
627
628// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec
629func (c *Context) RoutesHandler(builder Builder) http.Handler {630b := builder631if b == nil {632b = PassthroughBuilder633}634return NewRouter(c, b(NewOperationExecutor(c)))635}
636