| // Copyright 2010 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package http |
| |
| import ( |
| "bytes" |
| "io/ioutil" |
| "strings" |
| "testing" |
| ) |
| |
| type respWriteTest struct { |
| Resp Response |
| Raw string |
| } |
| |
| func TestResponseWrite(t *testing.T) { |
| respWriteTests := []respWriteTest{ |
| // HTTP/1.0, identity coding; no trailer |
| { |
| Response{ |
| StatusCode: 503, |
| ProtoMajor: 1, |
| ProtoMinor: 0, |
| Request: dummyReq("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
| ContentLength: 6, |
| }, |
| |
| "HTTP/1.0 503 Service Unavailable\r\n" + |
| "Content-Length: 6\r\n\r\n" + |
| "abcdef", |
| }, |
| // Unchunked response without Content-Length. |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 0, |
| Request: dummyReq("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
| ContentLength: -1, |
| }, |
| "HTTP/1.0 200 OK\r\n" + |
| "\r\n" + |
| "abcdef", |
| }, |
| // HTTP/1.1 response with unknown length and Connection: close |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
| ContentLength: -1, |
| Close: true, |
| }, |
| "HTTP/1.1 200 OK\r\n" + |
| "Connection: close\r\n" + |
| "\r\n" + |
| "abcdef", |
| }, |
| // HTTP/1.1 response with unknown length and not setting connection: close |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq11("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
| ContentLength: -1, |
| Close: false, |
| }, |
| "HTTP/1.1 200 OK\r\n" + |
| "Connection: close\r\n" + |
| "\r\n" + |
| "abcdef", |
| }, |
| // HTTP/1.1 response with unknown length and not setting connection: close, but |
| // setting chunked. |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq11("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
| ContentLength: -1, |
| TransferEncoding: []string{"chunked"}, |
| Close: false, |
| }, |
| "HTTP/1.1 200 OK\r\n" + |
| "Transfer-Encoding: chunked\r\n\r\n" + |
| "6\r\nabcdef\r\n0\r\n\r\n", |
| }, |
| // HTTP/1.1 response 0 content-length, and nil body |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq11("GET"), |
| Header: Header{}, |
| Body: nil, |
| ContentLength: 0, |
| Close: false, |
| }, |
| "HTTP/1.1 200 OK\r\n" + |
| "Content-Length: 0\r\n" + |
| "\r\n", |
| }, |
| // HTTP/1.1 response 0 content-length, and non-nil empty body |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq11("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("")), |
| ContentLength: 0, |
| Close: false, |
| }, |
| "HTTP/1.1 200 OK\r\n" + |
| "Content-Length: 0\r\n" + |
| "\r\n", |
| }, |
| // HTTP/1.1 response 0 content-length, and non-nil non-empty body |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq11("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("foo")), |
| ContentLength: 0, |
| Close: false, |
| }, |
| "HTTP/1.1 200 OK\r\n" + |
| "Connection: close\r\n" + |
| "\r\nfoo", |
| }, |
| // HTTP/1.1, chunked coding; empty trailer; close |
| { |
| Response{ |
| StatusCode: 200, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq("GET"), |
| Header: Header{}, |
| Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
| ContentLength: 6, |
| TransferEncoding: []string{"chunked"}, |
| Close: true, |
| }, |
| |
| "HTTP/1.1 200 OK\r\n" + |
| "Connection: close\r\n" + |
| "Transfer-Encoding: chunked\r\n\r\n" + |
| "6\r\nabcdef\r\n0\r\n\r\n", |
| }, |
| |
| // Header value with a newline character (Issue 914). |
| // Also tests removal of leading and trailing whitespace. |
| { |
| Response{ |
| StatusCode: 204, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: dummyReq("GET"), |
| Header: Header{ |
| "Foo": []string{" Bar\nBaz "}, |
| }, |
| Body: nil, |
| ContentLength: 0, |
| TransferEncoding: []string{"chunked"}, |
| Close: true, |
| }, |
| |
| "HTTP/1.1 204 No Content\r\n" + |
| "Connection: close\r\n" + |
| "Foo: Bar Baz\r\n" + |
| "\r\n", |
| }, |
| |
| // Want a single Content-Length header. Fixing issue 8180 where |
| // there were two. |
| { |
| Response{ |
| StatusCode: StatusOK, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: &Request{Method: "POST"}, |
| Header: Header{}, |
| ContentLength: 0, |
| TransferEncoding: nil, |
| Body: nil, |
| }, |
| "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", |
| }, |
| |
| // When a response to a POST has Content-Length: -1, make sure we don't |
| // write the Content-Length as -1. |
| { |
| Response{ |
| StatusCode: StatusOK, |
| ProtoMajor: 1, |
| ProtoMinor: 1, |
| Request: &Request{Method: "POST"}, |
| Header: Header{}, |
| ContentLength: -1, |
| Body: ioutil.NopCloser(strings.NewReader("abcdef")), |
| }, |
| "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nabcdef", |
| }, |
| |
| // Status code under 100 should be zero-padded to |
| // three digits. Still bogus, but less bogus. (be |
| // consistent with generating three digits, since the |
| // Transport requires it) |
| { |
| Response{ |
| StatusCode: 7, |
| Status: "license to violate specs", |
| ProtoMajor: 1, |
| ProtoMinor: 0, |
| Request: dummyReq("GET"), |
| Header: Header{}, |
| Body: nil, |
| }, |
| |
| "HTTP/1.0 007 license to violate specs\r\nContent-Length: 0\r\n\r\n", |
| }, |
| |
| // No stutter. |
| { |
| Response{ |
| StatusCode: 123, |
| Status: "123 Sesame Street", |
| ProtoMajor: 1, |
| ProtoMinor: 0, |
| Request: dummyReq("GET"), |
| Header: Header{}, |
| Body: nil, |
| }, |
| |
| "HTTP/1.0 123 Sesame Street\r\nContent-Length: 0\r\n\r\n", |
| }, |
| } |
| |
| for i := range respWriteTests { |
| tt := &respWriteTests[i] |
| var braw bytes.Buffer |
| err := tt.Resp.Write(&braw) |
| if err != nil { |
| t.Errorf("error writing #%d: %s", i, err) |
| continue |
| } |
| sraw := braw.String() |
| if sraw != tt.Raw { |
| t.Errorf("Test %d, expecting:\n%q\nGot:\n%q\n", i, tt.Raw, sraw) |
| continue |
| } |
| } |
| } |