tr: &Transport{},
}
cst.c = &Client{Transport: cst.tr}
+ cst.ts = httptest.NewUnstartedServer(h)
for _, opt := range opts {
switch opt := opt.(type) {
case func(*Transport):
opt(cst.tr)
+ case func(*httptest.Server):
+ opt(cst.ts)
default:
t.Fatalf("unhandled option type %T", opt)
}
}
if !h2 {
- cst.ts = httptest.NewServer(h)
+ cst.ts.Start()
return cst
}
- cst.ts = httptest.NewUnstartedServer(h)
ExportHttp2ConfigureServer(cst.ts.Config, nil)
cst.ts.TLS = cst.ts.Config.TLSConfig
cst.ts.StartTLS()
}
}
-func TestInterruptWithPanic_h1(t *testing.T) { testInterruptWithPanic(t, h1Mode) }
-func TestInterruptWithPanic_h2(t *testing.T) { testInterruptWithPanic(t, h2Mode) }
-func testInterruptWithPanic(t *testing.T, h2 bool) {
- log.SetOutput(ioutil.Discard) // is noisy otherwise
- defer log.SetOutput(os.Stderr)
-
+func TestInterruptWithPanic_h1(t *testing.T) { testInterruptWithPanic(t, h1Mode, "boom") }
+func TestInterruptWithPanic_h2(t *testing.T) { testInterruptWithPanic(t, h2Mode, "boom") }
+func TestInterruptWithPanic_nil_h1(t *testing.T) { testInterruptWithPanic(t, h1Mode, nil) }
+func TestInterruptWithPanic_nil_h2(t *testing.T) { testInterruptWithPanic(t, h2Mode, nil) }
+func TestInterruptWithPanic_ErrAbortHandler_h1(t *testing.T) {
+ testInterruptWithPanic(t, h1Mode, ErrAbortHandler)
+}
+func TestInterruptWithPanic_ErrAbortHandler_h2(t *testing.T) {
+ testInterruptWithPanic(t, h2Mode, ErrAbortHandler)
+}
+func testInterruptWithPanic(t *testing.T, h2 bool, panicValue interface{}) {
+ setParallel(t)
const msg = "hello"
defer afterTest(t)
+
+ var errorLog lockedBytesBuffer
+
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
io.WriteString(w, msg)
w.(Flusher).Flush()
- panic("no more")
- }))
+ panic(panicValue)
+ }), func(ts *httptest.Server) {
+ ts.Config.ErrorLog = log.New(&errorLog, "", 0)
+ })
defer cst.close()
res, err := cst.c.Get(cst.ts.URL)
if err != nil {
if err == nil {
t.Errorf("client read all successfully; want some error")
}
+ wantStackLogged := panicValue != nil && panicValue != ErrAbortHandler
+ errorLog.Lock()
+ gotLog := errorLog.String()
+ if !wantStackLogged {
+ if gotLog == "" {
+ return
+ }
+ if h2 {
+ t.Skip("TODO: make http2.Server respect ErrAbortHandler")
+ }
+ t.Fatalf("want no log output; got: %s", gotLog)
+ }
+ if gotLog == "" {
+ t.Fatalf("wanted a stack trace logged; got nothing")
+ }
+ if !strings.Contains(gotLog, "created by ") && strings.Count(gotLog, "\n") < 6 {
+ t.Errorf("output doesn't look like a panic stack trace. Got: %s", gotLog)
+ }
+}
+
+type lockedBytesBuffer struct {
+ sync.Mutex
+ bytes.Buffer
+}
+
+func (b *lockedBytesBuffer) Write(p []byte) (int, error) {
+ b.Lock()
+ defer b.Unlock()
+ return b.Buffer.Write(p)
}
// Issue 15366
func (e badRequestError) Error() string { return "Bad Request: " + string(e) }
+// ErrAbortHandler is a sentinel panic value to abort a handler.
+// While any panic from ServeHTTP aborts the response to the client,
+// panicking with ErrAbortHandler also suppresses logging of a stack
+// trace to the server's error log.
+var ErrAbortHandler = errors.New("net/http: abort Handler")
+
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
defer func() {
- if err := recover(); err != nil {
+ if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]