github.com/vmware/govmomi@v0.51.0/vim25/soap/debug.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package soap 6 7 import ( 8 "fmt" 9 "io" 10 "net/http" 11 "net/http/httputil" 12 "sync/atomic" 13 14 "github.com/vmware/govmomi/vim25/debug" 15 ) 16 17 var ( 18 // Trace reads an http request or response from rc and writes to w. 19 // The content type (kind) should be one of "xml" or "json". 20 Trace = func(rc io.ReadCloser, w io.Writer, kind string) io.ReadCloser { 21 return debug.NewTeeReader(rc, w) 22 } 23 ) 24 25 // debugRoundTrip contains state and logic needed to debug a single round trip. 26 type debugRoundTrip struct { 27 cn uint64 // Client number 28 rn uint64 // Request number 29 cs []io.Closer // Files that need closing when done 30 } 31 32 func (d *debugRoundTrip) enabled() bool { 33 return d != nil 34 } 35 36 func (d *debugRoundTrip) done() { 37 for _, c := range d.cs { 38 c.Close() 39 } 40 } 41 42 func (d *debugRoundTrip) newFile(suffix string) io.WriteCloser { 43 return debug.NewFile(fmt.Sprintf("%d-%04d.%s", d.cn, d.rn, suffix)) 44 } 45 46 func (d *debugRoundTrip) ext(h http.Header) string { 47 const json = "application/json" 48 ext := "xml" 49 if h.Get("Accept") == json || h.Get("Content-Type") == json { 50 ext = "json" 51 } 52 return ext 53 } 54 55 func (d *debugRoundTrip) debugRequest(req *http.Request) string { 56 if d == nil { 57 return "" 58 } 59 60 // Capture headers 61 var wc io.WriteCloser = d.newFile("req.headers") 62 b, _ := httputil.DumpRequest(req, false) 63 wc.Write(b) 64 wc.Close() 65 66 ext := d.ext(req.Header) 67 // Capture body 68 wc = d.newFile("req." + ext) 69 if req.Body != nil { 70 req.Body = Trace(req.Body, wc, ext) 71 } 72 73 // Delay closing until marked done 74 d.cs = append(d.cs, wc) 75 76 return ext 77 } 78 79 func (d *debugRoundTrip) debugResponse(res *http.Response, ext string) { 80 if d == nil { 81 return 82 } 83 84 // Capture headers 85 var wc io.WriteCloser = d.newFile("res.headers") 86 b, _ := httputil.DumpResponse(res, false) 87 wc.Write(b) 88 wc.Close() 89 90 // Capture body 91 wc = d.newFile("res." + ext) 92 res.Body = Trace(res.Body, wc, ext) 93 94 // Delay closing until marked done 95 d.cs = append(d.cs, wc) 96 } 97 98 var cn uint64 // Client counter 99 100 // debugContainer wraps the debugging state for a single client. 101 type debugContainer struct { 102 cn uint64 // Client number 103 rn uint64 // Request counter 104 } 105 106 func newDebug() *debugContainer { 107 d := debugContainer{ 108 cn: atomic.AddUint64(&cn, 1), 109 rn: 0, 110 } 111 112 if !debug.Enabled() { 113 return nil 114 } 115 return &d 116 } 117 118 func (d *debugContainer) newRoundTrip() *debugRoundTrip { 119 if d == nil { 120 return nil 121 } 122 123 drt := debugRoundTrip{ 124 cn: d.cn, 125 rn: atomic.AddUint64(&d.rn, 1), 126 } 127 128 return &drt 129 }