crowdsec/pkg/apiclient/client_http.go
mmetc 89f704ef18
light pkg/api{client,server} refact (#2659)
* tests: don't run crowdsec if not necessary
* make listen_uri report the random port number when 0 is requested
* move apiserver.getTLSAuthType() -> csconfig.TLSCfg.GetAuthType()
* move apiserver.isEnrolled() -> apiclient.ApiClient.IsEnrolled()
* extract function apiserver.recoverFromPanic()
* simplify and move APIServer.GetTLSConfig() -> TLSCfg.GetTLSConfig()
* moved TLSCfg type to csconfig/tls.go
* APIServer.InitController(): early return / happy path
* extract function apiserver.newGinLogger()
* lapi tests
* update unit test
* lint (testify)
* lint (whitespace, variable names)
* update docker tests
2023-12-14 14:54:11 +01:00

128 lines
2.4 KiB
Go

package apiclient
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httputil"
"net/url"
"strings"
log "github.com/sirupsen/logrus"
)
func (c *ApiClient) NewRequest(method, url string, body interface{}) (*http.Request, error) {
if !strings.HasSuffix(c.BaseURL.Path, "/") {
return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
}
u, err := c.BaseURL.Parse(url)
if err != nil {
return nil, err
}
var buf io.ReadWriter
if body != nil {
buf = &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
if err = enc.Encode(body); err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
return req, nil
}
func (c *ApiClient) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
if ctx == nil {
return nil, errors.New("context must be non-nil")
}
req = req.WithContext(ctx)
// Check rate limit
if c.UserAgent != "" {
req.Header.Add("User-Agent", c.UserAgent)
}
if log.GetLevel() >= log.DebugLevel {
log.Debugf("[URL] %s %s", req.Method, req.URL)
}
resp, err := c.client.Do(req)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// If the error type is *url.Error, sanitize its URL before returning.
if e, ok := err.(*url.Error); ok {
if url, err := url.Parse(e.URL); err == nil {
e.URL = url.String()
return newResponse(resp), e
}
return newResponse(resp), err
}
return newResponse(resp), err
}
if log.GetLevel() >= log.DebugLevel {
for k, v := range resp.Header {
log.Debugf("[headers] %s : %s", k, v)
}
dump, err := httputil.DumpResponse(resp, true)
if err == nil {
log.Debugf("Response: %s", string(dump))
}
}
response := newResponse(resp)
err = CheckResponse(resp)
if err != nil {
return response, err
}
if v != nil {
w, ok := v.(io.Writer)
if !ok {
decErr := json.NewDecoder(resp.Body).Decode(v)
if errors.Is(decErr, io.EOF) {
decErr = nil // ignore EOF errors caused by empty response body
}
return response, decErr
}
io.Copy(w, resp.Body)
}
return response, err
}