gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/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 "gitee.com/ks-custle/core-gm/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  //goland:noinspection GoUnusedParameter
   130  func (c *writeOnlyConn) Read(p []byte) (int, error) {
   131  	return 0, errors.New("conn is write-only")
   132  }
   133  
   134  func (c *writeOnlyConn) Close() error {
   135  	return nil
   136  }
   137  
   138  func TestGetValues(t *testing.T) {
   139  	var rec record
   140  	rec.h.Type = typeGetValues
   141  
   142  	wc := new(writeOnlyConn)
   143  	c := newChild(wc, nil)
   144  	err := c.handleRecord(&rec)
   145  	if err != nil {
   146  		t.Fatalf("handleRecord: %v", err)
   147  	}
   148  
   149  	const want = "\x01\n\x00\x00\x00\x12\x06\x00" +
   150  		"\x0f\x01FCGI_MPXS_CONNS1" +
   151  		"\x00\x00\x00\x00\x00\x00\x01\n\x00\x00\x00\x00\x00\x00"
   152  	if got := string(wc.buf); got != want {
   153  		t.Errorf(" got: %q\nwant: %q\n", got, want)
   154  	}
   155  }
   156  
   157  func nameValuePair11(nameData, valueData string) []byte {
   158  	return bytes.Join(
   159  		[][]byte{
   160  			{byte(len(nameData)), byte(len(valueData))},
   161  			[]byte(nameData),
   162  			[]byte(valueData),
   163  		},
   164  		nil,
   165  	)
   166  }
   167  
   168  func makeRecord(
   169  	recordType recType,
   170  	requestId uint16,
   171  	contentData []byte,
   172  ) []byte {
   173  	requestIdB1 := byte(requestId >> 8)
   174  	requestIdB0 := byte(requestId)
   175  
   176  	contentLength := len(contentData)
   177  	contentLengthB1 := byte(contentLength >> 8)
   178  	contentLengthB0 := byte(contentLength)
   179  	return bytes.Join([][]byte{
   180  		{1, byte(recordType), requestIdB1, requestIdB0, contentLengthB1,
   181  			contentLengthB0, 0, 0},
   182  		contentData,
   183  	},
   184  		nil)
   185  }
   186  
   187  // a series of FastCGI records that start a request and begin sending the
   188  // request body
   189  var streamBeginTypeStdin = bytes.Join([][]byte{
   190  	// set up request 1
   191  	makeRecord(typeBeginRequest, 1,
   192  		[]byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}),
   193  	// add required parameters to request 1
   194  	makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")),
   195  	makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")),
   196  	makeRecord(typeParams, 1, nil),
   197  	// begin sending body of request 1
   198  	makeRecord(typeStdin, 1, []byte("0123456789abcdef")),
   199  },
   200  	nil)
   201  
   202  var cleanUpTests = []struct {
   203  	input []byte
   204  	err   error
   205  }{
   206  	// confirm that child.handleRecord closes req.pw after aborting req
   207  	{
   208  		bytes.Join([][]byte{
   209  			streamBeginTypeStdin,
   210  			makeRecord(typeAbortRequest, 1, nil),
   211  		},
   212  			nil),
   213  		ErrRequestAborted,
   214  	},
   215  	// confirm that child.serve closes all pipes after error reading record
   216  	{
   217  		bytes.Join([][]byte{
   218  			streamBeginTypeStdin,
   219  			nil,
   220  		},
   221  			nil),
   222  		ErrConnClosed,
   223  	},
   224  }
   225  
   226  type nopWriteCloser struct {
   227  	io.Reader
   228  }
   229  
   230  func (nopWriteCloser) Write(buf []byte) (int, error) {
   231  	return len(buf), nil
   232  }
   233  
   234  func (nopWriteCloser) Close() error {
   235  	return nil
   236  }
   237  
   238  // Test that child.serve closes the bodies of aborted requests and closes the
   239  // bodies of all requests before returning. Causes deadlock if either condition
   240  // isn't met. See issue 6934.
   241  func TestChildServeCleansUp(t *testing.T) {
   242  	for _, tt := range cleanUpTests {
   243  		input := make([]byte, len(tt.input))
   244  		copy(input, tt.input)
   245  		rc := nopWriteCloser{bytes.NewReader(input)}
   246  		done := make(chan bool)
   247  		c := newChild(rc, http.HandlerFunc(func(
   248  			w http.ResponseWriter,
   249  			r *http.Request,
   250  		) {
   251  			// block on reading body of request
   252  			_, err := io.Copy(io.Discard, r.Body)
   253  			if err != tt.err {
   254  				t.Errorf("Expected %#v, got %#v", tt.err, err)
   255  			}
   256  			// not reached if body of request isn't closed
   257  			done <- true
   258  		}))
   259  		go c.serve()
   260  		// wait for body of request to be closed or all goroutines to block
   261  		<-done
   262  	}
   263  }
   264  
   265  type rwNopCloser struct {
   266  	io.Reader
   267  	io.Writer
   268  }
   269  
   270  func (rwNopCloser) Close() error {
   271  	return nil
   272  }
   273  
   274  // Verifies it doesn't crash. 	Issue 11824.
   275  func TestMalformedParams(t *testing.T) {
   276  	input := []byte{
   277  		// beginRequest, requestId=1, contentLength=8, role=1, keepConn=1
   278  		1, 1, 0, 1, 0, 8, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
   279  		// params, requestId=1, contentLength=10, k1Len=50, v1Len=50 (malformed, wrong length)
   280  		1, 4, 0, 1, 0, 10, 0, 0, 50, 50, 3, 4, 5, 6, 7, 8, 9, 10,
   281  		// end of params
   282  		1, 4, 0, 1, 0, 0, 0, 0,
   283  	}
   284  	rw := rwNopCloser{bytes.NewReader(input), io.Discard}
   285  	c := newChild(rw, http.DefaultServeMux)
   286  	c.serve()
   287  }
   288  
   289  // a series of FastCGI records that start and end a request
   290  var streamFullRequestStdin = bytes.Join([][]byte{
   291  	// set up request
   292  	makeRecord(typeBeginRequest, 1,
   293  		[]byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}),
   294  	// add required parameters
   295  	makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")),
   296  	makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")),
   297  	// set optional parameters
   298  	makeRecord(typeParams, 1, nameValuePair11("REMOTE_USER", "jane.doe")),
   299  	makeRecord(typeParams, 1, nameValuePair11("QUERY_STRING", "/foo/bar")),
   300  	makeRecord(typeParams, 1, nil),
   301  	// begin sending body of request
   302  	makeRecord(typeStdin, 1, []byte("0123456789abcdef")),
   303  	// end request
   304  	makeRecord(typeEndRequest, 1, nil),
   305  },
   306  	nil)
   307  
   308  var envVarTests = []struct {
   309  	input               []byte
   310  	envVar              string
   311  	expectedVal         string
   312  	expectedFilteredOut bool
   313  }{
   314  	{
   315  		streamFullRequestStdin,
   316  		"REMOTE_USER",
   317  		"jane.doe",
   318  		false,
   319  	},
   320  	{
   321  		streamFullRequestStdin,
   322  		"QUERY_STRING",
   323  		"",
   324  		true,
   325  	},
   326  }
   327  
   328  // Test that environment variables set for a request can be
   329  // read by a handler. Ensures that variables not set will not
   330  // be exposed to a handler.
   331  func TestChildServeReadsEnvVars(t *testing.T) {
   332  	for _, tt := range envVarTests {
   333  		input := make([]byte, len(tt.input))
   334  		copy(input, tt.input)
   335  		rc := nopWriteCloser{bytes.NewReader(input)}
   336  		done := make(chan bool)
   337  		c := newChild(rc, http.HandlerFunc(func(
   338  			w http.ResponseWriter,
   339  			r *http.Request,
   340  		) {
   341  			env := ProcessEnv(r)
   342  			if _, ok := env[tt.envVar]; ok && tt.expectedFilteredOut {
   343  				t.Errorf("Expected environment variable %s to not be set, but set to %s",
   344  					tt.envVar, env[tt.envVar])
   345  			} else if env[tt.envVar] != tt.expectedVal {
   346  				t.Errorf("Expected %s, got %s", tt.expectedVal, env[tt.envVar])
   347  			}
   348  			done <- true
   349  		}))
   350  		go c.serve()
   351  		<-done
   352  	}
   353  }
   354  
   355  func TestResponseWriterSniffsContentType(t *testing.T) {
   356  	var tests = []struct {
   357  		name   string
   358  		body   string
   359  		wantCT string
   360  	}{
   361  		{
   362  			name:   "no body",
   363  			wantCT: "text/plain; charset=utf-8",
   364  		},
   365  		{
   366  			name:   "html",
   367  			body:   "<html><head><title>test page</title></head><body>This is a body</body></html>",
   368  			wantCT: "text/html; charset=utf-8",
   369  		},
   370  		{
   371  			name:   "text",
   372  			body:   strings.Repeat("gopher", 86),
   373  			wantCT: "text/plain; charset=utf-8",
   374  		},
   375  		{
   376  			name:   "jpg",
   377  			body:   "\xFF\xD8\xFF" + strings.Repeat("B", 1024),
   378  			wantCT: "image/jpeg",
   379  		},
   380  	}
   381  	for _, tt := range tests {
   382  		t.Run(tt.name, func(t *testing.T) {
   383  			input := make([]byte, len(streamFullRequestStdin))
   384  			copy(input, streamFullRequestStdin)
   385  			rc := nopWriteCloser{bytes.NewReader(input)}
   386  			done := make(chan bool)
   387  			var resp *response
   388  			c := newChild(rc, http.HandlerFunc(func(
   389  				w http.ResponseWriter,
   390  				r *http.Request,
   391  			) {
   392  				_, _ = io.WriteString(w, tt.body)
   393  				resp = w.(*response)
   394  				done <- true
   395  			}))
   396  			defer c.cleanUp()
   397  			go c.serve()
   398  			<-done
   399  			if got := resp.Header().Get("Content-Type"); got != tt.wantCT {
   400  				t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT)
   401  			}
   402  		})
   403  	}
   404  }
   405  
   406  type signallingNopCloser struct {
   407  	io.Reader
   408  	closed chan bool
   409  }
   410  
   411  func (*signallingNopCloser) Write(buf []byte) (int, error) {
   412  	return len(buf), nil
   413  }
   414  
   415  func (rc *signallingNopCloser) Close() error {
   416  	close(rc.closed)
   417  	return nil
   418  }
   419  
   420  // Test whether server properly closes connection when processing slow
   421  // requests
   422  func TestSlowRequest(t *testing.T) {
   423  	pr, pw := io.Pipe()
   424  	go func(w io.Writer) {
   425  		for _, buf := range [][]byte{
   426  			streamBeginTypeStdin,
   427  			makeRecord(typeStdin, 1, nil),
   428  		} {
   429  			_, _ = pw.Write(buf)
   430  			time.Sleep(100 * time.Millisecond)
   431  		}
   432  	}(pw)
   433  
   434  	rc := &signallingNopCloser{pr, make(chan bool)}
   435  	handlerDone := make(chan bool)
   436  
   437  	c := newChild(rc, http.HandlerFunc(func(
   438  		w http.ResponseWriter,
   439  		r *http.Request,
   440  	) {
   441  		w.WriteHeader(200)
   442  		close(handlerDone)
   443  	}))
   444  	go c.serve()
   445  	defer c.cleanUp()
   446  
   447  	timeout := time.After(2 * time.Second)
   448  
   449  	<-handlerDone
   450  	select {
   451  	case <-rc.closed:
   452  		t.Log("FastCGI child closed connection")
   453  	case <-timeout:
   454  		t.Error("FastCGI child did not close socket after handling request")
   455  	}
   456  }