200 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2017 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package server
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	rt "runtime"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/emicklei/go-restful"
 | |
| 	"github.com/golang/glog"
 | |
| 
 | |
| 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/schema"
 | |
| 	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
 | |
| 	"k8s.io/apiserver/pkg/endpoints/request"
 | |
| 	"k8s.io/apiserver/pkg/server/mux"
 | |
| 	genericmux "k8s.io/apiserver/pkg/server/mux"
 | |
| )
 | |
| 
 | |
| // APIServerHandlers holds the different http.Handlers used by the API server.
 | |
| // This includes the full handler chain, the director (which chooses between gorestful and nonGoRestful,
 | |
| // the gorestful handler (used for the API) which falls through to the nonGoRestful handler on unregistered paths,
 | |
| // and the nonGoRestful handler (which can contain a fallthrough of its own)
 | |
| // FullHandlerChain -> Director -> {GoRestfulContainer,NonGoRestfulMux} based on inspection of registered web services
 | |
| type APIServerHandler struct {
 | |
| 	// FullHandlerChain is the one that is eventually served with.  It should include the full filter
 | |
| 	// chain and then call the Director.
 | |
| 	FullHandlerChain http.Handler
 | |
| 	// The registered APIs.  InstallAPIs uses this.  Other servers probably shouldn't access this directly.
 | |
| 	GoRestfulContainer *restful.Container
 | |
| 	// NonGoRestfulMux is the final HTTP handler in the chain.
 | |
| 	// It comes after all filters and the API handling
 | |
| 	// This is where other servers can attach handler to various parts of the chain.
 | |
| 	NonGoRestfulMux *mux.PathRecorderMux
 | |
| 
 | |
| 	// Director is here so that we can properly handle fall through and proxy cases.
 | |
| 	// This looks a bit bonkers, but here's what's happening.  We need to have /apis handling registered in gorestful in order to have
 | |
| 	// swagger generated for compatibility.  Doing that with `/apis` as a webservice, means that it forcibly 404s (no defaulting allowed)
 | |
| 	// all requests which are not /apis or /apis/.  We need those calls to fall through behind goresful for proper delegation.  Trying to
 | |
| 	// register for a pattern which includes everything behind it doesn't work because gorestful negotiates for verbs and content encoding
 | |
| 	// and all those things go crazy when gorestful really just needs to pass through.  In addition, openapi enforces unique verb constraints
 | |
| 	// 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
 | |
| 	//  containing webservice faces all the same problems listed above.
 | |
| 	// 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
 | |
| 	// decide if the the route is likely to be handled by goresful and route there if needed.  Otherwise, it goes to PostGoRestful mux in
 | |
| 	// order to handle "normal" paths and delegation. Hopefully no API consumers will ever have to deal with this level of detail.  I think
 | |
| 	// we should consider completely removing gorestful.
 | |
| 	// Other servers should only use this opaquely to delegate to an API server.
 | |
| 	Director http.Handler
 | |
| }
 | |
| 
 | |
| // HandlerChainBuilderFn is used to wrap the GoRestfulContainer handler using the provided handler chain.
 | |
| // It is normally used to apply filtering like authentication and authorization
 | |
| type HandlerChainBuilderFn func(apiHandler http.Handler) http.Handler
 | |
| 
 | |
| func NewAPIServerHandler(name string, contextMapper request.RequestContextMapper, s runtime.NegotiatedSerializer, handlerChainBuilder HandlerChainBuilderFn, notFoundHandler http.Handler) *APIServerHandler {
 | |
| 	nonGoRestfulMux := genericmux.NewPathRecorderMux(name)
 | |
| 	if notFoundHandler != nil {
 | |
| 		nonGoRestfulMux.NotFoundHandler(notFoundHandler)
 | |
| 	}
 | |
| 
 | |
| 	gorestfulContainer := restful.NewContainer()
 | |
| 	gorestfulContainer.ServeMux = http.NewServeMux()
 | |
| 	gorestfulContainer.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
 | |
| 	gorestfulContainer.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
 | |
| 		logStackOnRecover(s, panicReason, httpWriter)
 | |
| 	})
 | |
| 	gorestfulContainer.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
 | |
| 		ctx, ok := contextMapper.Get(request.Request)
 | |
| 		if !ok {
 | |
| 			responsewriters.InternalError(response.ResponseWriter, request.Request, errors.New("no context found for request"))
 | |
| 		}
 | |
| 		serviceErrorHandler(ctx, s, serviceErr, request, response)
 | |
| 	})
 | |
| 
 | |
| 	director := director{
 | |
| 		name:               name,
 | |
| 		goRestfulContainer: gorestfulContainer,
 | |
| 		nonGoRestfulMux:    nonGoRestfulMux,
 | |
| 	}
 | |
| 
 | |
