github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/gmhttp/fcgi/fcgi_test.go (about) 1 // Copyright 2011 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 fcgi 6 7 import ( 8 "bytes" 9 "errors" 10 "io" 11 "strings" 12 "testing" 13 "time" 14 15 http "github.com/hxx258456/ccgo/gmhttp" 16 ) 17 18 var sizeTests = []struct { 19 size uint32 20 bytes []byte 21 }{ 22 {0, []byte{0x00}}, 23 {127, []byte{0x7F}}, 24 {128, []byte{0x80, 0x00, 0x00, 0x80}}, 25 {1000, []byte{0x80, 0x00, 0x03, 0xE8}}, 26 {33554431, []byte{0x81, 0xFF, 0xFF, 0xFF}}, 27 } 28 29 func TestSize(t *testing.T) { 30 b := make([]byte, 4) 31 for i, test := range sizeTests { 32 n := encodeSize(b, test.size) 33 if !bytes.Equal(b[:n], test.bytes) { 34 t.Errorf("%d expected %x, encoded %x", i, test.bytes, b) 35 } 36 size, n := readSize(test.bytes) 37 if size != test.size { 38 t.Errorf("%d expected %d, read %d", i, test.size, size) 39 } 40 if len(test.bytes) != n { 41 t.Errorf("%d did not consume all the bytes", i) 42 } 43 } 44 } 45 46 var streamTests = []struct { 47 desc string 48 recType recType 49 reqId uint16 50 content []byte 51 raw []byte 52 }{ 53 {"single record", typeStdout, 1, nil, 54 []byte{1, byte(typeStdout), 0, 1, 0, 0, 0, 0}, 55 }, 56 // this data will have to be split into two records 57 {"two records", typeStdin, 300, make([]byte, 66000), 58 bytes.Join([][]byte{ 59 // header for the first record 60 {1, byte(typeStdin), 0x01, 0x2C, 0xFF, 0xFF, 1, 0}, 61 make([]byte, 65536), 62 // header for the second 63 {1, byte(typeStdin), 0x01, 0x2C, 0x01, 0xD1, 7, 0}, 64 make([]byte, 472), 65 // header for the empty record 66 {1, byte(typeStdin), 0x01, 0x2C, 0, 0, 0, 0}, 67 }, 68 nil), 69 }, 70 } 71 72 type nilCloser struct { 73 io.ReadWriter 74 } 75 76 func (c *nilCloser) Close() error { return nil } 77 78 func TestStreams(t *testing.T) { 79 var rec record 80 outer: 81 for _, test := range streamTests { 82 buf := bytes.NewBuffer(test.raw) 83 var content []byte 84 for buf.Len() > 0 { 85 if err := rec.read(buf); err != nil { 86 t.Errorf("%s: error reading record: %v", test.desc, err) 87 continue outer 88 } 89 content = append(content, rec.content()...) 90 } 91 if rec.h.Type != test.recType { 92 t.Errorf("%s: got type %d expected %d", test.desc, rec.h.Type, test.recType) 93 continue 94 } 95 if rec.h.Id != test.reqId { 96 t.Errorf("%s: got request ID %d expected %d", test.desc, rec.h.Id, test.reqId) 97 continue 98 } 99 if !bytes.Equal(content, test.content) { 100 t.Errorf("%s: read wrong content", test.desc) 101 continue 102 } 103 buf.Reset() 104 c := newConn(&nilCloser{buf}) 105 w := newWriter(c, test.recType, test.reqId) 106 if _, err := w.Write(test.content); err != nil { 107 t.Errorf("%s: error writing record: %v", test.desc, err) 108 continue 109 } 110 if err := w.Close(); err != nil { 111 t.Errorf("%s: error closing stream: %v", test.desc, err) 112 continue 113 } 114 if !bytes.Equal(buf.Bytes(), test.raw) { 115 t.Errorf("%s: wrote wrong content", test.desc) 116 } 117 } 118 } 119 120 type writeOnlyConn struct { 121 buf []byte 122 } 123 124 func (c *writeOnlyConn) Write(p []byte) (int, error) { 125 c.buf = append(c.buf, p...) 126 return len(p), nil 127 } 128 129 func (c *writeOnlyConn) Read(p []byte) (int, error) { 130 return 0, errors.New("conn is write-only") 131 } 132 133 func (c *writeOnlyConn) Close() error { 134 return nil 135 } 136 137 func TestGetValues(t *testing.T) { 138 var rec record 139 rec.h.Type = typeGetValues 140 141 wc := new(writeOnlyConn) 142 c := newChild(wc, nil) 143 err := c.handleRecord(&rec) 144 if err != nil { 145 t.Fatalf("handleRecord: %v", err) 146 } 147 148 const want = "\x01\n\x00\x00\x00\x12\x06\x00" + 149 "\x0f\x01FCGI_MPXS_CONNS1" + 150 "\x00\x00\x00\x00\x00\x00\x01\n\x00\x00\x00\x00\x00\x00" 151 if got := string(wc.buf); got != want { 152 t.Errorf(" got: %q\nwant: %q\n", got, want) 153 } 154 } 155 156 func nameValuePair11(nameData, valueData string) []byte { 157 return bytes.Join( 158 [][]byte{ 159 {byte(len(nameData)), byte(len(valueData))}, 160 []byte(nameData), 161 []byte(valueData), 162 }, 163 nil, 164 ) 165 } 166 167 func makeRecord( 168 recordType recType, 169 requestId uint16, 170 contentData []byte, 171 ) []byte { 172 requestIdB1 := byte(requestId >> 8) 173 requestIdB0 := byte(requestId) 174 175 contentLength := len(contentData) 176 contentLengthB1 := byte(contentLength >> 8) 177 contentLengthB0 := byte(contentLength) 178 return bytes.Join([][]byte{ 179 {1, byte(recordType), requestIdB1, requestIdB0, contentLengthB1, 180 contentLengthB0, 0, 0}, 181 contentData, 182 }, 183 nil) 184 } 185 186 // a series of FastCGI records that start a request and begin sending the 187 // request body 188 var streamBeginTypeStdin = bytes.Join([][]byte{ 189 // set up request 1 190 makeRecord(typeBeginRequest, 1, 191 []byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}), 192 // add required parameters to request 1 193 makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")), 194 makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")), 195 makeRecord(typeParams, 1, nil), 196 // begin sending body of request 1 197 makeRecord(typeStdin, 1, []byte("0123456789abcdef")), 198 }, 199 nil) 200 201 var cleanUpTests = []struct { 202 input []byte 203 err error 204 }{ 205 // confirm that child.handleRecord closes req.pw after aborting req 206 { 207 bytes.Join([][]byte{ 208 streamBeginTypeStdin, 209 makeRecord(typeAbortRequest, 1, nil), 210 }, 211 nil), 212 ErrRequestAborted, 213 }, 214 // confirm that child.serve closes all pipes after error reading record 215 { 216 bytes.Join([][]byte{ 217 streamBeginTypeStdin, 218 nil, 219 }, 220 nil), 221 ErrConnClosed, 222 }, 223 } 224 225 type nopWriteCloser struct { 226 io.Reader 227 } 228 229 func (nopWriteCloser) Write(buf []byte) (int, error) { 230 return len(buf), nil 231 } 232 233 func (nopWriteCloser) Close() error { 234 return nil 235 } 236 237 // Test that child.serve closes the bodies of aborted requests and closes the 238 // bodies of all requests before returning. Causes deadlock if either condition 239 // isn't met. See issue 6934. 240 func TestChildServeCleansUp(t *testing.T) { 241 for _, tt := range cleanUpTests { 242 input := make([]byte, len(tt.input)) 243 copy(input, tt.input) 244 rc := nopWriteCloser{bytes.NewReader(input)} 245 done := make(chan bool) 246 c := newChild(rc, http.HandlerFunc(func( 247 w http.ResponseWriter, 248 r *http.Request, 249 ) { 250 // block on reading body of request 251 _, err := io.Copy(io.Discard, r.Body) 252 if err != tt.err { 253 t.Errorf("Expected %#v, got %#v", tt.err, err) 254 } 255 // not reached if body of request isn't closed 256 done <- true 257 })) 258 go c.serve() 259 // wait for body of request to be closed or all goroutines to block 260 <-done 261 } 262 } 263 264 type rwNopCloser struct { 265 io.Reader 266 io.Writer 267 } 268 269 func (rwNopCloser) Close() error { 270 return nil 271 } 272 273 // Verifies it doesn't crash. Issue 11824. 274 func TestMalformedParams(t *testing.T) { 275 input := []byte{ 276 // beginRequest, requestId=1, contentLength=8, role=1, keepConn=1 277 1, 1, 0, 1, 0, 8, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 278 // params, requestId=1, contentLength=10, k1Len=50, v1Len=50 (malformed, wrong length) 279 1, 4, 0, 1, 0, 10, 0, 0, 50, 50, 3, 4, 5, 6, 7, 8, 9, 10, 280 // end of params 281 1, 4, 0, 1, 0, 0, 0, 0, 282 } 283 rw := rwNopCloser{bytes.NewReader(input), io.Discard} 284 c := newChild(rw, http.DefaultServeMux) 285 c.serve() 286 } 287 288 // a series of FastCGI records that start and end a request 289 var streamFullRequestStdin = bytes.Join([][]byte{ 290 // set up request 291 makeRecord(typeBeginRequest, 1, 292 []byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}), 293 // add required parameters 294 makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")), 295 makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")), 296 // set optional parameters 297 makeRecord(typeParams, 1, nameValuePair11("REMOTE_USER", "jane.doe")), 298 makeRecord(typeParams, 1, nameValuePair11("QUERY_STRING", "/foo/bar")), 299 makeRecord(typeParams, 1, nil), 300 // begin sending body of request 301 makeRecord(typeStdin, 1, []byte("0123456789abcdef")), 302 // end request 303 makeRecord(typeEndRequest, 1, nil), 304 }, 305 nil) 306 307 var envVarTests = []struct { 308 input []byte 309 envVar string 310 expectedVal string 311 expectedFilteredOut bool 312 }{ 313 { 314 streamFullRequestStdin, 315 "REMOTE_USER", 316 "jane.doe", 317 false, 318 }, 319 { 320 streamFullRequestStdin, 321 "QUERY_STRING", 322 "", 323 true, 324 }, 325 } 326 327 // Test that environment variables set for a request can be 328 // read by a handler. Ensures that variables not set will not 329 // be exposed to a handler. 330 func TestChildServeReadsEnvVars(t *testing.T) { 331 for _, tt := range envVarTests { 332 input := make([]byte, len(tt.input)) 333 copy(input, tt.input) 334 rc := nopWriteCloser{bytes.NewReader(input)} 335 done := make(chan bool) 336 c := newChild(rc, http.HandlerFunc(func( 337 w http.ResponseWriter, 338 r *http.Request, 339 ) { 340 env := ProcessEnv(r) 341 if _, ok := env[tt.envVar]; ok && tt.expectedFilteredOut { 342 t.Errorf("Expected environment variable %s to not be set, but set to %s", 343 tt.envVar, env[tt.envVar]) 344 } else if env[tt.envVar] != tt.expectedVal { 345 t.Errorf("Expected %s, got %s", tt.expectedVal, env[tt.envVar]) 346 } 347 done <- true 348 })) 349 go c.serve() 350 <-done 351 } 352 } 353 354 func TestResponseWriterSniffsContentType(t *testing.T) { 355 var tests = []struct { 356 name string 357 body string 358 wantCT string 359 }{ 360 { 361 name: "no body", 362 wantCT: "text/plain; charset=utf-8", 363 }, 364 { 365 name: "html", 366 body: "<html><head><title>test page</title></head><body>This is a body</body></html>", 367 wantCT: "text/html; charset=utf-8", 368 }, 369 { 370 name: "text", 371 body: strings.Repeat("gopher", 86), 372 wantCT: "text/plain; charset=utf-8", 373 }, 374 { 375 name: "jpg", 376 body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024), 377 wantCT: "image/jpeg", 378 }, 379 } 380 for _, tt := range tests { 381 t.Run(tt.name, func(t *testing.T) { 382 input := make([]byte, len(streamFullRequestStdin)) 383 copy(input, streamFullRequestStdin) 384 rc := nopWriteCloser{bytes.NewReader(input)} 385 done := make(chan bool) 386 var resp *response 387 c := newChild(rc, http.HandlerFunc(func( 388 w http.ResponseWriter, 389 r *http.Request, 390 ) { 391 io.WriteString(w, tt.body) 392 resp = w.(*response) 393 done <- true 394 })) 395 defer c.cleanUp() 396 go c.serve() 397 <-done 398 if got := resp.Header().Get("Content-Type"); got != tt.wantCT { 399 t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT) 400 } 401 }) 402 } 403 } 404 405 type signallingNopCloser struct { 406 io.Reader 407 closed chan bool 408 } 409 410 func (*signallingNopCloser) Write(buf []byte) (int, error) { 411 return len(buf), nil 412 } 413 414 func (rc *signallingNopCloser) Close() error { 415 close(rc.closed) 416 return nil 417 } 418 419 // Test whether server properly closes connection when processing slow 420 // requests 421 func TestSlowRequest(t *testing.T) { 422 pr, pw := io.Pipe() 423 go func(w io.Writer) { 424 for _, buf := range [][]byte{ 425 streamBeginTypeStdin, 426 makeRecord(typeStdin, 1, nil), 427 } { 428 pw.Write(buf) 429 time.Sleep(100 * time.Millisecond) 430 } 431 }(pw) 432 433 rc := &signallingNopCloser{pr, make(chan bool)} 434 handlerDone := make(chan bool) 435 436 c := newChild(rc, http.HandlerFunc(func( 437 w http.ResponseWriter, 438 r *http.Request, 439 ) { 440 w.WriteHeader(200) 441 close(handlerDone) 442 })) 443 go c.serve() 444 defer c.cleanUp() 445 446 timeout := time.After(2 * time.Second) 447 448 <-handlerDone 449 select { 450 case <-rc.closed: 451 t.Log("FastCGI child closed connection") 452 case <-timeout: 453 t.Error("FastCGI child did not close socket after handling request") 454 } 455 }