github.com/nsqio/nsq@v1.3.0/internal/http_api/api_request.go (about)

     1  package http_api
     2  
     3  import (
     4  	"crypto/tls"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"net/url"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  // A custom http.Transport with support for deadline timeouts
    17  func NewDeadlineTransport(connectTimeout time.Duration, requestTimeout time.Duration) *http.Transport {
    18  	// arbitrary values copied from http.DefaultTransport
    19  	transport := &http.Transport{
    20  		DialContext: (&net.Dialer{
    21  			Timeout:   connectTimeout,
    22  			KeepAlive: 30 * time.Second,
    23  			DualStack: true,
    24  		}).DialContext,
    25  		ResponseHeaderTimeout: requestTimeout,
    26  		MaxIdleConns:          100,
    27  		IdleConnTimeout:       90 * time.Second,
    28  		TLSHandshakeTimeout:   10 * time.Second,
    29  	}
    30  	return transport
    31  }
    32  
    33  type Client struct {
    34  	c *http.Client
    35  }
    36  
    37  func NewClient(tlsConfig *tls.Config, connectTimeout time.Duration, requestTimeout time.Duration) *Client {
    38  	transport := NewDeadlineTransport(connectTimeout, requestTimeout)
    39  	transport.TLSClientConfig = tlsConfig
    40  	return &Client{
    41  		c: &http.Client{
    42  			Transport: transport,
    43  			Timeout:   requestTimeout,
    44  		},
    45  	}
    46  }
    47  
    48  // GETV1 is a helper function to perform a V1 HTTP request
    49  // and parse our NSQ daemon's expected response format, with deadlines.
    50  func (c *Client) GETV1(endpoint string, v interface{}) error {
    51  retry:
    52  	req, err := http.NewRequest("GET", endpoint, nil)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	req.Header.Add("Accept", "application/vnd.nsq; version=1.0")
    58  
    59  	resp, err := c.c.Do(req)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	body, err := io.ReadAll(resp.Body)
    65  	resp.Body.Close()
    66  	if err != nil {
    67  		return err
    68  	}
    69  	if resp.StatusCode != 200 {
    70  		if resp.StatusCode == 403 && !strings.HasPrefix(endpoint, "https") {
    71  			endpoint, err = httpsEndpoint(endpoint, body)
    72  			if err != nil {
    73  				return err
    74  			}
    75  			goto retry
    76  		}
    77  		return fmt.Errorf("got response %s %q", resp.Status, body)
    78  	}
    79  	err = json.Unmarshal(body, &v)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  // PostV1 is a helper function to perform a V1 HTTP request
    88  // and parse our NSQ daemon's expected response format, with deadlines.
    89  func (c *Client) POSTV1(endpoint string) error {
    90  retry:
    91  	req, err := http.NewRequest("POST", endpoint, nil)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	req.Header.Add("Accept", "application/vnd.nsq; version=1.0")
    97  
    98  	resp, err := c.c.Do(req)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	body, err := io.ReadAll(resp.Body)
   104  	resp.Body.Close()
   105  	if err != nil {
   106  		return err
   107  	}
   108  	if resp.StatusCode != 200 {
   109  		if resp.StatusCode == 403 && !strings.HasPrefix(endpoint, "https") {
   110  			endpoint, err = httpsEndpoint(endpoint, body)
   111  			if err != nil {
   112  				return err
   113  			}
   114  			goto retry
   115  		}
   116  		return fmt.Errorf("got response %s %q", resp.Status, body)
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  func httpsEndpoint(endpoint string, body []byte) (string, error) {
   123  	var forbiddenResp struct {
   124  		HTTPSPort int `json:"https_port"`
   125  	}
   126  	err := json.Unmarshal(body, &forbiddenResp)
   127  	if err != nil {
   128  		return "", err
   129  	}
   130  
   131  	u, err := url.Parse(endpoint)
   132  	if err != nil {
   133  		return "", err
   134  	}
   135  
   136  	host, _, err := net.SplitHostPort(u.Host)
   137  	if err != nil {
   138  		return "", err
   139  	}
   140  
   141  	u.Scheme = "https"
   142  	u.Host = net.JoinHostPort(host, strconv.Itoa(forbiddenResp.HTTPSPort))
   143  	return u.String(), nil
   144  }