request.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. package httpexpect
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "mime/multipart"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "reflect"
  13. "sort"
  14. "strings"
  15. "time"
  16. "github.com/ajg/form"
  17. "github.com/fatih/structs"
  18. "github.com/gavv/monotime"
  19. "github.com/google/go-querystring/query"
  20. "github.com/imkira/go-interpol"
  21. )
  22. // Request provides methods to incrementally build http.Request object,
  23. // send it, and receive response.
  24. type Request struct {
  25. config Config
  26. chain chain
  27. http *http.Request
  28. path string
  29. query url.Values
  30. form url.Values
  31. formbuf *bytes.Buffer
  32. multipart *multipart.Writer
  33. forcetype bool
  34. typesetter string
  35. bodysetter string
  36. }
  37. // NewRequest returns a new Request object.
  38. //
  39. // method defines the HTTP method (GET, POST, PUT, etc.). path defines url path.
  40. //
  41. // Simple interpolation is allowed for {named} parameters in path:
  42. // - if pathargs is given, it's used to substitute first len(pathargs) parameters,
  43. // regardless of their names
  44. // - if WithPath() or WithPathObject() is called, it's used to substitute given
  45. // parameters by name
  46. //
  47. // For example:
  48. // req := NewRequest(config, "POST", "/repos/{user}/{repo}", "gavv", "httpexpect")
  49. // // path will be "/repos/gavv/httpexpect"
  50. //
  51. // Or:
  52. // req := NewRequest(config, "POST", "/repos/{user}/{repo}")
  53. // req.WithPath("user", "gavv")
  54. // req.WithPath("repo", "httpexpect")
  55. // // path will be "/repos/gavv/httpexpect"
  56. //
  57. // After interpolation, path is urlencoded and appended to Config.BaseURL,
  58. // separated by slash. If BaseURL ends with a slash and path (after interpolation)
  59. // starts with a slash, only single slash is inserted.
  60. func NewRequest(config Config, method, path string, pathargs ...interface{}) *Request {
  61. if config.RequestFactory == nil {
  62. panic("config.RequestFactory == nil")
  63. }
  64. if config.Client == nil {
  65. panic("config.Client == nil")
  66. }
  67. chain := makeChain(config.Reporter)
  68. n := 0
  69. path, err := interpol.WithFunc(path, func(k string, w io.Writer) error {
  70. if n < len(pathargs) {
  71. if pathargs[n] == nil {
  72. chain.fail(
  73. "\nunexpected nil argument for url path format string:\n"+
  74. " Request(\"%s\", %v...)", method, pathargs)
  75. } else {
  76. w.Write([]byte(fmt.Sprint(pathargs[n])))
  77. }
  78. } else {
  79. w.Write([]byte("{"))
  80. w.Write([]byte(k))
  81. w.Write([]byte("}"))
  82. }
  83. n++
  84. return nil
  85. })
  86. if err != nil {
  87. chain.fail(err.Error())
  88. }
  89. hr, err := config.RequestFactory.NewRequest(method, config.BaseURL, nil)
  90. if err != nil {
  91. chain.fail(err.Error())
  92. }
  93. return &Request{
  94. config: config,
  95. chain: chain,
  96. path: path,
  97. http: hr,
  98. }
  99. }
  100. // WithPath substitutes named parameters in url path.
  101. //
  102. // value is converted to string using fmt.Sprint(). If there is no named
  103. // parameter '{key}' in url path, failure is reported.
  104. //
  105. // Named parameters are case-insensitive.
  106. //
  107. // Example:
  108. // req := NewRequest(config, "POST", "/repos/{user}/{repo}")
  109. // req.WithPath("user", "gavv")
  110. // req.WithPath("repo", "httpexpect")
  111. // // path will be "/repos/gavv/httpexpect"
  112. func (r *Request) WithPath(key string, value interface{}) *Request {
  113. if r.chain.failed() {
  114. return r
  115. }
  116. ok := false
  117. path, err := interpol.WithFunc(r.path, func(k string, w io.Writer) error {
  118. if strings.EqualFold(k, key) {
  119. if value == nil {
  120. r.chain.fail(
  121. "\nunexpected nil argument for url path format string:\n"+
  122. " WithPath(\"%s\", %v)", key, value)
  123. } else {
  124. w.Write([]byte(fmt.Sprint(value)))
  125. ok = true
  126. }
  127. } else {
  128. w.Write([]byte("{"))
  129. w.Write([]byte(k))
  130. w.Write([]byte("}"))
  131. }
  132. return nil
  133. })
  134. if err == nil {
  135. r.path = path
  136. } else {
  137. r.chain.fail(err.Error())
  138. return r
  139. }
  140. if !ok {
  141. r.chain.fail("\nunexpected key for url path format string:\n"+
  142. " WithPath(\"%s\", %v)\n\npath:\n %q",
  143. key, value, r.path)
  144. return r
  145. }
  146. return r
  147. }
  148. // WithPathObject substitutes multiple named parameters in url path.
  149. //
  150. // object should be map or struct. If object is struct, it's converted
  151. // to map using https://github.com/fatih/structs. Structs may contain
  152. // "path" struct tag, similar to "json" struct tag for json.Marshal().
  153. //
  154. // Each map value is converted to string using fmt.Sprint(). If there
  155. // is no named parameter for some map '{key}' in url path, failure is
  156. // reported.
  157. //
  158. // Named parameters are case-insensitive.
  159. //
  160. // Example:
  161. // type MyPath struct {
  162. // Login string `path:"user"`
  163. // Repo string
  164. // }
  165. //
  166. // req := NewRequest(config, "POST", "/repos/{user}/{repo}")
  167. // req.WithPathObject(MyPath{"gavv", "httpexpect"})
  168. // // path will be "/repos/gavv/httpexpect"
  169. //
  170. // req := NewRequest(config, "POST", "/repos/{user}/{repo}")
  171. // req.WithPathObject(map[string]string{"user": "gavv", "repo": "httpexpect"})
  172. // // path will be "/repos/gavv/httpexpect"
  173. func (r *Request) WithPathObject(object interface{}) *Request {
  174. if r.chain.failed() {
  175. return r
  176. }
  177. if object == nil {
  178. return r
  179. }
  180. var (
  181. m map[string]interface{}
  182. ok bool
  183. )
  184. if reflect.Indirect(reflect.ValueOf(object)).Kind() == reflect.Struct {
  185. s := structs.New(object)
  186. s.TagName = "path"
  187. m = s.Map()
  188. } else {
  189. m, ok = canonMap(&r.chain, object)
  190. if !ok {
  191. return r
  192. }
  193. }
  194. for k, v := range m {
  195. r.WithPath(k, v)
  196. }
  197. return r
  198. }
  199. // WithQuery adds query parameter to request URL.
  200. //
  201. // value is converted to string using fmt.Sprint() and urlencoded.
  202. //
  203. // Example:
  204. // req := NewRequest(config, "PUT", "http://example.com/path")
  205. // req.WithQuery("a", 123)
  206. // req.WithQuery("b", "foo")
  207. // // URL is now http://example.com/path?a=123&b=foo
  208. func (r *Request) WithQuery(key string, value interface{}) *Request {
  209. if r.chain.failed() {
  210. return r
  211. }
  212. if r.query == nil {
  213. r.query = make(url.Values)
  214. }
  215. r.query.Add(key, fmt.Sprint(value))
  216. return r
  217. }
  218. // WithQueryObject adds multiple query parameters to request URL.
  219. //
  220. // object is converted to query string using github.com/google/go-querystring
  221. // if it's a struct or pointer to struct, or github.com/ajg/form otherwise.
  222. //
  223. // Various object types are supported. Structs may contain "url" struct tag,
  224. // similar to "json" struct tag for json.Marshal().
  225. //
  226. // Example:
  227. // type MyURL struct {
  228. // A int `url:"a"`
  229. // B string `url:"b"`
  230. // }
  231. //
  232. // req := NewRequest(config, "PUT", "http://example.com/path")
  233. // req.WithQueryObject(MyURL{A: 123, B: "foo"})
  234. // // URL is now http://example.com/path?a=123&b=foo
  235. //
  236. // req := NewRequest(config, "PUT", "http://example.com/path")
  237. // req.WithQueryObject(map[string]interface{}{"a": 123, "b": "foo"})
  238. // // URL is now http://example.com/path?a=123&b=foo
  239. func (r *Request) WithQueryObject(object interface{}) *Request {
  240. if r.chain.failed() {
  241. return r
  242. }
  243. if object == nil {
  244. return r
  245. }
  246. var (
  247. q url.Values
  248. err error
  249. )
  250. if reflect.Indirect(reflect.ValueOf(object)).Kind() == reflect.Struct {
  251. q, err = query.Values(object)
  252. if err != nil {
  253. r.chain.fail(err.Error())
  254. return r
  255. }
  256. } else {
  257. q, err = form.EncodeToValues(object)
  258. if err != nil {
  259. r.chain.fail(err.Error())
  260. return r
  261. }
  262. }
  263. if r.query == nil {
  264. r.query = make(url.Values)
  265. }
  266. for k, v := range q {
  267. r.query[k] = append(r.query[k], v...)
  268. }
  269. return r
  270. }
  271. // WithQueryString parses given query string and adds it to request URL.
  272. //
  273. // Example:
  274. // req := NewRequest(config, "PUT", "http://example.com/path")
  275. // req.WithQuery("a", 11)
  276. // req.WithQueryString("b=22&c=33")
  277. // // URL is now http://example.com/path?a=11&bb=22&c=33
  278. func (r *Request) WithQueryString(query string) *Request {
  279. if r.chain.failed() {
  280. return r
  281. }
  282. v, err := url.ParseQuery(query)
  283. if err != nil {
  284. r.chain.fail(err.Error())
  285. return r
  286. }
  287. if r.query == nil {
  288. r.query = make(url.Values)
  289. }
  290. for k, v := range v {
  291. r.query[k] = append(r.query[k], v...)
  292. }
  293. return r
  294. }
  295. // WithURL sets request URL.
  296. //
  297. // This URL overwrites Config.BaseURL. Request path passed to NewRequest()
  298. // is appended to this URL, separated by slash if necessary.
  299. //
  300. // Example:
  301. // req := NewRequest(config, "PUT", "/path")
  302. // req.WithURL("http://example.com")
  303. // // URL is now http://example.com/path
  304. func (r *Request) WithURL(urlStr string) *Request {
  305. if r.chain.failed() {
  306. return r
  307. }
  308. if u, err := url.Parse(urlStr); err == nil {
  309. r.http.URL = u
  310. } else {
  311. r.chain.fail(err.Error())
  312. }
  313. return r
  314. }
  315. // WithHeaders adds given headers to request.
  316. //
  317. // Example:
  318. // req := NewRequest(config, "PUT", "http://example.com/path")
  319. // req.WithHeaders(map[string]string{
  320. // "Content-Type": "application/json",
  321. // })
  322. func (r *Request) WithHeaders(headers map[string]string) *Request {
  323. if r.chain.failed() {
  324. return r
  325. }
  326. for k, v := range headers {
  327. r.WithHeader(k, v)
  328. }
  329. return r
  330. }
  331. // WithHeader adds given single header to request.
  332. //
  333. // Example:
  334. // req := NewRequest(config, "PUT", "http://example.com/path")
  335. // req.WithHeader("Content-Type": "application/json")
  336. func (r *Request) WithHeader(k, v string) *Request {
  337. if r.chain.failed() {
  338. return r
  339. }
  340. switch http.CanonicalHeaderKey(k) {
  341. case "Host":
  342. r.http.Host = v
  343. case "Content-Type":
  344. if !r.forcetype {
  345. delete(r.http.Header, "Content-Type")
  346. }
  347. r.forcetype = true
  348. r.typesetter = "WithHeader"
  349. r.http.Header.Add(k, v)
  350. default:
  351. r.http.Header.Add(k, v)
  352. }
  353. return r
  354. }
  355. // WithCookies adds given cookies to request.
  356. //
  357. // Example:
  358. // req := NewRequest(config, "PUT", "http://example.com/path")
  359. // req.WithCookies(map[string]string{
  360. // "foo": "aa",
  361. // "bar": "bb",
  362. // })
  363. func (r *Request) WithCookies(cookies map[string]string) *Request {
  364. if r.chain.failed() {
  365. return r
  366. }
  367. for k, v := range cookies {
  368. r.WithCookie(k, v)
  369. }
  370. return r
  371. }
  372. // WithCookie adds given single cookie to request.
  373. //
  374. // Example:
  375. // req := NewRequest(config, "PUT", "http://example.com/path")
  376. // req.WithCookie("name", "value")
  377. func (r *Request) WithCookie(k, v string) *Request {
  378. if r.chain.failed() {
  379. return r
  380. }
  381. r.http.AddCookie(&http.Cookie{
  382. Name: k,
  383. Value: v,
  384. })
  385. return r
  386. }
  387. // WithBasicAuth sets the request's Authorization header to use HTTP
  388. // Basic Authentication with the provided username and password.
  389. //
  390. // With HTTP Basic Authentication the provided username and password
  391. // are not encrypted.
  392. //
  393. // Example:
  394. // req := NewRequest(config, "PUT", "http://example.com/path")
  395. // req.WithBasicAuth("john", "secret")
  396. func (r *Request) WithBasicAuth(username, password string) *Request {
  397. if r.chain.failed() {
  398. return r
  399. }
  400. r.http.SetBasicAuth(username, password)
  401. return r
  402. }
  403. // WithProto sets HTTP protocol version.
  404. //
  405. // proto should have form of "HTTP/{major}.{minor}", e.g. "HTTP/1.1".
  406. //
  407. // Example:
  408. // req := NewRequest(config, "PUT", "http://example.com/path")
  409. // req.WithProto("HTTP/2.0")
  410. func (r *Request) WithProto(proto string) *Request {
  411. if r.chain.failed() {
  412. return r
  413. }
  414. major, minor, ok := http.ParseHTTPVersion(proto)
  415. if !ok {
  416. r.chain.fail(
  417. "\nunexpected protocol version %q, expected \"HTTP/{major}.{minor}\"",
  418. proto)
  419. return r
  420. }
  421. r.http.ProtoMajor = major
  422. r.http.ProtoMinor = minor
  423. return r
  424. }
  425. // WithChunked enables chunked encoding and sets request body reader.
  426. //
  427. // Expect() will read all available data from given reader. Content-Length
  428. // is not set, and "chunked" Transfer-Encoding is used.
  429. //
  430. // If protocol version is not at least HTTP/1.1 (required for chunked
  431. // encoding), failure is reported.
  432. //
  433. // Example:
  434. // req := NewRequest(config, "PUT", "http://example.com/upload")
  435. // fh, _ := os.Open("data")
  436. // defer fh.Close()
  437. // req.WithHeader("Content-Type": "application/octet-stream")
  438. // req.WithChunked(fh)
  439. func (r *Request) WithChunked(reader io.Reader) *Request {
  440. if r.chain.failed() {
  441. return r
  442. }
  443. if !r.http.ProtoAtLeast(1, 1) {
  444. r.chain.fail("chunked Transfer-Encoding requires at least \"HTTP/1.1\","+
  445. "but \"HTTP/%d.%d\" is enabled", r.http.ProtoMajor, r.http.ProtoMinor)
  446. return r
  447. }
  448. r.setBody("WithChunked", reader, -1, false)
  449. return r
  450. }
  451. // WithBytes sets request body to given slice of bytes.
  452. //
  453. // Example:
  454. // req := NewRequest(config, "PUT", "http://example.com/path")
  455. // req.WithHeader("Content-Type": "application/json")
  456. // req.WithBytes([]byte(`{"foo": 123}`))
  457. func (r *Request) WithBytes(b []byte) *Request {
  458. if r.chain.failed() {
  459. return r
  460. }
  461. if b == nil {
  462. r.setBody("WithBytes", nil, 0, false)
  463. } else {
  464. r.setBody("WithBytes", bytes.NewReader(b), len(b), false)
  465. }
  466. return r
  467. }
  468. // WithText sets Content-Type header to "text/plain; charset=utf-8" and
  469. // sets body to given string.
  470. //
  471. // Example:
  472. // req := NewRequest(config, "PUT", "http://example.com/path")
  473. // req.WithText("hello, world!")
  474. func (r *Request) WithText(s string) *Request {
  475. if r.chain.failed() {
  476. return r
  477. }
  478. r.setType("WithText", "text/plain; charset=utf-8", false)
  479. r.setBody("WithText", strings.NewReader(s), len(s), false)
  480. return r
  481. }
  482. // WithJSON sets Content-Type header to "application/json; charset=utf-8"
  483. // and sets body to object, marshaled using json.Marshal().
  484. //
  485. // Example:
  486. // type MyJSON struct {
  487. // Foo int `json:"foo"`
  488. // }
  489. //
  490. // req := NewRequest(config, "PUT", "http://example.com/path")
  491. // req.WithJSON(MyJSON{Foo: 123})
  492. //
  493. // req := NewRequest(config, "PUT", "http://example.com/path")
  494. // req.WithJSON(map[string]interface{}{"foo": 123})
  495. func (r *Request) WithJSON(object interface{}) *Request {
  496. if r.chain.failed() {
  497. return r
  498. }
  499. b, err := json.Marshal(object)
  500. if err != nil {
  501. r.chain.fail(err.Error())
  502. return r
  503. }
  504. r.setType("WithJSON", "application/json; charset=utf-8", false)
  505. r.setBody("WithJSON", bytes.NewReader(b), len(b), false)
  506. return r
  507. }
  508. // WithForm sets Content-Type header to "application/x-www-form-urlencoded"
  509. // or (if WithMultipart() was called) "multipart/form-data", converts given
  510. // object to url.Values using github.com/ajg/form, and adds it to request body.
  511. //
  512. // Various object types are supported, including maps and structs. Structs may
  513. // contain "form" struct tag, similar to "json" struct tag for json.Marshal().
  514. // See https://github.com/ajg/form for details.
  515. //
  516. // Multiple WithForm(), WithFormField(), and WithFile() calls may be combined.
  517. // If WithMultipart() is called, it should be called first.
  518. //
  519. // Example:
  520. // type MyForm struct {
  521. // Foo int `form:"foo"`
  522. // }
  523. //
  524. // req := NewRequest(config, "PUT", "http://example.com/path")
  525. // req.WithForm(MyForm{Foo: 123})
  526. //
  527. // req := NewRequest(config, "PUT", "http://example.com/path")
  528. // req.WithForm(map[string]interface{}{"foo": 123})
  529. func (r *Request) WithForm(object interface{}) *Request {
  530. if r.chain.failed() {
  531. return r
  532. }
  533. f, err := form.EncodeToValues(object)
  534. if err != nil {
  535. r.chain.fail(err.Error())
  536. return r
  537. }
  538. if r.multipart != nil {
  539. r.setType("WithForm", "multipart/form-data", false)
  540. var keys []string
  541. for k := range f {
  542. keys = append(keys, k)
  543. }
  544. sort.Strings(keys)
  545. for _, k := range keys {
  546. if err := r.multipart.WriteField(k, f[k][0]); err != nil {
  547. r.chain.fail(err.Error())
  548. return r
  549. }
  550. }
  551. } else {
  552. r.setType("WithForm", "application/x-www-form-urlencoded", false)
  553. if r.form == nil {
  554. r.form = make(url.Values)
  555. }
  556. for k, v := range f {
  557. r.form[k] = append(r.form[k], v...)
  558. }
  559. }
  560. return r
  561. }
  562. // WithFormField sets Content-Type header to "application/x-www-form-urlencoded"
  563. // or (if WithMultipart() was called) "multipart/form-data", converts given
  564. // value to string using fmt.Sprint(), and adds it to request body.
  565. //
  566. // Multiple WithForm(), WithFormField(), and WithFile() calls may be combined.
  567. // If WithMultipart() is called, it should be called first.
  568. //
  569. // Example:
  570. // req := NewRequest(config, "PUT", "http://example.com/path")
  571. // req.WithFormField("foo", 123).
  572. // WithFormField("bar", 456)
  573. func (r *Request) WithFormField(key string, value interface{}) *Request {
  574. if r.chain.failed() {
  575. return r
  576. }
  577. if r.multipart != nil {
  578. r.setType("WithFormField", "multipart/form-data", false)
  579. err := r.multipart.WriteField(key, fmt.Sprint(value))
  580. if err != nil {
  581. r.chain.fail(err.Error())
  582. return r
  583. }
  584. } else {
  585. r.setType("WithFormField", "application/x-www-form-urlencoded", false)
  586. if r.form == nil {
  587. r.form = make(url.Values)
  588. }
  589. r.form[key] = append(r.form[key], fmt.Sprint(value))
  590. }
  591. return r
  592. }
  593. // WithFile sets Content-Type header to "multipart/form-data", reads given
  594. // file and adds its contents to request body.
  595. //
  596. // If reader is given, it's used to read file contents. Otherwise, os.Open()
  597. // is used to read a file with given path.
  598. //
  599. // Multiple WithForm(), WithFormField(), and WithFile() calls may be combined.
  600. // WithMultipart() should be called before WithFile(), otherwise WithFile()
  601. // fails.
  602. //
  603. // Example:
  604. // req := NewRequest(config, "PUT", "http://example.com/path")
  605. // req.WithFile("avatar", "./john.png")
  606. //
  607. // req := NewRequest(config, "PUT", "http://example.com/path")
  608. // fh, _ := os.Open("./john.png")
  609. // req.WithMultipart().
  610. // WithFile("avatar", "john.png", fh)
  611. // fh.Close()
  612. func (r *Request) WithFile(key, path string, reader ...io.Reader) *Request {
  613. if r.chain.failed() {
  614. return r
  615. }
  616. r.setType("WithFile", "multipart/form-data", false)
  617. if r.multipart == nil {
  618. r.chain.fail("WithFile requires WithMultipart to be called first")
  619. return r
  620. }
  621. wr, err := r.multipart.CreateFormFile(key, path)
  622. if err != nil {
  623. r.chain.fail(err.Error())
  624. return r
  625. }
  626. var rd io.Reader
  627. if len(reader) != 0 && reader[0] != nil {
  628. rd = reader[0]
  629. } else {
  630. f, err := os.Open(path)
  631. if err != nil {
  632. r.chain.fail(err.Error())
  633. return r
  634. }
  635. rd = f
  636. defer f.Close()
  637. }
  638. if _, err := io.Copy(wr, rd); err != nil {
  639. r.chain.fail(err.Error())
  640. return r
  641. }
  642. return r
  643. }
  644. // WithFileBytes is like WithFile, but uses given slice of bytes as the
  645. // file contents.
  646. //
  647. // Example:
  648. // req := NewRequest(config, "PUT", "http://example.com/path")
  649. // fh, _ := os.Open("./john.png")
  650. // b, _ := ioutil.ReadAll(fh)
  651. // req.WithMultipart().
  652. // WithFileBytes("avatar", "john.png", b)
  653. // fh.Close()
  654. func (r *Request) WithFileBytes(key, path string, data []byte) *Request {
  655. if r.chain.failed() {
  656. return r
  657. }
  658. return r.WithFile(key, path, bytes.NewReader(data))
  659. }
  660. // WithMultipart sets Content-Type header to "multipart/form-data".
  661. //
  662. // After this call, WithForm() and WithFormField() switch to multipart
  663. // form instead of urlencoded form.
  664. //
  665. // If WithMultipart() is called, it should be called before WithForm(),
  666. // WithFormField(), and WithFile().
  667. //
  668. // WithFile() always requires WithMultipart() to be called first.
  669. //
  670. // Example:
  671. // req := NewRequest(config, "PUT", "http://example.com/path")
  672. // req.WithMultipart().
  673. // WithForm(map[string]interface{}{"foo": 123})
  674. func (r *Request) WithMultipart() *Request {
  675. if r.chain.failed() {
  676. return r
  677. }
  678. r.setType("WithMultipart", "multipart/form-data", false)
  679. if r.multipart == nil {
  680. r.formbuf = new(bytes.Buffer)
  681. r.multipart = multipart.NewWriter(r.formbuf)
  682. r.setBody("WithMultipart", r.formbuf, 0, false)
  683. }
  684. return r
  685. }
  686. // Expect constructs http.Request, sends it, receives http.Response, and
  687. // returns a new Response object to inspect received response.
  688. //
  689. // Request is sent using Config.Client interface.
  690. //
  691. // Example:
  692. // req := NewRequest(config, "PUT", "http://example.com/path")
  693. // req.WithJSON(map[string]interface{}{"foo": 123})
  694. // resp := req.Expect()
  695. // resp.Status(http.StatusOK)
  696. func (r *Request) Expect() *Response {
  697. r.encodeRequest()
  698. resp, elapsed := r.sendRequest()
  699. return makeResponse(r.chain, resp, elapsed)
  700. }
  701. func (r *Request) encodeRequest() {
  702. if r.chain.failed() {
  703. return
  704. }
  705. r.http.URL.Path = concatPaths(r.http.URL.Path, r.path)
  706. if r.query != nil {
  707. r.http.URL.RawQuery = r.query.Encode()
  708. }
  709. if r.multipart != nil {
  710. if err := r.multipart.Close(); err != nil {
  711. r.chain.fail(err.Error())
  712. return
  713. }
  714. r.setType("Expect", r.multipart.FormDataContentType(), true)
  715. r.setBody("Expect", r.formbuf, r.formbuf.Len(), true)
  716. } else if r.form != nil {
  717. s := r.form.Encode()
  718. r.setBody("WithForm or WithFormField", strings.NewReader(s), len(s), false)
  719. }
  720. }
  721. func (r *Request) sendRequest() (resp *http.Response, elapsed time.Duration) {
  722. if r.chain.failed() {
  723. return
  724. }
  725. for _, printer := range r.config.Printers {
  726. printer.Request(r.http)
  727. }
  728. start := monotime.Now()
  729. resp, err := r.config.Client.Do(r.http)
  730. elapsed = monotime.Since(start)
  731. if err != nil {
  732. r.chain.fail(err.Error())
  733. return
  734. }
  735. for _, printer := range r.config.Printers {
  736. printer.Response(resp, elapsed)
  737. }
  738. return
  739. }
  740. func (r *Request) setType(newSetter, newType string, overwrite bool) {
  741. if r.forcetype {
  742. return
  743. }
  744. if !overwrite {
  745. previousType := r.http.Header.Get("Content-Type")
  746. if previousType != "" && previousType != newType {
  747. r.chain.fail(
  748. "\nambiguous request \"Content-Type\" header values:\n %q (set by %s)\n\n"+
  749. "and:\n %q (wanted by %s)",
  750. previousType, r.typesetter,
  751. newType, newSetter)
  752. return
  753. }
  754. }
  755. r.typesetter = newSetter
  756. r.http.Header["Content-Type"] = []string{newType}
  757. }
  758. func (r *Request) setBody(setter string, reader io.Reader, len int, overwrite bool) {
  759. if !overwrite && r.bodysetter != "" {
  760. r.chain.fail(
  761. "\nambiguous request body contents:\n set by %s\n overwritten by %s",
  762. r.bodysetter, setter)
  763. return
  764. }
  765. if len > 0 && reader == nil {
  766. panic("invalid length")
  767. }
  768. if reader == nil {
  769. r.http.Body = nil
  770. r.http.ContentLength = 0
  771. } else {
  772. r.http.Body = ioutil.NopCloser(reader)
  773. r.http.ContentLength = int64(len)
  774. }
  775. r.bodysetter = setter
  776. }
  777. func concatPaths(a, b string) string {
  778. if a == "" {
  779. return b
  780. }
  781. if b == "" {
  782. return a
  783. }
  784. a = strings.TrimSuffix(a, "/")
  785. b = strings.TrimPrefix(b, "/")
  786. return a + "/" + b
  787. }