Example #1
0
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
}
Example #2
0
// 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)
	}
}
Example #3
0
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)
}
Example #4
0
// 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)
	}
}
Example #5
0
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)
}
Example #6
0
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)
	}
}
Example #8
0
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)
}