2014-05-06 19:50:31 +04:00
// Copyright 2013 The Beego Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package httplib
2014-11-18 23:13:08 +03:00
// NOTE: last sync 57e62e5 on Oct 29, 2014.
2014-08-23 17:13:55 +04:00
2014-05-06 19:50:31 +04:00
import (
"bytes"
"crypto/tls"
"encoding/json"
"encoding/xml"
"io"
"io/ioutil"
2014-11-18 23:13:08 +03:00
"log"
2014-08-23 17:13:55 +04:00
"mime/multipart"
2014-05-06 19:50:31 +04:00
"net"
"net/http"
2014-08-23 17:13:55 +04:00
"net/http/cookiejar"
2014-05-06 19:50:31 +04:00
"net/http/httputil"
"net/url"
"os"
"strings"
2014-08-23 17:13:55 +04:00
"sync"
2014-05-06 19:50:31 +04:00
"time"
)
2014-08-23 17:13:55 +04:00
var defaultSetting = BeegoHttpSettings { false , "beegoServer" , 60 * time . Second , 60 * time . Second , nil , nil , nil , false }
var defaultCookieJar http . CookieJar
var settingMutex sync . Mutex
// createDefaultCookie creates a global cookiejar to store cookies.
func createDefaultCookie ( ) {
settingMutex . Lock ( )
defer settingMutex . Unlock ( )
defaultCookieJar , _ = cookiejar . New ( nil )
}
// Overwrite default settings
func SetDefaultSetting ( setting BeegoHttpSettings ) {
settingMutex . Lock ( )
defer settingMutex . Unlock ( )
defaultSetting = setting
if defaultSetting . ConnectTimeout == 0 {
defaultSetting . ConnectTimeout = 60 * time . Second
}
if defaultSetting . ReadWriteTimeout == 0 {
defaultSetting . ReadWriteTimeout = 60 * time . Second
}
}
// return *BeegoHttpRequest with specific method
func newBeegoRequest ( url , method string ) * BeegoHttpRequest {
var resp http . Response
req := http . Request {
Method : method ,
Header : make ( http . Header ) ,
Proto : "HTTP/1.1" ,
ProtoMajor : 1 ,
ProtoMinor : 1 ,
}
return & BeegoHttpRequest { url , & req , map [ string ] string { } , map [ string ] string { } , defaultSetting , & resp , nil }
}
2014-05-06 19:50:31 +04:00
// Get returns *BeegoHttpRequest with GET method.
func Get ( url string ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
return newBeegoRequest ( url , "GET" )
2014-05-06 19:50:31 +04:00
}
// Post returns *BeegoHttpRequest with POST method.
func Post ( url string ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
return newBeegoRequest ( url , "POST" )
2014-05-06 19:50:31 +04:00
}
// Put returns *BeegoHttpRequest with PUT method.
func Put ( url string ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
return newBeegoRequest ( url , "PUT" )
2014-05-06 19:50:31 +04:00
}
2014-08-23 17:13:55 +04:00
// Delete returns *BeegoHttpRequest DELETE method.
2014-05-06 19:50:31 +04:00
func Delete ( url string ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
return newBeegoRequest ( url , "DELETE" )
2014-05-06 19:50:31 +04:00
}
// Head returns *BeegoHttpRequest with HEAD method.
func Head ( url string ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
return newBeegoRequest ( url , "HEAD" )
}
// BeegoHttpSettings
type BeegoHttpSettings struct {
ShowDebug bool
UserAgent string
ConnectTimeout time . Duration
ReadWriteTimeout time . Duration
TlsClientConfig * tls . Config
Proxy func ( * http . Request ) ( * url . URL , error )
Transport http . RoundTripper
EnableCookie bool
2014-05-06 19:50:31 +04:00
}
// BeegoHttpRequest provides more useful methods for requesting one url than http.Request.
type BeegoHttpRequest struct {
2014-08-23 17:13:55 +04:00
url string
req * http . Request
params map [ string ] string
files map [ string ] string
setting BeegoHttpSettings
resp * http . Response
body [ ] byte
}
// Change request settings
func ( b * BeegoHttpRequest ) Setting ( setting BeegoHttpSettings ) * BeegoHttpRequest {
b . setting = setting
return b
}
// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
func ( b * BeegoHttpRequest ) SetBasicAuth ( username , password string ) * BeegoHttpRequest {
b . req . SetBasicAuth ( username , password )
return b
}
// SetEnableCookie sets enable/disable cookiejar
func ( b * BeegoHttpRequest ) SetEnableCookie ( enable bool ) * BeegoHttpRequest {
b . setting . EnableCookie = enable
return b
}
// SetUserAgent sets User-Agent header field
func ( b * BeegoHttpRequest ) SetUserAgent ( useragent string ) * BeegoHttpRequest {
b . setting . UserAgent = useragent
return b
2014-05-06 19:50:31 +04:00
}
// Debug sets show debug or not when executing request.
func ( b * BeegoHttpRequest ) Debug ( isdebug bool ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
b . setting . ShowDebug = isdebug
2014-05-06 19:50:31 +04:00
return b
}
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
func ( b * BeegoHttpRequest ) SetTimeout ( connectTimeout , readWriteTimeout time . Duration ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
b . setting . ConnectTimeout = connectTimeout
b . setting . ReadWriteTimeout = readWriteTimeout
2014-05-06 19:50:31 +04:00
return b
}
// SetTLSClientConfig sets tls connection configurations if visiting https url.
func ( b * BeegoHttpRequest ) SetTLSClientConfig ( config * tls . Config ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
b . setting . TlsClientConfig = config
2014-05-06 19:50:31 +04:00
return b
}
// Header add header item string in request.
func ( b * BeegoHttpRequest ) Header ( key , value string ) * BeegoHttpRequest {
b . req . Header . Set ( key , value )
return b
}
2014-08-23 17:13:55 +04:00
// Set the protocol version for incoming requests.
// Client requests always use HTTP/1.1.
func ( b * BeegoHttpRequest ) SetProtocolVersion ( vers string ) * BeegoHttpRequest {
if len ( vers ) == 0 {
vers = "HTTP/1.1"
}
major , minor , ok := http . ParseHTTPVersion ( vers )
if ok {
b . req . Proto = vers
b . req . ProtoMajor = major
b . req . ProtoMinor = minor
}
return b
}
2014-05-06 19:50:31 +04:00
// SetCookie add cookie into request.
func ( b * BeegoHttpRequest ) SetCookie ( cookie * http . Cookie ) * BeegoHttpRequest {
b . req . Header . Add ( "Cookie" , cookie . String ( ) )
return b
}
// Set transport to
func ( b * BeegoHttpRequest ) SetTransport ( transport http . RoundTripper ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
b . setting . Transport = transport
2014-05-06 19:50:31 +04:00
return b
}
// Set http proxy
// example:
//
// func(req *http.Request) (*url.URL, error) {
// u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
// return u, nil
// }
func ( b * BeegoHttpRequest ) SetProxy ( proxy func ( * http . Request ) ( * url . URL , error ) ) * BeegoHttpRequest {
2014-08-23 17:13:55 +04:00
b . setting . Proxy = proxy
2014-05-06 19:50:31 +04:00
return b
}
// Param adds query param in to request.
// params build query string as ?key1=value1&key2=value2...
func ( b * BeegoHttpRequest ) Param ( key , value string ) * BeegoHttpRequest {
b . params [ key ] = value
return b
}
2014-08-23 17:13:55 +04:00
func ( b * BeegoHttpRequest ) PostFile ( formname , filename string ) * BeegoHttpRequest {
b . files [ formname ] = filename
return b
}
2014-05-06 19:50:31 +04:00
// Body adds request raw body.
// it supports string and []byte.
func ( b * BeegoHttpRequest ) Body ( data interface { } ) * BeegoHttpRequest {
switch t := data . ( type ) {
case string :
bf := bytes . NewBufferString ( t )
b . req . Body = ioutil . NopCloser ( bf )
b . req . ContentLength = int64 ( len ( t ) )
case [ ] byte :
bf := bytes . NewBuffer ( t )
b . req . Body = ioutil . NopCloser ( bf )
b . req . ContentLength = int64 ( len ( t ) )
}
return b
}
func ( b * BeegoHttpRequest ) getResponse ( ) ( * http . Response , error ) {
2014-08-23 17:13:55 +04:00
if b . resp . StatusCode != 0 {
return b . resp , nil
}
2014-05-06 19:50:31 +04:00
var paramBody string
if len ( b . params ) > 0 {
var buf bytes . Buffer
for k , v := range b . params {
buf . WriteString ( url . QueryEscape ( k ) )
buf . WriteByte ( '=' )
buf . WriteString ( url . QueryEscape ( v ) )
buf . WriteByte ( '&' )
}
paramBody = buf . String ( )
paramBody = paramBody [ 0 : len ( paramBody ) - 1 ]
}
if b . req . Method == "GET" && len ( paramBody ) > 0 {
if strings . Index ( b . url , "?" ) != - 1 {
b . url += "&" + paramBody
} else {
b . url = b . url + "?" + paramBody
}
2014-11-18 23:13:08 +03:00
} else if b . req . Method == "POST" && b . req . Body == nil {
2014-08-23 17:13:55 +04:00
if len ( b . files ) > 0 {
2014-11-18 23:13:08 +03:00
pr , pw := io . Pipe ( )
bodyWriter := multipart . NewWriter ( pw )
go func ( ) {
for formname , filename := range b . files {
fileWriter , err := bodyWriter . CreateFormFile ( formname , filename )
if err != nil {
log . Fatal ( err )
}
fh , err := os . Open ( filename )
if err != nil {
log . Fatal ( err )
}
//iocopy
_ , err = io . Copy ( fileWriter , fh )
fh . Close ( )
if err != nil {
log . Fatal ( err )
}
2014-08-23 17:13:55 +04:00
}
2014-11-18 23:13:08 +03:00
for k , v := range b . params {
bodyWriter . WriteField ( k , v )
2014-08-23 17:13:55 +04:00
}
2014-11-18 23:13:08 +03:00
bodyWriter . Close ( )
pw . Close ( )
} ( )
b . Header ( "Content-Type" , bodyWriter . FormDataContentType ( ) )
b . req . Body = ioutil . NopCloser ( pr )
} else if len ( paramBody ) > 0 {
2014-08-23 17:13:55 +04:00
b . Header ( "Content-Type" , "application/x-www-form-urlencoded" )
b . Body ( paramBody )
}
2014-05-06 19:50:31 +04:00
}
url , err := url . Parse ( b . url )
if err != nil {
return nil , err
}
b . req . URL = url
2014-08-23 17:13:55 +04:00
trans := b . setting . Transport
2014-05-06 19:50:31 +04:00
if trans == nil {
// create default transport
trans = & http . Transport {
2014-08-23 17:13:55 +04:00
TLSClientConfig : b . setting . TlsClientConfig ,
Proxy : b . setting . Proxy ,
Dial : TimeoutDialer ( b . setting . ConnectTimeout , b . setting . ReadWriteTimeout ) ,
2014-05-06 19:50:31 +04:00
}
} else {
// if b.transport is *http.Transport then set the settings.
if t , ok := trans . ( * http . Transport ) ; ok {
if t . TLSClientConfig == nil {
2014-08-23 17:13:55 +04:00
t . TLSClientConfig = b . setting . TlsClientConfig
2014-05-06 19:50:31 +04:00
}
if t . Proxy == nil {
2014-08-23 17:13:55 +04:00
t . Proxy = b . setting . Proxy
2014-05-06 19:50:31 +04:00
}
if t . Dial == nil {
2014-08-23 17:13:55 +04:00
t . Dial = TimeoutDialer ( b . setting . ConnectTimeout , b . setting . ReadWriteTimeout )
2014-05-06 19:50:31 +04:00
}
}
}
2014-08-23 17:13:55 +04:00
var jar http . CookieJar
if b . setting . EnableCookie {
if defaultCookieJar == nil {
createDefaultCookie ( )
}
jar = defaultCookieJar
} else {
jar = nil
}
2014-05-06 19:50:31 +04:00
client := & http . Client {
Transport : trans ,
2014-08-23 17:13:55 +04:00
Jar : jar ,
}
2014-11-18 23:13:08 +03:00
if len ( b . setting . UserAgent ) > 0 && len ( b . req . Header . Get ( "User-Agent" ) ) == 0 {
2014-08-23 17:13:55 +04:00
b . req . Header . Set ( "User-Agent" , b . setting . UserAgent )
}
if b . setting . ShowDebug {
dump , err := httputil . DumpRequest ( b . req , true )
if err != nil {
println ( err . Error ( ) )
}
println ( string ( dump ) )
2014-05-06 19:50:31 +04:00
}
resp , err := client . Do ( b . req )
if err != nil {
return nil , err
}
2014-08-23 17:13:55 +04:00
b . resp = resp
2014-05-06 19:50:31 +04:00
return resp , nil
}
// String returns the body string in response.
// it calls Response inner.
func ( b * BeegoHttpRequest ) String ( ) ( string , error ) {
data , err := b . Bytes ( )
if err != nil {
return "" , err
}
return string ( data ) , nil
}
// Bytes returns the body []byte in response.
// it calls Response inner.
func ( b * BeegoHttpRequest ) Bytes ( ) ( [ ] byte , error ) {
2014-08-23 17:13:55 +04:00
if b . body != nil {
return b . body , nil
}
2014-05-06 19:50:31 +04:00
resp , err := b . getResponse ( )
if err != nil {
return nil , err
}
if resp . Body == nil {
return nil , nil
}
defer resp . Body . Close ( )
data , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return nil , err
}
2014-08-23 17:13:55 +04:00
b . body = data
2014-05-06 19:50:31 +04:00
return data , nil
}
// ToFile saves the body data in response to one file.
// it calls Response inner.
func ( b * BeegoHttpRequest ) ToFile ( filename string ) error {
f , err := os . Create ( filename )
if err != nil {
return err
}
defer f . Close ( )
resp , err := b . getResponse ( )
if err != nil {
return err
}
if resp . Body == nil {
return nil
}
defer resp . Body . Close ( )
_ , err = io . Copy ( f , resp . Body )
2014-08-23 17:13:55 +04:00
return err
2014-05-06 19:50:31 +04:00
}
// ToJson returns the map that marshals from the body bytes as json in response .
// it calls Response inner.
func ( b * BeegoHttpRequest ) ToJson ( v interface { } ) error {
data , err := b . Bytes ( )
if err != nil {
return err
}
err = json . Unmarshal ( data , v )
2014-08-23 17:13:55 +04:00
return err
2014-05-06 19:50:31 +04:00
}
// ToXml returns the map that marshals from the body bytes as xml in response .
// it calls Response inner.
2014-08-23 17:13:55 +04:00
func ( b * BeegoHttpRequest ) ToXml ( v interface { } ) error {
2014-05-06 19:50:31 +04:00
data , err := b . Bytes ( )
if err != nil {
return err
}
err = xml . Unmarshal ( data , v )
2014-08-23 17:13:55 +04:00
return err
2014-05-06 19:50:31 +04:00
}
// Response executes request client gets response mannually.
func ( b * BeegoHttpRequest ) Response ( ) ( * http . Response , error ) {
return b . getResponse ( )
}
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
func TimeoutDialer ( cTimeout time . Duration , rwTimeout time . Duration ) func ( net , addr string ) ( c net . Conn , err error ) {
return func ( netw , addr string ) ( net . Conn , error ) {
conn , err := net . DialTimeout ( netw , addr , cTimeout )
if err != nil {
return nil , err
}
conn . SetDeadline ( time . Now ( ) . Add ( rwTimeout ) )
return conn , nil
}
}