...

Source file src/net/http/fcgi/fcgi_test.go

Documentation: net/http/fcgi

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

View as plain text