session.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. // Copyright (c) 2012-2013 Jason McVetta. This is Free Software, released
  2. // under the terms of the GPL v3. See http://www.gnu.org/copyleft/gpl.html for
  3. // details. Resist intellectual serfdom - the ownership of ideas is akin to
  4. // slavery.
  5. package napping
  6. /*
  7. This module provides a Session object to manage and persist settings across
  8. requests (cookies, auth, proxies).
  9. */
  10. import (
  11. "bytes"
  12. "encoding/base64"
  13. "encoding/json"
  14. "errors"
  15. "io/ioutil"
  16. "log"
  17. "net/http"
  18. "net/url"
  19. "strings"
  20. "time"
  21. )
  22. // Session defines the napping session structure
  23. type Session struct {
  24. Client *http.Client
  25. Log bool // Log request and response
  26. // Optional
  27. Userinfo *url.Userinfo
  28. // Optional defaults - can be overridden in a Request
  29. Header *http.Header
  30. Params *url.Values
  31. }
  32. // Send constructs and sends an HTTP request.
  33. func (s *Session) Send(r *Request) (response *Response, err error) {
  34. r.Method = strings.ToUpper(r.Method)
  35. //
  36. // Create a URL object from the raw url string. This will allow us to compose
  37. // query parameters programmatically and be guaranteed of a well-formed URL.
  38. //
  39. u, err := url.Parse(r.Url)
  40. if err != nil {
  41. s.log("URL", r.Url)
  42. s.log(err)
  43. return
  44. }
  45. //
  46. // Default query parameters
  47. //
  48. p := url.Values{}
  49. if s.Params != nil {
  50. for k, v := range *s.Params {
  51. p[k] = v
  52. }
  53. }
  54. //
  55. // Parameters that were present in URL
  56. //
  57. if u.Query() != nil {
  58. for k, v := range u.Query() {
  59. p[k] = v
  60. }
  61. }
  62. //
  63. // User-supplied params override default
  64. //
  65. if r.Params != nil {
  66. for k, v := range *r.Params {
  67. p[k] = v
  68. }
  69. }
  70. //
  71. // Encode parameters
  72. //
  73. u.RawQuery = p.Encode()
  74. //
  75. // Attach params to response
  76. //
  77. r.Params = &p
  78. //
  79. // Create a Request object; if populated, Data field is JSON encoded as
  80. // request body
  81. //
  82. header := http.Header{}
  83. if s.Header != nil {
  84. for k := range *s.Header {
  85. v := s.Header.Get(k)
  86. header.Set(k, v)
  87. }
  88. }
  89. var req *http.Request
  90. var buf *bytes.Buffer
  91. if r.Payload != nil {
  92. if r.RawPayload {
  93. var ok bool
  94. // buf can be nil interface at this point
  95. // so we'll do extra nil check
  96. buf, ok = r.Payload.(*bytes.Buffer)
  97. if !ok {
  98. err = errors.New("Payload must be of type *bytes.Buffer if RawPayload is set to true")
  99. return
  100. }
  101. // do not overwrite the content type with raw payload
  102. } else {
  103. var b []byte
  104. b, err = json.Marshal(&r.Payload)
  105. if err != nil {
  106. s.log(err)
  107. return
  108. }
  109. buf = bytes.NewBuffer(b)
  110. // Overwrite the content type to json since we're pushing the payload as json
  111. header.Set("Content-Type", "application/json")
  112. }
  113. if buf != nil {
  114. req, err = http.NewRequest(r.Method, u.String(), buf)
  115. } else {
  116. req, err = http.NewRequest(r.Method, u.String(), nil)
  117. }
  118. if err != nil {
  119. s.log(err)
  120. return
  121. }
  122. } else { // no data to encode
  123. req, err = http.NewRequest(r.Method, u.String(), nil)
  124. if err != nil {
  125. s.log(err)
  126. return
  127. }
  128. }
  129. //
  130. // Merge Session and Request options
  131. //
  132. var userinfo *url.Userinfo
  133. if u.User != nil {
  134. userinfo = u.User
  135. }
  136. if s.Userinfo != nil {
  137. userinfo = s.Userinfo
  138. }
  139. // Prefer Request's user credentials
  140. if r.Userinfo != nil {
  141. userinfo = r.Userinfo
  142. }
  143. if r.Header != nil {
  144. for k, v := range *r.Header {
  145. header.Set(k, v[0]) // Is there always guarnateed to be at least one value for a header?
  146. }
  147. }
  148. if header.Get("Accept") == "" {
  149. header.Add("Accept", "application/json") // Default, can be overridden with Opts
  150. }
  151. req.Header = header
  152. //
  153. // Set HTTP Basic authentication if userinfo is supplied
  154. //
  155. if userinfo != nil {
  156. pwd, _ := userinfo.Password()
  157. req.SetBasicAuth(userinfo.Username(), pwd)
  158. if u.Scheme != "https" {
  159. s.log("WARNING: Using HTTP Basic Auth in cleartext is insecure.")
  160. }
  161. }
  162. //
  163. // Execute the HTTP request
  164. //
  165. // Debug log request
  166. if s.Log {
  167. s.log("--------------------------------------------------------------------------------")
  168. s.log("REQUEST")
  169. s.log("--------------------------------------------------------------------------------")
  170. s.log("Method:", req.Method)
  171. s.log("URL:", req.URL)
  172. s.log("Header:", req.Header)
  173. s.log("Form:", req.Form)
  174. s.log("Payload:")
  175. if r.RawPayload && s.Log && buf != nil {
  176. s.log(base64.StdEncoding.EncodeToString(buf.Bytes()))
  177. } else {
  178. s.log(pretty(r.Payload))
  179. }
  180. }
  181. r.timestamp = time.Now()
  182. var client *http.Client
  183. if s.Client != nil {
  184. client = s.Client
  185. } else {
  186. client = &http.Client{}
  187. if r.Transport != nil {
  188. client.Transport = r.Transport
  189. }
  190. s.Client = client
  191. }
  192. resp, err := client.Do(req)
  193. if err != nil {
  194. s.log(err)
  195. return
  196. }
  197. defer resp.Body.Close()
  198. r.status = resp.StatusCode
  199. r.response = resp
  200. //
  201. // Unmarshal
  202. //
  203. r.body, err = ioutil.ReadAll(resp.Body)
  204. if err != nil {
  205. s.log(err)
  206. return
  207. }
  208. if string(r.body) != "" {
  209. if resp.StatusCode < 300 && r.Result != nil {
  210. err = json.Unmarshal(r.body, r.Result)
  211. }
  212. if resp.StatusCode >= 400 && r.Error != nil {
  213. json.Unmarshal(r.body, r.Error) // Should we ignore unmarshal error?
  214. }
  215. }
  216. if r.CaptureResponseBody {
  217. r.ResponseBody = bytes.NewBuffer(r.body)
  218. }
  219. rsp := Response(*r)
  220. response = &rsp
  221. // Debug log response
  222. if s.Log {
  223. s.log("--------------------------------------------------------------------------------")
  224. s.log("RESPONSE")
  225. s.log("--------------------------------------------------------------------------------")
  226. s.log("Status: ", response.status)
  227. s.log("Header:")
  228. s.log(pretty(response.HttpResponse().Header))
  229. s.log("Body:")
  230. if response.body != nil {
  231. raw := json.RawMessage{}
  232. if json.Unmarshal(response.body, &raw) == nil {
  233. s.log(pretty(&raw))
  234. } else {
  235. s.log(pretty(response.RawText()))
  236. }
  237. } else {
  238. s.log("Empty response body")
  239. }
  240. }
  241. return
  242. }
  243. // Get sends a GET request.
  244. func (s *Session) Get(url string, p *url.Values, result, errMsg interface{}) (*Response, error) {
  245. r := Request{
  246. Method: "GET",
  247. Url: url,
  248. Params: p,
  249. Result: result,
  250. Error: errMsg,
  251. }
  252. return s.Send(&r)
  253. }
  254. // Options sends an OPTIONS request.
  255. func (s *Session) Options(url string, result, errMsg interface{}) (*Response, error) {
  256. r := Request{
  257. Method: "OPTIONS",
  258. Url: url,
  259. Result: result,
  260. Error: errMsg,
  261. }
  262. return s.Send(&r)
  263. }
  264. // Head sends a HEAD request.
  265. func (s *Session) Head(url string, result, errMsg interface{}) (*Response, error) {
  266. r := Request{
  267. Method: "HEAD",
  268. Url: url,
  269. Result: result,
  270. Error: errMsg,
  271. }
  272. return s.Send(&r)
  273. }
  274. // Post sends a POST request.
  275. func (s *Session) Post(url string, payload, result, errMsg interface{}) (*Response, error) {
  276. r := Request{
  277. Method: "POST",
  278. Url: url,
  279. Payload: payload,
  280. Result: result,
  281. Error: errMsg,
  282. }
  283. return s.Send(&r)
  284. }
  285. // Put sends a PUT request.
  286. func (s *Session) Put(url string, payload, result, errMsg interface{}) (*Response, error) {
  287. r := Request{
  288. Method: "PUT",
  289. Url: url,
  290. Payload: payload,
  291. Result: result,
  292. Error: errMsg,
  293. }
  294. return s.Send(&r)
  295. }
  296. // Patch sends a PATCH request.
  297. func (s *Session) Patch(url string, payload, result, errMsg interface{}) (*Response, error) {
  298. r := Request{
  299. Method: "PATCH",
  300. Url: url,
  301. Payload: payload,
  302. Result: result,
  303. Error: errMsg,
  304. }
  305. return s.Send(&r)
  306. }
  307. // Delete sends a DELETE request.
  308. func (s *Session) Delete(url string, p *url.Values, result, errMsg interface{}) (*Response, error) {
  309. r := Request{
  310. Method: "DELETE",
  311. Url: url,
  312. Params: p,
  313. Result: result,
  314. Error: errMsg,
  315. }
  316. return s.Send(&r)
  317. }
  318. // Debug method for logging
  319. // Centralizing logging in one method
  320. // avoids spreading conditionals everywhere
  321. func (s *Session) log(args ...interface{}) {
  322. if s.Log {
  323. log.Println(args...)
  324. }
  325. }