You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

448 lines
11 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. // Copyright 2013 The Beego Authors. All rights reserved.
  2. // Copyright 2014 The Gogs 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. package httplib
  6. // NOTE: last sync 57e62e5 on Oct 29, 2014.
  7. import (
  8. "bytes"
  9. "crypto/tls"
  10. "encoding/json"
  11. "encoding/xml"
  12. "io"
  13. "io/ioutil"
  14. "log"
  15. "mime/multipart"
  16. "net"
  17. "net/http"
  18. "net/http/cookiejar"
  19. "net/http/httputil"
  20. "net/url"
  21. "os"
  22. "strings"
  23. "sync"
  24. "time"
  25. )
  26. var defaultSetting = BeegoHttpSettings{false, "beegoServer", 60 * time.Second, 60 * time.Second, nil, nil, nil, false}
  27. var defaultCookieJar http.CookieJar
  28. var settingMutex sync.Mutex
  29. // createDefaultCookie creates a global cookiejar to store cookies.
  30. func createDefaultCookie() {
  31. settingMutex.Lock()
  32. defer settingMutex.Unlock()
  33. defaultCookieJar, _ = cookiejar.New(nil)
  34. }
  35. // Overwrite default settings
  36. func SetDefaultSetting(setting BeegoHttpSettings) {
  37. settingMutex.Lock()
  38. defer settingMutex.Unlock()
  39. defaultSetting = setting
  40. if defaultSetting.ConnectTimeout == 0 {
  41. defaultSetting.ConnectTimeout = 60 * time.Second
  42. }
  43. if defaultSetting.ReadWriteTimeout == 0 {
  44. defaultSetting.ReadWriteTimeout = 60 * time.Second
  45. }
  46. }
  47. // return *BeegoHttpRequest with specific method
  48. func newBeegoRequest(url, method string) *BeegoHttpRequest {
  49. var resp http.Response
  50. req := http.Request{
  51. Method: method,
  52. Header: make(http.Header),
  53. Proto: "HTTP/1.1",
  54. ProtoMajor: 1,
  55. ProtoMinor: 1,
  56. }
  57. return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil}
  58. }
  59. // Get returns *BeegoHttpRequest with GET method.
  60. func Get(url string) *BeegoHttpRequest {
  61. return newBeegoRequest(url, "GET")
  62. }
  63. // Post returns *BeegoHttpRequest with POST method.
  64. func Post(url string) *BeegoHttpRequest {
  65. return newBeegoRequest(url, "POST")
  66. }
  67. // Put returns *BeegoHttpRequest with PUT method.
  68. func Put(url string) *BeegoHttpRequest {
  69. return newBeegoRequest(url, "PUT")
  70. }
  71. // Delete returns *BeegoHttpRequest DELETE method.
  72. func Delete(url string) *BeegoHttpRequest {
  73. return newBeegoRequest(url, "DELETE")
  74. }
  75. // Head returns *BeegoHttpRequest with HEAD method.
  76. func Head(url string) *BeegoHttpRequest {
  77. return newBeegoRequest(url, "HEAD")
  78. }
  79. // BeegoHttpSettings
  80. type BeegoHttpSettings struct {
  81. ShowDebug bool
  82. UserAgent string
  83. ConnectTimeout time.Duration
  84. ReadWriteTimeout time.Duration
  85. TlsClientConfig *tls.Config
  86. Proxy func(*http.Request) (*url.URL, error)
  87. Transport http.RoundTripper
  88. EnableCookie bool
  89. }
  90. // BeegoHttpRequest provides more useful methods for requesting one url than http.Request.
  91. type BeegoHttpRequest struct {
  92. url string
  93. req *http.Request
  94. params map[string]string
  95. files map[string]string
  96. setting BeegoHttpSettings
  97. resp *http.Response
  98. body []byte
  99. }
  100. // Change request settings
  101. func (b *BeegoHttpRequest) Setting(setting BeegoHttpSettings) *BeegoHttpRequest {
  102. b.setting = setting
  103. return b
  104. }
  105. // SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
  106. func (b *BeegoHttpRequest) SetBasicAuth(username, password string) *BeegoHttpRequest {
  107. b.req.SetBasicAuth(username, password)
  108. return b
  109. }
  110. // SetEnableCookie sets enable/disable cookiejar
  111. func (b *BeegoHttpRequest) SetEnableCookie(enable bool) *BeegoHttpRequest {
  112. b.setting.EnableCookie = enable
  113. return b
  114. }
  115. // SetUserAgent sets User-Agent header field
  116. func (b *BeegoHttpRequest) SetUserAgent(useragent string) *BeegoHttpRequest {
  117. b.setting.UserAgent = useragent
  118. return b
  119. }
  120. // Debug sets show debug or not when executing request.
  121. func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
  122. b.setting.ShowDebug = isdebug
  123. return b
  124. }
  125. // SetTimeout sets connect time out and read-write time out for BeegoRequest.
  126. func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest {
  127. b.setting.ConnectTimeout = connectTimeout
  128. b.setting.ReadWriteTimeout = readWriteTimeout
  129. return b
  130. }
  131. // SetTLSClientConfig sets tls connection configurations if visiting https url.
  132. func (b *BeegoHttpRequest) SetTLSClientConfig(config *tls.Config) *BeegoHttpRequest {
  133. b.setting.TlsClientConfig = config
  134. return b
  135. }
  136. // Header add header item string in request.
  137. func (b *BeegoHttpRequest) Header(key, value string) *BeegoHttpRequest {
  138. b.req.Header.Set(key, value)
  139. return b
  140. }
  141. // Set the protocol version for incoming requests.
  142. // Client requests always use HTTP/1.1.
  143. func (b *BeegoHttpRequest) SetProtocolVersion(vers string) *BeegoHttpRequest {
  144. if len(vers) == 0 {
  145. vers = "HTTP/1.1"
  146. }
  147. major, minor, ok := http.ParseHTTPVersion(vers)
  148. if ok {
  149. b.req.Proto = vers
  150. b.req.ProtoMajor = major
  151. b.req.ProtoMinor = minor
  152. }
  153. return b
  154. }
  155. // SetCookie add cookie into request.
  156. func (b *BeegoHttpRequest) SetCookie(cookie *http.Cookie) *BeegoHttpRequest {
  157. b.req.Header.Add("Cookie", cookie.String())
  158. return b
  159. }
  160. // Set transport to
  161. func (b *BeegoHttpRequest) SetTransport(transport http.RoundTripper) *BeegoHttpRequest {
  162. b.setting.Transport = transport
  163. return b
  164. }
  165. // Set http proxy
  166. // example:
  167. //
  168. // func(req *http.Request) (*url.URL, error) {
  169. // u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
  170. // return u, nil
  171. // }
  172. func (b *BeegoHttpRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHttpRequest {
  173. b.setting.Proxy = proxy
  174. return b
  175. }
  176. // Param adds query param in to request.
  177. // params build query string as ?key1=value1&key2=value2...
  178. func (b *BeegoHttpRequest) Param(key, value string) *BeegoHttpRequest {
  179. b.params[key] = value
  180. return b
  181. }
  182. func (b *BeegoHttpRequest) PostFile(formname, filename string) *BeegoHttpRequest {
  183. b.files[formname] = filename
  184. return b
  185. }
  186. // Body adds request raw body.
  187. // it supports string and []byte.
  188. func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
  189. switch t := data.(type) {
  190. case string:
  191. bf := bytes.NewBufferString(t)
  192. b.req.Body = ioutil.NopCloser(bf)
  193. b.req.ContentLength = int64(len(t))
  194. case []byte:
  195. bf := bytes.NewBuffer(t)
  196. b.req.Body = ioutil.NopCloser(bf)
  197. b.req.ContentLength = int64(len(t))
  198. }
  199. return b
  200. }
  201. func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
  202. if b.resp.StatusCode != 0 {
  203. return b.resp, nil
  204. }
  205. var paramBody string
  206. if len(b.params) > 0 {
  207. var buf bytes.Buffer
  208. for k, v := range b.params {
  209. buf.WriteString(url.QueryEscape(k))
  210. buf.WriteByte('=')
  211. buf.WriteString(url.QueryEscape(v))
  212. buf.WriteByte('&')
  213. }
  214. paramBody = buf.String()
  215. paramBody = paramBody[0 : len(paramBody)-1]
  216. }
  217. if b.req.Method == "GET" && len(paramBody) > 0 {
  218. if strings.Index(b.url, "?") != -1 {
  219. b.url += "&" + paramBody
  220. } else {
  221. b.url = b.url + "?" + paramBody
  222. }
  223. } else if b.req.Method == "POST" && b.req.Body == nil {
  224. if len(b.files) > 0 {
  225. pr, pw := io.Pipe()
  226. bodyWriter := multipart.NewWriter(pw)
  227. go func() {
  228. for formname, filename := range b.files {
  229. fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
  230. if err != nil {
  231. log.Fatal(err)
  232. }
  233. fh, err := os.Open(filename)
  234. if err != nil {
  235. log.Fatal(err)
  236. }
  237. //iocopy
  238. _, err = io.Copy(fileWriter, fh)
  239. fh.Close()
  240. if err != nil {
  241. log.Fatal(err)
  242. }
  243. }
  244. for k, v := range b.params {
  245. bodyWriter.WriteField(k, v)
  246. }
  247. bodyWriter.Close()
  248. pw.Close()
  249. }()
  250. b.Header("Content-Type", bodyWriter.FormDataContentType())
  251. b.req.Body = ioutil.NopCloser(pr)
  252. } else if len(paramBody) > 0 {
  253. b.Header("Content-Type", "application/x-www-form-urlencoded")
  254. b.Body(paramBody)
  255. }
  256. }
  257. url, err := url.Parse(b.url)
  258. if err != nil {
  259. return nil, err
  260. }
  261. b.req.URL = url
  262. trans := b.setting.Transport
  263. if trans == nil {
  264. // create default transport
  265. trans = &http.Transport{
  266. TLSClientConfig: b.setting.TlsClientConfig,
  267. Proxy: b.setting.Proxy,
  268. Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
  269. }
  270. } else {
  271. // if b.transport is *http.Transport then set the settings.
  272. if t, ok := trans.(*http.Transport); ok {
  273. if t.TLSClientConfig == nil {
  274. t.TLSClientConfig = b.setting.TlsClientConfig
  275. }
  276. if t.Proxy == nil {
  277. t.Proxy = b.setting.Proxy
  278. }
  279. if t.Dial == nil {
  280. t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
  281. }
  282. }
  283. }
  284. var jar http.CookieJar
  285. if b.setting.EnableCookie {
  286. if defaultCookieJar == nil {
  287. createDefaultCookie()
  288. }
  289. jar = defaultCookieJar
  290. } else {
  291. jar = nil
  292. }
  293. client := &http.Client{
  294. Transport: trans,
  295. Jar: jar,
  296. }
  297. if len(b.setting.UserAgent) > 0 && len(b.req.Header.Get("User-Agent")) == 0 {
  298. b.req.Header.Set("User-Agent", b.setting.UserAgent)
  299. }
  300. if b.setting.ShowDebug {
  301. dump, err := httputil.DumpRequest(b.req, true)
  302. if err != nil {
  303. println(err.Error())
  304. }
  305. println(string(dump))
  306. }
  307. resp, err := client.Do(b.req)
  308. if err != nil {
  309. return nil, err
  310. }
  311. b.resp = resp
  312. return resp, nil
  313. }
  314. // String returns the body string in response.
  315. // it calls Response inner.
  316. func (b *BeegoHttpRequest) String() (string, error) {
  317. data, err := b.Bytes()
  318. if err != nil {
  319. return "", err
  320. }
  321. return string(data), nil
  322. }
  323. // Bytes returns the body []byte in response.
  324. // it calls Response inner.
  325. func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
  326. if b.body != nil {
  327. return b.body, nil
  328. }
  329. resp, err := b.getResponse()
  330. if err != nil {
  331. return nil, err
  332. }
  333. if resp.Body == nil {
  334. return nil, nil
  335. }
  336. defer resp.Body.Close()
  337. data, err := ioutil.ReadAll(resp.Body)
  338. if err != nil {
  339. return nil, err
  340. }
  341. b.body = data
  342. return data, nil
  343. }
  344. // ToFile saves the body data in response to one file.
  345. // it calls Response inner.
  346. func (b *BeegoHttpRequest) ToFile(filename string) error {
  347. f, err := os.Create(filename)
  348. if err != nil {
  349. return err
  350. }
  351. defer f.Close()
  352. resp, err := b.getResponse()
  353. if err != nil {
  354. return err
  355. }
  356. if resp.Body == nil {
  357. return nil
  358. }
  359. defer resp.Body.Close()
  360. _, err = io.Copy(f, resp.Body)
  361. return err
  362. }
  363. // ToJson returns the map that marshals from the body bytes as json in response .
  364. // it calls Response inner.
  365. func (b *BeegoHttpRequest) ToJson(v interface{}) error {
  366. data, err := b.Bytes()
  367. if err != nil {
  368. return err
  369. }
  370. err = json.Unmarshal(data, v)
  371. return err
  372. }
  373. // ToXml returns the map that marshals from the body bytes as xml in response .
  374. // it calls Response inner.
  375. func (b *BeegoHttpRequest) ToXml(v interface{}) error {
  376. data, err := b.Bytes()
  377. if err != nil {
  378. return err
  379. }
  380. err = xml.Unmarshal(data, v)
  381. return err
  382. }
  383. // Response executes request client gets response mannually.
  384. func (b *BeegoHttpRequest) Response() (*http.Response, error) {
  385. return b.getResponse()
  386. }
  387. // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
  388. func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
  389. return func(netw, addr string) (net.Conn, error) {
  390. conn, err := net.DialTimeout(netw, addr, cTimeout)
  391. if err != nil {
  392. return nil, err
  393. }
  394. conn.SetDeadline(time.Now().Add(rwTimeout))
  395. return conn, nil
  396. }
  397. }