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  }