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 }