• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5package http
6
7import (
8	"io"
9	"strings"
10	"testing"
11)
12
13type respWriteTest struct {
14	Resp Response
15	Raw  string
16}
17
18func TestResponseWrite(t *testing.T) {
19	respWriteTests := []respWriteTest{
20		// HTTP/1.0, identity coding; no trailer
21		{
22			Response{
23				StatusCode:    503,
24				ProtoMajor:    1,
25				ProtoMinor:    0,
26				Request:       dummyReq("GET"),
27				Header:        Header{},
28				Body:          io.NopCloser(strings.NewReader("abcdef")),
29				ContentLength: 6,
30			},
31
32			"HTTP/1.0 503 Service Unavailable\r\n" +
33				"Content-Length: 6\r\n\r\n" +
34				"abcdef",
35		},
36		// Unchunked response without Content-Length.
37		{
38			Response{
39				StatusCode:    200,
40				ProtoMajor:    1,
41				ProtoMinor:    0,
42				Request:       dummyReq("GET"),
43				Header:        Header{},
44				Body:          io.NopCloser(strings.NewReader("abcdef")),
45				ContentLength: -1,
46			},
47			"HTTP/1.0 200 OK\r\n" +
48				"\r\n" +
49				"abcdef",
50		},
51		// HTTP/1.1 response with unknown length and Connection: close
52		{
53			Response{
54				StatusCode:    200,
55				ProtoMajor:    1,
56				ProtoMinor:    1,
57				Request:       dummyReq("GET"),
58				Header:        Header{},
59				Body:          io.NopCloser(strings.NewReader("abcdef")),
60				ContentLength: -1,
61				Close:         true,
62			},
63			"HTTP/1.1 200 OK\r\n" +
64				"Connection: close\r\n" +
65				"\r\n" +
66				"abcdef",
67		},
68		// HTTP/1.1 response with unknown length and not setting connection: close
69		{
70			Response{
71				StatusCode:    200,
72				ProtoMajor:    1,
73				ProtoMinor:    1,
74				Request:       dummyReq11("GET"),
75				Header:        Header{},
76				Body:          io.NopCloser(strings.NewReader("abcdef")),
77				ContentLength: -1,
78				Close:         false,
79			},
80			"HTTP/1.1 200 OK\r\n" +
81				"Connection: close\r\n" +
82				"\r\n" +
83				"abcdef",
84		},
85		// HTTP/1.1 response with unknown length and not setting connection: close, but
86		// setting chunked.
87		{
88			Response{
89				StatusCode:       200,
90				ProtoMajor:       1,
91				ProtoMinor:       1,
92				Request:          dummyReq11("GET"),
93				Header:           Header{},
94				Body:             io.NopCloser(strings.NewReader("abcdef")),
95				ContentLength:    -1,
96				TransferEncoding: []string{"chunked"},
97				Close:            false,
98			},
99			"HTTP/1.1 200 OK\r\n" +
100				"Transfer-Encoding: chunked\r\n\r\n" +
101				"6\r\nabcdef\r\n0\r\n\r\n",
102		},
103		// HTTP/1.1 response 0 content-length, and nil body
104		{
105			Response{
106				StatusCode:    200,
107				ProtoMajor:    1,
108				ProtoMinor:    1,
109				Request:       dummyReq11("GET"),
110				Header:        Header{},
111				Body:          nil,
112				ContentLength: 0,
113				Close:         false,
114			},
115			"HTTP/1.1 200 OK\r\n" +
116				"Content-Length: 0\r\n" +
117				"\r\n",
118		},
119		// HTTP/1.1 response 0 content-length, and non-nil empty body
120		{
121			Response{
122				StatusCode:    200,
123				ProtoMajor:    1,
124				ProtoMinor:    1,
125				Request:       dummyReq11("GET"),
126				Header:        Header{},
127				Body:          io.NopCloser(strings.NewReader("")),
128				ContentLength: 0,
129				Close:         false,
130			},
131			"HTTP/1.1 200 OK\r\n" +
132				"Content-Length: 0\r\n" +
133				"\r\n",
134		},
135		// HTTP/1.1 response 0 content-length, and non-nil non-empty body
136		{
137			Response{
138				StatusCode:    200,
139				ProtoMajor:    1,
140				ProtoMinor:    1,
141				Request:       dummyReq11("GET"),
142				Header:        Header{},
143				Body:          io.NopCloser(strings.NewReader("foo")),
144				ContentLength: 0,
145				Close:         false,
146			},
147			"HTTP/1.1 200 OK\r\n" +
148				"Connection: close\r\n" +
149				"\r\nfoo",
150		},
151		// HTTP/1.1, chunked coding; empty trailer; close
152		{
153			Response{
154				StatusCode:       200,
155				ProtoMajor:       1,
156				ProtoMinor:       1,
157				Request:          dummyReq("GET"),
158				Header:           Header{},
159				Body:             io.NopCloser(strings.NewReader("abcdef")),
160				ContentLength:    6,
161				TransferEncoding: []string{"chunked"},
162				Close:            true,
163			},
164
165			"HTTP/1.1 200 OK\r\n" +
166				"Connection: close\r\n" +
167				"Transfer-Encoding: chunked\r\n\r\n" +
168				"6\r\nabcdef\r\n0\r\n\r\n",
169		},
170
171		// Header value with a newline character (Issue 914).
172		// Also tests removal of leading and trailing whitespace.
173		{
174			Response{
175				StatusCode: 204,
176				ProtoMajor: 1,
177				ProtoMinor: 1,
178				Request:    dummyReq("GET"),
179				Header: Header{
180					"Foo": []string{" Bar\nBaz "},
181				},
182				Body:             nil,
183				ContentLength:    0,
184				TransferEncoding: []string{"chunked"},
185				Close:            true,
186			},
187
188			"HTTP/1.1 204 No Content\r\n" +
189				"Connection: close\r\n" +
190				"Foo: Bar Baz\r\n" +
191				"\r\n",
192		},
193
194		// Want a single Content-Length header. Fixing issue 8180 where
195		// there were two.
196		{
197			Response{
198				StatusCode:       StatusOK,
199				ProtoMajor:       1,
200				ProtoMinor:       1,
201				Request:          &Request{Method: "POST"},
202				Header:           Header{},
203				ContentLength:    0,
204				TransferEncoding: nil,
205				Body:             nil,
206			},
207			"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
208		},
209
210		// When a response to a POST has Content-Length: -1, make sure we don't
211		// write the Content-Length as -1.
212		{
213			Response{
214				StatusCode:    StatusOK,
215				ProtoMajor:    1,
216				ProtoMinor:    1,
217				Request:       &Request{Method: "POST"},
218				Header:        Header{},
219				ContentLength: -1,
220				Body:          io.NopCloser(strings.NewReader("abcdef")),
221			},
222			"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nabcdef",
223		},
224
225		// Status code under 100 should be zero-padded to
226		// three digits.  Still bogus, but less bogus. (be
227		// consistent with generating three digits, since the
228		// Transport requires it)
229		{
230			Response{
231				StatusCode: 7,
232				Status:     "license to violate specs",
233				ProtoMajor: 1,
234				ProtoMinor: 0,
235				Request:    dummyReq("GET"),
236				Header:     Header{},
237				Body:       nil,
238			},
239
240			"HTTP/1.0 007 license to violate specs\r\nContent-Length: 0\r\n\r\n",
241		},
242
243		// No stutter.  Status code in 1xx range response should
244		// not include a Content-Length header.  See issue #16942.
245		{
246			Response{
247				StatusCode: 123,
248				Status:     "123 Sesame Street",
249				ProtoMajor: 1,
250				ProtoMinor: 0,
251				Request:    dummyReq("GET"),
252				Header:     Header{},
253				Body:       nil,
254			},
255
256			"HTTP/1.0 123 Sesame Street\r\n\r\n",
257		},
258
259		// Status code 204 (No content) response should not include a
260		// Content-Length header.  See issue #16942.
261		{
262			Response{
263				StatusCode: 204,
264				Status:     "No Content",
265				ProtoMajor: 1,
266				ProtoMinor: 0,
267				Request:    dummyReq("GET"),
268				Header:     Header{},
269				Body:       nil,
270			},
271
272			"HTTP/1.0 204 No Content\r\n\r\n",
273		},
274	}
275
276	for i := range respWriteTests {
277		tt := &respWriteTests[i]
278		var braw strings.Builder
279		err := tt.Resp.Write(&braw)
280		if err != nil {
281			t.Errorf("error writing #%d: %s", i, err)
282			continue
283		}
284		sraw := braw.String()
285		if sraw != tt.Raw {
286			t.Errorf("Test %d, expecting:\n%q\nGot:\n%q\n", i, tt.Raw, sraw)
287			continue
288		}
289	}
290}
291