code.gitea.io/gitea@v1.19.3/modules/httplib/httplib.go (about)

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