// monitorFilter creates a filter that reports the metrics for a given resource and action. func monitorFilter(action, resource string) restful.FilterFunction { return func(req *restful.Request, res *restful.Response, chain *restful.FilterChain) { reqStart := time.Now() chain.ProcessFilter(req, res) httpCode := res.StatusCode() metrics.Monitor(&action, &resource, util.GetClient(req.Request), &httpCode, reqStart) } }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var verb string var apiResource string var httpCode int reqStart := time.Now() defer metrics.Monitor(&verb, &apiResource, util.GetClient(req), &httpCode, reqStart) requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req) if err != nil { notFound(w, req) httpCode = http.StatusNotFound return } verb = requestInfo.Verb namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts ctx, ok := r.context.Get(req) if !ok { ctx = api.NewContext() } ctx = api.WithNamespace(ctx, namespace) if len(parts) < 2 { 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) notFound(w, req) httpCode = http.StatusNotFound return } apiResource = resource redirector, ok := storage.(rest.Redirector) if !ok { httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource) httpCode = errorJSON(errors.NewMethodNotSupported(resource, "proxy"), r.codec, w) return } location, transport, err := redirector.ResourceLocation(ctx, id) if err != nil { httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err) status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w, true) httpCode = status.Code return } if location == nil { httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id) notFound(w, req) httpCode = http.StatusNotFound return } // If we have a custom dialer, and no pre-existing transport, initialize it to use the dialer. if transport == nil && r.dial != nil { transport = &http.Transport{Dial: r.dial} } // 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 { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w, true) notFound(w, req) httpCode = status.Code return } httpCode = http.StatusOK newReq.Header = req.Header // 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, transport) { return } // Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/" // This is essentially a hack for https://github.com/qingyuancloud/QingYuan/issues/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 } proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host}) if transport == nil { prepend := path.Join(r.prefix, resource, id) if len(namespace) > 0 { prepend = path.Join(r.prefix, "namespaces", namespace, resource, id) } transport = &proxyutil.Transport{ Scheme: req.URL.Scheme, Host: req.URL.Host, PathPrepend: prepend, } } proxy.Transport = transport proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) }