func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var verb string var apiResource string var httpCode int reqStart := time.Now() defer monitor("redirect", &verb, &apiResource, &httpCode, reqStart) requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req) if err != nil { notFound(w, req) httpCode = http.StatusNotFound return } verb = requestInfo.Verb resource, parts := requestInfo.Resource, requestInfo.Parts ctx, ok := r.context.Get(req) if !ok { ctx = api.NewContext() } ctx = api.WithNamespace(ctx, requestInfo.Namespace) // redirection requires /resource/resourceName path parts if len(parts) != 2 || req.Method != "GET" { notFound(w, req) httpCode = http.StatusNotFound return } id := parts[1] 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.(Redirector) if !ok { httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource) httpCode = errorJSON(errors.NewMethodNotSupported(resource, "redirect"), r.codec, w) return } location, err := redirector.ResourceLocation(ctx, id) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) httpCode = status.Code return } w.Header().Set("Location", fmt.Sprintf("http://%s", location)) w.WriteHeader(http.StatusTemporaryRedirect) httpCode = http.StatusTemporaryRedirect }
// ServeHTTP processes watch requests. func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if req.Method != "GET" { notFound(w, req) return } namespace, kind, _, err := KindAndNamespace(req) if err != nil { notFound(w, req) return } ctx := api.WithNamespace(api.NewContext(), namespace) storage := h.storage[kind] if storage == nil { notFound(w, req) return } watcher, ok := storage.(ResourceWatcher) if !ok { errorJSON(errors.NewMethodNotSupported(kind, "watch"), h.codec, w) return } label, field, resourceVersion, err := getWatchParams(req.URL.Query()) if err != nil { errorJSON(err, h.codec, w) return } watching, err := watcher.Watch(ctx, label, field, resourceVersion) if err != nil { errorJSON(err, h.codec, w) return } // TODO: This is one watch per connection. We want to multiplex, so that // multiple watches of the same thing don't create two watches downstream. watchServer := &WatchServer{watching, h.codec, func(obj runtime.Object) { if err := h.setSelfLinkAddName(obj, req); err != nil { glog.Errorf("Failed to set self link for object %#v", obj) } }} if isWebsocketRequest(req) { websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req) } else { watchServer.ServeHTTP(w, req) } }
func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { namespace, kind, parts, err := KindAndNamespace(req) if err != nil { notFound(w, req) return } ctx := api.WithNamespace(api.NewContext(), namespace) // redirection requires /kind/resourceName path parts if len(parts) != 2 || req.Method != "GET" { notFound(w, req) return } id := parts[1] storage, ok := r.storage[kind] if !ok { httplog.LogOf(req, w).Addf("'%v' has no storage object", kind) notFound(w, req) return } redirector, ok := storage.(Redirector) if !ok { httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind) errorJSON(errors.NewMethodNotSupported(kind, "redirect"), r.codec, w) return } location, err := redirector.ResourceLocation(ctx, id) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return } w.Header().Set("Location", location) w.WriteHeader(http.StatusTemporaryRedirect) }
// ServeHTTP processes watch requests. func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var verb string var apiResource string var httpCode int reqStart := time.Now() defer monitor("watch", &verb, &apiResource, &httpCode, reqStart) if req.Method != "GET" { httpCode = errorJSON(errors.NewBadRequest( fmt.Sprintf("unsupported method for watch: %s", req.Method)), h.codec, w) return } requestInfo, err := h.info.GetAPIRequestInfo(req) if err != nil { httpCode = errorJSON(errors.NewBadRequest( fmt.Sprintf("failed to find api request info: %s", err.Error())), h.codec, w) return } verb = requestInfo.Verb ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace) storage := h.storage[requestInfo.Resource] if storage == nil { httpCode = errorJSON(errors.NewNotFound(requestInfo.Resource, "Resource"), h.codec, w) return } apiResource = requestInfo.Resource watcher, ok := storage.(ResourceWatcher) if !ok { httpCode = errorJSON(errors.NewMethodNotSupported(requestInfo.Resource, "watch"), h.codec, w) return } label, field, err := parseSelectorQueryParams(req.URL.Query(), requestInfo.APIVersion, apiResource) if err != nil { httpCode = errorJSON(err, h.codec, w) return } resourceVersion := req.URL.Query().Get("resourceVersion") watching, err := watcher.Watch(ctx, label, field, resourceVersion) if err != nil { httpCode = errorJSON(err, h.codec, w) return } httpCode = http.StatusOK // TODO: This is one watch per connection. We want to multiplex, so that // multiple watches of the same thing don't create two watches downstream. watchServer := &WatchServer{watching, h.codec, func(obj runtime.Object) { if err := h.setSelfLinkAddName(obj, req); err != nil { glog.Errorf("Failed to set self link for object %#v", obj) } }} if isWebsocketRequest(req) { websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req) } else { watchServer.ServeHTTP(w, req) } }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var verb string var apiResource string var httpCode int reqStart := time.Now() defer monitor("proxy", &verb, &apiResource, &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] rest := "" if len(parts) > 2 { proxyParts := parts[2:] rest = 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. rest = rest + "/" } } 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.(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, 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) httpCode = status.Code return } destURL, err := url.Parse(location) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) httpCode = status.Code return } if destURL.Scheme == "" { // If no scheme was present in location, url.Parse sometimes mistakes // hosts for paths. destURL.Host = location } destURL.Path = rest destURL.RawQuery = req.URL.RawQuery newReq, err := http.NewRequest(req.Method, destURL.String(), req.Body) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) 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, destURL) { return } proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: destURL.Host}) proxy.Transport = &proxyTransport{ proxyScheme: req.URL.Scheme, proxyHost: req.URL.Host, proxyPathPrepend: requestInfo.URLPath(), } proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) }
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { namespace, kind, parts, err := KindAndNamespace(req) if err != nil { notFound(w, req) return } ctx := api.WithNamespace(api.NewContext(), namespace) if len(parts) < 2 { notFound(w, req) return } id := parts[1] rest := "" if len(parts) > 2 { proxyParts := parts[2:] rest = strings.Join(proxyParts, "/") } storage, ok := r.storage[kind] if !ok { httplog.LogOf(req, w).Addf("'%v' has no storage object", kind) notFound(w, req) return } redirector, ok := storage.(Redirector) if !ok { httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind) errorJSON(errors.NewMethodNotSupported(kind, "proxy"), r.codec, w) return } location, 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) return } if location == "" { httplog.LogOf(req, w).Addf("ResourceLocation for %v returned ''", id) notFound(w, req) return } destURL, err := url.Parse(location) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) return } if destURL.Scheme == "" { // If no scheme was present in location, url.Parse sometimes mistakes // hosts for paths. destURL.Host = location } destURL.Path = rest destURL.RawQuery = req.URL.RawQuery newReq, err := http.NewRequest(req.Method, destURL.String(), req.Body) if err != nil { status := errToAPIStatus(err) writeJSON(status.Code, r.codec, status, w) notFound(w, req) return } newReq.Header = req.Header proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: destURL.Host}) proxy.Transport = &proxyTransport{ proxyScheme: req.URL.Scheme, proxyHost: req.URL.Host, proxyPathPrepend: path.Join(r.prefix, "ns", namespace, kind, id), } proxy.FlushInterval = 200 * time.Millisecond proxy.ServeHTTP(w, newReq) }
// handleRESTStorage is the main dispatcher for a storage object. It switches on the HTTP method, and then // on path length, according to the following table: // Method Path Action // GET /foo list // GET /foo/bar get 'bar' // POST /foo create // PUT /foo/bar update 'bar' // DELETE /foo/bar delete 'bar' // Returns 404 if the method/pattern doesn't match one of these entries // The s accepts several query parameters: // sync=[false|true] Synchronous request (only applies to create, update, delete operations) // timeout=<duration> Timeout for synchronous requests, only applies if sync=true // labels=<label-selector> Used for filtering list operations func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage, namespace, kind string) { ctx := api.WithNamespace(api.NewContext(), namespace) sync := req.URL.Query().Get("sync") == "true" timeout := parseTimeout(req.URL.Query().Get("timeout")) switch req.Method { case "GET": switch len(parts) { case 1: label, err := labels.ParseSelector(req.URL.Query().Get("labels")) if err != nil { errorJSON(err, h.codec, w) return } field, err := labels.ParseSelector(req.URL.Query().Get("fields")) if err != nil { errorJSON(err, h.codec, w) return } lister, ok := storage.(RESTLister) if !ok { errorJSON(errors.NewMethodNotSupported(kind, "list"), h.codec, w) return } list, err := lister.List(ctx, label, field) if err != nil { errorJSON(err, h.codec, w) return } if err := h.setSelfLink(list, req); err != nil { errorJSON(err, h.codec, w) return } writeJSON(http.StatusOK, h.codec, list, w) case 2: getter, ok := storage.(RESTGetter) if !ok { errorJSON(errors.NewMethodNotSupported(kind, "get"), h.codec, w) return } item, err := getter.Get(ctx, parts[1]) if err != nil { errorJSON(err, h.codec, w) return } if err := h.setSelfLink(item, req); err != nil { errorJSON(err, h.codec, w) return } writeJSON(http.StatusOK, h.codec, item, w) default: notFound(w, req) } case "POST": if len(parts) != 1 { notFound(w, req) return } creater, ok := storage.(RESTCreater) if !ok { errorJSON(errors.NewMethodNotSupported(kind, "create"), h.codec, w) return } body, err := readBody(req) if err != nil { errorJSON(err, h.codec, w) return } obj := storage.New() err = h.codec.DecodeInto(body, obj) if err != nil { errorJSON(err, h.codec, w) return } // invoke admission control err = h.admissionControl.Admit(admission.NewAttributesRecord(obj, namespace, parts[0], "CREATE")) if err != nil { errorJSON(err, h.codec, w) return } out, err := creater.Create(ctx, obj) if err != nil { errorJSON(err, h.codec, w) return } op := h.createOperation(out, sync, timeout, curry(h.setSelfLinkAddName, req)) h.finishReq(op, req, w) case "DELETE": if len(parts) != 2 { notFound(w, req) return } deleter, ok := storage.(RESTDeleter) if !ok { errorJSON(errors.NewMethodNotSupported(kind, "delete"), h.codec, w) return } // invoke admission control err := h.admissionControl.Admit(admission.NewAttributesRecord(nil, namespace, parts[0], "DELETE")) if err != nil { errorJSON(err, h.codec, w) return } out, err := deleter.Delete(ctx, parts[1]) if err != nil { errorJSON(err, h.codec, w) return } op := h.createOperation(out, sync, timeout, nil) h.finishReq(op, req, w) case "PUT": if len(parts) != 2 { notFound(w, req) return } updater, ok := storage.(RESTUpdater) if !ok { errorJSON(errors.NewMethodNotSupported(kind, "create"), h.codec, w) return } body, err := readBody(req) if err != nil { errorJSON(err, h.codec, w) return } obj := storage.New() err = h.codec.DecodeInto(body, obj) if err != nil { errorJSON(err, h.codec, w) return } // invoke admission control err = h.admissionControl.Admit(admission.NewAttributesRecord(obj, namespace, parts[0], "UPDATE")) if err != nil { errorJSON(err, h.codec, w) return } out, err := updater.Update(ctx, obj) if err != nil { errorJSON(err, h.codec, w) return } op := h.createOperation(out, sync, timeout, curry(h.setSelfLink, req)) h.finishReq(op, req, w) default: notFound(w, req) } }
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 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, roundTripper, 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 roundTripper == nil && r.dial != nil { glog.V(5).Infof("[%x: %v] making a dial-only transport...", proxyHandlerTraceID, req.URL) roundTripper = &http.Transport{Dial: r.dial} } else 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 { 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, roundTripper) { return } // Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/" // This is essentially a hack for https://github.com/GoogleCloudPlatform/kubernetes/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 } 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 reriting 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) }