PageRenderTime 42ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/third_party/gofrontend/libgo/go/net/http/requestwrite_test.go

http://github.com/axw/llgo
Go | 654 lines | 541 code | 70 blank | 43 comment | 40 complexity | e52a6745be438d0ce71017ce5f5ecd4e MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. // Copyright 2010 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package http
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "net/url"
  12. "strings"
  13. "testing"
  14. )
  15. type reqWriteTest struct {
  16. Req Request
  17. Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
  18. // Any of these three may be empty to skip that test.
  19. WantWrite string // Request.Write
  20. WantProxy string // Request.WriteProxy
  21. WantError error // wanted error from Request.Write
  22. }
  23. var reqWriteTests = []reqWriteTest{
  24. // HTTP/1.1 => chunked coding; no body; no trailer
  25. {
  26. Req: Request{
  27. Method: "GET",
  28. URL: &url.URL{
  29. Scheme: "http",
  30. Host: "www.techcrunch.com",
  31. Path: "/",
  32. },
  33. Proto: "HTTP/1.1",
  34. ProtoMajor: 1,
  35. ProtoMinor: 1,
  36. Header: Header{
  37. "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
  38. "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
  39. "Accept-Encoding": {"gzip,deflate"},
  40. "Accept-Language": {"en-us,en;q=0.5"},
  41. "Keep-Alive": {"300"},
  42. "Proxy-Connection": {"keep-alive"},
  43. "User-Agent": {"Fake"},
  44. },
  45. Body: nil,
  46. Close: false,
  47. Host: "www.techcrunch.com",
  48. Form: map[string][]string{},
  49. },
  50. WantWrite: "GET / HTTP/1.1\r\n" +
  51. "Host: www.techcrunch.com\r\n" +
  52. "User-Agent: Fake\r\n" +
  53. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
  54. "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
  55. "Accept-Encoding: gzip,deflate\r\n" +
  56. "Accept-Language: en-us,en;q=0.5\r\n" +
  57. "Keep-Alive: 300\r\n" +
  58. "Proxy-Connection: keep-alive\r\n\r\n",
  59. WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
  60. "Host: www.techcrunch.com\r\n" +
  61. "User-Agent: Fake\r\n" +
  62. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
  63. "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
  64. "Accept-Encoding: gzip,deflate\r\n" +
  65. "Accept-Language: en-us,en;q=0.5\r\n" +
  66. "Keep-Alive: 300\r\n" +
  67. "Proxy-Connection: keep-alive\r\n\r\n",
  68. },
  69. // HTTP/1.1 => chunked coding; body; empty trailer
  70. {
  71. Req: Request{
  72. Method: "GET",
  73. URL: &url.URL{
  74. Scheme: "http",
  75. Host: "www.google.com",
  76. Path: "/search",
  77. },
  78. ProtoMajor: 1,
  79. ProtoMinor: 1,
  80. Header: Header{},
  81. TransferEncoding: []string{"chunked"},
  82. },
  83. Body: []byte("abcdef"),
  84. WantWrite: "GET /search HTTP/1.1\r\n" +
  85. "Host: www.google.com\r\n" +
  86. "User-Agent: Go-http-client/1.1\r\n" +
  87. "Transfer-Encoding: chunked\r\n\r\n" +
  88. chunk("abcdef") + chunk(""),
  89. WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" +
  90. "Host: www.google.com\r\n" +
  91. "User-Agent: Go-http-client/1.1\r\n" +
  92. "Transfer-Encoding: chunked\r\n\r\n" +
  93. chunk("abcdef") + chunk(""),
  94. },
  95. // HTTP/1.1 POST => chunked coding; body; empty trailer
  96. {
  97. Req: Request{
  98. Method: "POST",
  99. URL: &url.URL{
  100. Scheme: "http",
  101. Host: "www.google.com",
  102. Path: "/search",
  103. },
  104. ProtoMajor: 1,
  105. ProtoMinor: 1,
  106. Header: Header{},
  107. Close: true,
  108. TransferEncoding: []string{"chunked"},
  109. },
  110. Body: []byte("abcdef"),
  111. WantWrite: "POST /search HTTP/1.1\r\n" +
  112. "Host: www.google.com\r\n" +
  113. "User-Agent: Go-http-client/1.1\r\n" +
  114. "Connection: close\r\n" +
  115. "Transfer-Encoding: chunked\r\n\r\n" +
  116. chunk("abcdef") + chunk(""),
  117. WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
  118. "Host: www.google.com\r\n" +
  119. "User-Agent: Go-http-client/1.1\r\n" +
  120. "Connection: close\r\n" +
  121. "Transfer-Encoding: chunked\r\n\r\n" +
  122. chunk("abcdef") + chunk(""),
  123. },
  124. // HTTP/1.1 POST with Content-Length, no chunking
  125. {
  126. Req: Request{
  127. Method: "POST",
  128. URL: &url.URL{
  129. Scheme: "http",
  130. Host: "www.google.com",
  131. Path: "/search",
  132. },
  133. ProtoMajor: 1,
  134. ProtoMinor: 1,
  135. Header: Header{},
  136. Close: true,
  137. ContentLength: 6,
  138. },
  139. Body: []byte("abcdef"),
  140. WantWrite: "POST /search HTTP/1.1\r\n" +
  141. "Host: www.google.com\r\n" +
  142. "User-Agent: Go-http-client/1.1\r\n" +
  143. "Connection: close\r\n" +
  144. "Content-Length: 6\r\n" +
  145. "\r\n" +
  146. "abcdef",
  147. WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
  148. "Host: www.google.com\r\n" +
  149. "User-Agent: Go-http-client/1.1\r\n" +
  150. "Connection: close\r\n" +
  151. "Content-Length: 6\r\n" +
  152. "\r\n" +
  153. "abcdef",
  154. },
  155. // HTTP/1.1 POST with Content-Length in headers
  156. {
  157. Req: Request{
  158. Method: "POST",
  159. URL: mustParseURL("http://example.com/"),
  160. Host: "example.com",
  161. Header: Header{
  162. "Content-Length": []string{"10"}, // ignored
  163. },
  164. ContentLength: 6,
  165. },
  166. Body: []byte("abcdef"),
  167. WantWrite: "POST / HTTP/1.1\r\n" +
  168. "Host: example.com\r\n" +
  169. "User-Agent: Go-http-client/1.1\r\n" +
  170. "Content-Length: 6\r\n" +
  171. "\r\n" +
  172. "abcdef",
  173. WantProxy: "POST http://example.com/ HTTP/1.1\r\n" +
  174. "Host: example.com\r\n" +
  175. "User-Agent: Go-http-client/1.1\r\n" +
  176. "Content-Length: 6\r\n" +
  177. "\r\n" +
  178. "abcdef",
  179. },
  180. // default to HTTP/1.1
  181. {
  182. Req: Request{
  183. Method: "GET",
  184. URL: mustParseURL("/search"),
  185. Host: "www.google.com",
  186. },
  187. WantWrite: "GET /search HTTP/1.1\r\n" +
  188. "Host: www.google.com\r\n" +
  189. "User-Agent: Go-http-client/1.1\r\n" +
  190. "\r\n",
  191. },
  192. // Request with a 0 ContentLength and a 0 byte body.
  193. {
  194. Req: Request{
  195. Method: "POST",
  196. URL: mustParseURL("/"),
  197. Host: "example.com",
  198. ProtoMajor: 1,
  199. ProtoMinor: 1,
  200. ContentLength: 0, // as if unset by user
  201. },
  202. Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) },
  203. // RFC 2616 Section 14.13 says Content-Length should be specified
  204. // unless body is prohibited by the request method.
  205. // Also, nginx expects it for POST and PUT.
  206. WantWrite: "POST / HTTP/1.1\r\n" +
  207. "Host: example.com\r\n" +
  208. "User-Agent: Go-http-client/1.1\r\n" +
  209. "Content-Length: 0\r\n" +
  210. "\r\n",
  211. WantProxy: "POST / HTTP/1.1\r\n" +
  212. "Host: example.com\r\n" +
  213. "User-Agent: Go-http-client/1.1\r\n" +
  214. "Content-Length: 0\r\n" +
  215. "\r\n",
  216. },
  217. // Request with a 0 ContentLength and a 1 byte body.
  218. {
  219. Req: Request{
  220. Method: "POST",
  221. URL: mustParseURL("/"),
  222. Host: "example.com",
  223. ProtoMajor: 1,
  224. ProtoMinor: 1,
  225. ContentLength: 0, // as if unset by user
  226. },
  227. Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) },
  228. WantWrite: "POST / HTTP/1.1\r\n" +
  229. "Host: example.com\r\n" +
  230. "User-Agent: Go-http-client/1.1\r\n" +
  231. "Transfer-Encoding: chunked\r\n\r\n" +
  232. chunk("x") + chunk(""),
  233. WantProxy: "POST / HTTP/1.1\r\n" +
  234. "Host: example.com\r\n" +
  235. "User-Agent: Go-http-client/1.1\r\n" +
  236. "Transfer-Encoding: chunked\r\n\r\n" +
  237. chunk("x") + chunk(""),
  238. },
  239. // Request with a ContentLength of 10 but a 5 byte body.
  240. {
  241. Req: Request{
  242. Method: "POST",
  243. URL: mustParseURL("/"),
  244. Host: "example.com",
  245. ProtoMajor: 1,
  246. ProtoMinor: 1,
  247. ContentLength: 10, // but we're going to send only 5 bytes
  248. },
  249. Body: []byte("12345"),
  250. WantError: errors.New("http: ContentLength=10 with Body length 5"),
  251. },
  252. // Request with a ContentLength of 4 but an 8 byte body.
  253. {
  254. Req: Request{
  255. Method: "POST",
  256. URL: mustParseURL("/"),
  257. Host: "example.com",
  258. ProtoMajor: 1,
  259. ProtoMinor: 1,
  260. ContentLength: 4, // but we're going to try to send 8 bytes
  261. },
  262. Body: []byte("12345678"),
  263. WantError: errors.New("http: ContentLength=4 with Body length 8"),
  264. },
  265. // Request with a 5 ContentLength and nil body.
  266. {
  267. Req: Request{
  268. Method: "POST",
  269. URL: mustParseURL("/"),
  270. Host: "example.com",
  271. ProtoMajor: 1,
  272. ProtoMinor: 1,
  273. ContentLength: 5, // but we'll omit the body
  274. },
  275. WantError: errors.New("http: Request.ContentLength=5 with nil Body"),
  276. },
  277. // Request with a 0 ContentLength and a body with 1 byte content and an error.
  278. {
  279. Req: Request{
  280. Method: "POST",
  281. URL: mustParseURL("/"),
  282. Host: "example.com",
  283. ProtoMajor: 1,
  284. ProtoMinor: 1,
  285. ContentLength: 0, // as if unset by user
  286. },
  287. Body: func() io.ReadCloser {
  288. err := errors.New("Custom reader error")
  289. errReader := &errorReader{err}
  290. return ioutil.NopCloser(io.MultiReader(strings.NewReader("x"), errReader))
  291. },
  292. WantError: errors.New("Custom reader error"),
  293. },
  294. // Request with a 0 ContentLength and a body without content and an error.
  295. {
  296. Req: Request{
  297. Method: "POST",
  298. URL: mustParseURL("/"),
  299. Host: "example.com",
  300. ProtoMajor: 1,
  301. ProtoMinor: 1,
  302. ContentLength: 0, // as if unset by user
  303. },
  304. Body: func() io.ReadCloser {
  305. err := errors.New("Custom reader error")
  306. errReader := &errorReader{err}
  307. return ioutil.NopCloser(errReader)
  308. },
  309. WantError: errors.New("Custom reader error"),
  310. },
  311. // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
  312. // and doesn't add a User-Agent.
  313. {
  314. Req: Request{
  315. Method: "GET",
  316. URL: mustParseURL("/foo"),
  317. ProtoMajor: 1,
  318. ProtoMinor: 0,
  319. Header: Header{
  320. "X-Foo": []string{"X-Bar"},
  321. },
  322. },
  323. WantWrite: "GET /foo HTTP/1.1\r\n" +
  324. "Host: \r\n" +
  325. "User-Agent: Go-http-client/1.1\r\n" +
  326. "X-Foo: X-Bar\r\n\r\n",
  327. },
  328. // If no Request.Host and no Request.URL.Host, we send
  329. // an empty Host header, and don't use
  330. // Request.Header["Host"]. This is just testing that
  331. // we don't change Go 1.0 behavior.
  332. {
  333. Req: Request{
  334. Method: "GET",
  335. Host: "",
  336. URL: &url.URL{
  337. Scheme: "http",
  338. Host: "",
  339. Path: "/search",
  340. },
  341. ProtoMajor: 1,
  342. ProtoMinor: 1,
  343. Header: Header{
  344. "Host": []string{"bad.example.com"},
  345. },
  346. },
  347. WantWrite: "GET /search HTTP/1.1\r\n" +
  348. "Host: \r\n" +
  349. "User-Agent: Go-http-client/1.1\r\n\r\n",
  350. },
  351. // Opaque test #1 from golang.org/issue/4860
  352. {
  353. Req: Request{
  354. Method: "GET",
  355. URL: &url.URL{
  356. Scheme: "http",
  357. Host: "www.google.com",
  358. Opaque: "/%2F/%2F/",
  359. },
  360. ProtoMajor: 1,
  361. ProtoMinor: 1,
  362. Header: Header{},
  363. },
  364. WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" +
  365. "Host: www.google.com\r\n" +
  366. "User-Agent: Go-http-client/1.1\r\n\r\n",
  367. },
  368. // Opaque test #2 from golang.org/issue/4860
  369. {
  370. Req: Request{
  371. Method: "GET",
  372. URL: &url.URL{
  373. Scheme: "http",
  374. Host: "x.google.com",
  375. Opaque: "//y.google.com/%2F/%2F/",
  376. },
  377. ProtoMajor: 1,
  378. ProtoMinor: 1,
  379. Header: Header{},
  380. },
  381. WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" +
  382. "Host: x.google.com\r\n" +
  383. "User-Agent: Go-http-client/1.1\r\n\r\n",
  384. },
  385. // Testing custom case in header keys. Issue 5022.
  386. {
  387. Req: Request{
  388. Method: "GET",
  389. URL: &url.URL{
  390. Scheme: "http",
  391. Host: "www.google.com",
  392. Path: "/",
  393. },
  394. Proto: "HTTP/1.1",
  395. ProtoMajor: 1,
  396. ProtoMinor: 1,
  397. Header: Header{
  398. "ALL-CAPS": {"x"},
  399. },
  400. },
  401. WantWrite: "GET / HTTP/1.1\r\n" +
  402. "Host: www.google.com\r\n" +
  403. "User-Agent: Go-http-client/1.1\r\n" +
  404. "ALL-CAPS: x\r\n" +
  405. "\r\n",
  406. },
  407. // Request with host header field; IPv6 address with zone identifier
  408. {
  409. Req: Request{
  410. Method: "GET",
  411. URL: &url.URL{
  412. Host: "[fe80::1%en0]",
  413. },
  414. },
  415. WantWrite: "GET / HTTP/1.1\r\n" +
  416. "Host: [fe80::1]\r\n" +
  417. "User-Agent: Go-http-client/1.1\r\n" +
  418. "\r\n",
  419. },
  420. // Request with optional host header field; IPv6 address with zone identifier
  421. {
  422. Req: Request{
  423. Method: "GET",
  424. URL: &url.URL{
  425. Host: "www.example.com",
  426. },
  427. Host: "[fe80::1%en0]:8080",
  428. },
  429. WantWrite: "GET / HTTP/1.1\r\n" +
  430. "Host: [fe80::1]:8080\r\n" +
  431. "User-Agent: Go-http-client/1.1\r\n" +
  432. "\r\n",
  433. },
  434. }
  435. func TestRequestWrite(t *testing.T) {
  436. for i := range reqWriteTests {
  437. tt := &reqWriteTests[i]
  438. setBody := func() {
  439. if tt.Body == nil {
  440. return
  441. }
  442. switch b := tt.Body.(type) {
  443. case []byte:
  444. tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b))
  445. case func() io.ReadCloser:
  446. tt.Req.Body = b()
  447. }
  448. }
  449. setBody()
  450. if tt.Req.Header == nil {
  451. tt.Req.Header = make(Header)
  452. }
  453. var braw bytes.Buffer
  454. err := tt.Req.Write(&braw)
  455. if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
  456. t.Errorf("writing #%d, err = %q, want %q", i, g, e)
  457. continue
  458. }
  459. if err != nil {
  460. continue
  461. }
  462. if tt.WantWrite != "" {
  463. sraw := braw.String()
  464. if sraw != tt.WantWrite {
  465. t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
  466. continue
  467. }
  468. }
  469. if tt.WantProxy != "" {
  470. setBody()
  471. var praw bytes.Buffer
  472. err = tt.Req.WriteProxy(&praw)
  473. if err != nil {
  474. t.Errorf("WriteProxy #%d: %s", i, err)
  475. continue
  476. }
  477. sraw := praw.String()
  478. if sraw != tt.WantProxy {
  479. t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
  480. continue
  481. }
  482. }
  483. }
  484. }
  485. type closeChecker struct {
  486. io.Reader
  487. closed bool
  488. }
  489. func (rc *closeChecker) Close() error {
  490. rc.closed = true
  491. return nil
  492. }
  493. // TestRequestWriteClosesBody tests that Request.Write does close its request.Body.
  494. // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer
  495. // inside a NopCloser, and that it serializes it correctly.
  496. func TestRequestWriteClosesBody(t *testing.T) {
  497. rc := &closeChecker{Reader: strings.NewReader("my body")}
  498. req, _ := NewRequest("POST", "http://foo.com/", rc)
  499. if req.ContentLength != 0 {
  500. t.Errorf("got req.ContentLength %d, want 0", req.ContentLength)
  501. }
  502. buf := new(bytes.Buffer)
  503. req.Write(buf)
  504. if !rc.closed {
  505. t.Error("body not closed after write")
  506. }
  507. expected := "POST / HTTP/1.1\r\n" +
  508. "Host: foo.com\r\n" +
  509. "User-Agent: Go-http-client/1.1\r\n" +
  510. "Transfer-Encoding: chunked\r\n\r\n" +
  511. // TODO: currently we don't buffer before chunking, so we get a
  512. // single "m" chunk before the other chunks, as this was the 1-byte
  513. // read from our MultiReader where we stiched the Body back together
  514. // after sniffing whether the Body was 0 bytes or not.
  515. chunk("m") +
  516. chunk("y body") +
  517. chunk("")
  518. if buf.String() != expected {
  519. t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected)
  520. }
  521. }
  522. func chunk(s string) string {
  523. return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
  524. }
  525. func mustParseURL(s string) *url.URL {
  526. u, err := url.Parse(s)
  527. if err != nil {
  528. panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
  529. }
  530. return u
  531. }
  532. type writerFunc func([]byte) (int, error)
  533. func (f writerFunc) Write(p []byte) (int, error) { return f(p) }
  534. // TestRequestWriteError tests the Write err != nil checks in (*Request).write.
  535. func TestRequestWriteError(t *testing.T) {
  536. failAfter, writeCount := 0, 0
  537. errFail := errors.New("fake write failure")
  538. // w is the buffered io.Writer to write the request to. It
  539. // fails exactly once on its Nth Write call, as controlled by
  540. // failAfter. It also tracks the number of calls in
  541. // writeCount.
  542. w := struct {
  543. io.ByteWriter // to avoid being wrapped by a bufio.Writer
  544. io.Writer
  545. }{
  546. nil,
  547. writerFunc(func(p []byte) (n int, err error) {
  548. writeCount++
  549. if failAfter == 0 {
  550. err = errFail
  551. }
  552. failAfter--
  553. return len(p), err
  554. }),
  555. }
  556. req, _ := NewRequest("GET", "http://example.com/", nil)
  557. const writeCalls = 4 // number of Write calls in current implementation
  558. sawGood := false
  559. for n := 0; n <= writeCalls+2; n++ {
  560. failAfter = n
  561. writeCount = 0
  562. err := req.Write(w)
  563. var wantErr error
  564. if n < writeCalls {
  565. wantErr = errFail
  566. }
  567. if err != wantErr {
  568. t.Errorf("for fail-after %d Writes, err = %v; want %v", n, err, wantErr)
  569. continue
  570. }
  571. if err == nil {
  572. sawGood = true
  573. if writeCount != writeCalls {
  574. t.Fatalf("writeCalls constant is outdated in test")
  575. }
  576. }
  577. if writeCount > writeCalls || writeCount > n+1 {
  578. t.Errorf("for fail-after %d, saw unexpectedly high (%d) write calls", n, writeCount)
  579. }
  580. }
  581. if !sawGood {
  582. t.Fatalf("writeCalls constant is outdated in test")
  583. }
  584. }