blob: e4e7d9aee06b6724f9b3169774f05b9ed92e2839 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package server
18
19import (
20 "bytes"
21 "fmt"
22 "net/http"
23 rt "runtime"
24 "sort"
25 "strings"
26
27 "github.com/emicklei/go-restful"
28 "github.com/golang/glog"
29
30 apierrors "k8s.io/apimachinery/pkg/api/errors"
31 "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/apimachinery/pkg/runtime/schema"
33 "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
34 "k8s.io/apiserver/pkg/server/mux"
35)
36
37// APIServerHandlers holds the different http.Handlers used by the API server.
38// This includes the full handler chain, the director (which chooses between gorestful and nonGoRestful,
39// the gorestful handler (used for the API) which falls through to the nonGoRestful handler on unregistered paths,
40// and the nonGoRestful handler (which can contain a fallthrough of its own)
41// FullHandlerChain -> Director -> {GoRestfulContainer,NonGoRestfulMux} based on inspection of registered web services
42type APIServerHandler struct {
43 // FullHandlerChain is the one that is eventually served with. It should include the full filter
44 // chain and then call the Director.
45 FullHandlerChain http.Handler
46 // The registered APIs. InstallAPIs uses this. Other servers probably shouldn't access this directly.
47 GoRestfulContainer *restful.Container
48 // NonGoRestfulMux is the final HTTP handler in the chain.
49 // It comes after all filters and the API handling
50 // This is where other servers can attach handler to various parts of the chain.
51 NonGoRestfulMux *mux.PathRecorderMux
52
53 // Director is here so that we can properly handle fall through and proxy cases.
54 // This looks a bit bonkers, but here's what's happening. We need to have /apis handling registered in gorestful in order to have
55 // swagger generated for compatibility. Doing that with `/apis` as a webservice, means that it forcibly 404s (no defaulting allowed)
56 // all requests which are not /apis or /apis/. We need those calls to fall through behind goresful for proper delegation. Trying to
57 // register for a pattern which includes everything behind it doesn't work because gorestful negotiates for verbs and content encoding
58 // and all those things go crazy when gorestful really just needs to pass through. In addition, openapi enforces unique verb constraints
59 // which we don't fit into and it still muddies up swagger. Trying to switch the webservices into a route doesn't work because the
60 // containing webservice faces all the same problems listed above.
61 // This leads to the crazy thing done here. Our mux does what we need, so we'll place it in front of gorestful. It will introspect to
62 // decide if the route is likely to be handled by goresful and route there if needed. Otherwise, it goes to PostGoRestful mux in
63 // order to handle "normal" paths and delegation. Hopefully no API consumers will ever have to deal with this level of detail. I think
64 // we should consider completely removing gorestful.
65 // Other servers should only use this opaquely to delegate to an API server.
66 Director http.Handler
67}
68
69// HandlerChainBuilderFn is used to wrap the GoRestfulContainer handler using the provided handler chain.
70// It is normally used to apply filtering like authentication and authorization
71type HandlerChainBuilderFn func(apiHandler http.Handler) http.Handler
72
73func NewAPIServerHandler(name string, s runtime.NegotiatedSerializer, handlerChainBuilder HandlerChainBuilderFn, notFoundHandler http.Handler) *APIServerHandler {
74 nonGoRestfulMux := mux.NewPathRecorderMux(name)
75 if notFoundHandler != nil {
76 nonGoRestfulMux.NotFoundHandler(notFoundHandler)
77 }
78
79 gorestfulContainer := restful.NewContainer()
80 gorestfulContainer.ServeMux = http.NewServeMux()
81 gorestfulContainer.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
82 gorestfulContainer.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
83 logStackOnRecover(s, panicReason, httpWriter)
84 })
85 gorestfulContainer.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
86 serviceErrorHandler(s, serviceErr, request, response)
87 })
88
89 director := director{
90 name: name,
91 goRestfulContainer: gorestfulContainer,
92 nonGoRestfulMux: nonGoRestfulMux,
93 }
94
95 return &APIServerHandler{
96 FullHandlerChain: handlerChainBuilder(director),
97 GoRestfulContainer: gorestfulContainer,
98 NonGoRestfulMux: nonGoRestfulMux,
99 Director: director,
100 }
101}
102
103// ListedPaths returns the paths that should be shown under /
104func (a *APIServerHandler) ListedPaths() []string {
105 var handledPaths []string
106 // Extract the paths handled using restful.WebService
107 for _, ws := range a.GoRestfulContainer.RegisteredWebServices() {
108 handledPaths = append(handledPaths, ws.RootPath())
109 }
110 handledPaths = append(handledPaths, a.NonGoRestfulMux.ListedPaths()...)
111 sort.Strings(handledPaths)
112
113 return handledPaths
114}
115
116type director struct {
117 name string
118 goRestfulContainer *restful.Container
119 nonGoRestfulMux *mux.PathRecorderMux
120}
121
122func (d director) ServeHTTP(w http.ResponseWriter, req *http.Request) {
123 path := req.URL.Path
124
125 // check to see if our webservices want to claim this path
126 for _, ws := range d.goRestfulContainer.RegisteredWebServices() {
127 switch {
128 case ws.RootPath() == "/apis":
129 // if we are exactly /apis or /apis/, then we need special handling in loop.
130 // normally these are passed to the nonGoRestfulMux, but if discovery is enabled, it will go directly.
131 // We can't rely on a prefix match since /apis matches everything (see the big comment on Director above)
132 if path == "/apis" || path == "/apis/" {
133 glog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
134 // don't use servemux here because gorestful servemuxes get messed up when removing webservices
135 // TODO fix gorestful, remove TPRs, or stop using gorestful
136 d.goRestfulContainer.Dispatch(w, req)
137 return
138 }
139
140 case strings.HasPrefix(path, ws.RootPath()):
141 // ensure an exact match or a path boundary match
142 if len(path) == len(ws.RootPath()) || path[len(ws.RootPath())] == '/' {
143 glog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
144 // don't use servemux here because gorestful servemuxes get messed up when removing webservices
145 // TODO fix gorestful, remove TPRs, or stop using gorestful
146 d.goRestfulContainer.Dispatch(w, req)
147 return
148 }
149 }
150 }
151
152 // if we didn't find a match, then we just skip gorestful altogether
153 glog.V(5).Infof("%v: %v %q satisfied by nonGoRestful", d.name, req.Method, path)
154 d.nonGoRestfulMux.ServeHTTP(w, req)
155}
156
157//TODO: Unify with RecoverPanics?
158func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
159 var buffer bytes.Buffer
160 buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
161 for i := 2; ; i++ {
162 _, file, line, ok := rt.Caller(i)
163 if !ok {
164 break
165 }
166 buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
167 }
168 glog.Errorln(buffer.String())
169
170 headers := http.Header{}
171 if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
172 headers.Set("Accept", ct)
173 }
174 responsewriters.ErrorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", schema.GroupResource{}, "", "", 0, false), s, schema.GroupVersion{}, w, &http.Request{Header: headers})
175}
176
177func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, resp *restful.Response) {
178 responsewriters.ErrorNegotiated(
179 apierrors.NewGenericServerResponse(serviceErr.Code, "", schema.GroupResource{}, "", serviceErr.Message, 0, false),
180 s,
181 schema.GroupVersion{},
182 resp,
183 request.Request,
184 )
185}
186
187// ServeHTTP makes it an http.Handler
188func (a *APIServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
189 a.FullHandlerChain.ServeHTTP(w, r)
190}