| 	return &APIServerHandler{
 | |
| 		FullHandlerChain:   handlerChainBuilder(director),
 | |
| 		GoRestfulContainer: gorestfulContainer,
 | |
| 		NonGoRestfulMux:    nonGoRestfulMux,
 | |
| 		Director:           director,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ListedPaths returns the paths that should be shown under /
 | |
| func (a *APIServerHandler) ListedPaths() []string {
 | |
| 	var handledPaths []string
 | |
| 	// Extract the paths handled using restful.WebService
 | |
| 	for _, ws := range a.GoRestfulContainer.RegisteredWebServices() {
 | |
| 		handledPaths = append(handledPaths, ws.RootPath())
 | |
| 	}
 | |
| 	handledPaths = append(handledPaths, a.NonGoRestfulMux.ListedPaths()...)
 | |
| 	sort.Strings(handledPaths)
 | |
| 
 | |
| 	return handledPaths
 | |
| }
 | |
| 
 | |
| type director struct {
 | |
| 	name               string
 | |
| 	goRestfulContainer *restful.Container
 | |
| 	nonGoRestfulMux    *mux.PathRecorderMux
 | |
| }
 | |
| 
 | |
| func (d director) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | |
| 	path := req.URL.Path
 | |
| 
 | |
| 	// check to see if our webservices want to claim this path
 | |
| 	for _, ws := range d.goRestfulContainer.RegisteredWebServices() {
 | |
| 		switch {
 | |
| 		case ws.RootPath() == "/apis":
 | |
| 			// if we are exactly /apis or /apis/, then we need special handling in loop.
 | |
| 			// normally these are passed to the nonGoRestfulMux, but if discovery is enabled, it will go directly.
 | |
| 			// We can't rely on a prefix match since /apis matches everything (see the big comment on Director above)
 | |
| 			if path == "/apis" || path == "/apis/" {
 | |
| 				glog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
 | |
| 				// don't use servemux here because gorestful servemuxes get messed up when removing webservices
 | |
| 				// TODO fix gorestful, remove TPRs, or stop using gorestful
 | |
| 				d.goRestfulContainer.Dispatch(w, req)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 		case strings.HasPrefix(path, ws.RootPath()):
 | |
| 			// ensure an exact match or a path boundary match
 | |
| 			if len(path) == len(ws.RootPath()) || path[len(ws.RootPath())] == '/' {
 | |
| 				glog.V(5).Infof("%v: %v %q satisfied by gorestful with webservice %v", d.name, req.Method, path, ws.RootPath())
 | |
| 				// don't use servemux here because gorestful servemuxes get messed up when removing webservices
 | |
| 				// TODO fix gorestful, remove TPRs, or stop using gorestful
 | |
| 				d.goRestfulContainer.Dispatch(w, req)
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// if we didn't find a match, then we just skip gorestful altogether
 | |
| 	glog.V(5).Infof("%v: %v %q satisfied by nonGoRestful", d.name, req.Method, path)
 | |
| 	d.nonGoRestfulMux.ServeHTTP(w, req)
 | |
| }
 | |
| 
 | |
| //TODO: Unify with RecoverPanics?
 | |
| func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
 | |
| 	var buffer bytes.Buffer
 | |
| 	buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
 | |
| 	for i := 2; ; i++ {
 | |
| 		_, file, line, ok := rt.Caller(i)
 | |
| 		if !ok {
 | |
| 			break
 | |
| 		}
 | |
| 		buffer.WriteString(fmt.Sprintf("    %s:%d\r\n", file, line))
 | |
| 	}
 | |
| 	glog.Errorln(buffer.String())
 | |
| 
 | |
| 	headers := http.Header{}
 | |
| 	if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
 | |
| 		headers.Set("Accept", ct)
 | |
| 	}
 | |
| 	emptyContext := request.NewContext() // best we can do here: we don't know the request
 | |
| 	responsewriters.ErrorNegotiated(emptyContext, apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", schema.GroupResource{}, "", "", 0, false), s, schema.GroupVersion{}, w, &http.Request{Header: headers})
 | |
| }
 | |
| 
 | |
| func serviceErrorHandler(ctx request.Context, s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, resp *restful.Response) {
 | |
| 	responsewriters.ErrorNegotiated(
 | |
| 		ctx,
 | |
| 		apierrors.NewGenericServerResponse(serviceErr.Code, "", schema.GroupResource{}, "", serviceErr.Message, 0, false),
 | |
| 		s,
 | |
| 		schema.GroupVersion{},
 | |
| 		resp,
 | |
| 		request.Request,
 | |
| 	)
 | |
| }
 | |
| 
 | |
| // ServeHTTP makes it an http.Handler
 | |
| func (a *APIServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | |
| 	a.FullHandlerChain.ServeHTTP(w, r)
 | |
| }
 |