func GetAuthorizerAttributes(ctx request.Context) (authorizer.Attributes, error) { attribs := authorizer.AttributesRecord{} user, ok := request.UserFrom(ctx) if ok { attribs.User = user } requestInfo, found := request.RequestInfoFrom(ctx) if !found { return nil, errors.New("no RequestInfo found in the context") } // Start with common attributes that apply to resource and non-resource requests attribs.ResourceRequest = requestInfo.IsResourceRequest attribs.Path = requestInfo.Path attribs.Verb = requestInfo.Verb attribs.APIGroup = requestInfo.APIGroup attribs.APIVersion = requestInfo.APIVersion attribs.Resource = requestInfo.Resource attribs.Subresource = requestInfo.Subresource attribs.Namespace = requestInfo.Namespace attribs.Name = requestInfo.Name return &attribs, nil }
// WithMaxInFlightLimit limits the number of in-flight requests to buffer size of the passed in channel. func WithMaxInFlightLimit( handler http.Handler, nonMutatingLimit int, mutatingLimit int, requestContextMapper genericapirequest.RequestContextMapper, longRunningRequestCheck LongRunningRequestCheck, ) http.Handler { if nonMutatingLimit == 0 && mutatingLimit == 0 { return handler } var nonMutatingChan chan bool var mutatingChan chan bool if nonMutatingLimit != 0 { nonMutatingChan = make(chan bool, nonMutatingLimit) } if mutatingLimit != 0 { mutatingChan = make(chan bool, mutatingLimit) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx, ok := requestContextMapper.Get(r) if !ok { handleError(w, r, fmt.Errorf("no context found for request, handler chain must be wrong")) return } requestInfo, ok := apirequest.RequestInfoFrom(ctx) if !ok { handleError(w, r, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong")) return } // Skip tracking long running events. if longRunningRequestCheck != nil && longRunningRequestCheck(r, requestInfo) { handler.ServeHTTP(w, r) return } var c chan bool if !nonMutatingRequestVerbs.Has(requestInfo.Verb) { c = mutatingChan } else { c = nonMutatingChan } if c == nil { handler.ServeHTTP(w, r) } else { select { case c <- true: defer func() { <-c }() handler.ServeHTTP(w, r) default: tooManyRequests(r, w) } } }) }
// WithPanicRecovery wraps an http Handler to recover and log panics. func WithPanicRecovery(handler http.Handler, requestContextMapper apirequest.RequestContextMapper) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { defer runtime.HandleCrash(func(err interface{}) { http.Error(w, "This request caused apisever to panic. Look in log for details.", http.StatusInternalServerError) glog.Errorf("APIServer panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, err, debug.Stack()) }) logger := httplog.NewLogged(req, &w) var requestInfo *apirequest.RequestInfo ctx, ok := requestContextMapper.Get(req) if !ok { glog.Errorf("no context found for request, handler chain must be wrong") } else { requestInfo, ok = apirequest.RequestInfoFrom(ctx) if !ok { glog.Errorf("no RequestInfo found in context, handler chain must be wrong") } } if !ok || requestInfo.Verb != "proxy" { logger.StacktraceWhen( httplog.StatusIsNot( http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusBadRequest, http.StatusMovedPermanently, http.StatusTemporaryRedirect, http.StatusConflict, http.StatusNotFound, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotModified, apierrors.StatusUnprocessableEntity, http.StatusSwitchingProtocols, ), ) } defer logger.Log() // Dispatch to the internal handler handler.ServeHTTP(w, req) }) }
// WithTimeoutForNonLongRunningRequests times out non-long-running requests after the time given by globalTimeout. func WithTimeoutForNonLongRunningRequests(handler http.Handler, requestContextMapper apirequest.RequestContextMapper, longRunning LongRunningRequestCheck) http.Handler { if longRunning == nil { return handler } timeoutFunc := func(req *http.Request) (<-chan time.Time, string) { // TODO unify this with apiserver.MaxInFlightLimit ctx, ok := requestContextMapper.Get(req) if !ok { return time.After(globalTimeout), "" } requestInfo, ok := apirequest.RequestInfoFrom(ctx) if !ok { return time.After(globalTimeout), "" } if longRunning(req, requestInfo) { return nil, "" } return time.After(globalTimeout), "" } return WithTimeout(handler, timeoutFunc) }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { proxyHandlerTraceID := rand.Int63() var verb string var apiResource string var httpCode int reqStart := time.Now() defer func() { metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart) }() ctx, ok := r.Mapper.Get(req) if !ok { responsewriters.InternalError(w, req, errors.New("Error getting request context")) httpCode = http.StatusInternalServerError return } requestInfo, ok := request.RequestInfoFrom(ctx) if !ok { responsewriters.InternalError(w, req, errors.New("Error getting RequestInfo from context")) httpCode = http.StatusInternalServerError return } if !requestInfo.IsResourceRequest { responsewriters.NotFound(w, req) httpCode = http.StatusNotFound return } verb = requestInfo.Verb namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts ctx = request.WithNamespace(ctx, namespace) if len(parts) < 2 { responsewriters.NotFound(w, req) httpCode = http.StatusNotFound return } id := parts[1] remainder := "" if len(parts) > 2 { proxyParts := parts[2:] remainder = strings.Join(proxyParts, "/") if strings.HasSuffix(req.URL.Path, "/") { // The original path had a trailing slash, which has been stripped // by KindAndNamespace(). We should add it back because some // servers (like etcd) require it. remainder = remainder + "/" } } storage, ok := r.Storage[resource] if !ok { httplog.LogOf(req, w).Addf("'%v' has no storage object", resource) responsewriters.NotFound(w, req) httpCode = http.StatusNotFound return } apiResource = resource gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} redirector, ok := storage.(rest.Redirector) if !ok { httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource) httpCode = responsewriters.ErrorNegotiated(apierrors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.Serializer, gv, w, req) return } location, roundTripper, err := redirector.ResourceLocation(ctx, id) if err != nil { httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err) httpCode = responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req) return } if location == nil { httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id) responsewriters.NotFound(w, req) httpCode = http.StatusNotFound return } if roundTripper != nil { glog.V(5).Infof("[%x: %v] using transport %T...", proxyHandlerTraceID, req.URL, roundTripper) } // Default to http if location.Scheme == "" { location.Scheme = "http" } // Add the subpath if len(remainder) > 0 { location.Path = singleJoiningSlash(location.Path, remainder) } // Start with anything returned from the storage, and add the original request's parameters values := location.Query() for k, vs := range req.URL.Query() { for _, v := range vs { values.Add(k, v) } } location.RawQuery = values.Encode() newReq, err := http.NewRequest(req.Method, location.String(), req.Body) if err != nil { httpCode = responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req) return } httpCode = http.StatusOK newReq.Header = req.Header newReq.ContentLength = req.ContentLength // Copy the TransferEncoding is for future-proofing. Currently Go only supports "chunked" and // it can determine the TransferEncoding based on ContentLength and the Body. newReq.TransferEncoding = req.TransferEncoding // TODO convert this entire proxy to an UpgradeAwareProxy similar to // https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go. // That proxy needs to be modified to support multiple backends, not just 1. if r.tryUpgrade(w, req, newReq, location, roundTripper, gv) { return } // Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/" // This is essentially a hack for http://issue.k8s.io/4958. // Note: Keep this code after tryUpgrade to not break that flow. if len(parts) == 2 && !strings.HasSuffix(req.URL.Path, "/") { var queryPart string if len(req.URL.RawQuery) > 0 { queryPart = "?" + req.URL.RawQuery } w.Header().Set("Location", req.URL.Path+"/"+queryPart) w.WriteHeader(http.StatusMovedPermanently) return } start := time.Now() glog.V(4).Infof("[%x] Beginning proxy %s...", proxyHandlerTraceID, req.URL) defer func() { glog.V(4).Infof("[%x] Proxy %v finished %v.", proxyHandlerTraceID, req.URL, time.Now().Sub(start)) }() proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host}) alreadyRewriting := false if roundTripper != nil { _, alreadyRewriting = roundTripper.(*proxyutil.Transport) glog.V(5).Infof("[%x] Not making a rewriting transport for proxy %s...", proxyHandlerTraceID, req.URL) } if !alreadyRewriting { glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL) prepend := path.Join(r.Prefix, resource, id) if len(namespace) > 0 { prepend = path.Join(r.Prefix, "namespaces", namespace, resource, id) } pTransport := &proxyutil.Transport{ Scheme: req.URL.Scheme, Host: req.URL.Host, PathPrepend: prepend, RoundTripper: roundTripper, } roundTripper = pTransport } proxy.Transport = roundTripper proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) }