github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/httplib/httplib.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  // Copyright 2013 The Beego Authors. All rights reserved.
     7  // Copyright 2014 The Gogs Authors. All rights reserved.
     8  // Use of this source code is governed by a MIT-style
     9  // license that can be found in the LICENSE file.
    10  
    11  package httplib
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"crypto/tls"
    17  	"io"
    18  	"net"
    19  	"net/http"
    20  	"net/url"
    21  	"strings"
    22  	"time"
    23  )
    24  
    25  var defaultSetting = Settings{"GiteaServer", 60 * time.Second, 60 * time.Second, nil, nil}
    26  
    27  // newRequest returns *Request with specific method
    28  func newRequest(url, method string) *Request {
    29  	var resp http.Response
    30  	req := http.Request{
    31  		Method:     method,
    32  		Header:     make(http.Header),
    33  		Proto:      "HTTP/1.1",
    34  		ProtoMajor: 1,
    35  		ProtoMinor: 1,
    36  	}
    37  	return &Request{url, &req, map[string]string{}, defaultSetting, &resp, nil}
    38  }
    39  
    40  // NewRequest returns *Request with specific method
    41  func NewRequest(url, method string) *Request {
    42  	return newRequest(url, method)
    43  }
    44  
    45  // Settings is the default settings for http client
    46  type Settings struct {
    47  	UserAgent        string
    48  	ConnectTimeout   time.Duration
    49  	ReadWriteTimeout time.Duration
    50  	TLSClientConfig  *tls.Config
    51  	Transport        http.RoundTripper
    52  }
    53  
    54  // Request provides more useful methods for requesting one url than http.Request.
    55  type Request struct {
    56  	url     string
    57  	req     *http.Request
    58  	params  map[string]string
    59  	setting Settings
    60  	resp    *http.Response
    61  	body    []byte
    62  }
    63  
    64  // SetContext sets the request's Context
    65  func (r *Request) SetContext(ctx context.Context) *Request {
    66  	r.req = r.req.WithContext(ctx)
    67  	return r
    68  }
    69  
    70  // SetTimeout sets connect time out and read-write time out for BeegoRequest.
    71  func (r *Request) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *Request {
    72  	r.setting.ConnectTimeout = connectTimeout
    73  	r.setting.ReadWriteTimeout = readWriteTimeout
    74  	return r
    75  }
    76  
    77  // SetTLSClientConfig sets tls connection configurations if visiting https url.
    78  func (r *Request) SetTLSClientConfig(config *tls.Config) *Request {
    79  	r.setting.TLSClientConfig = config
    80  	return r
    81  }
    82  
    83  // Header add header item string in request.
    84  func (r *Request) Header(key, value string) *Request {
    85  	r.req.Header.Set(key, value)
    86  	return r
    87  }
    88  
    89  // SetTransport sets transport to
    90  func (r *Request) SetTransport(transport http.RoundTripper) *Request {
    91  	r.setting.Transport = transport
    92  	return r
    93  }
    94  
    95  // Param adds query param in to request.
    96  // params build query string as ?key1=value1&key2=value2...
    97  func (r *Request) Param(key, value string) *Request {
    98  	r.params[key] = value
    99  	return r
   100  }
   101  
   102  // Body adds request raw body.
   103  // it supports string and []byte.
   104  func (r *Request) Body(data interface{}) *Request {
   105  	switch t := data.(type) {
   106  	case string:
   107  		bf := bytes.NewBufferString(t)
   108  		r.req.Body = io.NopCloser(bf)
   109  		r.req.ContentLength = int64(len(t))
   110  	case []byte:
   111  		bf := bytes.NewBuffer(t)
   112  		r.req.Body = io.NopCloser(bf)
   113  		r.req.ContentLength = int64(len(t))
   114  	}
   115  	return r
   116  }
   117  
   118  func (r *Request) getResponse() (*http.Response, error) {
   119  	if r.resp.StatusCode != 0 {
   120  		return r.resp, nil
   121  	}
   122  
   123  	var paramBody string
   124  	if len(r.params) > 0 {
   125  		var buf bytes.Buffer
   126  		for k, v := range r.params {
   127  			buf.WriteString(url.QueryEscape(k))
   128  			buf.WriteByte('=')
   129  			buf.WriteString(url.QueryEscape(v))
   130  			buf.WriteByte('&')
   131  		}
   132  		paramBody = buf.String()
   133  		paramBody = paramBody[0 : len(paramBody)-1]
   134  	}
   135  
   136  	if r.req.Method == "GET" && len(paramBody) > 0 {
   137  		if strings.Contains(r.url, "?") {
   138  			r.url += "&" + paramBody
   139  		} else {
   140  			r.url = r.url + "?" + paramBody
   141  		}
   142  	} else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 {
   143  		r.Header("Content-Type", "application/x-www-form-urlencoded")
   144  		r.Body(paramBody)
   145  	}
   146  
   147  	url, err := url.Parse(r.url)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	r.req.URL = url
   152  
   153  	trans := r.setting.Transport
   154  	if trans == nil {
   155  		// create default transport
   156  		trans = &http.Transport{
   157  			TLSClientConfig: r.setting.TLSClientConfig,
   158  			Proxy:           http.ProxyFromEnvironment,
   159  			DialContext:     TimeoutDialer(r.setting.ConnectTimeout),
   160  		}
   161  	} else if t, ok := trans.(*http.Transport); ok {
   162  		if t.TLSClientConfig == nil {
   163  			t.TLSClientConfig = r.setting.TLSClientConfig
   164  		}
   165  		if t.DialContext == nil {
   166  			t.DialContext = TimeoutDialer(r.setting.ConnectTimeout)
   167  		}
   168  	}
   169  
   170  	client := &http.Client{
   171  		Transport: trans,
   172  		Timeout:   r.setting.ReadWriteTimeout,
   173  	}
   174  
   175  	if len(r.setting.UserAgent) > 0 && len(r.req.Header.Get("User-Agent")) == 0 {
   176  		r.req.Header.Set("User-Agent", r.setting.UserAgent)
   177  	}
   178  
   179  	resp, err := client.Do(r.req)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	r.resp = resp
   184  	return resp, nil
   185  }
   186  
   187  // Response executes request client gets response manually.
   188  func (r *Request) Response() (*http.Response, error) {
   189  	return r.getResponse()
   190  }
   191  
   192  // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
   193  func TimeoutDialer(cTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) {
   194  	return func(ctx context.Context, netw, addr string) (net.Conn, error) {
   195  		d := net.Dialer{Timeout: cTimeout}
   196  		conn, err := d.DialContext(ctx, netw, addr)
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  		return conn, nil
   201  	}
   202  }