2 Copyright 2016 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
34 "golang.org/x/net/http2"
38 // JoinPreservingTrailingSlash does a path.Join of the specified elements,
39 // preserving any trailing slash on the last non-empty segment
40 func JoinPreservingTrailingSlash(elem ...string) string {
41 // do the basic path join
42 result := path.Join(elem...)
44 // find the last non-empty segment
45 for i := len(elem) - 1; i >= 0; i-- {
47 // if the last segment ended in a slash, ensure our result does as well
48 if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") {
58 // IsProbableEOF returns true if the given error resembles a connection termination
59 // scenario that would justify assuming that the watch is empty.
60 // These errors are what the Go http stack returns back to us which are general
61 // connection closure errors (strongly correlated) and callers that need to
62 // differentiate probable errors in connection behavior between normal "this is
63 // disconnected" should use the method.
64 func IsProbableEOF(err error) bool {
68 if uerr, ok := err.(*url.Error); ok {
74 case err.Error() == "http: can't write HTTP request on broken connection":
76 case strings.Contains(err.Error(), "connection reset by peer"):
78 case strings.Contains(strings.ToLower(err.Error()), "use of closed network connection"):
84 var defaultTransport = http.DefaultTransport.(*http.Transport)
86 // SetOldTransportDefaults applies the defaults from http.DefaultTransport
87 // for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
88 func SetOldTransportDefaults(t *http.Transport) *http.Transport {
89 if t.Proxy == nil || isDefault(t.Proxy) {
90 // http.ProxyFromEnvironment doesn't respect CIDRs and that makes it impossible to exclude things like pod and service IPs from proxy settings
91 // ProxierWithNoProxyCIDR allows CIDR rules in NO_PROXY
92 t.Proxy = NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
94 // If no custom dialer is set, use the default context dialer
95 if t.DialContext == nil && t.Dial == nil {
96 t.DialContext = defaultTransport.DialContext
98 if t.TLSHandshakeTimeout == 0 {
99 t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout
104 // SetTransportDefaults applies the defaults from http.DefaultTransport
105 // for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
106 func SetTransportDefaults(t *http.Transport) *http.Transport {
107 t = SetOldTransportDefaults(t)
108 // Allow clients to disable http2 if needed.
109 if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {
110 klog.Infof("HTTP2 has been explicitly disabled")
112 if err := http2.ConfigureTransport(t); err != nil {
113 klog.Warningf("Transport failed http2 configuration: %v", err)
119 type RoundTripperWrapper interface {
121 WrappedRoundTripper() http.RoundTripper
124 type DialFunc func(ctx context.Context, net, addr string) (net.Conn, error)
126 func DialerFor(transport http.RoundTripper) (DialFunc, error) {
127 if transport == nil {
131 switch transport := transport.(type) {
132 case *http.Transport:
133 // transport.DialContext takes precedence over transport.Dial
134 if transport.DialContext != nil {
135 return transport.DialContext, nil
137 // adapt transport.Dial to the DialWithContext signature
138 if transport.Dial != nil {
139 return func(ctx context.Context, net, addr string) (net.Conn, error) {
140 return transport.Dial(net, addr)
143 // otherwise return nil
145 case RoundTripperWrapper:
146 return DialerFor(transport.WrappedRoundTripper())
148 return nil, fmt.Errorf("unknown transport type: %T", transport)
152 type TLSClientConfigHolder interface {
153 TLSClientConfig() *tls.Config
156 func TLSClientConfig(transport http.RoundTripper) (*tls.Config, error) {
157 if transport == nil {
161 switch transport := transport.(type) {
162 case *http.Transport:
163 return transport.TLSClientConfig, nil
164 case TLSClientConfigHolder:
165 return transport.TLSClientConfig(), nil
166 case RoundTripperWrapper:
167 return TLSClientConfig(transport.WrappedRoundTripper())
169 return nil, fmt.Errorf("unknown transport type: %T", transport)
173 func FormatURL(scheme string, host string, port int, path string) *url.URL {
176 Host: net.JoinHostPort(host, strconv.Itoa(port)),
181 func GetHTTPClient(req *http.Request) string {
182 if ua := req.UserAgent(); len(ua) != 0 {
188 // SourceIPs splits the comma separated X-Forwarded-For header or returns the X-Real-Ip header or req.RemoteAddr,
189 // in that order, ignoring invalid IPs. It returns nil if all of these are empty or invalid.
190 func SourceIPs(req *http.Request) []net.IP {
192 // First check the X-Forwarded-For header for requests via proxy.
193 hdrForwardedFor := hdr.Get("X-Forwarded-For")
194 forwardedForIPs := []net.IP{}
195 if hdrForwardedFor != "" {
196 // X-Forwarded-For can be a csv of IPs in case of multiple proxies.
197 // Use the first valid one.
198 parts := strings.Split(hdrForwardedFor, ",")
199 for _, part := range parts {
200 ip := net.ParseIP(strings.TrimSpace(part))
202 forwardedForIPs = append(forwardedForIPs, ip)
206 if len(forwardedForIPs) > 0 {
207 return forwardedForIPs
210 // Try the X-Real-Ip header.
211 hdrRealIp := hdr.Get("X-Real-Ip")
213 ip := net.ParseIP(hdrRealIp)
219 // Fallback to Remote Address in request, which will give the correct client IP when there is no proxy.
220 // Remote Address in Go's HTTP server is in the form host:port so we need to split that first.
221 host, _, err := net.SplitHostPort(req.RemoteAddr)
223 if remoteIP := net.ParseIP(host); remoteIP != nil {
224 return []net.IP{remoteIP}
228 // Fallback if Remote Address was just IP.
229 if remoteIP := net.ParseIP(req.RemoteAddr); remoteIP != nil {
230 return []net.IP{remoteIP}
236 // Extracts and returns the clients IP from the given request.
237 // Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order.
238 // Returns nil if none of them are set or is set to an invalid value.
239 func GetClientIP(req *http.Request) net.IP {
240 ips := SourceIPs(req)
247 // Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's
248 // IP address to the X-Forwarded-For chain.
249 func AppendForwardedForHeader(req *http.Request) {
250 // Copied from net/http/httputil/reverseproxy.go:
251 if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
252 // If we aren't the first proxy retain prior
253 // X-Forwarded-For information as a comma+space
254 // separated list and fold multiple headers into one.
255 if prior, ok := req.Header["X-Forwarded-For"]; ok {
256 clientIP = strings.Join(prior, ", ") + ", " + clientIP
258 req.Header.Set("X-Forwarded-For", clientIP)
262 var defaultProxyFuncPointer = fmt.Sprintf("%p", http.ProxyFromEnvironment)
264 // isDefault checks to see if the transportProxierFunc is pointing to the default one
265 func isDefault(transportProxier func(*http.Request) (*url.URL, error)) bool {
266 transportProxierPointer := fmt.Sprintf("%p", transportProxier)
267 return transportProxierPointer == defaultProxyFuncPointer
270 // NewProxierWithNoProxyCIDR constructs a Proxier function that respects CIDRs in NO_PROXY and delegates if
271 // no matching CIDRs are found
272 func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) {
273 // we wrap the default method, so we only need to perform our check if the NO_PROXY (or no_proxy) envvar has a CIDR in it
274 noProxyEnv := os.Getenv("NO_PROXY")
275 if noProxyEnv == "" {
276 noProxyEnv = os.Getenv("no_proxy")
278 noProxyRules := strings.Split(noProxyEnv, ",")
280 cidrs := []*net.IPNet{}
281 for _, noProxyRule := range noProxyRules {
282 _, cidr, _ := net.ParseCIDR(noProxyRule)
284 cidrs = append(cidrs, cidr)
292 return func(req *http.Request) (*url.URL, error) {
293 ip := net.ParseIP(req.URL.Hostname())
298 for _, cidr := range cidrs {
299 if cidr.Contains(ip) {
308 // DialerFunc implements Dialer for the provided function.
309 type DialerFunc func(req *http.Request) (net.Conn, error)
311 func (fn DialerFunc) Dial(req *http.Request) (net.Conn, error) {
315 // Dialer dials a host and writes a request to it.
316 type Dialer interface {
317 // Dial connects to the host specified by req's URL, writes the request to the connection, and
318 // returns the opened net.Conn.
319 Dial(req *http.Request) (net.Conn, error)
322 // ConnectWithRedirects uses dialer to send req, following up to 10 redirects (relative to
323 // originalLocation). It returns the opened net.Conn and the raw response bytes.
324 // If requireSameHostRedirects is true, only redirects to the same host are permitted.
325 func ConnectWithRedirects(originalMethod string, originalLocation *url.URL, header http.Header, originalBody io.Reader, dialer Dialer, requireSameHostRedirects bool) (net.Conn, []byte, error) {
327 maxRedirects = 9 // Fail on the 10th redirect
328 maxResponseSize = 16384 // play it safe to allow the potential for lots of / large headers
332 location = originalLocation
333 method = originalMethod
334 intermediateConn net.Conn
335 rawResponse = bytes.NewBuffer(make([]byte, 0, 256))
340 if intermediateConn != nil {
341 intermediateConn.Close()
346 for redirects := 0; ; redirects++ {
347 if redirects > maxRedirects {
348 return nil, nil, fmt.Errorf("too many redirects (%d)", redirects)
351 req, err := http.NewRequest(method, location.String(), body)
358 intermediateConn, err = dialer.Dial(req)
363 // Peek at the backend response.
365 respReader := bufio.NewReader(io.TeeReader(
366 io.LimitReader(intermediateConn, maxResponseSize), // Don't read more than maxResponseSize bytes.
367 rawResponse)) // Save the raw response.
368 resp, err := http.ReadResponse(respReader, nil)
370 // Unable to read the backend response; let the client handle it.
371 klog.Warningf("Error reading backend response: %v", err)
375 switch resp.StatusCode {
376 case http.StatusFound:
377 // Redirect, continue.
383 // Redirected requests switch to "GET" according to the HTTP spec:
384 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3
386 // don't send a body when following redirects
389 resp.Body.Close() // not used
391 // Prepare to follow the redirect.
392 redirectStr := resp.Header.Get("Location")
393 if redirectStr == "" {
394 return nil, nil, fmt.Errorf("%d response missing Location header", resp.StatusCode)
396 // We have to parse relative to the current location, NOT originalLocation. For example,
397 // if we request http://foo.com/a and get back "http://bar.com/b", the result should be
398 // http://bar.com/b. If we then make that request and get back a redirect to "/c", the result
399 // should be http://bar.com/c, not http://foo.com/c.
400 location, err = location.Parse(redirectStr)
402 return nil, nil, fmt.Errorf("malformed Location header: %v", err)
405 // Only follow redirects to the same host. Otherwise, propagate the redirect response back.
406 if requireSameHostRedirects && location.Hostname() != originalLocation.Hostname() {
410 // Reset the connection.
411 intermediateConn.Close()
412 intermediateConn = nil
415 connToReturn := intermediateConn
416 intermediateConn = nil // Don't close the connection when we return it.
417 return connToReturn, rawResponse.Bytes(), nil
420 // CloneRequest creates a shallow copy of the request along with a deep copy of the Headers.
421 func CloneRequest(req *http.Request) *http.Request {
422 r := new(http.Request)
428 r.Header = CloneHeader(req.Header)
433 // CloneHeader creates a deep copy of an http.Header.
434 func CloneHeader(in http.Header) http.Header {
435 out := make(http.Header, len(in))
436 for key, values := range in {
437 newValues := make([]string, len(values))
438 copy(newValues, values)