Break responseWriter into small and large parts.
diff --git a/server.go b/server.go
index 5856322..51db688 100644
--- a/server.go
+++ b/server.go
@@ -19,6 +19,7 @@
 	"net/url"
 	"strconv"
 	"strings"
+	"sync"
 
 	"github.com/bradfitz/http2/hpack"
 )
@@ -690,15 +691,24 @@
 			req.ContentLength = -1
 		}
 	}
-	rw := &responseWriter{
-		sc:       sc,
-		streamID: rp.stream.id,
-		req:      req,
-		body:     body,
-	}
+
+	rws := responseWriterStatePool.Get().(*responseWriterState)
+	rws.sc = sc
+	rws.streamID = rp.stream.id
+	rws.req = req
+	rws.body = body
+	rws.wbuf.Reset()
+
+	rw := &responseWriter{rws: rws}
 	return rw, req, nil
 }
 
+var responseWriterStatePool = sync.Pool{
+	New: func() interface{} {
+		return new(responseWriterState)
+	},
+}
+
 // Run on its own goroutine.
 func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request) {
 	defer rw.handlerDone()
@@ -811,14 +821,40 @@
 	return
 }
 
+// responseWriter is the http.ResponseWriter implementation.  It's
+// intentionally small (1 pointer wide) to minimize garbage.  The
+// responseWriterState pointer inside is zeroed at the end of a
+// request (in handlerDone) and calls on the responseWriter thereafter
+// simply crash (caller's mistake), but the much larger responseWriterState
+// and buffers are reused between multiple requests.
 type responseWriter struct {
-	sc           *serverConn
-	streamID     uint32
-	wroteHeaders bool
-	h            http.Header
+	rws *responseWriterState
+}
 
-	req  *http.Request
-	body *requestBody // to close at end of request, if DATA frames didn't
+type responseWriterState struct {
+	// immutable within a request:
+	sc       *serverConn
+	streamID uint32
+	req      *http.Request
+	body     *requestBody // to close at end of request, if DATA frames didn't
+
+	wbuf bytes.Buffer
+
+	// mutated by http.Handler goroutine:
+	h            http.Header // h goes from maybe-nil to non-nil; contents changed by http.Handler goroutine
+	wroteHeaders bool
+	calledHeader bool
+}
+
+// Optional http.ResponseWriter interfaces implemented.
+var (
+	_ http.Flusher = (*responseWriter)(nil)
+	_ stringWriter = (*responseWriter)(nil)
+	// TODO: hijacker for websockets
+)
+
+func (w *responseWriter) Flush() {
+	// TODO: implement
 }
 
 // TODO: bufio writing of responseWriter. add Flush, add pools of
@@ -826,14 +862,23 @@
 // updates from peer? For now: naive.
 
 func (w *responseWriter) Header() http.Header {
-	if w.h == nil {
-		w.h = make(http.Header)
+	rws := w.rws
+	if rws == nil {
+		panic("Header called after Handler finished")
 	}
-	return w.h
+	if rws.h == nil {
+		rws.h = make(http.Header)
+	}
+	rws.calledHeader = true
+	return rws.h
 }
 
 func (w *responseWriter) WriteHeader(code int) {
-	if w.wroteHeaders {
+	rws := w.rws
+	if rws == nil {
+		panic("WriteHeader called after Handler finished")
+	}
+	if rws.wroteHeaders {
 		return
 	}
 	// TODO: defer actually writing this frame until a Flush or
@@ -841,30 +886,43 @@
 	// e.g. a 204 response to have a Header response frame with
 	// END_STREAM set, without a separate frame being sent in
 	// handleDone.
-	w.wroteHeaders = true
-	w.sc.writeHeader(headerWriteReq{
-		streamID:    w.streamID,
+	rws.wroteHeaders = true
+	rws.sc.writeHeader(headerWriteReq{
+		streamID:    rws.streamID,
 		httpResCode: code,
-		h:           w.h,
+		h:           rws.h,
 	})
 }
 
-// TODO: responseWriter.WriteString too?
+func (w *responseWriter) WriteString(s string) (n int, err error) {
+	// TODO: better impl
+	return w.Write([]byte(s))
+}
 
 func (w *responseWriter) Write(p []byte) (n int, err error) {
-	if !w.wroteHeaders {
+	rws := w.rws
+	if rws == nil {
+		panic("Write called after Handler finished")
+	}
+	if !rws.wroteHeaders {
 		w.WriteHeader(200)
 	}
-	return w.sc.writeData(w.streamID, p) // blocks waiting for tokens
+	return rws.sc.writeData(rws.streamID, p) // blocks waiting for tokens
 }
 
 func (w *responseWriter) handlerDone() {
-	if !w.wroteHeaders {
-		w.sc.writeHeader(headerWriteReq{
-			streamID:    w.streamID,
+	rws := w.rws
+	if rws == nil {
+		panic("handlerDone called twice")
+	}
+	w.rws = nil
+	if !rws.wroteHeaders {
+		rws.sc.writeHeader(headerWriteReq{
+			streamID:    rws.streamID,
 			httpResCode: 200,
-			h:           w.h,
+			h:           rws.h,
 			endStream:   true, // handler has finished; can't be any data.
 		})
 	}
+	// TODO: recycle rws back into a pool
 }