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
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"
)
2015-08-27 18:06:14 +03:00
var defaultSetting = Settings { false , "GogsServer" , 60 * time . Second , 60 * time . Second , nil , nil , nil , false }
2014-08-23 17:13:55 +04:00
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
2015-08-27 18:06:14 +03:00
func SetDefaultSetting ( setting Settings ) {
2014-08-23 17:13:55 +04:00
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
}
}
2015-08-27 18:06:14 +03:00
// return *Request with specific method
2015-10-26 16:16:24 +03:00
func newRequest ( url , method string ) * Request {
2014-08-23 17:13:55 +04:00
var resp http . Response
req := http . Request {
Method : method ,
Header : make ( http . Header ) ,
Proto : "HTTP/1.1" ,
ProtoMajor : 1 ,
ProtoMinor : 1 ,
}
2015-08-27 18:06:14 +03:00
return & Request { url , & req , map [ string ] string { } , map [ string ] string { } , defaultSetting , & resp , nil }
2014-08-23 17:13:55 +04:00
}
2014-05-06 19:50:31 +04:00
2015-08-27 18:06:14 +03:00
// Get returns *Request with GET method.
func Get ( url string ) * Request {
2015-10-26 16:16:24 +03:00
return newRequest ( url , "GET" )
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
// Post returns *Request with POST method.
func Post ( url string ) * Request {
2015-10-26 16:16:24 +03:00
return newRequest ( url , "POST" )
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
// Put returns *Request with PUT method.
func Put ( url string ) * Request {
2015-10-26 16:16:24 +03:00
return newRequest ( url , "PUT" )
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
// Delete returns *Request DELETE method.
func Delete ( url string ) * Request {
2015-10-26 16:16:24 +03:00
return newRequest ( url , "DELETE" )
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
// Head returns *Request with HEAD method.
func Head ( url string ) * Request {
2015-10-26 16:16:24 +03:00
return newRequest ( url , "HEAD" )
2014-08-23 17:13:55 +04:00
}
2015-08-27 18:06:14 +03:00
type Settings struct {
2014-08-23 17:13:55 +04:00
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
}
2015-08-27 18:06:14 +03:00
// HttpRequest provides more useful methods for requesting one url than http.Request.
type Request struct {
2014-08-23 17:13:55 +04:00
url string
req * http . Request
params map [ string ] string
files map [ string ] string
2015-08-27 18:06:14 +03:00
setting Settings
2014-08-23 17:13:55 +04:00
resp * http . Response
body [ ] byte
}
// Change request settings
2015-08-27 18:06:14 +03:00
func ( r * Request ) Setting ( setting Settings ) * Request {
r . setting = setting
return r
2014-08-23 17:13:55 +04:00
}
// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetBasicAuth ( username , password string ) * Request {
r . req . SetBasicAuth ( username , password )
return r
2014-08-23 17:13:55 +04:00
}
// SetEnableCookie sets enable/disable cookiejar
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetEnableCookie ( enable bool ) * Request {
r . setting . EnableCookie = enable
return r
2014-08-23 17:13:55 +04:00
}
// SetUserAgent sets User-Agent header field
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetUserAgent ( useragent string ) * Request {
r . setting . UserAgent = useragent
return r
2014-05-06 19:50:31 +04:00
}
// Debug sets show debug or not when executing request.
2015-08-27 18:06:14 +03:00
func ( r * Request ) Debug ( isdebug bool ) * Request {
r . setting . ShowDebug = isdebug
return r
2014-05-06 19:50:31 +04:00
}
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetTimeout ( connectTimeout , readWriteTimeout time . Duration ) * Request {
r . setting . ConnectTimeout = connectTimeout
r . setting . ReadWriteTimeout = readWriteTimeout
return r
2014-05-06 19:50:31 +04:00
}
// SetTLSClientConfig sets tls connection configurations if visiting https url.
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetTLSClientConfig ( config * tls . Config ) * Request {
r . setting . TlsClientConfig = config
return r
2014-05-06 19:50:31 +04:00
}
// Header add header item string in request.
2015-08-27 18:06:14 +03:00
func ( r * Request ) Header ( key , value string ) * Request {
r . req . Header . Set ( key , value )
return r
}
func ( r * Request ) Headers ( ) http . Header {
return r . req . Header
2014-05-06 19:50:31 +04:00
}
2014-08-23 17:13:55 +04:00
// Set the protocol version for incoming requests.
// Client requests always use HTTP/1.1.
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetProtocolVersion ( vers string ) * Request {
2014-08-23 17:13:55 +04:00
if len ( vers ) == 0 {
vers = "HTTP/1.1"
}
major , minor , ok := http . ParseHTTPVersion ( vers )
if ok {
2015-08-27 18:06:14 +03:00
r . req . Proto = vers
r . req . ProtoMajor = major
r . req . ProtoMinor = minor
2014-08-23 17:13:55 +04:00
}
2015-08-27 18:06:14 +03:00
return r
2014-08-23 17:13:55 +04:00
}
2014-05-06 19:50:31 +04:00
// SetCookie add cookie into request.
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetCookie ( cookie * http . Cookie ) * Request {
r . req . Header . Add ( "Cookie" , cookie . String ( ) )
return r
2014-05-06 19:50:31 +04:00
}
// Set transport to
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetTransport ( transport http . RoundTripper ) * Request {
r . setting . Transport = transport
return r
2014-05-06 19:50:31 +04:00
}
// Set http proxy
// example:
//
// func(req *http.Request) (*url.URL, error) {
// u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
// return u, nil
// }
2015-08-27 18:06:14 +03:00
func ( r * Request ) SetProxy ( proxy func ( * http . Request ) ( * url . URL , error ) ) * Request {
r . setting . Proxy = proxy
return r
2014-05-06 19:50:31 +04:00
}
// Param adds query param in to request.
// params build query string as ?key1=value1&key2=value2...
2015-08-27 18:06:14 +03:00
func ( r * Request ) Param ( key , value string ) * Request {
r . params [ key ] = value
return r
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
func ( r * Request ) PostFile ( formname , filename string ) * Request {
r . files [ formname ] = filename
return r
2014-08-23 17:13:55 +04:00
}
2014-05-06 19:50:31 +04:00
// Body adds request raw body.
// it supports string and []byte.
2015-08-27 18:06:14 +03:00
func ( r * Request ) Body ( data interface { } ) * Request {
2014-05-06 19:50:31 +04:00
switch t := data . ( type ) {
case string :
bf := bytes . NewBufferString ( t )
2015-08-27 18:06:14 +03:00
r . req . Body = ioutil . NopCloser ( bf )
r . req . ContentLength = int64 ( len ( t ) )
2014-05-06 19:50:31 +04:00
case [ ] byte :
bf := bytes . NewBuffer ( t )
2015-08-27 18:06:14 +03:00
r . req . Body = ioutil . NopCloser ( bf )
r . req . ContentLength = int64 ( len ( t ) )
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
return r
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
func ( r * Request ) getResponse ( ) ( * http . Response , error ) {
if r . resp . StatusCode != 0 {
return r . resp , nil
2014-08-23 17:13:55 +04:00
}
2014-05-06 19:50:31 +04:00
var paramBody string
2015-08-27 18:06:14 +03:00
if len ( r . params ) > 0 {
2014-05-06 19:50:31 +04:00
var buf bytes . Buffer
2015-08-27 18:06:14 +03:00
for k , v := range r . params {
2014-05-06 19:50:31 +04:00
buf . WriteString ( url . QueryEscape ( k ) )
buf . WriteByte ( '=' )
buf . WriteString ( url . QueryEscape ( v ) )
buf . WriteByte ( '&' )
}
paramBody = buf . String ( )
paramBody = paramBody [ 0 : len ( paramBody ) - 1 ]
}
2015-08-27 18:06:14 +03:00
if r . req . Method == "GET" && len ( paramBody ) > 0 {
if strings . Index ( r . url , "?" ) != - 1 {
r . url += "&" + paramBody
2014-05-06 19:50:31 +04:00
} else {
2015-08-27 18:06:14 +03:00
r . url = r . url + "?" + paramBody
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
} else if r . req . Method == "POST" && r . req . Body == nil {
if len ( r . files ) > 0 {
2014-11-18 23:13:08 +03:00
pr , pw := io . Pipe ( )
bodyWriter := multipart . NewWriter ( pw )
go func ( ) {
2015-08-27 18:06:14 +03:00
for formname , filename := range r . files {
2014-11-18 23:13:08 +03:00
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
}
2015-08-27 18:06:14 +03:00
for k , v := range r . params {
2014-11-18 23:13:08 +03:00
bodyWriter . WriteField ( k , v )
2014-08-23 17:13:55 +04:00
}
2014-11-18 23:13:08 +03:00
bodyWriter . Close ( )
pw . Close ( )
} ( )
2015-08-27 18:06:14 +03:00
r . Header ( "Content-Type" , bodyWriter . FormDataContentType ( ) )
r . req . Body = ioutil . NopCloser ( pr )
2014-11-18 23:13:08 +03:00
} else if len ( paramBody ) > 0 {
2015-08-27 18:06:14 +03:00
r . Header ( "Content-Type" , "application/x-www-form-urlencoded" )
r . Body ( paramBody )
2014-08-23 17:13:55 +04:00
}
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
url , err := url . Parse ( r . url )
2014-05-06 19:50:31 +04:00
if err != nil {
return nil , err
}
2015-08-27 18:06:14 +03:00
r . req . URL = url
2014-05-06 19:50:31 +04:00
2015-08-27 18:06:14 +03:00
trans := r . setting . Transport
2014-05-06 19:50:31 +04:00
if trans == nil {
// create default transport
trans = & http . Transport {
2015-08-27 18:06:14 +03:00
TLSClientConfig : r . setting . TlsClientConfig ,
Proxy : r . setting . Proxy ,
Dial : TimeoutDialer ( r . setting . ConnectTimeout , r . setting . ReadWriteTimeout ) ,
2014-05-06 19:50:31 +04:00
}
} else {
2015-08-27 18:06:14 +03:00
// if r.transport is *http.Transport then set the settings.
2014-05-06 19:50:31 +04:00
if t , ok := trans . ( * http . Transport ) ; ok {
if t . TLSClientConfig == nil {
2015-08-27 18:06:14 +03:00
t . TLSClientConfig = r . setting . TlsClientConfig
2014-05-06 19:50:31 +04:00
}
if t . Proxy == nil {
2015-08-27 18:06:14 +03:00
t . Proxy = r . setting . Proxy
2014-05-06 19:50:31 +04:00
}
if t . Dial == nil {
2015-08-27 18:06:14 +03:00
t . Dial = TimeoutDialer ( r . setting . ConnectTimeout , r . setting . ReadWriteTimeout )
2014-05-06 19:50:31 +04:00
}
}
}
2014-08-23 17:13:55 +04:00
var jar http . CookieJar
2015-08-27 18:06:14 +03:00
if r . setting . EnableCookie {
2014-08-23 17:13:55 +04:00
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 ,
}
2015-08-27 18:06:14 +03:00
if len ( r . setting . UserAgent ) > 0 && len ( r . req . Header . Get ( "User-Agent" ) ) == 0 {
r . req . Header . Set ( "User-Agent" , r . setting . UserAgent )
2014-08-23 17:13:55 +04:00
}
2015-08-27 18:06:14 +03:00
if r . setting . ShowDebug {
dump , err := httputil . DumpRequest ( r . req , true )
2014-08-23 17:13:55 +04:00
if err != nil {
println ( err . Error ( ) )
}
println ( string ( dump ) )
2014-05-06 19:50:31 +04:00
}
2015-08-27 18:06:14 +03:00
resp , err := client . Do ( r . req )
2014-05-06 19:50:31 +04:00
if err != nil {
return nil , err
}
2015-08-27 18:06:14 +03:00
r . resp = resp
2014-05-06 19:50:31 +04:00
return resp , nil
}
// String returns the body string in response.
// it calls Response inner.
2015-08-27 18:06:14 +03:00
func ( r * Request ) String ( ) ( string , error ) {
data , err := r . Bytes ( )
2014-05-06 19:50:31 +04:00
if err != nil {
return "" , err
}
return string ( data ) , nil
}
// Bytes returns the body []byte in response.
// it calls Response inner.
2015-08-27 18:06:14 +03:00
func ( r * Request ) Bytes ( ) ( [ ] byte , error ) {
if r . body != nil {
return r . body , nil
2014-08-23 17:13:55 +04:00
}
2015-08-27 18:06:14 +03:00
resp , err := r . getResponse ( )
2014-05-06 19:50:31 +04:00
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
}
2015-08-27 18:06:14 +03:00
r . 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.
2015-08-27 18:06:14 +03:00
func ( r * Request ) ToFile ( filename string ) error {
2014-05-06 19:50:31 +04:00
f , err := os . Create ( filename )
if err != nil {
return err
}
defer f . Close ( )
2015-08-27 18:06:14 +03:00
resp , err := r . getResponse ( )
2014-05-06 19:50:31 +04:00
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.
2015-08-27 18:06:14 +03:00
func ( r * Request ) ToJson ( v interface { } ) error {
data , err := r . Bytes ( )
2014-05-06 19:50:31 +04:00
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.
2015-08-27 18:06:14 +03:00
func ( r * Request ) ToXml ( v interface { } ) error {
data , err := r . Bytes ( )
2014-05-06 19:50:31 +04:00
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.
2015-08-27 18:06:14 +03:00
func ( r * Request ) Response ( ) ( * http . Response , error ) {
return r . getResponse ( )
2014-05-06 19:50:31 +04:00
}
// 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
}
}