...

Source file src/net/http/requestwrite_test.go

Documentation: net/http

		 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  
		 5  package http
		 6  
		 7  import (
		 8  	"bufio"
		 9  	"bytes"
		10  	"errors"
		11  	"fmt"
		12  	"io"
		13  	"net"
		14  	"net/url"
		15  	"strings"
		16  	"testing"
		17  	"testing/iotest"
		18  	"time"
		19  )
		20  
		21  type reqWriteTest struct {
		22  	Req	Request
		23  	Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
		24  
		25  	// Any of these three may be empty to skip that test.
		26  	WantWrite string // Request.Write
		27  	WantProxy string // Request.WriteProxy
		28  
		29  	WantError error // wanted error from Request.Write
		30  }
		31  
		32  var reqWriteTests = []reqWriteTest{
		33  	// HTTP/1.1 => chunked coding; no body; no trailer
		34  	0: {
		35  		Req: Request{
		36  			Method: "GET",
		37  			URL: &url.URL{
		38  				Scheme: "http",
		39  				Host:	 "www.techcrunch.com",
		40  				Path:	 "/",
		41  			},
		42  			Proto:			"HTTP/1.1",
		43  			ProtoMajor: 1,
		44  			ProtoMinor: 1,
		45  			Header: Header{
		46  				"Accept":					 {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
		47  				"Accept-Charset":	 {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
		48  				"Accept-Encoding":	{"gzip,deflate"},
		49  				"Accept-Language":	{"en-us,en;q=0.5"},
		50  				"Keep-Alive":			 {"300"},
		51  				"Proxy-Connection": {"keep-alive"},
		52  				"User-Agent":			 {"Fake"},
		53  			},
		54  			Body:	nil,
		55  			Close: false,
		56  			Host:	"www.techcrunch.com",
		57  			Form:	map[string][]string{},
		58  		},
		59  
		60  		WantWrite: "GET / HTTP/1.1\r\n" +
		61  			"Host: www.techcrunch.com\r\n" +
		62  			"User-Agent: Fake\r\n" +
		63  			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
		64  			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
		65  			"Accept-Encoding: gzip,deflate\r\n" +
		66  			"Accept-Language: en-us,en;q=0.5\r\n" +
		67  			"Keep-Alive: 300\r\n" +
		68  			"Proxy-Connection: keep-alive\r\n\r\n",
		69  
		70  		WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
		71  			"Host: www.techcrunch.com\r\n" +
		72  			"User-Agent: Fake\r\n" +
		73  			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
		74  			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
		75  			"Accept-Encoding: gzip,deflate\r\n" +
		76  			"Accept-Language: en-us,en;q=0.5\r\n" +
		77  			"Keep-Alive: 300\r\n" +
		78  			"Proxy-Connection: keep-alive\r\n\r\n",
		79  	},
		80  	// HTTP/1.1 => chunked coding; body; empty trailer
		81  	1: {
		82  		Req: Request{
		83  			Method: "GET",
		84  			URL: &url.URL{
		85  				Scheme: "http",
		86  				Host:	 "www.google.com",
		87  				Path:	 "/search",
		88  			},
		89  			ProtoMajor:			 1,
		90  			ProtoMinor:			 1,
		91  			Header:					 Header{},
		92  			TransferEncoding: []string{"chunked"},
		93  		},
		94  
		95  		Body: []byte("abcdef"),
		96  
		97  		WantWrite: "GET /search HTTP/1.1\r\n" +
		98  			"Host: www.google.com\r\n" +
		99  			"User-Agent: Go-http-client/1.1\r\n" +
	 100  			"Transfer-Encoding: chunked\r\n\r\n" +
	 101  			chunk("abcdef") + chunk(""),
	 102  
	 103  		WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" +
	 104  			"Host: www.google.com\r\n" +
	 105  			"User-Agent: Go-http-client/1.1\r\n" +
	 106  			"Transfer-Encoding: chunked\r\n\r\n" +
	 107  			chunk("abcdef") + chunk(""),
	 108  	},
	 109  	// HTTP/1.1 POST => chunked coding; body; empty trailer
	 110  	2: {
	 111  		Req: Request{
	 112  			Method: "POST",
	 113  			URL: &url.URL{
	 114  				Scheme: "http",
	 115  				Host:	 "www.google.com",
	 116  				Path:	 "/search",
	 117  			},
	 118  			ProtoMajor:			 1,
	 119  			ProtoMinor:			 1,
	 120  			Header:					 Header{},
	 121  			Close:						true,
	 122  			TransferEncoding: []string{"chunked"},
	 123  		},
	 124  
	 125  		Body: []byte("abcdef"),
	 126  
	 127  		WantWrite: "POST /search HTTP/1.1\r\n" +
	 128  			"Host: www.google.com\r\n" +
	 129  			"User-Agent: Go-http-client/1.1\r\n" +
	 130  			"Connection: close\r\n" +
	 131  			"Transfer-Encoding: chunked\r\n\r\n" +
	 132  			chunk("abcdef") + chunk(""),
	 133  
	 134  		WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
	 135  			"Host: www.google.com\r\n" +
	 136  			"User-Agent: Go-http-client/1.1\r\n" +
	 137  			"Connection: close\r\n" +
	 138  			"Transfer-Encoding: chunked\r\n\r\n" +
	 139  			chunk("abcdef") + chunk(""),
	 140  	},
	 141  
	 142  	// HTTP/1.1 POST with Content-Length, no chunking
	 143  	3: {
	 144  		Req: Request{
	 145  			Method: "POST",
	 146  			URL: &url.URL{
	 147  				Scheme: "http",
	 148  				Host:	 "www.google.com",
	 149  				Path:	 "/search",
	 150  			},
	 151  			ProtoMajor:		1,
	 152  			ProtoMinor:		1,
	 153  			Header:				Header{},
	 154  			Close:				 true,
	 155  			ContentLength: 6,
	 156  		},
	 157  
	 158  		Body: []byte("abcdef"),
	 159  
	 160  		WantWrite: "POST /search HTTP/1.1\r\n" +
	 161  			"Host: www.google.com\r\n" +
	 162  			"User-Agent: Go-http-client/1.1\r\n" +
	 163  			"Connection: close\r\n" +
	 164  			"Content-Length: 6\r\n" +
	 165  			"\r\n" +
	 166  			"abcdef",
	 167  
	 168  		WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
	 169  			"Host: www.google.com\r\n" +
	 170  			"User-Agent: Go-http-client/1.1\r\n" +
	 171  			"Connection: close\r\n" +
	 172  			"Content-Length: 6\r\n" +
	 173  			"\r\n" +
	 174  			"abcdef",
	 175  	},
	 176  
	 177  	// HTTP/1.1 POST with Content-Length in headers
	 178  	4: {
	 179  		Req: Request{
	 180  			Method: "POST",
	 181  			URL:		mustParseURL("http://example.com/"),
	 182  			Host:	 "example.com",
	 183  			Header: Header{
	 184  				"Content-Length": []string{"10"}, // ignored
	 185  			},
	 186  			ContentLength: 6,
	 187  		},
	 188  
	 189  		Body: []byte("abcdef"),
	 190  
	 191  		WantWrite: "POST / HTTP/1.1\r\n" +
	 192  			"Host: example.com\r\n" +
	 193  			"User-Agent: Go-http-client/1.1\r\n" +
	 194  			"Content-Length: 6\r\n" +
	 195  			"\r\n" +
	 196  			"abcdef",
	 197  
	 198  		WantProxy: "POST http://example.com/ HTTP/1.1\r\n" +
	 199  			"Host: example.com\r\n" +
	 200  			"User-Agent: Go-http-client/1.1\r\n" +
	 201  			"Content-Length: 6\r\n" +
	 202  			"\r\n" +
	 203  			"abcdef",
	 204  	},
	 205  
	 206  	// default to HTTP/1.1
	 207  	5: {
	 208  		Req: Request{
	 209  			Method: "GET",
	 210  			URL:		mustParseURL("/search"),
	 211  			Host:	 "www.google.com",
	 212  		},
	 213  
	 214  		WantWrite: "GET /search HTTP/1.1\r\n" +
	 215  			"Host: www.google.com\r\n" +
	 216  			"User-Agent: Go-http-client/1.1\r\n" +
	 217  			"\r\n",
	 218  	},
	 219  
	 220  	// Request with a 0 ContentLength and a 0 byte body.
	 221  	6: {
	 222  		Req: Request{
	 223  			Method:				"POST",
	 224  			URL:					 mustParseURL("/"),
	 225  			Host:					"example.com",
	 226  			ProtoMajor:		1,
	 227  			ProtoMinor:		1,
	 228  			ContentLength: 0, // as if unset by user
	 229  		},
	 230  
	 231  		Body: func() io.ReadCloser { return io.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) },
	 232  
	 233  		WantWrite: "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" +
	 237  			"\r\n0\r\n\r\n",
	 238  
	 239  		WantProxy: "POST / HTTP/1.1\r\n" +
	 240  			"Host: example.com\r\n" +
	 241  			"User-Agent: Go-http-client/1.1\r\n" +
	 242  			"Transfer-Encoding: chunked\r\n" +
	 243  			"\r\n0\r\n\r\n",
	 244  	},
	 245  
	 246  	// Request with a 0 ContentLength and a nil body.
	 247  	7: {
	 248  		Req: Request{
	 249  			Method:				"POST",
	 250  			URL:					 mustParseURL("/"),
	 251  			Host:					"example.com",
	 252  			ProtoMajor:		1,
	 253  			ProtoMinor:		1,
	 254  			ContentLength: 0, // as if unset by user
	 255  		},
	 256  
	 257  		Body: func() io.ReadCloser { return nil },
	 258  
	 259  		WantWrite: "POST / HTTP/1.1\r\n" +
	 260  			"Host: example.com\r\n" +
	 261  			"User-Agent: Go-http-client/1.1\r\n" +
	 262  			"Content-Length: 0\r\n" +
	 263  			"\r\n",
	 264  
	 265  		WantProxy: "POST / HTTP/1.1\r\n" +
	 266  			"Host: example.com\r\n" +
	 267  			"User-Agent: Go-http-client/1.1\r\n" +
	 268  			"Content-Length: 0\r\n" +
	 269  			"\r\n",
	 270  	},
	 271  
	 272  	// Request with a 0 ContentLength and a 1 byte body.
	 273  	8: {
	 274  		Req: Request{
	 275  			Method:				"POST",
	 276  			URL:					 mustParseURL("/"),
	 277  			Host:					"example.com",
	 278  			ProtoMajor:		1,
	 279  			ProtoMinor:		1,
	 280  			ContentLength: 0, // as if unset by user
	 281  		},
	 282  
	 283  		Body: func() io.ReadCloser { return io.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) },
	 284  
	 285  		WantWrite: "POST / HTTP/1.1\r\n" +
	 286  			"Host: example.com\r\n" +
	 287  			"User-Agent: Go-http-client/1.1\r\n" +
	 288  			"Transfer-Encoding: chunked\r\n\r\n" +
	 289  			chunk("x") + chunk(""),
	 290  
	 291  		WantProxy: "POST / HTTP/1.1\r\n" +
	 292  			"Host: example.com\r\n" +
	 293  			"User-Agent: Go-http-client/1.1\r\n" +
	 294  			"Transfer-Encoding: chunked\r\n\r\n" +
	 295  			chunk("x") + chunk(""),
	 296  	},
	 297  
	 298  	// Request with a ContentLength of 10 but a 5 byte body.
	 299  	9: {
	 300  		Req: Request{
	 301  			Method:				"POST",
	 302  			URL:					 mustParseURL("/"),
	 303  			Host:					"example.com",
	 304  			ProtoMajor:		1,
	 305  			ProtoMinor:		1,
	 306  			ContentLength: 10, // but we're going to send only 5 bytes
	 307  		},
	 308  		Body:			[]byte("12345"),
	 309  		WantError: errors.New("http: ContentLength=10 with Body length 5"),
	 310  	},
	 311  
	 312  	// Request with a ContentLength of 4 but an 8 byte body.
	 313  	10: {
	 314  		Req: Request{
	 315  			Method:				"POST",
	 316  			URL:					 mustParseURL("/"),
	 317  			Host:					"example.com",
	 318  			ProtoMajor:		1,
	 319  			ProtoMinor:		1,
	 320  			ContentLength: 4, // but we're going to try to send 8 bytes
	 321  		},
	 322  		Body:			[]byte("12345678"),
	 323  		WantError: errors.New("http: ContentLength=4 with Body length 8"),
	 324  	},
	 325  
	 326  	// Request with a 5 ContentLength and nil body.
	 327  	11: {
	 328  		Req: Request{
	 329  			Method:				"POST",
	 330  			URL:					 mustParseURL("/"),
	 331  			Host:					"example.com",
	 332  			ProtoMajor:		1,
	 333  			ProtoMinor:		1,
	 334  			ContentLength: 5, // but we'll omit the body
	 335  		},
	 336  		WantError: errors.New("http: Request.ContentLength=5 with nil Body"),
	 337  	},
	 338  
	 339  	// Request with a 0 ContentLength and a body with 1 byte content and an error.
	 340  	12: {
	 341  		Req: Request{
	 342  			Method:				"POST",
	 343  			URL:					 mustParseURL("/"),
	 344  			Host:					"example.com",
	 345  			ProtoMajor:		1,
	 346  			ProtoMinor:		1,
	 347  			ContentLength: 0, // as if unset by user
	 348  		},
	 349  
	 350  		Body: func() io.ReadCloser {
	 351  			err := errors.New("Custom reader error")
	 352  			errReader := iotest.ErrReader(err)
	 353  			return io.NopCloser(io.MultiReader(strings.NewReader("x"), errReader))
	 354  		},
	 355  
	 356  		WantError: errors.New("Custom reader error"),
	 357  	},
	 358  
	 359  	// Request with a 0 ContentLength and a body without content and an error.
	 360  	13: {
	 361  		Req: Request{
	 362  			Method:				"POST",
	 363  			URL:					 mustParseURL("/"),
	 364  			Host:					"example.com",
	 365  			ProtoMajor:		1,
	 366  			ProtoMinor:		1,
	 367  			ContentLength: 0, // as if unset by user
	 368  		},
	 369  
	 370  		Body: func() io.ReadCloser {
	 371  			err := errors.New("Custom reader error")
	 372  			errReader := iotest.ErrReader(err)
	 373  			return io.NopCloser(errReader)
	 374  		},
	 375  
	 376  		WantError: errors.New("Custom reader error"),
	 377  	},
	 378  
	 379  	// Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
	 380  	// and doesn't add a User-Agent.
	 381  	14: {
	 382  		Req: Request{
	 383  			Method:		 "GET",
	 384  			URL:				mustParseURL("/foo"),
	 385  			ProtoMajor: 1,
	 386  			ProtoMinor: 0,
	 387  			Header: Header{
	 388  				"X-Foo": []string{"X-Bar"},
	 389  			},
	 390  		},
	 391  
	 392  		WantWrite: "GET /foo HTTP/1.1\r\n" +
	 393  			"Host: \r\n" +
	 394  			"User-Agent: Go-http-client/1.1\r\n" +
	 395  			"X-Foo: X-Bar\r\n\r\n",
	 396  	},
	 397  
	 398  	// If no Request.Host and no Request.URL.Host, we send
	 399  	// an empty Host header, and don't use
	 400  	// Request.Header["Host"]. This is just testing that
	 401  	// we don't change Go 1.0 behavior.
	 402  	15: {
	 403  		Req: Request{
	 404  			Method: "GET",
	 405  			Host:	 "",
	 406  			URL: &url.URL{
	 407  				Scheme: "http",
	 408  				Host:	 "",
	 409  				Path:	 "/search",
	 410  			},
	 411  			ProtoMajor: 1,
	 412  			ProtoMinor: 1,
	 413  			Header: Header{
	 414  				"Host": []string{"bad.example.com"},
	 415  			},
	 416  		},
	 417  
	 418  		WantWrite: "GET /search HTTP/1.1\r\n" +
	 419  			"Host: \r\n" +
	 420  			"User-Agent: Go-http-client/1.1\r\n\r\n",
	 421  	},
	 422  
	 423  	// Opaque test #1 from golang.org/issue/4860
	 424  	16: {
	 425  		Req: Request{
	 426  			Method: "GET",
	 427  			URL: &url.URL{
	 428  				Scheme: "http",
	 429  				Host:	 "www.google.com",
	 430  				Opaque: "/%2F/%2F/",
	 431  			},
	 432  			ProtoMajor: 1,
	 433  			ProtoMinor: 1,
	 434  			Header:		 Header{},
	 435  		},
	 436  
	 437  		WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" +
	 438  			"Host: www.google.com\r\n" +
	 439  			"User-Agent: Go-http-client/1.1\r\n\r\n",
	 440  	},
	 441  
	 442  	// Opaque test #2 from golang.org/issue/4860
	 443  	17: {
	 444  		Req: Request{
	 445  			Method: "GET",
	 446  			URL: &url.URL{
	 447  				Scheme: "http",
	 448  				Host:	 "x.google.com",
	 449  				Opaque: "//y.google.com/%2F/%2F/",
	 450  			},
	 451  			ProtoMajor: 1,
	 452  			ProtoMinor: 1,
	 453  			Header:		 Header{},
	 454  		},
	 455  
	 456  		WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" +
	 457  			"Host: x.google.com\r\n" +
	 458  			"User-Agent: Go-http-client/1.1\r\n\r\n",
	 459  	},
	 460  
	 461  	// Testing custom case in header keys. Issue 5022.
	 462  	18: {
	 463  		Req: Request{
	 464  			Method: "GET",
	 465  			URL: &url.URL{
	 466  				Scheme: "http",
	 467  				Host:	 "www.google.com",
	 468  				Path:	 "/",
	 469  			},
	 470  			Proto:			"HTTP/1.1",
	 471  			ProtoMajor: 1,
	 472  			ProtoMinor: 1,
	 473  			Header: Header{
	 474  				"ALL-CAPS": {"x"},
	 475  			},
	 476  		},
	 477  
	 478  		WantWrite: "GET / HTTP/1.1\r\n" +
	 479  			"Host: www.google.com\r\n" +
	 480  			"User-Agent: Go-http-client/1.1\r\n" +
	 481  			"ALL-CAPS: x\r\n" +
	 482  			"\r\n",
	 483  	},
	 484  
	 485  	// Request with host header field; IPv6 address with zone identifier
	 486  	19: {
	 487  		Req: Request{
	 488  			Method: "GET",
	 489  			URL: &url.URL{
	 490  				Host: "[fe80::1%en0]",
	 491  			},
	 492  		},
	 493  
	 494  		WantWrite: "GET / HTTP/1.1\r\n" +
	 495  			"Host: [fe80::1]\r\n" +
	 496  			"User-Agent: Go-http-client/1.1\r\n" +
	 497  			"\r\n",
	 498  	},
	 499  
	 500  	// Request with optional host header field; IPv6 address with zone identifier
	 501  	20: {
	 502  		Req: Request{
	 503  			Method: "GET",
	 504  			URL: &url.URL{
	 505  				Host: "www.example.com",
	 506  			},
	 507  			Host: "[fe80::1%en0]:8080",
	 508  		},
	 509  
	 510  		WantWrite: "GET / HTTP/1.1\r\n" +
	 511  			"Host: [fe80::1]:8080\r\n" +
	 512  			"User-Agent: Go-http-client/1.1\r\n" +
	 513  			"\r\n",
	 514  	},
	 515  
	 516  	// CONNECT without Opaque
	 517  	21: {
	 518  		Req: Request{
	 519  			Method: "CONNECT",
	 520  			URL: &url.URL{
	 521  				Scheme: "https", // of proxy.com
	 522  				Host:	 "proxy.com",
	 523  			},
	 524  		},
	 525  		// What we used to do, locking that behavior in:
	 526  		WantWrite: "CONNECT proxy.com HTTP/1.1\r\n" +
	 527  			"Host: proxy.com\r\n" +
	 528  			"User-Agent: Go-http-client/1.1\r\n" +
	 529  			"\r\n",
	 530  	},
	 531  
	 532  	// CONNECT with Opaque
	 533  	22: {
	 534  		Req: Request{
	 535  			Method: "CONNECT",
	 536  			URL: &url.URL{
	 537  				Scheme: "https", // of proxy.com
	 538  				Host:	 "proxy.com",
	 539  				Opaque: "backend:443",
	 540  			},
	 541  		},
	 542  		WantWrite: "CONNECT backend:443 HTTP/1.1\r\n" +
	 543  			"Host: proxy.com\r\n" +
	 544  			"User-Agent: Go-http-client/1.1\r\n" +
	 545  			"\r\n",
	 546  	},
	 547  
	 548  	// Verify that a nil header value doesn't get written.
	 549  	23: {
	 550  		Req: Request{
	 551  			Method: "GET",
	 552  			URL:		mustParseURL("/foo"),
	 553  			Header: Header{
	 554  				"X-Foo":						 []string{"X-Bar"},
	 555  				"X-Idempotency-Key": nil,
	 556  			},
	 557  		},
	 558  
	 559  		WantWrite: "GET /foo HTTP/1.1\r\n" +
	 560  			"Host: \r\n" +
	 561  			"User-Agent: Go-http-client/1.1\r\n" +
	 562  			"X-Foo: X-Bar\r\n\r\n",
	 563  	},
	 564  	24: {
	 565  		Req: Request{
	 566  			Method: "GET",
	 567  			URL:		mustParseURL("/foo"),
	 568  			Header: Header{
	 569  				"X-Foo":						 []string{"X-Bar"},
	 570  				"X-Idempotency-Key": []string{},
	 571  			},
	 572  		},
	 573  
	 574  		WantWrite: "GET /foo HTTP/1.1\r\n" +
	 575  			"Host: \r\n" +
	 576  			"User-Agent: Go-http-client/1.1\r\n" +
	 577  			"X-Foo: X-Bar\r\n\r\n",
	 578  	},
	 579  
	 580  	25: {
	 581  		Req: Request{
	 582  			Method: "GET",
	 583  			URL: &url.URL{
	 584  				Host:		 "www.example.com",
	 585  				RawQuery: "new\nline", // or any CTL
	 586  			},
	 587  		},
	 588  		WantError: errors.New("net/http: can't write control character in Request.URL"),
	 589  	},
	 590  
	 591  	26: { // Request with nil body and PATCH method. Issue #40978
	 592  		Req: Request{
	 593  			Method:				"PATCH",
	 594  			URL:					 mustParseURL("/"),
	 595  			Host:					"example.com",
	 596  			ProtoMajor:		1,
	 597  			ProtoMinor:		1,
	 598  			ContentLength: 0, // as if unset by user
	 599  		},
	 600  		Body: nil,
	 601  		WantWrite: "PATCH / HTTP/1.1\r\n" +
	 602  			"Host: example.com\r\n" +
	 603  			"User-Agent: Go-http-client/1.1\r\n" +
	 604  			"Content-Length: 0\r\n\r\n",
	 605  		WantProxy: "PATCH / HTTP/1.1\r\n" +
	 606  			"Host: example.com\r\n" +
	 607  			"User-Agent: Go-http-client/1.1\r\n" +
	 608  			"Content-Length: 0\r\n\r\n",
	 609  	},
	 610  }
	 611  
	 612  func TestRequestWrite(t *testing.T) {
	 613  	for i := range reqWriteTests {
	 614  		tt := &reqWriteTests[i]
	 615  
	 616  		setBody := func() {
	 617  			if tt.Body == nil {
	 618  				return
	 619  			}
	 620  			switch b := tt.Body.(type) {
	 621  			case []byte:
	 622  				tt.Req.Body = io.NopCloser(bytes.NewReader(b))
	 623  			case func() io.ReadCloser:
	 624  				tt.Req.Body = b()
	 625  			}
	 626  		}
	 627  		setBody()
	 628  		if tt.Req.Header == nil {
	 629  			tt.Req.Header = make(Header)
	 630  		}
	 631  
	 632  		var braw bytes.Buffer
	 633  		err := tt.Req.Write(&braw)
	 634  		if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
	 635  			t.Errorf("writing #%d, err = %q, want %q", i, g, e)
	 636  			continue
	 637  		}
	 638  		if err != nil {
	 639  			continue
	 640  		}
	 641  
	 642  		if tt.WantWrite != "" {
	 643  			sraw := braw.String()
	 644  			if sraw != tt.WantWrite {
	 645  				t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
	 646  				continue
	 647  			}
	 648  		}
	 649  
	 650  		if tt.WantProxy != "" {
	 651  			setBody()
	 652  			var praw bytes.Buffer
	 653  			err = tt.Req.WriteProxy(&praw)
	 654  			if err != nil {
	 655  				t.Errorf("WriteProxy #%d: %s", i, err)
	 656  				continue
	 657  			}
	 658  			sraw := praw.String()
	 659  			if sraw != tt.WantProxy {
	 660  				t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
	 661  				continue
	 662  			}
	 663  		}
	 664  	}
	 665  }
	 666  
	 667  func TestRequestWriteTransport(t *testing.T) {
	 668  	t.Parallel()
	 669  
	 670  	matchSubstr := func(substr string) func(string) error {
	 671  		return func(written string) error {
	 672  			if !strings.Contains(written, substr) {
	 673  				return fmt.Errorf("expected substring %q in request: %s", substr, written)
	 674  			}
	 675  			return nil
	 676  		}
	 677  	}
	 678  
	 679  	noContentLengthOrTransferEncoding := func(req string) error {
	 680  		if strings.Contains(req, "Content-Length: ") {
	 681  			return fmt.Errorf("unexpected Content-Length in request: %s", req)
	 682  		}
	 683  		if strings.Contains(req, "Transfer-Encoding: ") {
	 684  			return fmt.Errorf("unexpected Transfer-Encoding in request: %s", req)
	 685  		}
	 686  		return nil
	 687  	}
	 688  
	 689  	all := func(checks ...func(string) error) func(string) error {
	 690  		return func(req string) error {
	 691  			for _, c := range checks {
	 692  				if err := c(req); err != nil {
	 693  					return err
	 694  				}
	 695  			}
	 696  			return nil
	 697  		}
	 698  	}
	 699  
	 700  	type testCase struct {
	 701  		method string
	 702  		clen	 int64 // ContentLength
	 703  		body	 io.ReadCloser
	 704  		want	 func(string) error
	 705  
	 706  		// optional:
	 707  		init				 func(*testCase)
	 708  		afterReqRead func()
	 709  	}
	 710  
	 711  	tests := []testCase{
	 712  		{
	 713  			method: "GET",
	 714  			want:	 noContentLengthOrTransferEncoding,
	 715  		},
	 716  		{
	 717  			method: "GET",
	 718  			body:	 io.NopCloser(strings.NewReader("")),
	 719  			want:	 noContentLengthOrTransferEncoding,
	 720  		},
	 721  		{
	 722  			method: "GET",
	 723  			clen:	 -1,
	 724  			body:	 io.NopCloser(strings.NewReader("")),
	 725  			want:	 noContentLengthOrTransferEncoding,
	 726  		},
	 727  		// A GET with a body, with explicit content length:
	 728  		{
	 729  			method: "GET",
	 730  			clen:	 7,
	 731  			body:	 io.NopCloser(strings.NewReader("foobody")),
	 732  			want: all(matchSubstr("Content-Length: 7"),
	 733  				matchSubstr("foobody")),
	 734  		},
	 735  		// A GET with a body, sniffing the leading "f" from "foobody".
	 736  		{
	 737  			method: "GET",
	 738  			clen:	 -1,
	 739  			body:	 io.NopCloser(strings.NewReader("foobody")),
	 740  			want: all(matchSubstr("Transfer-Encoding: chunked"),
	 741  				matchSubstr("\r\n1\r\nf\r\n"),
	 742  				matchSubstr("oobody")),
	 743  		},
	 744  		// But a POST request is expected to have a body, so
	 745  		// no sniffing happens:
	 746  		{
	 747  			method: "POST",
	 748  			clen:	 -1,
	 749  			body:	 io.NopCloser(strings.NewReader("foobody")),
	 750  			want: all(matchSubstr("Transfer-Encoding: chunked"),
	 751  				matchSubstr("foobody")),
	 752  		},
	 753  		{
	 754  			method: "POST",
	 755  			clen:	 -1,
	 756  			body:	 io.NopCloser(strings.NewReader("")),
	 757  			want:	 all(matchSubstr("Transfer-Encoding: chunked")),
	 758  		},
	 759  		// Verify that a blocking Request.Body doesn't block forever.
	 760  		{
	 761  			method: "GET",
	 762  			clen:	 -1,
	 763  			init: func(tt *testCase) {
	 764  				pr, pw := io.Pipe()
	 765  				tt.afterReqRead = func() {
	 766  					pw.Close()
	 767  				}
	 768  				tt.body = io.NopCloser(pr)
	 769  			},
	 770  			want: matchSubstr("Transfer-Encoding: chunked"),
	 771  		},
	 772  	}
	 773  
	 774  	for i, tt := range tests {
	 775  		if tt.init != nil {
	 776  			tt.init(&tt)
	 777  		}
	 778  		req := &Request{
	 779  			Method: tt.method,
	 780  			URL: &url.URL{
	 781  				Scheme: "http",
	 782  				Host:	 "example.com",
	 783  			},
	 784  			Header:				make(Header),
	 785  			ContentLength: tt.clen,
	 786  			Body:					tt.body,
	 787  		}
	 788  		got, err := dumpRequestOut(req, tt.afterReqRead)
	 789  		if err != nil {
	 790  			t.Errorf("test[%d]: %v", i, err)
	 791  			continue
	 792  		}
	 793  		if err := tt.want(string(got)); err != nil {
	 794  			t.Errorf("test[%d]: %v", i, err)
	 795  		}
	 796  	}
	 797  }
	 798  
	 799  type closeChecker struct {
	 800  	io.Reader
	 801  	closed bool
	 802  }
	 803  
	 804  func (rc *closeChecker) Close() error {
	 805  	rc.closed = true
	 806  	return nil
	 807  }
	 808  
	 809  // TestRequestWriteClosesBody tests that Request.Write closes its request.Body.
	 810  // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer
	 811  // inside a NopCloser, and that it serializes it correctly.
	 812  func TestRequestWriteClosesBody(t *testing.T) {
	 813  	rc := &closeChecker{Reader: strings.NewReader("my body")}
	 814  	req, err := NewRequest("POST", "http://foo.com/", rc)
	 815  	if err != nil {
	 816  		t.Fatal(err)
	 817  	}
	 818  	buf := new(bytes.Buffer)
	 819  	if err := req.Write(buf); err != nil {
	 820  		t.Error(err)
	 821  	}
	 822  	if !rc.closed {
	 823  		t.Error("body not closed after write")
	 824  	}
	 825  	expected := "POST / HTTP/1.1\r\n" +
	 826  		"Host: foo.com\r\n" +
	 827  		"User-Agent: Go-http-client/1.1\r\n" +
	 828  		"Transfer-Encoding: chunked\r\n\r\n" +
	 829  		chunk("my body") +
	 830  		chunk("")
	 831  	if buf.String() != expected {
	 832  		t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected)
	 833  	}
	 834  }
	 835  
	 836  func chunk(s string) string {
	 837  	return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
	 838  }
	 839  
	 840  func mustParseURL(s string) *url.URL {
	 841  	u, err := url.Parse(s)
	 842  	if err != nil {
	 843  		panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
	 844  	}
	 845  	return u
	 846  }
	 847  
	 848  type writerFunc func([]byte) (int, error)
	 849  
	 850  func (f writerFunc) Write(p []byte) (int, error) { return f(p) }
	 851  
	 852  // TestRequestWriteError tests the Write err != nil checks in (*Request).write.
	 853  func TestRequestWriteError(t *testing.T) {
	 854  	failAfter, writeCount := 0, 0
	 855  	errFail := errors.New("fake write failure")
	 856  
	 857  	// w is the buffered io.Writer to write the request to. It
	 858  	// fails exactly once on its Nth Write call, as controlled by
	 859  	// failAfter. It also tracks the number of calls in
	 860  	// writeCount.
	 861  	w := struct {
	 862  		io.ByteWriter // to avoid being wrapped by a bufio.Writer
	 863  		io.Writer
	 864  	}{
	 865  		nil,
	 866  		writerFunc(func(p []byte) (n int, err error) {
	 867  			writeCount++
	 868  			if failAfter == 0 {
	 869  				err = errFail
	 870  			}
	 871  			failAfter--
	 872  			return len(p), err
	 873  		}),
	 874  	}
	 875  
	 876  	req, _ := NewRequest("GET", "http://example.com/", nil)
	 877  	const writeCalls = 4 // number of Write calls in current implementation
	 878  	sawGood := false
	 879  	for n := 0; n <= writeCalls+2; n++ {
	 880  		failAfter = n
	 881  		writeCount = 0
	 882  		err := req.Write(w)
	 883  		var wantErr error
	 884  		if n < writeCalls {
	 885  			wantErr = errFail
	 886  		}
	 887  		if err != wantErr {
	 888  			t.Errorf("for fail-after %d Writes, err = %v; want %v", n, err, wantErr)
	 889  			continue
	 890  		}
	 891  		if err == nil {
	 892  			sawGood = true
	 893  			if writeCount != writeCalls {
	 894  				t.Fatalf("writeCalls constant is outdated in test")
	 895  			}
	 896  		}
	 897  		if writeCount > writeCalls || writeCount > n+1 {
	 898  			t.Errorf("for fail-after %d, saw unexpectedly high (%d) write calls", n, writeCount)
	 899  		}
	 900  	}
	 901  	if !sawGood {
	 902  		t.Fatalf("writeCalls constant is outdated in test")
	 903  	}
	 904  }
	 905  
	 906  // dumpRequestOut is a modified copy of net/http/httputil.DumpRequestOut.
	 907  // Unlike the original, this version doesn't mutate the req.Body and
	 908  // try to restore it. It always dumps the whole body.
	 909  // And it doesn't support https.
	 910  func dumpRequestOut(req *Request, onReadHeaders func()) ([]byte, error) {
	 911  
	 912  	// Use the actual Transport code to record what we would send
	 913  	// on the wire, but not using TCP.	Use a Transport with a
	 914  	// custom dialer that returns a fake net.Conn that waits
	 915  	// for the full input (and recording it), and then responds
	 916  	// with a dummy response.
	 917  	var buf bytes.Buffer // records the output
	 918  	pr, pw := io.Pipe()
	 919  	defer pr.Close()
	 920  	defer pw.Close()
	 921  	dr := &delegateReader{c: make(chan io.Reader)}
	 922  
	 923  	t := &Transport{
	 924  		Dial: func(net, addr string) (net.Conn, error) {
	 925  			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
	 926  		},
	 927  	}
	 928  	defer t.CloseIdleConnections()
	 929  
	 930  	// Wait for the request before replying with a dummy response:
	 931  	go func() {
	 932  		req, err := ReadRequest(bufio.NewReader(pr))
	 933  		if err == nil {
	 934  			if onReadHeaders != nil {
	 935  				onReadHeaders()
	 936  			}
	 937  			// Ensure all the body is read; otherwise
	 938  			// we'll get a partial dump.
	 939  			io.Copy(io.Discard, req.Body)
	 940  			req.Body.Close()
	 941  		}
	 942  		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
	 943  	}()
	 944  
	 945  	_, err := t.RoundTrip(req)
	 946  	if err != nil {
	 947  		return nil, err
	 948  	}
	 949  	return buf.Bytes(), nil
	 950  }
	 951  
	 952  // delegateReader is a reader that delegates to another reader,
	 953  // once it arrives on a channel.
	 954  type delegateReader struct {
	 955  	c chan io.Reader
	 956  	r io.Reader // nil until received from c
	 957  }
	 958  
	 959  func (r *delegateReader) Read(p []byte) (int, error) {
	 960  	if r.r == nil {
	 961  		r.r = <-r.c
	 962  	}
	 963  	return r.r.Read(p)
	 964  }
	 965  
	 966  // dumpConn is a net.Conn that writes to Writer and reads from Reader.
	 967  type dumpConn struct {
	 968  	io.Writer
	 969  	io.Reader
	 970  }
	 971  
	 972  func (c *dumpConn) Close() error											 { return nil }
	 973  func (c *dumpConn) LocalAddr() net.Addr								{ return nil }
	 974  func (c *dumpConn) RemoteAddr() net.Addr							 { return nil }
	 975  func (c *dumpConn) SetDeadline(t time.Time) error			{ return nil }
	 976  func (c *dumpConn) SetReadDeadline(t time.Time) error	{ return nil }
	 977  func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
	 978  

View as plain text