internalError(err)
return
}
+ if hook := testHookStartProcess; hook != nil {
+ hook(cmd.Process)
+ }
defer cmd.Wait()
defer stdoutRead.Close()
_, err = io.Copy(rw, linebody)
if err != nil {
h.printf("cgi: copy error: %v", err)
+ // And kill the child CGI process so we don't hang on
+ // the deferred cmd.Wait above if the error was just
+ // the client (rw) going away. If it was a read error
+ // (because the child died itself), then the extra
+ // kill of an already-dead process is harmless (the PID
+ // won't be reused until the Wait above).
+ cmd.Process.Kill()
}
}
// TODO: other transformations in spec or practice?
return r
}
+
+var testHookStartProcess func(*os.Process) // nil except for some tests
package cgi
import (
+ "bytes"
+ "errors"
"fmt"
+ "io"
"net/http"
+ "net/http/httptest"
"os"
"testing"
+ "time"
)
// This test is a CGI host (testing host.go) that runs its own binary
}
}
-// Test that a child handler only writing headers works.
+type customWriterRecorder struct {
+ w io.Writer
+ *httptest.ResponseRecorder
+}
+
+func (r *customWriterRecorder) Write(p []byte) (n int, err error) {
+ return r.w.Write(p)
+}
+
+type limitWriter struct {
+ w io.Writer
+ n int
+}
+
+func (w *limitWriter) Write(p []byte) (n int, err error) {
+ if len(p) > w.n {
+ p = p[:w.n]
+ }
+ if len(p) > 0 {
+ n, err = w.w.Write(p)
+ w.n -= n
+ }
+ if w.n == 0 {
+ err = errors.New("past write limit")
+ }
+ return
+}
+
+// If there's an error copying the child's output to the parent, test
+// that we kill the child.
+func TestKillChildAfterCopyError(t *testing.T) {
+ defer func() { testHookStartProcess = nil }()
+ proc := make(chan *os.Process, 1)
+ testHookStartProcess = func(p *os.Process) {
+ proc <- p
+ }
+
+ h := &Handler{
+ Path: os.Args[0],
+ Root: "/test.go",
+ Args: []string{"-test.run=TestBeChildCGIProcess"},
+ }
+ req, _ := http.NewRequest("GET", "http://example.com/test.cgi?write-forever=1", nil)
+ rec := httptest.NewRecorder()
+ var out bytes.Buffer
+ const writeLen = 50 << 10
+ rw := &customWriterRecorder{&limitWriter{&out, writeLen}, rec}
+
+ donec := make(chan bool, 1)
+ go func() {
+ h.ServeHTTP(rw, req)
+ donec <- true
+ }()
+
+ select {
+ case <-donec:
+ if out.Len() != writeLen || out.Bytes()[0] != 'a' {
+ t.Errorf("unexpected output: %q", out.Bytes())
+ }
+ case <-time.After(5 * time.Second):
+ t.Errorf("timeout. ServeHTTP hung and didn't kill the child process?")
+ select {
+ case p := <-proc:
+ p.Kill()
+ t.Logf("killed process")
+ default:
+ t.Logf("didn't kill process")
+ }
+ }
+}
+
+// Test that a child handler writing only headers works.
func TestChildOnlyHeaders(t *testing.T) {
h := &Handler{
Path: os.Args[0],
}
}
+type neverEnding byte
+
+func (b neverEnding) Read(p []byte) (n int, err error) {
+ for i := range p {
+ p[i] = byte(b)
+ }
+ return len(p), nil
+}
+
// Note: not actually a test.
func TestBeChildCGIProcess(t *testing.T) {
if os.Getenv("REQUEST_METHOD") == "" {
if req.FormValue("no-body") == "1" {
return
}
+ if req.FormValue("write-forever") == "1" {
+ io.Copy(rw, neverEnding('a'))
+ for {
+ time.Sleep(5 * time.Second) // hang forever, until killed
+ }
+ }
fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
for k, vv := range req.Form {
for _, v := range vv {