github.com/yanyiwu/go@v0.0.0-20150106053140-03d6637dbb7f/src/net/http/cgi/matryoshka_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  // Tests a Go CGI program running under a Go CGI host process.
     6  // Further, the two programs are the same binary, just checking
     7  // their environment to figure out what mode to run in.
     8  
     9  package cgi
    10  
    11  import (
    12  	"bytes"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"net/http"
    17  	"net/http/httptest"
    18  	"os"
    19  	"runtime"
    20  	"testing"
    21  	"time"
    22  )
    23  
    24  // This test is a CGI host (testing host.go) that runs its own binary
    25  // as a child process testing the other half of CGI (child.go).
    26  func TestHostingOurselves(t *testing.T) {
    27  	if runtime.GOOS == "nacl" {
    28  		t.Skip("skipping on nacl")
    29  	}
    30  
    31  	h := &Handler{
    32  		Path: os.Args[0],
    33  		Root: "/test.go",
    34  		Args: []string{"-test.run=TestBeChildCGIProcess"},
    35  	}
    36  	expectedMap := map[string]string{
    37  		"test":                  "Hello CGI-in-CGI",
    38  		"param-a":               "b",
    39  		"param-foo":             "bar",
    40  		"env-GATEWAY_INTERFACE": "CGI/1.1",
    41  		"env-HTTP_HOST":         "example.com",
    42  		"env-PATH_INFO":         "",
    43  		"env-QUERY_STRING":      "foo=bar&a=b",
    44  		"env-REMOTE_ADDR":       "1.2.3.4",
    45  		"env-REMOTE_HOST":       "1.2.3.4",
    46  		"env-REQUEST_METHOD":    "GET",
    47  		"env-REQUEST_URI":       "/test.go?foo=bar&a=b",
    48  		"env-SCRIPT_FILENAME":   os.Args[0],
    49  		"env-SCRIPT_NAME":       "/test.go",
    50  		"env-SERVER_NAME":       "example.com",
    51  		"env-SERVER_PORT":       "80",
    52  		"env-SERVER_SOFTWARE":   "go",
    53  	}
    54  	replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
    55  
    56  	if expected, got := "text/html; charset=utf-8", replay.Header().Get("Content-Type"); got != expected {
    57  		t.Errorf("got a Content-Type of %q; expected %q", got, expected)
    58  	}
    59  	if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
    60  		t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
    61  	}
    62  }
    63  
    64  type customWriterRecorder struct {
    65  	w io.Writer
    66  	*httptest.ResponseRecorder
    67  }
    68  
    69  func (r *customWriterRecorder) Write(p []byte) (n int, err error) {
    70  	return r.w.Write(p)
    71  }
    72  
    73  type limitWriter struct {
    74  	w io.Writer
    75  	n int
    76  }
    77  
    78  func (w *limitWriter) Write(p []byte) (n int, err error) {
    79  	if len(p) > w.n {
    80  		p = p[:w.n]
    81  	}
    82  	if len(p) > 0 {
    83  		n, err = w.w.Write(p)
    84  		w.n -= n
    85  	}
    86  	if w.n == 0 {
    87  		err = errors.New("past write limit")
    88  	}
    89  	return
    90  }
    91  
    92  // If there's an error copying the child's output to the parent, test
    93  // that we kill the child.
    94  func TestKillChildAfterCopyError(t *testing.T) {
    95  	if runtime.GOOS == "nacl" {
    96  		t.Skip("skipping on nacl")
    97  	}
    98  
    99  	defer func() { testHookStartProcess = nil }()
   100  	proc := make(chan *os.Process, 1)
   101  	testHookStartProcess = func(p *os.Process) {
   102  		proc <- p
   103  	}
   104  
   105  	h := &Handler{
   106  		Path: os.Args[0],
   107  		Root: "/test.go",
   108  		Args: []string{"-test.run=TestBeChildCGIProcess"},
   109  	}
   110  	req, _ := http.NewRequest("GET", "http://example.com/test.cgi?write-forever=1", nil)
   111  	rec := httptest.NewRecorder()
   112  	var out bytes.Buffer
   113  	const writeLen = 50 << 10
   114  	rw := &customWriterRecorder{&limitWriter{&out, writeLen}, rec}
   115  
   116  	donec := make(chan bool, 1)
   117  	go func() {
   118  		h.ServeHTTP(rw, req)
   119  		donec <- true
   120  	}()
   121  
   122  	select {
   123  	case <-donec:
   124  		if out.Len() != writeLen || out.Bytes()[0] != 'a' {
   125  			t.Errorf("unexpected output: %q", out.Bytes())
   126  		}
   127  	case <-time.After(5 * time.Second):
   128  		t.Errorf("timeout. ServeHTTP hung and didn't kill the child process?")
   129  		select {
   130  		case p := <-proc:
   131  			p.Kill()
   132  			t.Logf("killed process")
   133  		default:
   134  			t.Logf("didn't kill process")
   135  		}
   136  	}
   137  }
   138  
   139  // Test that a child handler writing only headers works.
   140  // golang.org/issue/7196
   141  func TestChildOnlyHeaders(t *testing.T) {
   142  	if runtime.GOOS == "nacl" {
   143  		t.Skip("skipping on nacl")
   144  	}
   145  
   146  	h := &Handler{
   147  		Path: os.Args[0],
   148  		Root: "/test.go",
   149  		Args: []string{"-test.run=TestBeChildCGIProcess"},
   150  	}
   151  	expectedMap := map[string]string{
   152  		"_body": "",
   153  	}
   154  	replay := runCgiTest(t, h, "GET /test.go?no-body=1 HTTP/1.0\nHost: example.com\n\n", expectedMap)
   155  	if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
   156  		t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
   157  	}
   158  }
   159  
   160  // golang.org/issue/7198
   161  func Test500WithNoHeaders(t *testing.T)     { want500Test(t, "/immediate-disconnect") }
   162  func Test500WithNoContentType(t *testing.T) { want500Test(t, "/no-content-type") }
   163  func Test500WithEmptyHeaders(t *testing.T)  { want500Test(t, "/empty-headers") }
   164  
   165  func want500Test(t *testing.T, path string) {
   166  	h := &Handler{
   167  		Path: os.Args[0],
   168  		Root: "/test.go",
   169  		Args: []string{"-test.run=TestBeChildCGIProcess"},
   170  	}
   171  	expectedMap := map[string]string{
   172  		"_body": "",
   173  	}
   174  	replay := runCgiTest(t, h, "GET "+path+" HTTP/1.0\nHost: example.com\n\n", expectedMap)
   175  	if replay.Code != 500 {
   176  		t.Errorf("Got code %d; want 500", replay.Code)
   177  	}
   178  }
   179  
   180  type neverEnding byte
   181  
   182  func (b neverEnding) Read(p []byte) (n int, err error) {
   183  	for i := range p {
   184  		p[i] = byte(b)
   185  	}
   186  	return len(p), nil
   187  }
   188  
   189  // Note: not actually a test.
   190  func TestBeChildCGIProcess(t *testing.T) {
   191  	if os.Getenv("REQUEST_METHOD") == "" {
   192  		// Not in a CGI environment; skipping test.
   193  		return
   194  	}
   195  	switch os.Getenv("REQUEST_URI") {
   196  	case "/immediate-disconnect":
   197  		os.Exit(0)
   198  	case "/no-content-type":
   199  		fmt.Printf("Content-Length: 6\n\nHello\n")
   200  		os.Exit(0)
   201  	case "/empty-headers":
   202  		fmt.Printf("\nHello")
   203  		os.Exit(0)
   204  	}
   205  	Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   206  		rw.Header().Set("X-Test-Header", "X-Test-Value")
   207  		req.ParseForm()
   208  		if req.FormValue("no-body") == "1" {
   209  			return
   210  		}
   211  		if req.FormValue("write-forever") == "1" {
   212  			io.Copy(rw, neverEnding('a'))
   213  			for {
   214  				time.Sleep(5 * time.Second) // hang forever, until killed
   215  			}
   216  		}
   217  		fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
   218  		for k, vv := range req.Form {
   219  			for _, v := range vv {
   220  				fmt.Fprintf(rw, "param-%s=%s\n", k, v)
   221  			}
   222  		}
   223  		for _, kv := range os.Environ() {
   224  			fmt.Fprintf(rw, "env-%s\n", kv)
   225  		}
   226  	}))
   227  	os.Exit(0)
   228  }