github.com/w3security/vervet/v5@v5.3.1-0.20230618081846-5bd9b5d799dc/versionware/handler.go (about) 1 // Package versionware provides routing and middleware for building versioned 2 // HTTP services. 3 package versionware 4 5 import ( 6 "fmt" 7 "net/http" 8 9 "github.com/w3security/vervet/v5" 10 ) 11 12 const ( 13 // HeaderW3SecurityVersionRequested is a response header acknowledging the API 14 // version that was requested. 15 HeaderW3SecurityVersionRequested = "w3security-version-requested" 16 17 // HeaderW3SecurityVersionServed is a response header indicating the actual API 18 // version that was matched and served the response. 19 HeaderW3SecurityVersionServed = "w3security-version-served" 20 ) 21 22 // Handler is a multiplexing http.Handler that dispatches requests based on the 23 // version query parameter according to vervet's API version matching rules. 24 type Handler struct { 25 handlers map[vervet.Version]http.Handler 26 index vervet.VersionIndex 27 errFunc VersionErrorHandler 28 } 29 30 // VersionErrorHandler defines a function which handles versioning error 31 // responses in requests. 32 type VersionErrorHandler func(w http.ResponseWriter, r *http.Request, status int, err error) 33 34 // VersionHandler expresses a pairing of Version and http.Handler. 35 type VersionHandler struct { 36 Version vervet.Version 37 Handler http.Handler 38 } 39 40 // NewHandler returns a new Handler instance, which handles versioned requests 41 // with the matching version handler. 42 func NewHandler(vhs ...VersionHandler) *Handler { 43 h := &Handler{ 44 handlers: map[vervet.Version]http.Handler{}, 45 errFunc: DefaultVersionError, 46 } 47 versions := make([]vervet.Version, len(vhs)) 48 for i := range vhs { 49 v := vhs[i].Version 50 versions[i] = v 51 h.handlers[v] = vhs[i].Handler 52 } 53 h.index = vervet.NewVersionIndex(versions) 54 return h 55 } 56 57 // DefaultVersionError provides a basic implementation of VersionErrorHandler 58 // that uses http.Error. 59 func DefaultVersionError(w http.ResponseWriter, r *http.Request, status int, err error) { 60 http.Error(w, http.StatusText(status), status) 61 } 62 63 // HandleErrors changes the default error handler to the provided function. It 64 // may be used to control the format of versioning error responses. 65 func (h *Handler) HandleErrors(errFunc VersionErrorHandler) { 66 h.errFunc = errFunc 67 } 68 69 // Resolve returns the resolved version and its associated http.Handler for the 70 // requested version. 71 func (h *Handler) Resolve(requested vervet.Version) (*vervet.Version, http.Handler, error) { 72 resolvedVersion, err := h.index.Resolve(requested) 73 if err != nil { 74 return nil, nil, err 75 } 76 return &resolvedVersion, h.handlers[resolvedVersion], nil 77 } 78 79 // ServeHTTP implements http.Handler with the handler matching the version 80 // query parameter on the request. If no matching version is found, responds 81 // 404. 82 func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 83 versionParam := req.URL.Query().Get("version") 84 if versionParam == "" { 85 h.errFunc(w, req, http.StatusBadRequest, fmt.Errorf("missing required query parameter 'version'")) 86 return 87 } 88 requested, err := vervet.ParseVersion(versionParam) 89 if err != nil { 90 h.errFunc(w, req, http.StatusBadRequest, err) 91 return 92 } 93 resolved, handler, err := h.Resolve(requested) 94 if err != nil { 95 h.errFunc(w, req, http.StatusNotFound, err) 96 return 97 } 98 w.Header().Set(HeaderW3SecurityVersionRequested, requested.String()) 99 w.Header().Set(HeaderW3SecurityVersionServed, resolved.String()) 100 handler.ServeHTTP(w, req) 101 }