| /* |
| Copyright (c) 2015 VMware, Inc. All Rights Reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package soap |
| |
| import ( |
| "fmt" |
| "io" |
| "net/http" |
| "net/http/httputil" |
| "strings" |
| "sync/atomic" |
| "time" |
| |
| "github.com/vmware/govmomi/vim25/debug" |
| ) |
| |
| // teeReader wraps io.TeeReader and patches through the Close() function. |
| type teeReader struct { |
| io.Reader |
| io.Closer |
| } |
| |
| func newTeeReader(rc io.ReadCloser, w io.Writer) io.ReadCloser { |
| return teeReader{ |
| Reader: io.TeeReader(rc, w), |
| Closer: rc, |
| } |
| } |
| |
| // debugRoundTrip contains state and logic needed to debug a single round trip. |
| type debugRoundTrip struct { |
| cn uint64 // Client number |
| rn uint64 // Request number |
| log io.WriteCloser // Request log |
| cs []io.Closer // Files that need closing when done |
| } |
| |
| func (d *debugRoundTrip) logf(format string, a ...interface{}) { |
| now := time.Now().Format("2006-01-02T15-04-05.000000000") |
| fmt.Fprintf(d.log, "%s - %04d: ", now, d.rn) |
| fmt.Fprintf(d.log, format, a...) |
| fmt.Fprintf(d.log, "\n") |
| } |
| |
| func (d *debugRoundTrip) enabled() bool { |
| return d != nil |
| } |
| |
| func (d *debugRoundTrip) done() { |
| for _, c := range d.cs { |
| c.Close() |
| } |
| } |
| |
| func (d *debugRoundTrip) newFile(suffix string) io.WriteCloser { |
| return debug.NewFile(fmt.Sprintf("%d-%04d.%s", d.cn, d.rn, suffix)) |
| } |
| |
| func (d *debugRoundTrip) ext(h http.Header) string { |
| ext := "xml" |
| if strings.Contains(h.Get("Content-Type"), "/json") { |
| ext = "json" |
| } |
| return ext |
| } |
| |
| func (d *debugRoundTrip) debugRequest(req *http.Request) { |
| if d == nil { |
| return |
| } |
| |
| var wc io.WriteCloser |
| |
| // Capture headers |
| wc = d.newFile("req.headers") |
| b, _ := httputil.DumpRequest(req, false) |
| wc.Write(b) |
| wc.Close() |
| |
| // Capture body |
| wc = d.newFile("req." + d.ext(req.Header)) |
| req.Body = newTeeReader(req.Body, wc) |
| |
| // Delay closing until marked done |
| d.cs = append(d.cs, wc) |
| } |
| |
| func (d *debugRoundTrip) debugResponse(res *http.Response) { |
| if d == nil { |
| return |
| } |
| |
| var wc io.WriteCloser |
| |
| // Capture headers |
| wc = d.newFile("res.headers") |
| b, _ := httputil.DumpResponse(res, false) |
| wc.Write(b) |
| wc.Close() |
| |
| // Capture body |
| wc = d.newFile("res." + d.ext(res.Header)) |
| res.Body = newTeeReader(res.Body, wc) |
| |
| // Delay closing until marked done |
| d.cs = append(d.cs, wc) |
| } |
| |
| var cn uint64 // Client counter |
| |
| // debugContainer wraps the debugging state for a single client. |
| type debugContainer struct { |
| cn uint64 // Client number |
| rn uint64 // Request counter |
| log io.WriteCloser // Request log |
| } |
| |
| func newDebug() *debugContainer { |
| d := debugContainer{ |
| cn: atomic.AddUint64(&cn, 1), |
| rn: 0, |
| } |
| |
| if !debug.Enabled() { |
| return nil |
| } |
| |
| d.log = debug.NewFile(fmt.Sprintf("%d-client.log", d.cn)) |
| return &d |
| } |
| |
| func (d *debugContainer) newRoundTrip() *debugRoundTrip { |
| if d == nil { |
| return nil |
| } |
| |
| drt := debugRoundTrip{ |
| cn: d.cn, |
| rn: atomic.AddUint64(&d.rn, 1), |
| log: d.log, |
| } |
| |
| return &drt |
| } |