mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Merge pull request #4778 from lavalamp/fix7
Construct path correctly in proxy
This commit is contained in:
commit
32694a770c
@ -19,6 +19,7 @@ package apiserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
@ -214,8 +215,22 @@ type APIRequestInfo struct {
|
|||||||
Kind string
|
Kind string
|
||||||
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
|
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
|
||||||
Name string
|
Name string
|
||||||
// Parts are the path parts for the request relative to /{resource}/{name}
|
// Parts are the path parts for the request, always starting with /{resource}/{name}
|
||||||
Parts []string
|
Parts []string
|
||||||
|
// Raw is the unparsed form of everything other than parts.
|
||||||
|
// Raw + Parts = complete URL path
|
||||||
|
Raw []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLPath returns the URL path for this request, including /{resource}/{name} if present but nothing
|
||||||
|
// following that.
|
||||||
|
func (info APIRequestInfo) URLPath() string {
|
||||||
|
p := info.Parts
|
||||||
|
if n := len(p); n > 2 {
|
||||||
|
// Only take resource and name
|
||||||
|
p = p[:2]
|
||||||
|
}
|
||||||
|
return path.Join("/", path.Join(info.Raw...), path.Join(p...))
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIRequestInfoResolver struct {
|
type APIRequestInfoResolver struct {
|
||||||
@ -247,9 +262,11 @@ type APIRequestInfoResolver struct {
|
|||||||
// /api/{version}/*
|
// /api/{version}/*
|
||||||
// /api/{version}/*
|
// /api/{version}/*
|
||||||
func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) {
|
func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) {
|
||||||
requestInfo := APIRequestInfo{}
|
requestInfo := APIRequestInfo{
|
||||||
|
Raw: splitPath(req.URL.Path),
|
||||||
|
}
|
||||||
|
|
||||||
currentParts := splitPath(req.URL.Path)
|
currentParts := requestInfo.Raw
|
||||||
if len(currentParts) < 1 {
|
if len(currentParts) < 1 {
|
||||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
||||||
}
|
}
|
||||||
@ -322,6 +339,8 @@ func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIReques
|
|||||||
|
|
||||||
// parsing successful, so we now know the proper value for .Parts
|
// parsing successful, so we now know the proper value for .Parts
|
||||||
requestInfo.Parts = currentParts
|
requestInfo.Parts = currentParts
|
||||||
|
// Raw should have everything not in Parts
|
||||||
|
requestInfo.Raw = requestInfo.Raw[:len(requestInfo.Raw)-len(currentParts)]
|
||||||
|
|
||||||
// if there's another part remaining after the kind, then that's the resource name
|
// if there's another part remaining after the kind, then that's the resource name
|
||||||
if len(requestInfo.Parts) >= 2 {
|
if len(requestInfo.Parts) >= 2 {
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
@ -142,6 +143,9 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
|
if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
|
||||||
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
|
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
|
||||||
}
|
}
|
||||||
|
if e, a := strings.Split(successCase.url, "?")[0], apiRequestInfo.URLPath(); e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errorCases := map[string]string{
|
errorCases := map[string]string{
|
||||||
|
@ -182,64 +182,73 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
// TODO convert this entire proxy to an UpgradeAwareProxy similar to
|
// TODO convert this entire proxy to an UpgradeAwareProxy similar to
|
||||||
// https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go.
|
// 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.
|
// That proxy needs to be modified to support multiple backends, not just 1.
|
||||||
connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
|
if r.tryUpgrade(w, req, newReq, destURL) {
|
||||||
if strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) && len(req.Header.Get(httpstream.HeaderUpgrade)) > 0 {
|
return
|
||||||
//TODO support TLS? Doesn't look like proxyTransport does anything special ...
|
|
||||||
dialAddr := netutil.CanonicalAddr(destURL)
|
|
||||||
backendConn, err := net.Dial("tcp", dialAddr)
|
|
||||||
if err != nil {
|
|
||||||
status := errToAPIStatus(err)
|
|
||||||
writeJSON(status.Code, r.codec, status, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer backendConn.Close()
|
|
||||||
|
|
||||||
// TODO should we use _ (a bufio.ReadWriter) instead of requestHijackedConn
|
|
||||||
// when copying between the client and the backend? Docker doesn't when they
|
|
||||||
// hijack, just for reference...
|
|
||||||
requestHijackedConn, _, err := w.(http.Hijacker).Hijack()
|
|
||||||
if err != nil {
|
|
||||||
status := errToAPIStatus(err)
|
|
||||||
writeJSON(status.Code, r.codec, status, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer requestHijackedConn.Close()
|
|
||||||
|
|
||||||
if err = newReq.Write(backendConn); err != nil {
|
|
||||||
status := errToAPIStatus(err)
|
|
||||||
writeJSON(status.Code, r.codec, status, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{}, 2)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(backendConn, requestHijackedConn)
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
|
||||||
glog.Errorf("Error proxying data from client to backend: %v", err)
|
|
||||||
}
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(requestHijackedConn, backendConn)
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
|
||||||
glog.Errorf("Error proxying data from backend to client: %v", err)
|
|
||||||
}
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
<-done
|
|
||||||
} else {
|
|
||||||
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, resource, id),
|
|
||||||
}
|
|
||||||
proxy.FlushInterval = 200 * time.Millisecond
|
|
||||||
proxy.ServeHTTP(w, newReq)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryUpgrade returns true if the request was handled.
|
||||||
|
func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Request, destURL *url.URL) bool {
|
||||||
|
connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
|
||||||
|
if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || len(req.Header.Get(httpstream.HeaderUpgrade)) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
//TODO support TLS? Doesn't look like proxyTransport does anything special ...
|
||||||
|
dialAddr := netutil.CanonicalAddr(destURL)
|
||||||
|
backendConn, err := net.Dial("tcp", dialAddr)
|
||||||
|
if err != nil {
|
||||||
|
status := errToAPIStatus(err)
|
||||||
|
writeJSON(status.Code, r.codec, status, w)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
defer backendConn.Close()
|
||||||
|
|
||||||
|
// TODO should we use _ (a bufio.ReadWriter) instead of requestHijackedConn
|
||||||
|
// when copying between the client and the backend? Docker doesn't when they
|
||||||
|
// hijack, just for reference...
|
||||||
|
requestHijackedConn, _, err := w.(http.Hijacker).Hijack()
|
||||||
|
if err != nil {
|
||||||
|
status := errToAPIStatus(err)
|
||||||
|
writeJSON(status.Code, r.codec, status, w)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
defer requestHijackedConn.Close()
|
||||||
|
|
||||||
|
if err = newReq.Write(backendConn); err != nil {
|
||||||
|
status := errToAPIStatus(err)
|
||||||
|
writeJSON(status.Code, r.codec, status, w)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{}, 2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := io.Copy(backendConn, requestHijackedConn)
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
glog.Errorf("Error proxying data from client to backend: %v", err)
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := io.Copy(requestHijackedConn, backendConn)
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
glog.Errorf("Error proxying data from backend to client: %v", err)
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-done
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type proxyTransport struct {
|
type proxyTransport struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user