package httpdebug import ( "bytes" "fmt" "io" "io/ioutil" "log" "net/http" "strings" "unicode" ) type Mode int const ( ModeOneline Mode = iota ModeTrimmed ModeFull ) var ModeDefault = ModeOneline // Wrap returns an http handler that serves h while logging requests and responses to stderr. It uses ModeDefault. func Wrap(h http.Handler) http.Handler { return WrapMode(h, ModeDefault) } // WrapMode returns an http handler that serves h while logging requests and responses to stderr. func WrapMode(h http.Handler, m Mode) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch m { case ModeTrimmed: serveTrimmed(h, w, r, 1024, 128) case ModeFull: serveTrimmed(h, w, r, 0, 0) case ModeOneline: buf := &bytes.Buffer{} rw := &rw{ ResponseWriter: w, w: buf, } h.ServeHTTP(rw, r) log.Println(r.Method, r.URL.Path, strings.Repeat("\t", 5-((len(r.Method)+len(r.URL.Path))/12)+1), rw.status, http.StatusText(rw.status), buf.Len(), "bytes", "\t", "("+w.Header().Get("Content-Type")+")") } }) } func serveTrimmed(h http.Handler, w http.ResponseWriter, r *http.Request, trimAt, trimTo int64) { log.Println(r.Method, r.URL.Path, r.Proto) hBuf := &bytes.Buffer{} r.Header.Write(hBuf) log.Println(hBuf.String()) requestBuf := &bytes.Buffer{} responseBuf := &bytes.Buffer{} defer r.Body.Close() r.Body = ioutil.NopCloser(io.TeeReader(r.Body, requestBuf)) rw := &rw{ ResponseWriter: w, w: responseBuf, } h.ServeHTTP(rw, r) if trimAt > 0 && trimTo > 0 { if int64(requestBuf.Len()) > trimAt { trimmed := &bytes.Buffer{} io.CopyN(trimmed, requestBuf, trimTo) requestBuf = trimmed } } log.Println() log.Println(logsafe(requestBuf.String())) log.Println(rw.status) hBuf.Reset() w.Header().Write(hBuf) log.Println(hBuf.String()) if trimTo > 0 { responseBuf = trim(responseBuf, int(trimTo)) } log.Println() log.Println(logsafe(responseBuf.String())) } type rw struct { http.ResponseWriter w io.Writer status int } func (rw *rw) WriteHeader(s int) { rw.status = s rw.ResponseWriter.WriteHeader(s) } func (rw *rw) Write(b []byte) (int, error) { if rw.status == 0 { rw.WriteHeader(http.StatusOK) } if rw.w != nil { rw.w.Write(b) } return rw.ResponseWriter.Write(b) } func logsafe(str string) string { buf := &bytes.Buffer{} for _, r := range str { if unicode.IsPrint(r) || unicode.IsSpace(r) { fmt.Fprintf(buf, "%c", r) } else { fmt.Fprintf(buf, "%x", r) } } return buf.String() } func trim(b *bytes.Buffer, n int) *bytes.Buffer { trimmed := &bytes.Buffer{} io.CopyN(trimmed, b, int64(n)) return trimmed }