github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/utils/http.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"os"
     9  	"runtime"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/alphadose/haxmap"
    14  	"github.com/docker/go-connections/tlsconfig"
    15  
    16  	"github.com/projecteru2/core/log"
    17  	"github.com/projecteru2/core/types"
    18  )
    19  
    20  var defaultHTTPClient = &http.Client{
    21  	CheckRedirect: checkRedirect,
    22  	Transport:     getDefaultTransport(),
    23  }
    24  
    25  var defaultUnixSockClient = &http.Client{
    26  	Transport: getDefaultUnixSockTransport(),
    27  }
    28  
    29  var httpsClientCache = haxmap.New[string, *http.Client]()
    30  
    31  // GetHTTPClient returns a HTTP client
    32  func GetHTTPClient() *http.Client {
    33  	return defaultHTTPClient
    34  }
    35  
    36  // GetUnixSockClient .
    37  func GetUnixSockClient() *http.Client {
    38  	return defaultUnixSockClient
    39  }
    40  
    41  // GetHTTPSClient returns an HTTPS client
    42  // if cert_path/ca/cert/key is empty, it returns an HTTP client instead
    43  func GetHTTPSClient(ctx context.Context, certPath, name, ca, cert, key string) (client *http.Client, err error) {
    44  	if certPath == "" || ca == "" || cert == "" || key == "" {
    45  		return GetHTTPClient(), nil
    46  	}
    47  
    48  	cacheKey := name + SHA256(fmt.Sprintf("%s-%s-%s-%s-%s", certPath, name, ca, cert, key))[:8]
    49  	if httpsClient, ok := httpsClientCache.Get(cacheKey); ok {
    50  		return httpsClient, nil
    51  	}
    52  
    53  	caFile, err := os.CreateTemp(certPath, fmt.Sprintf("ca-%s", name))
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	certFile, err := os.CreateTemp(certPath, fmt.Sprintf("cert-%s", name))
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	keyFile, err := os.CreateTemp(certPath, fmt.Sprintf("key-%s", name))
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	if err = dumpFromString(ctx, caFile, certFile, keyFile, ca, cert, key); err != nil {
    66  		return nil, err
    67  	}
    68  	options := tlsconfig.Options{
    69  		CAFile:             caFile.Name(),
    70  		CertFile:           certFile.Name(),
    71  		KeyFile:            keyFile.Name(),
    72  		InsecureSkipVerify: true,
    73  	}
    74  	defer os.Remove(caFile.Name())
    75  	defer os.Remove(certFile.Name())
    76  	defer os.Remove(keyFile.Name())
    77  	tlsc, err := tlsconfig.Client(options)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	transport := getDefaultTransport()
    82  	transport.TLSClientConfig = tlsc
    83  
    84  	client = &http.Client{
    85  		CheckRedirect: checkRedirect,
    86  		Transport:     transport,
    87  	}
    88  	httpsClientCache.Set(cacheKey, client)
    89  	return client, nil
    90  }
    91  
    92  func getDefaultTransport() *http.Transport {
    93  	return &http.Transport{
    94  		DialContext: (&net.Dialer{
    95  			KeepAlive: time.Second * 30,
    96  			Timeout:   time.Second * 30,
    97  		}).DialContext,
    98  
    99  		IdleConnTimeout:     time.Second * 90,
   100  		MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
   101  		Proxy:               http.ProxyFromEnvironment,
   102  	}
   103  }
   104  
   105  func getDefaultUnixSockTransport() *http.Transport {
   106  	return &http.Transport{
   107  		DialContext: func(_ context.Context, _, addr string) (net.Conn, error) {
   108  			return net.DialTimeout("unix", strings.Split(addr, ":")[0], time.Second*30)
   109  		},
   110  
   111  		IdleConnTimeout:     time.Second * 90,
   112  		MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
   113  		DisableCompression:  true,
   114  	}
   115  }
   116  
   117  func dumpFromString(ctx context.Context, ca, cert, key *os.File, caStr, certStr, keyStr string) error {
   118  	files := []*os.File{ca, cert, key}
   119  	data := []string{caStr, certStr, keyStr}
   120  	for i := 0; i < 3; i++ {
   121  		if _, err := files[i].WriteString(data[i]); err != nil {
   122  			return err
   123  		}
   124  		if err := files[i].Chmod(0444); err != nil {
   125  			return err
   126  		}
   127  		if err := files[i].Close(); err != nil {
   128  			return err
   129  		}
   130  	}
   131  	log.WithFunc("utils.dumpFromString").Debug(ctx, "Dump ca.pem, cert.pem, key.pem from string")
   132  	return nil
   133  }
   134  
   135  func checkRedirect(_ *http.Request, via []*http.Request) error {
   136  	if via[0].Method == http.MethodGet {
   137  		return http.ErrUseLastResponse
   138  	}
   139  	return types.ErrUnexpectedRedirect
   140  }