github.com/vmware/govmomi@v0.37.1/vim25/soap/debug.go (about) 1 /* 2 Copyright (c) 2015 VMware, Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package soap 18 19 import ( 20 "fmt" 21 "io" 22 "net/http" 23 "net/http/httputil" 24 "sync/atomic" 25 26 "github.com/vmware/govmomi/vim25/debug" 27 ) 28 29 var ( 30 // Trace reads an http request or response from rc and writes to w. 31 // The content type (kind) should be one of "xml" or "json". 32 Trace = func(rc io.ReadCloser, w io.Writer, kind string) io.ReadCloser { 33 return debug.NewTeeReader(rc, w) 34 } 35 ) 36 37 // debugRoundTrip contains state and logic needed to debug a single round trip. 38 type debugRoundTrip struct { 39 cn uint64 // Client number 40 rn uint64 // Request number 41 cs []io.Closer // Files that need closing when done 42 } 43 44 func (d *debugRoundTrip) enabled() bool { 45 return d != nil 46 } 47 48 func (d *debugRoundTrip) done() { 49 for _, c := range d.cs { 50 c.Close() 51 } 52 } 53 54 func (d *debugRoundTrip) newFile(suffix string) io.WriteCloser { 55 return debug.NewFile(fmt.Sprintf("%d-%04d.%s", d.cn, d.rn, suffix)) 56 } 57 58 func (d *debugRoundTrip) ext(h http.Header) string { 59 const json = "application/json" 60 ext := "xml" 61 if h.Get("Accept") == json || h.Get("Content-Type") == json { 62 ext = "json" 63 } 64 return ext 65 } 66 67 func (d *debugRoundTrip) debugRequest(req *http.Request) string { 68 if d == nil { 69 return "" 70 } 71 72 // Capture headers 73 var wc io.WriteCloser = d.newFile("req.headers") 74 b, _ := httputil.DumpRequest(req, false) 75 wc.Write(b) 76 wc.Close() 77 78 ext := d.ext(req.Header) 79 // Capture body 80 wc = d.newFile("req." + ext) 81 if req.Body != nil { 82 req.Body = Trace(req.Body, wc, ext) 83 } 84 85 // Delay closing until marked done 86 d.cs = append(d.cs, wc) 87 88 return ext 89 } 90 91 func (d *debugRoundTrip) debugResponse(res *http.Response, ext string) { 92 if d == nil { 93 return 94 } 95 96 // Capture headers 97 var wc io.WriteCloser = d.newFile("res.headers") 98 b, _ := httputil.DumpResponse(res, false) 99 wc.Write(b) 100 wc.Close() 101 102 // Capture body 103 wc = d.newFile("res." + ext) 104 res.Body = Trace(res.Body, wc, ext) 105 106 // Delay closing until marked done 107 d.cs = append(d.cs, wc) 108 } 109 110 var cn uint64 // Client counter 111 112 // debugContainer wraps the debugging state for a single client. 113 type debugContainer struct { 114 cn uint64 // Client number 115 rn uint64 // Request counter 116 } 117 118 func newDebug() *debugContainer { 119 d := debugContainer{ 120 cn: atomic.AddUint64(&cn, 1), 121 rn: 0, 122 } 123 124 if !debug.Enabled() { 125 return nil 126 } 127 return &d 128 } 129 130 func (d *debugContainer) newRoundTrip() *debugRoundTrip { 131 if d == nil { 132 return nil 133 } 134 135 drt := debugRoundTrip{ 136 cn: d.cn, 137 rn: atomic.AddUint64(&d.rn, 1), 138 } 139 140 return &drt 141 }