github.com/google/cloudprober@v0.11.3/probes/http/request.go (about)

     1  // Copyright 2019-2020 The Cloudprober Authors.
     2  //
     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  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package http
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"net"
    21  	"net/http"
    22  	"strings"
    23  
    24  	"github.com/google/cloudprober/targets/endpoint"
    25  )
    26  
    27  const relURLLabel = "relative_url"
    28  
    29  // requestBody encapsulates the request body and implements the io.Reader()
    30  // interface.
    31  type requestBody struct {
    32  	b []byte
    33  }
    34  
    35  // Read implements the io.Reader interface. Instead of using buffered read,
    36  // it simply copies the bytes to the provided slice in one go (depending on
    37  // the input slice capacity) and returns io.EOF. Buffered reads require
    38  // resetting the buffer before re-use, restricting our ability to use the
    39  // request object concurrently.
    40  func (rb *requestBody) Read(p []byte) (int, error) {
    41  	return copy(p, rb.b), io.EOF
    42  }
    43  
    44  // resolveFunc resolves the given host for the IP version.
    45  // This type is mainly used for testing. For all other cases, a nil function
    46  // should be passed to the httpRequestForTarget function.
    47  type resolveFunc func(host string, ipVer int) (net.IP, error)
    48  
    49  func hostWithPort(host string, port int) string {
    50  	if port == 0 {
    51  		return host
    52  	}
    53  	return fmt.Sprintf("%s:%d", host, port)
    54  }
    55  
    56  // hostHeaderForTarget computes request's Host header for a target.
    57  //  - If host header is set in the probe, it overrides everything else.
    58  //  - If target's fqdn is provided in its labels, use that along with the port.
    59  //  - Finally, use target's name with port.
    60  func hostHeaderForTarget(target endpoint.Endpoint, probeHostHeader string, port int) string {
    61  	if probeHostHeader != "" {
    62  		return probeHostHeader
    63  	}
    64  
    65  	if target.Labels["fqdn"] != "" {
    66  		return hostWithPort(target.Labels["fqdn"], port)
    67  	}
    68  
    69  	return hostWithPort(target.Name, port)
    70  }
    71  
    72  func urlHostForTarget(target endpoint.Endpoint) string {
    73  	if target.Labels["fqdn"] != "" {
    74  		return target.Labels["fqdn"]
    75  	}
    76  
    77  	return target.Name
    78  }
    79  
    80  func relURLForTarget(target endpoint.Endpoint, probeURL string) string {
    81  	if probeURL != "" {
    82  		return probeURL
    83  	}
    84  
    85  	if target.Labels[relURLLabel] != "" {
    86  		return target.Labels[relURLLabel]
    87  	}
    88  
    89  	return ""
    90  }
    91  
    92  func (p *Probe) httpRequestForTarget(target endpoint.Endpoint, resolveF resolveFunc) *http.Request {
    93  	// Prepare HTTP.Request for Client.Do
    94  	port := int(p.c.GetPort())
    95  	// If port is not configured explicitly, use target's port if available.
    96  	if port == 0 {
    97  		port = target.Port
    98  	}
    99  
   100  	urlHost := urlHostForTarget(target)
   101  
   102  	if p.c.GetResolveFirst() {
   103  		if resolveF == nil {
   104  			resolveF = p.opts.Targets.Resolve
   105  		}
   106  
   107  		ip, err := resolveF(target.Name, p.opts.IPVersion)
   108  		if err != nil {
   109  			p.l.Error("target: ", target.Name, ", resolve error: ", err.Error())
   110  			return nil
   111  		}
   112  		urlHost = ip.String()
   113  	}
   114  
   115  	// Put square brackets around literal IPv6 hosts. This is the same logic as
   116  	// net.JoinHostPort, but we cannot use net.JoinHostPort as it works only for
   117  	// non default ports.
   118  	if strings.IndexByte(urlHost, ':') >= 0 {
   119  		urlHost = "[" + urlHost + "]"
   120  	}
   121  
   122  	url := fmt.Sprintf("%s://%s%s", p.protocol, hostWithPort(urlHost, port), relURLForTarget(target, p.url))
   123  
   124  	// Prepare request body
   125  	var body io.Reader
   126  	if len(p.requestBody) > 0 {
   127  		body = &requestBody{p.requestBody}
   128  	}
   129  	req, err := http.NewRequest(p.method, url, body)
   130  	if err != nil {
   131  		p.l.Error("target: ", target.Name, ", error creating HTTP request: ", err.Error())
   132  		return nil
   133  	}
   134  
   135  	var probeHostHeader string
   136  	for _, header := range p.c.GetHeaders() {
   137  		if header.GetName() == "Host" {
   138  			probeHostHeader = header.GetValue()
   139  			continue
   140  		}
   141  		req.Header.Set(header.GetName(), header.GetValue())
   142  	}
   143  
   144  	// Host header is set by http.NewRequest based on the URL, update it based
   145  	// on various conditions.
   146  	req.Host = hostHeaderForTarget(target, probeHostHeader, port)
   147  
   148  	if p.bearerToken != "" {
   149  		req.Header.Set("Authorization", "Bearer "+p.bearerToken)
   150  	}
   151  
   152  	return req
   153  }