PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/pkg/apiserver/proxy.go

https://gitlab.com/hasura/kubernetes
Go | 268 lines | 205 code | 27 blank | 36 comment | 53 complexity | 71d6ffd82e338e5a607bff0a3b1532c7 MD5 | raw file
  1. /*
  2. Copyright 2014 The Kubernetes Authors All rights reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package apiserver
  14. import (
  15. "io"
  16. "math/rand"
  17. "net/http"
  18. "net/http/httputil"
  19. "net/url"
  20. "path"
  21. "strings"
  22. "time"
  23. "k8s.io/kubernetes/pkg/api"
  24. "k8s.io/kubernetes/pkg/api/errors"
  25. "k8s.io/kubernetes/pkg/api/rest"
  26. "k8s.io/kubernetes/pkg/api/unversioned"
  27. "k8s.io/kubernetes/pkg/apiserver/metrics"
  28. "k8s.io/kubernetes/pkg/httplog"
  29. "k8s.io/kubernetes/pkg/runtime"
  30. "k8s.io/kubernetes/pkg/util/httpstream"
  31. "k8s.io/kubernetes/pkg/util/net"
  32. proxyutil "k8s.io/kubernetes/pkg/util/proxy"
  33. "github.com/golang/glog"
  34. )
  35. // ProxyHandler provides a http.Handler which will proxy traffic to locations
  36. // specified by items implementing Redirector.
  37. type ProxyHandler struct {
  38. prefix string
  39. storage map[string]rest.Storage
  40. serializer runtime.NegotiatedSerializer
  41. context api.RequestContextMapper
  42. requestInfoResolver *RequestInfoResolver
  43. }
  44. func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  45. proxyHandlerTraceID := rand.Int63()
  46. var verb string
  47. var apiResource string
  48. var httpCode int
  49. reqStart := time.Now()
  50. defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), httpCode, reqStart)
  51. requestInfo, err := r.requestInfoResolver.GetRequestInfo(req)
  52. if err != nil || !requestInfo.IsResourceRequest {
  53. notFound(w, req)
  54. httpCode = http.StatusNotFound
  55. return
  56. }
  57. verb = requestInfo.Verb
  58. namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
  59. ctx, ok := r.context.Get(req)
  60. if !ok {
  61. ctx = api.NewContext()
  62. }
  63. ctx = api.WithNamespace(ctx, namespace)
  64. if len(parts) < 2 {
  65. notFound(w, req)
  66. httpCode = http.StatusNotFound
  67. return
  68. }
  69. id := parts[1]
  70. remainder := ""
  71. if len(parts) > 2 {
  72. proxyParts := parts[2:]
  73. remainder = strings.Join(proxyParts, "/")
  74. if strings.HasSuffix(req.URL.Path, "/") {
  75. // The original path had a trailing slash, which has been stripped
  76. // by KindAndNamespace(). We should add it back because some
  77. // servers (like etcd) require it.
  78. remainder = remainder + "/"
  79. }
  80. }
  81. storage, ok := r.storage[resource]
  82. if !ok {
  83. httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
  84. notFound(w, req)
  85. httpCode = http.StatusNotFound
  86. return
  87. }
  88. apiResource = resource
  89. gv := unversioned.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
  90. redirector, ok := storage.(rest.Redirector)
  91. if !ok {
  92. httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
  93. httpCode = errorNegotiated(errors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req)
  94. return
  95. }
  96. location, roundTripper, err := redirector.ResourceLocation(ctx, id)
  97. if err != nil {
  98. httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err)
  99. httpCode = errorNegotiated(err, r.serializer, gv, w, req)
  100. return
  101. }
  102. if location == nil {
  103. httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
  104. notFound(w, req)
  105. httpCode = http.StatusNotFound
  106. return
  107. }
  108. if roundTripper != nil {
  109. glog.V(5).Infof("[%x: %v] using transport %T...", proxyHandlerTraceID, req.URL, roundTripper)
  110. }
  111. // Default to http
  112. if location.Scheme == "" {
  113. location.Scheme = "http"
  114. }
  115. // Add the subpath
  116. if len(remainder) > 0 {
  117. location.Path = singleJoiningSlash(location.Path, remainder)
  118. }
  119. // Start with anything returned from the storage, and add the original request's parameters
  120. values := location.Query()
  121. for k, vs := range req.URL.Query() {
  122. for _, v := range vs {
  123. values.Add(k, v)
  124. }
  125. }
  126. location.RawQuery = values.Encode()
  127. newReq, err := http.NewRequest(req.Method, location.String(), req.Body)
  128. if err != nil {
  129. httpCode = errorNegotiated(err, r.serializer, gv, w, req)
  130. return
  131. }
  132. httpCode = http.StatusOK
  133. newReq.Header = req.Header
  134. newReq.ContentLength = req.ContentLength
  135. // Copy the TransferEncoding is for future-proofing. Currently Go only supports "chunked" and
  136. // it can determine the TransferEncoding based on ContentLength and the Body.
  137. newReq.TransferEncoding = req.TransferEncoding
  138. // TODO convert this entire proxy to an UpgradeAwareProxy similar to
  139. // https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go.
  140. // That proxy needs to be modified to support multiple backends, not just 1.
  141. if r.tryUpgrade(w, req, newReq, location, roundTripper, gv) {
  142. return
  143. }
  144. // Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/"
  145. // This is essentially a hack for http://issue.k8s.io/4958.
  146. // Note: Keep this code after tryUpgrade to not break that flow.
  147. if len(parts) == 2 && !strings.HasSuffix(req.URL.Path, "/") {
  148. var queryPart string
  149. if len(req.URL.RawQuery) > 0 {
  150. queryPart = "?" + req.URL.RawQuery
  151. }
  152. w.Header().Set("Location", req.URL.Path+"/"+queryPart)
  153. w.WriteHeader(http.StatusMovedPermanently)
  154. return
  155. }
  156. start := time.Now()
  157. glog.V(4).Infof("[%x] Beginning proxy %s...", proxyHandlerTraceID, req.URL)
  158. defer func() {
  159. glog.V(4).Infof("[%x] Proxy %v finished %v.", proxyHandlerTraceID, req.URL, time.Now().Sub(start))
  160. }()
  161. proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host})
  162. alreadyRewriting := false
  163. if roundTripper != nil {
  164. _, alreadyRewriting = roundTripper.(*proxyutil.Transport)
  165. glog.V(5).Infof("[%x] Not making a reriting transport for proxy %s...", proxyHandlerTraceID, req.URL)
  166. }
  167. if !alreadyRewriting {
  168. glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL)
  169. prepend := path.Join(r.prefix, resource, id)
  170. if len(namespace) > 0 {
  171. prepend = path.Join(r.prefix, "namespaces", namespace, resource, id)
  172. }
  173. pTransport := &proxyutil.Transport{
  174. Scheme: req.URL.Scheme,
  175. Host: req.URL.Host,
  176. PathPrepend: prepend,
  177. RoundTripper: roundTripper,
  178. }
  179. roundTripper = pTransport
  180. }
  181. proxy.Transport = roundTripper
  182. proxy.FlushInterval = 200 * time.Millisecond
  183. proxy.ServeHTTP(w, newReq)
  184. }
  185. // tryUpgrade returns true if the request was handled.
  186. func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Request, location *url.URL, transport http.RoundTripper, gv unversioned.GroupVersion) bool {
  187. if !httpstream.IsUpgradeRequest(req) {
  188. return false
  189. }
  190. backendConn, err := proxyutil.DialURL(location, transport)
  191. if err != nil {
  192. errorNegotiated(err, r.serializer, gv, w, req)
  193. return true
  194. }
  195. defer backendConn.Close()
  196. // TODO should we use _ (a bufio.ReadWriter) instead of requestHijackedConn
  197. // when copying between the client and the backend? Docker doesn't when they
  198. // hijack, just for reference...
  199. requestHijackedConn, _, err := w.(http.Hijacker).Hijack()
  200. if err != nil {
  201. errorNegotiated(err, r.serializer, gv, w, req)
  202. return true
  203. }
  204. defer requestHijackedConn.Close()
  205. if err = newReq.Write(backendConn); err != nil {
  206. errorNegotiated(err, r.serializer, gv, w, req)
  207. return true
  208. }
  209. done := make(chan struct{}, 2)
  210. go func() {
  211. _, err := io.Copy(backendConn, requestHijackedConn)
  212. if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
  213. glog.Errorf("Error proxying data from client to backend: %v", err)
  214. }
  215. done <- struct{}{}
  216. }()
  217. go func() {
  218. _, err := io.Copy(requestHijackedConn, backendConn)
  219. if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
  220. glog.Errorf("Error proxying data from backend to client: %v", err)
  221. }
  222. done <- struct{}{}
  223. }()
  224. <-done
  225. return true
  226. }
  227. // borrowed from net/http/httputil/reverseproxy.go
  228. func singleJoiningSlash(a, b string) string {
  229. aslash := strings.HasSuffix(a, "/")
  230. bslash := strings.HasPrefix(b, "/")
  231. switch {
  232. case aslash && bslash:
  233. return a + b[1:]
  234. case !aslash && !bslash:
  235. return a + "/" + b
  236. }
  237. return a + b
  238. }