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 }