Skip to content

Commit

Permalink
Support flexible dump
Browse files Browse the repository at this point in the history
Add fields for DumpOptions to control dump output more fine-grained:
* RequestOutput
* ResponseOutput
* RequestHeaderOutput
* RequestBodyOutput
* ResponseHeaderOutput
* ResponseBodyOutput
  • Loading branch information
imroc committed Oct 17, 2022
1 parent 333d103 commit b97c579
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 67 deletions.
61 changes: 55 additions & 6 deletions dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ import (

// DumpOptions controls the dump behavior.
type DumpOptions struct {
Output io.Writer
RequestHeader bool
RequestBody bool
ResponseHeader bool
ResponseBody bool
Async bool
Output io.Writer
RequestOutput io.Writer
ResponseOutput io.Writer
RequestHeaderOutput io.Writer
RequestBodyOutput io.Writer
ResponseHeaderOutput io.Writer
ResponseBodyOutput io.Writer
RequestHeader bool
RequestBody bool
ResponseHeader bool
ResponseBody bool
Async bool
}

// Clone return a copy of DumpOptions
Expand All @@ -30,9 +36,52 @@ type dumpOptions struct {
}

func (o dumpOptions) Output() io.Writer {
if o.DumpOptions.Output == nil {
return os.Stdout
}
return o.DumpOptions.Output
}

func (o dumpOptions) RequestHeaderOutput() io.Writer {
if o.DumpOptions.RequestHeaderOutput != nil {
return o.DumpOptions.RequestHeaderOutput
}
if o.DumpOptions.RequestOutput != nil {
return o.DumpOptions.RequestOutput
}
return o.Output()
}

func (o dumpOptions) RequestBodyOutput() io.Writer {
if o.DumpOptions.RequestBodyOutput != nil {
return o.DumpOptions.RequestBodyOutput
}
if o.DumpOptions.RequestOutput != nil {
return o.DumpOptions.RequestOutput
}
return o.Output()
}

func (o dumpOptions) ResponseHeaderOutput() io.Writer {
if o.DumpOptions.ResponseHeaderOutput != nil {
return o.DumpOptions.ResponseHeaderOutput
}
if o.DumpOptions.ResponseOutput != nil {
return o.DumpOptions.ResponseOutput
}
return o.Output()
}

func (o dumpOptions) ResponseBodyOutput() io.Writer {
if o.DumpOptions.ResponseBodyOutput != nil {
return o.DumpOptions.ResponseBodyOutput
}
if o.DumpOptions.ResponseOutput != nil {
return o.DumpOptions.ResponseOutput
}
return o.Output()
}

func (o dumpOptions) RequestHeader() bool {
return o.DumpOptions.RequestHeader
}
Expand Down
106 changes: 76 additions & 30 deletions internal/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
// Options controls the dump behavior.
type Options interface {
Output() io.Writer
RequestHeaderOutput() io.Writer
RequestBodyOutput() io.Writer
ResponseHeaderOutput() io.Writer
ResponseBodyOutput() io.Writer
RequestHeader() bool
RequestBody() bool
ResponseHeader() bool
Expand All @@ -17,52 +21,70 @@ type Options interface {
Clone() Options
}

func (d *Dumper) WrapReadCloser(rc io.ReadCloser) io.ReadCloser {
return &dumpReadCloser{rc, d}
func (d *Dumper) WrapResponseBodyReadCloser(rc io.ReadCloser) io.ReadCloser {
return &dumpReponseBodyReadCloser{rc, d}
}

type dumpReadCloser struct {
type dumpReponseBodyReadCloser struct {
io.ReadCloser
dump *Dumper
}

func (r *dumpReadCloser) Read(p []byte) (n int, err error) {
func (r *dumpReponseBodyReadCloser) Read(p []byte) (n int, err error) {
n, err = r.ReadCloser.Read(p)
r.dump.Dump(p[:n])
r.dump.DumpResponseBody(p[:n])
if err == io.EOF {
r.dump.Dump([]byte("\r\n"))
r.dump.DumpDefault([]byte("\r\n"))
}
return
}

func (d *Dumper) WrapWriteCloser(rc io.WriteCloser) io.WriteCloser {
return &dumpWriteCloser{rc, d}
func (d *Dumper) WrapRequestBodyWriteCloser(rc io.WriteCloser) io.WriteCloser {
return &dumpRequestBodyWriteCloser{rc, d}
}

type dumpWriteCloser struct {
type dumpRequestBodyWriteCloser struct {
io.WriteCloser
dump *Dumper
}

func (w *dumpWriteCloser) Write(p []byte) (n int, err error) {
func (w *dumpRequestBodyWriteCloser) Write(p []byte) (n int, err error) {
n, err = w.WriteCloser.Write(p)
w.dump.Dump(p[:n])
w.dump.DumpRequestBody(p[:n])
return
}

type dumpWriter struct {
type dumpRequestHeaderWriter struct {
w io.Writer
dump *Dumper
}

func (w *dumpWriter) Write(p []byte) (n int, err error) {
func (w *dumpRequestHeaderWriter) Write(p []byte) (n int, err error) {
n, err = w.w.Write(p)
w.dump.Dump(p[:n])
w.dump.DumpRequestHeader(p[:n])
return
}

func (d *Dumper) WrapWriter(w io.Writer) io.Writer {
return &dumpWriter{
func (d *Dumper) WrapRequestHeaderWriter(w io.Writer) io.Writer {
return &dumpRequestHeaderWriter{
w: w,
dump: d,
}
}

type dumpRequestBodyWriter struct {
w io.Writer
dump *Dumper
}

func (w *dumpRequestBodyWriter) Write(p []byte) (n int, err error) {
n, err = w.w.Write(p)
w.dump.DumpRequestBody(p[:n])
return
}

func (d *Dumper) WrapRequestBodyWriter(w io.Writer) io.Writer {
return &dumpRequestBodyWriter{
w: w,
dump: d,
}
Expand All @@ -88,24 +110,28 @@ func (ds Dumpers) ShouldDump() bool {
return len(ds) > 0
}

// Dump with all dumpers.
func (ds Dumpers) Dump(p []byte) {
func (ds Dumpers) DumpResponseHeader(p []byte) {
for _, d := range ds {
d.Dump(p)
d.DumpResponseHeader(p)
}
}

// Dumper is the dump tool.
type Dumper struct {
Options
ch chan []byte
ch chan *dumpTask
}

type dumpTask struct {
Data []byte
Output io.Writer
}

// NewDumper create a new Dumper.
func NewDumper(opt Options) *Dumper {
d := &Dumper{
Options: opt,
ch: make(chan []byte, 20),
ch: make(chan *dumpTask, 20),
}
return d
}
Expand All @@ -121,33 +147,53 @@ func (d *Dumper) Clone() *Dumper {
}
return &Dumper{
Options: d.Options.Clone(),
ch: make(chan []byte, 20),
ch: make(chan *dumpTask, 20),
}
}

func (d *Dumper) Dump(p []byte) {
if len(p) == 0 {
func (d *Dumper) DumpTo(p []byte, output io.Writer) {
if len(p) == 0 || output == nil {
return
}
if d.Async() {
b := make([]byte, len(p))
copy(b, p)
d.ch <- b
d.ch <- &dumpTask{Data: b, Output: output}
return
}
d.Output().Write(p)
output.Write(p)
}

func (d *Dumper) DumpDefault(p []byte) {
d.DumpTo(p, d.Output())
}

func (d *Dumper) DumpRequestHeader(p []byte) {
d.DumpTo(p, d.RequestHeaderOutput())
}

func (d *Dumper) DumpRequestBody(p []byte) {
d.DumpTo(p, d.RequestBodyOutput())
}

func (d *Dumper) DumpResponseHeader(p []byte) {
d.DumpTo(p, d.ResponseHeaderOutput())
}

func (d *Dumper) DumpResponseBody(p []byte) {
d.DumpTo(p, d.ResponseBodyOutput())
}

func (d *Dumper) Stop() {
d.ch <- nil
}

func (d *Dumper) Start() {
for b := range d.ch {
if b == nil {
for t := range d.ch {
if t == nil {
return
}
d.Output().Write(b)
t.Output.Write(t.Data)
}
}

Expand All @@ -170,7 +216,7 @@ func WrapResponseBodyIfNeeded(res *http.Response, req *http.Request, dump *Dumpe
dumps := GetDumpers(req.Context(), dump)
for _, d := range dumps {
if d.ResponseBody() {
res.Body = d.WrapReadCloser(res.Body)
res.Body = d.WrapResponseBodyReadCloser(res.Body)
}
}
}
9 changes: 4 additions & 5 deletions internal/http2/frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ func (h2f *Framer) ReadFrame() (Frame, error) {
hr, err := h2f.readMetaFrame(f.(*HeadersFrame), dumps)
if err == nil && len(dumps) > 0 {
for _, dump := range dumps {
dump.Dump([]byte("\r\n"))
dump.DumpResponseHeader([]byte("\r\n"))
}
}
return hr, err
Expand Down Expand Up @@ -1587,11 +1587,10 @@ func (h2f *Framer) readMetaFrame(hf *HeadersFrame, dumps []*dump.Dumper) (*MetaH
}
emitFunc := rawEmitFunc

if len(dumps) > 0 {
ds := dump.Dumpers(dumps)
if ds.ShouldDump() {
emitFunc = func(hf hpack.HeaderField) {
for _, dump := range dumps {
dump.Dump([]byte(fmt.Sprintf("%s: %s\r\n", hf.Name, hf.Value)))
}
ds.DumpResponseHeader([]byte(fmt.Sprintf("%s: %s\r\n", hf.Name, hf.Value)))
rawEmitFunc(hf)
}
}
Expand Down
10 changes: 5 additions & 5 deletions internal/http2/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -1239,7 +1239,7 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
} else {
cs.sentEndStream = true
for _, dump := range bodyDumps {
dump.Dump([]byte("\r\n\r\n"))
dump.DumpDefault([]byte("\r\n\r\n"))
}
}
}
Expand Down Expand Up @@ -1499,7 +1499,7 @@ func (cs *clientStream) writeRequestBody(req *http.Request, dumps []*dump.Dumper
if len(dumps) > 0 {
writeData = func(streamID uint32, endStream bool, data []byte) error {
for _, dump := range dumps {
dump.Dump(data)
dump.DumpRequestBody(data)
}
return cc.fr.WriteData(streamID, endStream, data)
}
Expand Down Expand Up @@ -1818,7 +1818,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
if len(headerDumps) > 0 {
writeHeader = func(name, value string) {
for _, dump := range headerDumps {
dump.Dump([]byte(fmt.Sprintf("%s: %s\r\n", name, value)))
dump.DumpRequestHeader([]byte(fmt.Sprintf("%s: %s\r\n", name, value)))
}
cc.writeHeader(name, value)
}
Expand All @@ -1840,7 +1840,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
})

for _, dump := range headerDumps {
dump.Dump([]byte("\r\n"))
dump.DumpRequestHeader([]byte("\r\n"))
}

return cc.hbuf.Bytes(), nil
Expand Down Expand Up @@ -1887,7 +1887,7 @@ func (cc *ClientConn) encodeTrailers(trailer http.Header, dumps []*dump.Dumper)
if len(dumps) > 0 {
writeHeader = func(name, value string) {
for _, dump := range dumps {
dump.Dump([]byte(fmt.Sprintf("%s: %s\r\n", name, value)))
dump.DumpRequestHeader([]byte(fmt.Sprintf("%s: %s\r\n", name, value)))
}
cc.writeHeader(name, value)
}
Expand Down
10 changes: 5 additions & 5 deletions internal/http3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func (c *client) sendRequestBody(str Stream, body io.ReadCloser, dumps []*dump.D
if len(dumps) > 0 {
writeData = func(data []byte) error {
for _, dump := range dumps {
dump.Dump(data)
dump.DumpRequestBody(data)
}
if _, err := str.Write(data); err != nil {
return err
Expand All @@ -338,7 +338,7 @@ func (c *client) sendRequestBody(str Stream, body io.ReadCloser, dumps []*dump.D
}
if rerr == io.EOF {
for _, dump := range dumps {
dump.Dump([]byte("\r\n\r\n"))
dump.DumpDefault([]byte("\r\n\r\n"))
}
break
}
Expand All @@ -349,7 +349,7 @@ func (c *client) sendRequestBody(str Stream, body io.ReadCloser, dumps []*dump.D
if rerr != nil {
if rerr == io.EOF {
for _, dump := range dumps {
dump.Dump([]byte("\r\n\r\n"))
dump.DumpDefault([]byte("\r\n\r\n"))
}
break
}
Expand Down Expand Up @@ -424,11 +424,11 @@ func (c *client) doRequest(req *http.Request, str quic.Stream, opt RoundTripOpt,
if len(respHeaderDumps) > 0 {
for _, hf := range hfs {
for _, dump := range respHeaderDumps {
dump.Dump([]byte(fmt.Sprintf("%s: %s\r\n", hf.Name, hf.Value)))
dump.DumpResponseHeader([]byte(fmt.Sprintf("%s: %s\r\n", hf.Name, hf.Value)))
}
}
for _, dump := range respHeaderDumps {
dump.Dump([]byte("\r\n"))
dump.DumpResponseHeader([]byte("\r\n"))
}
}
if err != nil {
Expand Down
Loading

0 comments on commit b97c579

Please sign in to comment.