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