github.com/crowdsecurity/crowdsec@v1.6.1/pkg/apiclient/client_http.go (about)

     1  package apiclient
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/http/httputil"
    12  	"net/url"
    13  	"strings"
    14  
    15  	log "github.com/sirupsen/logrus"
    16  )
    17  
    18  func (c *ApiClient) NewRequest(method, url string, body interface{}) (*http.Request, error) {
    19  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
    20  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
    21  	}
    22  
    23  	u, err := c.BaseURL.Parse(url)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  
    28  	var buf io.ReadWriter
    29  	if body != nil {
    30  		buf = &bytes.Buffer{}
    31  		enc := json.NewEncoder(buf)
    32  		enc.SetEscapeHTML(false)
    33  
    34  		if err = enc.Encode(body); err != nil {
    35  			return nil, err
    36  		}
    37  	}
    38  
    39  	req, err := http.NewRequest(method, u.String(), buf)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	if body != nil {
    45  		req.Header.Set("Content-Type", "application/json")
    46  	}
    47  
    48  	return req, nil
    49  }
    50  
    51  func (c *ApiClient) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
    52  	if ctx == nil {
    53  		return nil, errors.New("context must be non-nil")
    54  	}
    55  
    56  	req = req.WithContext(ctx)
    57  
    58  	// Check rate limit
    59  
    60  	if c.UserAgent != "" {
    61  		req.Header.Add("User-Agent", c.UserAgent)
    62  	}
    63  
    64  	if log.GetLevel() >= log.DebugLevel {
    65  		log.Debugf("[URL] %s %s", req.Method, req.URL)
    66  	}
    67  
    68  	resp, err := c.client.Do(req)
    69  	if resp != nil && resp.Body != nil {
    70  		defer resp.Body.Close()
    71  	}
    72  
    73  	if err != nil {
    74  		// If we got an error, and the context has been canceled,
    75  		// the context's error is probably more useful.
    76  		select {
    77  		case <-ctx.Done():
    78  			return nil, ctx.Err()
    79  		default:
    80  		}
    81  
    82  		// If the error type is *url.Error, sanitize its URL before returning.
    83  		if e, ok := err.(*url.Error); ok {
    84  			if url, err := url.Parse(e.URL); err == nil {
    85  				e.URL = url.String()
    86  				return newResponse(resp), e
    87  			}
    88  
    89  			return newResponse(resp), err
    90  		}
    91  
    92  		return newResponse(resp), err
    93  	}
    94  
    95  	if log.GetLevel() >= log.DebugLevel {
    96  		for k, v := range resp.Header {
    97  			log.Debugf("[headers] %s: %s", k, v)
    98  		}
    99  
   100  		dump, err := httputil.DumpResponse(resp, true)
   101  		if err == nil {
   102  			log.Debugf("Response: %s", string(dump))
   103  		}
   104  	}
   105  
   106  	response := newResponse(resp)
   107  
   108  	err = CheckResponse(resp)
   109  	if err != nil {
   110  		return response, err
   111  	}
   112  
   113  	if v != nil {
   114  		w, ok := v.(io.Writer)
   115  		if !ok {
   116  			decErr := json.NewDecoder(resp.Body).Decode(v)
   117  			if errors.Is(decErr, io.EOF) {
   118  				decErr = nil // ignore EOF errors caused by empty response body
   119  			}
   120  
   121  			return response, decErr
   122  		}
   123  
   124  		io.Copy(w, resp.Body)
   125  	}
   126  
   127  	return response, err
   128  }