...

Source file src/github.com/go-kivik/kivik/v4/couchdb/chttp/trace.go

Documentation: github.com/go-kivik/kivik/v4/couchdb/chttp

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  package chttp
    14  
    15  import (
    16  	"bytes"
    17  	"context"
    18  	"io"
    19  	"net/http"
    20  )
    21  
    22  var clientTraceContextKey = &struct{ name string }{"client trace"}
    23  
    24  // ContextClientTrace returns the ClientTrace associated with the
    25  // provided context. If none, it returns nil.
    26  func ContextClientTrace(ctx context.Context) *ClientTrace {
    27  	trace, _ := ctx.Value(clientTraceContextKey).(*ClientTrace)
    28  	return trace
    29  }
    30  
    31  // ClientTrace is a set of hooks to run at various stages of an outgoing
    32  // HTTP request. Any particular hook may be nil. Functions may be
    33  // called concurrently from different goroutines and some may be called
    34  // after the request has completed or failed.
    35  type ClientTrace struct {
    36  	// HTTPResponse returns a cloe of the *http.Response received from the
    37  	// server, with the body set to nil. If you need the body, use the more
    38  	// expensive HTTPResponseBody.
    39  	HTTPResponse func(*http.Response)
    40  
    41  	// HTTPResponseBody returns a clone of the *http.Response received from the
    42  	// server, with the body cloned. This can be expensive for responses
    43  	// with large bodies.
    44  	HTTPResponseBody func(*http.Response)
    45  
    46  	// HTTPRequest returns a clone of the *http.Request sent to the server, with
    47  	// the body set to nil. If you need the body, use the more expensive
    48  	// HTTPRequestBody.
    49  	HTTPRequest func(*http.Request)
    50  
    51  	// HTTPRequestBody returns a clone of the *http.Request sent to the server,
    52  	// with the body cloned, if it is set. This can be expensive for requests
    53  	// with large bodies.
    54  	HTTPRequestBody func(*http.Request)
    55  }
    56  
    57  // WithClientTrace returns a new context based on the provided parent
    58  // ctx. HTTP client requests made with the returned context will use
    59  // the provided trace hooks, in addition to any previous hooks
    60  // registered with ctx. Any hooks defined in the provided trace will
    61  // be called first.
    62  func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context {
    63  	if trace == nil {
    64  		panic("nil trace")
    65  	}
    66  	return context.WithValue(ctx, clientTraceContextKey, trace)
    67  }
    68  
    69  func (t *ClientTrace) httpResponse(r *http.Response) {
    70  	if t.HTTPResponse == nil || r == nil {
    71  		return
    72  	}
    73  	clone := new(http.Response)
    74  	*clone = *r
    75  	clone.Body = nil
    76  	t.HTTPResponse(clone)
    77  }
    78  
    79  func (t *ClientTrace) httpResponseBody(r *http.Response) {
    80  	if t.HTTPResponseBody == nil || r == nil {
    81  		return
    82  	}
    83  	clone := new(http.Response)
    84  	*clone = *r
    85  	rBody := r.Body
    86  	body, readErr := io.ReadAll(rBody)
    87  	closeErr := rBody.Close()
    88  	r.Body = newReplay(body, readErr, closeErr)
    89  	clone.Body = newReplay(body, readErr, closeErr)
    90  	t.HTTPResponseBody(clone)
    91  }
    92  
    93  func (t *ClientTrace) httpRequest(r *http.Request) {
    94  	if t.HTTPRequest == nil {
    95  		return
    96  	}
    97  	clone := new(http.Request)
    98  	*clone = *r
    99  	clone.Body = nil
   100  	t.HTTPRequest(clone)
   101  }
   102  
   103  func (t *ClientTrace) httpRequestBody(r *http.Request) {
   104  	if t.HTTPRequestBody == nil {
   105  		return
   106  	}
   107  	clone := new(http.Request)
   108  	*clone = *r
   109  	if r.Body != nil {
   110  		rBody := r.Body
   111  		body, readErr := io.ReadAll(rBody)
   112  		closeErr := rBody.Close()
   113  		r.Body = newReplay(body, readErr, closeErr)
   114  		clone.Body = newReplay(body, readErr, closeErr)
   115  	}
   116  	t.HTTPRequestBody(clone)
   117  }
   118  
   119  func newReplay(body []byte, readErr, closeErr error) io.ReadCloser {
   120  	if readErr == nil && closeErr == nil {
   121  		return io.NopCloser(bytes.NewReader(body))
   122  	}
   123  	return &replayReadCloser{
   124  		Reader:   io.NopCloser(bytes.NewReader(body)),
   125  		readErr:  readErr,
   126  		closeErr: closeErr,
   127  	}
   128  }
   129  
   130  // replayReadCloser replays read and close errors
   131  type replayReadCloser struct {
   132  	io.Reader
   133  	readErr  error
   134  	closeErr error
   135  }
   136  
   137  func (r *replayReadCloser) Read(p []byte) (int, error) {
   138  	c, err := r.Reader.Read(p)
   139  	if err == io.EOF && r.readErr != nil {
   140  		err = r.readErr
   141  	}
   142  	return c, err
   143  }
   144  
   145  func (r *replayReadCloser) Close() error {
   146  	return r.closeErr
   147  }
   148  

View as plain text