code.gitea.io/gitea@v1.22.3/modules/httplib/request.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 "fmt" 12 "io" 13 "net" 14 "net/http" 15 "net/url" 16 "strings" 17 "time" 18 ) 19 20 var defaultSetting = Settings{"GiteaServer", 60 * time.Second, 60 * time.Second, nil, nil} 21 22 // newRequest returns *Request with specific method 23 func newRequest(url, method string) *Request { 24 var resp http.Response 25 req := http.Request{ 26 Method: method, 27 Header: make(http.Header), 28 Proto: "HTTP/1.1", 29 ProtoMajor: 1, 30 ProtoMinor: 1, 31 } 32 return &Request{url, &req, map[string]string{}, defaultSetting, &resp, nil} 33 } 34 35 // NewRequest returns *Request with specific method 36 func NewRequest(url, method string) *Request { 37 return newRequest(url, method) 38 } 39 40 // Settings is the default settings for http client 41 type Settings struct { 42 UserAgent string 43 ConnectTimeout time.Duration 44 ReadWriteTimeout time.Duration 45 TLSClientConfig *tls.Config 46 Transport http.RoundTripper 47 } 48 49 // Request provides more useful methods for requesting one url than http.Request. 50 type Request struct { 51 url string 52 req *http.Request 53 params map[string]string 54 setting Settings 55 resp *http.Response 56 body []byte 57 } 58 59 // SetContext sets the request's Context 60 func (r *Request) SetContext(ctx context.Context) *Request { 61 r.req = r.req.WithContext(ctx) 62 return r 63 } 64 65 // SetTimeout sets connect time out and read-write time out for BeegoRequest. 66 func (r *Request) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *Request { 67 r.setting.ConnectTimeout = connectTimeout 68 r.setting.ReadWriteTimeout = readWriteTimeout 69 return r 70 } 71 72 func (r *Request) SetReadWriteTimeout(readWriteTimeout time.Duration) *Request { 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 any) *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 var err error 148 r.req.URL, err = url.Parse(r.url) 149 if err != nil { 150 return nil, err 151 } 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 } 203 204 func (r *Request) GoString() string { 205 return fmt.Sprintf("%s %s", r.req.Method, r.url) 206 }