| /* |
| Copyright 2015 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package http |
| |
| import ( |
| "crypto/tls" |
| "errors" |
| "fmt" |
| "net/http" |
| "time" |
| |
| utilnet "k8s.io/apimachinery/pkg/util/net" |
| "k8s.io/kubernetes/pkg/probe" |
| |
| "k8s.io/klog/v2" |
| utilio "k8s.io/utils/io" |
| ) |
| |
| const ( |
| maxRespBodyLength = 10 * 1 << 10 // 10KB |
| ) |
| |
| // New creates Prober that will skip TLS verification while probing. |
| // followNonLocalRedirects configures whether the prober should follow redirects to a different hostname. |
| // If disabled, redirects to other hosts will trigger a warning result. |
| func New(followNonLocalRedirects bool) Prober { |
| tlsConfig := &tls.Config{InsecureSkipVerify: true} |
| return NewWithTLSConfig(tlsConfig, followNonLocalRedirects) |
| } |
| |
| // NewWithTLSConfig takes tls config as parameter. |
| // followNonLocalRedirects configures whether the prober should follow redirects to a different hostname. |
| // If disabled, redirects to other hosts will trigger a warning result. |
| func NewWithTLSConfig(config *tls.Config, followNonLocalRedirects bool) Prober { |
| // We do not want the probe use node's local proxy set. |
| transport := utilnet.SetTransportDefaults( |
| &http.Transport{ |
| TLSClientConfig: config, |
| DisableKeepAlives: true, |
| Proxy: http.ProxyURL(nil), |
| DisableCompression: true, // removes Accept-Encoding header |
| // DialContext creates unencrypted TCP connections |
| // and is also used by the transport for HTTPS connection |
| DialContext: probe.ProbeDialer().DialContext, |
| }) |
| |
| return httpProber{transport, followNonLocalRedirects} |
| } |
| |
| // Prober is an interface that defines the Probe function for doing HTTP readiness/liveness checks. |
| type Prober interface { |
| Probe(req *http.Request, timeout time.Duration) (probe.Result, string, error) |
| } |
| |
| type httpProber struct { |
| transport *http.Transport |
| followNonLocalRedirects bool |
| } |
| |
| // Probe returns a ProbeRunner capable of running an HTTP check. |
| func (pr httpProber) Probe(req *http.Request, timeout time.Duration) (probe.Result, string, error) { |
| client := &http.Client{ |
| Timeout: timeout, |
| Transport: pr.transport, |
| CheckRedirect: RedirectChecker(pr.followNonLocalRedirects), |
| } |
| return DoHTTPProbe(req, client) |
| } |
| |
| // GetHTTPInterface is an interface for making HTTP requests, that returns a response and error. |
| type GetHTTPInterface interface { |
| Do(req *http.Request) (*http.Response, error) |
| } |
| |
| // DoHTTPProbe checks if a GET request to the url succeeds. |
| // If the HTTP response code is successful (i.e. 400 > code >= 200), it returns Success. |
| // If the HTTP response code is unsuccessful or HTTP communication fails, it returns Failure. |
| // This is exported because some other packages may want to do direct HTTP probes. |
| func DoHTTPProbe(req *http.Request, client GetHTTPInterface) (probe.Result, string, error) { |
| url := req.URL |
| headers := req.Header |
| res, err := client.Do(req) |
| if err != nil { |
| // Convert errors into failures to catch timeouts. |
| return probe.Failure, err.Error(), nil |
| } |
| defer res.Body.Close() |
| b, err := utilio.ReadAtMost(res.Body, maxRespBodyLength) |
| if err != nil { |
| if err == utilio.ErrLimitReached { |
| klog.V(4).Infof("Non fatal body truncation for %s, Response: %v", url.String(), *res) |
| } else { |
| return probe.Failure, "", err |
| } |
| } |
| body := string(b) |
| if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest { |
| if res.StatusCode >= http.StatusMultipleChoices { // Redirect |
| klog.V(4).Infof("Probe terminated redirects for %s, Response: %v", url.String(), *res) |
| return probe.Warning, fmt.Sprintf("Probe terminated redirects, Response body: %v", body), nil |
| } |
| klog.V(4).Infof("Probe succeeded for %s, Response: %v", url.String(), *res) |
| return probe.Success, body, nil |
| } |
| klog.V(4).Infof("Probe failed for %s with request headers %v, response body: %v", url.String(), headers, body) |
| // Note: Until https://issue.k8s.io/99425 is addressed, this user-facing failure message must not contain the response body. |
| failureMsg := fmt.Sprintf("HTTP probe failed with statuscode: %d", res.StatusCode) |
| return probe.Failure, failureMsg, nil |
| } |
| |
| // RedirectChecker returns a function that can be used to check HTTP redirects. |
| func RedirectChecker(followNonLocalRedirects bool) func(*http.Request, []*http.Request) error { |
| if followNonLocalRedirects { |
| return nil // Use the default http client checker. |
| } |
| |
| return func(req *http.Request, via []*http.Request) error { |
| if req.URL.Hostname() != via[0].URL.Hostname() { |
| return http.ErrUseLastResponse |
| } |
| // Default behavior: stop after 10 redirects. |
| if len(via) >= 10 { |
| return errors.New("stopped after 10 redirects") |
| } |
| return nil |
| } |
| } |