github.com/astaxie/beego@v1.12.3/httplib/httplib.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package httplib is used as http.Client 16 // Usage: 17 // 18 // import "github.com/astaxie/beego/httplib" 19 // 20 // b := httplib.Post("http://beego.me/") 21 // b.Param("username","astaxie") 22 // b.Param("password","123456") 23 // b.PostFile("uploadfile1", "httplib.pdf") 24 // b.PostFile("uploadfile2", "httplib.txt") 25 // str, err := b.String() 26 // if err != nil { 27 // t.Fatal(err) 28 // } 29 // fmt.Println(str) 30 // 31 // more docs http://beego.me/docs/module/httplib.md 32 package httplib 33 34 import ( 35 "bytes" 36 "compress/gzip" 37 "crypto/tls" 38 "encoding/json" 39 "encoding/xml" 40 "io" 41 "io/ioutil" 42 "log" 43 "mime/multipart" 44 "net" 45 "net/http" 46 "net/http/cookiejar" 47 "net/http/httputil" 48 "net/url" 49 "os" 50 "path" 51 "strings" 52 "sync" 53 "time" 54 55 "gopkg.in/yaml.v2" 56 ) 57 58 var defaultSetting = BeegoHTTPSettings{ 59 UserAgent: "beegoServer", 60 ConnectTimeout: 60 * time.Second, 61 ReadWriteTimeout: 60 * time.Second, 62 Gzip: true, 63 DumpBody: true, 64 } 65 66 var defaultCookieJar http.CookieJar 67 var settingMutex sync.Mutex 68 69 // createDefaultCookie creates a global cookiejar to store cookies. 70 func createDefaultCookie() { 71 settingMutex.Lock() 72 defer settingMutex.Unlock() 73 defaultCookieJar, _ = cookiejar.New(nil) 74 } 75 76 // SetDefaultSetting Overwrite default settings 77 func SetDefaultSetting(setting BeegoHTTPSettings) { 78 settingMutex.Lock() 79 defer settingMutex.Unlock() 80 defaultSetting = setting 81 } 82 83 // NewBeegoRequest return *BeegoHttpRequest with specific method 84 func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { 85 var resp http.Response 86 u, err := url.Parse(rawurl) 87 if err != nil { 88 log.Println("Httplib:", err) 89 } 90 req := http.Request{ 91 URL: u, 92 Method: method, 93 Header: make(http.Header), 94 Proto: "HTTP/1.1", 95 ProtoMajor: 1, 96 ProtoMinor: 1, 97 } 98 return &BeegoHTTPRequest{ 99 url: rawurl, 100 req: &req, 101 params: map[string][]string{}, 102 files: map[string]string{}, 103 setting: defaultSetting, 104 resp: &resp, 105 } 106 } 107 108 // Get returns *BeegoHttpRequest with GET method. 109 func Get(url string) *BeegoHTTPRequest { 110 return NewBeegoRequest(url, "GET") 111 } 112 113 // Post returns *BeegoHttpRequest with POST method. 114 func Post(url string) *BeegoHTTPRequest { 115 return NewBeegoRequest(url, "POST") 116 } 117 118 // Put returns *BeegoHttpRequest with PUT method. 119 func Put(url string) *BeegoHTTPRequest { 120 return NewBeegoRequest(url, "PUT") 121 } 122 123 // Delete returns *BeegoHttpRequest DELETE method. 124 func Delete(url string) *BeegoHTTPRequest { 125 return NewBeegoRequest(url, "DELETE") 126 } 127 128 // Head returns *BeegoHttpRequest with HEAD method. 129 func Head(url string) *BeegoHTTPRequest { 130 return NewBeegoRequest(url, "HEAD") 131 } 132 133 // BeegoHTTPSettings is the http.Client setting 134 type BeegoHTTPSettings struct { 135 ShowDebug bool 136 UserAgent string 137 ConnectTimeout time.Duration 138 ReadWriteTimeout time.Duration 139 TLSClientConfig *tls.Config 140 Proxy func(*http.Request) (*url.URL, error) 141 Transport http.RoundTripper 142 CheckRedirect func(req *http.Request, via []*http.Request) error 143 EnableCookie bool 144 Gzip bool 145 DumpBody bool 146 Retries int // if set to -1 means will retry forever 147 RetryDelay time.Duration 148 } 149 150 // BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. 151 type BeegoHTTPRequest struct { 152 url string 153 req *http.Request 154 params map[string][]string 155 files map[string]string 156 setting BeegoHTTPSettings 157 resp *http.Response 158 body []byte 159 dump []byte 160 } 161 162 // GetRequest return the request object 163 func (b *BeegoHTTPRequest) GetRequest() *http.Request { 164 return b.req 165 } 166 167 // Setting Change request settings 168 func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { 169 b.setting = setting 170 return b 171 } 172 173 // SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password. 174 func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest { 175 b.req.SetBasicAuth(username, password) 176 return b 177 } 178 179 // SetEnableCookie sets enable/disable cookiejar 180 func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest { 181 b.setting.EnableCookie = enable 182 return b 183 } 184 185 // SetUserAgent sets User-Agent header field 186 func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest { 187 b.setting.UserAgent = useragent 188 return b 189 } 190 191 // Debug sets show debug or not when executing request. 192 func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { 193 b.setting.ShowDebug = isdebug 194 return b 195 } 196 197 // Retries sets Retries times. 198 // default is 0 means no retried. 199 // -1 means retried forever. 200 // others means retried times. 201 func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest { 202 b.setting.Retries = times 203 return b 204 } 205 206 func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest { 207 b.setting.RetryDelay = delay 208 return b 209 } 210 211 // DumpBody setting whether need to Dump the Body. 212 func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { 213 b.setting.DumpBody = isdump 214 return b 215 } 216 217 // DumpRequest return the DumpRequest 218 func (b *BeegoHTTPRequest) DumpRequest() []byte { 219 return b.dump 220 } 221 222 // SetTimeout sets connect time out and read-write time out for BeegoRequest. 223 func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest { 224 b.setting.ConnectTimeout = connectTimeout 225 b.setting.ReadWriteTimeout = readWriteTimeout 226 return b 227 } 228 229 // SetTLSClientConfig sets tls connection configurations if visiting https url. 230 func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { 231 b.setting.TLSClientConfig = config 232 return b 233 } 234 235 // Header add header item string in request. 236 func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { 237 b.req.Header.Set(key, value) 238 return b 239 } 240 241 // SetHost set the request host 242 func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { 243 b.req.Host = host 244 return b 245 } 246 247 // SetProtocolVersion Set the protocol version for incoming requests. 248 // Client requests always use HTTP/1.1. 249 func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { 250 if len(vers) == 0 { 251 vers = "HTTP/1.1" 252 } 253 254 major, minor, ok := http.ParseHTTPVersion(vers) 255 if ok { 256 b.req.Proto = vers 257 b.req.ProtoMajor = major 258 b.req.ProtoMinor = minor 259 } 260 261 return b 262 } 263 264 // SetCookie add cookie into request. 265 func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { 266 b.req.Header.Add("Cookie", cookie.String()) 267 return b 268 } 269 270 // SetTransport set the setting transport 271 func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { 272 b.setting.Transport = transport 273 return b 274 } 275 276 // SetProxy set the http proxy 277 // example: 278 // 279 // func(req *http.Request) (*url.URL, error) { 280 // u, _ := url.ParseRequestURI("http://127.0.0.1:8118") 281 // return u, nil 282 // } 283 func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest { 284 b.setting.Proxy = proxy 285 return b 286 } 287 288 // SetCheckRedirect specifies the policy for handling redirects. 289 // 290 // If CheckRedirect is nil, the Client uses its default policy, 291 // which is to stop after 10 consecutive requests. 292 func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest { 293 b.setting.CheckRedirect = redirect 294 return b 295 } 296 297 // Param adds query param in to request. 298 // params build query string as ?key1=value1&key2=value2... 299 func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { 300 if param, ok := b.params[key]; ok { 301 b.params[key] = append(param, value) 302 } else { 303 b.params[key] = []string{value} 304 } 305 return b 306 } 307 308 // PostFile add a post file to the request 309 func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { 310 b.files[formname] = filename 311 return b 312 } 313 314 // Body adds request raw body. 315 // it supports string and []byte. 316 func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { 317 switch t := data.(type) { 318 case string: 319 bf := bytes.NewBufferString(t) 320 b.req.Body = ioutil.NopCloser(bf) 321 b.req.ContentLength = int64(len(t)) 322 case []byte: 323 bf := bytes.NewBuffer(t) 324 b.req.Body = ioutil.NopCloser(bf) 325 b.req.ContentLength = int64(len(t)) 326 } 327 return b 328 } 329 330 // XMLBody adds request raw body encoding by XML. 331 func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { 332 if b.req.Body == nil && obj != nil { 333 byts, err := xml.Marshal(obj) 334 if err != nil { 335 return b, err 336 } 337 b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) 338 b.req.ContentLength = int64(len(byts)) 339 b.req.Header.Set("Content-Type", "application/xml") 340 } 341 return b, nil 342 } 343 344 // YAMLBody adds request raw body encoding by YAML. 345 func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) { 346 if b.req.Body == nil && obj != nil { 347 byts, err := yaml.Marshal(obj) 348 if err != nil { 349 return b, err 350 } 351 b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) 352 b.req.ContentLength = int64(len(byts)) 353 b.req.Header.Set("Content-Type", "application/x+yaml") 354 } 355 return b, nil 356 } 357 358 // JSONBody adds request raw body encoding by JSON. 359 func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { 360 if b.req.Body == nil && obj != nil { 361 byts, err := json.Marshal(obj) 362 if err != nil { 363 return b, err 364 } 365 b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) 366 b.req.ContentLength = int64(len(byts)) 367 b.req.Header.Set("Content-Type", "application/json") 368 } 369 return b, nil 370 } 371 372 func (b *BeegoHTTPRequest) buildURL(paramBody string) { 373 // build GET url with query string 374 if b.req.Method == "GET" && len(paramBody) > 0 { 375 if strings.Contains(b.url, "?") { 376 b.url += "&" + paramBody 377 } else { 378 b.url = b.url + "?" + paramBody 379 } 380 return 381 } 382 383 // build POST/PUT/PATCH url and body 384 if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH" || b.req.Method == "DELETE") && b.req.Body == nil { 385 // with files 386 if len(b.files) > 0 { 387 pr, pw := io.Pipe() 388 bodyWriter := multipart.NewWriter(pw) 389 go func() { 390 for formname, filename := range b.files { 391 fileWriter, err := bodyWriter.CreateFormFile(formname, filename) 392 if err != nil { 393 log.Println("Httplib:", err) 394 } 395 fh, err := os.Open(filename) 396 if err != nil { 397 log.Println("Httplib:", err) 398 } 399 //iocopy 400 _, err = io.Copy(fileWriter, fh) 401 fh.Close() 402 if err != nil { 403 log.Println("Httplib:", err) 404 } 405 } 406 for k, v := range b.params { 407 for _, vv := range v { 408 bodyWriter.WriteField(k, vv) 409 } 410 } 411 bodyWriter.Close() 412 pw.Close() 413 }() 414 b.Header("Content-Type", bodyWriter.FormDataContentType()) 415 b.req.Body = ioutil.NopCloser(pr) 416 b.Header("Transfer-Encoding", "chunked") 417 return 418 } 419 420 // with params 421 if len(paramBody) > 0 { 422 b.Header("Content-Type", "application/x-www-form-urlencoded") 423 b.Body(paramBody) 424 } 425 } 426 } 427 428 func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { 429 if b.resp.StatusCode != 0 { 430 return b.resp, nil 431 } 432 resp, err := b.DoRequest() 433 if err != nil { 434 return nil, err 435 } 436 b.resp = resp 437 return resp, nil 438 } 439 440 // DoRequest will do the client.Do 441 func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { 442 var paramBody string 443 if len(b.params) > 0 { 444 var buf bytes.Buffer 445 for k, v := range b.params { 446 for _, vv := range v { 447 buf.WriteString(url.QueryEscape(k)) 448 buf.WriteByte('=') 449 buf.WriteString(url.QueryEscape(vv)) 450 buf.WriteByte('&') 451 } 452 } 453 paramBody = buf.String() 454 paramBody = paramBody[0 : len(paramBody)-1] 455 } 456 457 b.buildURL(paramBody) 458 urlParsed, err := url.Parse(b.url) 459 if err != nil { 460 return nil, err 461 } 462 463 b.req.URL = urlParsed 464 465 trans := b.setting.Transport 466 467 if trans == nil { 468 // create default transport 469 trans = &http.Transport{ 470 TLSClientConfig: b.setting.TLSClientConfig, 471 Proxy: b.setting.Proxy, 472 Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), 473 MaxIdleConnsPerHost: 100, 474 } 475 } else { 476 // if b.transport is *http.Transport then set the settings. 477 if t, ok := trans.(*http.Transport); ok { 478 if t.TLSClientConfig == nil { 479 t.TLSClientConfig = b.setting.TLSClientConfig 480 } 481 if t.Proxy == nil { 482 t.Proxy = b.setting.Proxy 483 } 484 if t.Dial == nil { 485 t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) 486 } 487 } 488 } 489 490 var jar http.CookieJar 491 if b.setting.EnableCookie { 492 if defaultCookieJar == nil { 493 createDefaultCookie() 494 } 495 jar = defaultCookieJar 496 } 497 498 client := &http.Client{ 499 Transport: trans, 500 Jar: jar, 501 } 502 503 if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" { 504 b.req.Header.Set("User-Agent", b.setting.UserAgent) 505 } 506 507 if b.setting.CheckRedirect != nil { 508 client.CheckRedirect = b.setting.CheckRedirect 509 } 510 511 if b.setting.ShowDebug { 512 dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody) 513 if err != nil { 514 log.Println(err.Error()) 515 } 516 b.dump = dump 517 } 518 // retries default value is 0, it will run once. 519 // retries equal to -1, it will run forever until success 520 // retries is setted, it will retries fixed times. 521 // Sleeps for a 400ms in between calls to reduce spam 522 for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ { 523 resp, err = client.Do(b.req) 524 if err == nil { 525 break 526 } 527 time.Sleep(b.setting.RetryDelay) 528 } 529 return resp, err 530 } 531 532 // String returns the body string in response. 533 // it calls Response inner. 534 func (b *BeegoHTTPRequest) String() (string, error) { 535 data, err := b.Bytes() 536 if err != nil { 537 return "", err 538 } 539 540 return string(data), nil 541 } 542 543 // Bytes returns the body []byte in response. 544 // it calls Response inner. 545 func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { 546 if b.body != nil { 547 return b.body, nil 548 } 549 resp, err := b.getResponse() 550 if err != nil { 551 return nil, err 552 } 553 if resp.Body == nil { 554 return nil, nil 555 } 556 defer resp.Body.Close() 557 if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" { 558 reader, err := gzip.NewReader(resp.Body) 559 if err != nil { 560 return nil, err 561 } 562 b.body, err = ioutil.ReadAll(reader) 563 return b.body, err 564 } 565 b.body, err = ioutil.ReadAll(resp.Body) 566 return b.body, err 567 } 568 569 // ToFile saves the body data in response to one file. 570 // it calls Response inner. 571 func (b *BeegoHTTPRequest) ToFile(filename string) error { 572 resp, err := b.getResponse() 573 if err != nil { 574 return err 575 } 576 if resp.Body == nil { 577 return nil 578 } 579 defer resp.Body.Close() 580 err = pathExistAndMkdir(filename) 581 if err != nil { 582 return err 583 } 584 f, err := os.Create(filename) 585 if err != nil { 586 return err 587 } 588 defer f.Close() 589 _, err = io.Copy(f, resp.Body) 590 return err 591 } 592 593 //Check that the file directory exists, there is no automatically created 594 func pathExistAndMkdir(filename string) (err error) { 595 filename = path.Dir(filename) 596 _, err = os.Stat(filename) 597 if err == nil { 598 return nil 599 } 600 if os.IsNotExist(err) { 601 err = os.MkdirAll(filename, os.ModePerm) 602 if err == nil { 603 return nil 604 } 605 } 606 return err 607 } 608 609 // ToJSON returns the map that marshals from the body bytes as json in response . 610 // it calls Response inner. 611 func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { 612 data, err := b.Bytes() 613 if err != nil { 614 return err 615 } 616 return json.Unmarshal(data, v) 617 } 618 619 // ToXML returns the map that marshals from the body bytes as xml in response . 620 // it calls Response inner. 621 func (b *BeegoHTTPRequest) ToXML(v interface{}) error { 622 data, err := b.Bytes() 623 if err != nil { 624 return err 625 } 626 return xml.Unmarshal(data, v) 627 } 628 629 // ToYAML returns the map that marshals from the body bytes as yaml in response . 630 // it calls Response inner. 631 func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { 632 data, err := b.Bytes() 633 if err != nil { 634 return err 635 } 636 return yaml.Unmarshal(data, v) 637 } 638 639 // Response executes request client gets response mannually. 640 func (b *BeegoHTTPRequest) Response() (*http.Response, error) { 641 return b.getResponse() 642 } 643 644 // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. 645 func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { 646 return func(netw, addr string) (net.Conn, error) { 647 conn, err := net.DialTimeout(netw, addr, cTimeout) 648 if err != nil { 649 return nil, err 650 } 651 err = conn.SetDeadline(time.Now().Add(rwTimeout)) 652 return conn, err 653 } 654 }