github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/testing/roundtripper.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package testing
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strings"
    11  )
    12  
    13  // CannedRoundTripper can be used to provide canned "http" responses without
    14  // actually starting an HTTP server.
    15  //
    16  // Use this in conjunction with ProxyRoundTripper. A ProxyRoundTripper is
    17  // what gets registered as the default handler for a given protocol (such as
    18  // "test") and then tests can direct the ProxyRoundTripper to delegate to a
    19  // CannedRoundTripper. The reason for this is that we can register a
    20  // roundtripper to handle a scheme, but there is no way to unregister it: you
    21  // may need to re-use the same ProxyRoundTripper but use different
    22  // CannedRoundTrippers to return different results.
    23  type CannedRoundTripper struct {
    24  	// files maps file names to their contents. If the roundtripper
    25  	// receives a request for any of these files, and none of the entries
    26  	// in errorURLs below matches, it will return the contents associated
    27  	// with that filename here.
    28  	// TODO(jtv): Do something more sensible here: either make files take
    29  	// precedence over errors, or return the given error *with* the given
    30  	// contents, or just disallow overlap.
    31  	files map[string]string
    32  
    33  	// errorURLs are prefixes that should return specific HTTP status
    34  	// codes. If a request's URL matches any of these prefixes, the
    35  	// associated error status is returned.
    36  	// There is no clever longest-prefix selection here. If more than
    37  	// one prefix matches, any one of them may be used.
    38  	// TODO(jtv): Decide what to do about multiple matching prefixes.
    39  	errorURLS map[string]int
    40  }
    41  
    42  var _ http.RoundTripper = (*CannedRoundTripper)(nil)
    43  
    44  // ProxyRoundTripper is an http.RoundTripper implementation that does nothing
    45  // but delegate to another RoundTripper. This lets tests change how they handle
    46  // requests for a given scheme, despite the fact that the standard library does
    47  // not support un-registration, or registration of a new roundtripper with a
    48  // URL scheme that's already handled.
    49  //
    50  // Use the RegisterForScheme method to install this as the standard handler
    51  // for a particular protocol. For example, if you call
    52  // prt.RegisterForScheme("test") then afterwards, any request to "test:///foo"
    53  // will be routed to prt.
    54  type ProxyRoundTripper struct {
    55  	// Sub is the roundtripper that this roundtripper delegates to, if any.
    56  	// If you leave this nil, this roundtripper is effectively disabled.
    57  	Sub http.RoundTripper
    58  }
    59  
    60  var _ http.RoundTripper = (*ProxyRoundTripper)(nil)
    61  
    62  // RegisterForScheme registers a ProxyRoundTripper as the default roundtripper
    63  // for the given URL scheme.
    64  //
    65  // This cannot be undone, nor overwritten with a different roundtripper. If
    66  // you change your mind later about what the roundtripper should do, set its
    67  // "Sub" field to delegate to a different roundtripper (or to nil if you don't
    68  // want to handle its requests at all any more).
    69  func (prt *ProxyRoundTripper) RegisterForScheme(scheme string) {
    70  	http.DefaultTransport.(*http.Transport).RegisterProtocol(scheme, prt)
    71  }
    72  
    73  // RegisterForTransportScheme registers a ProxyRoundTripper as the transport
    74  // roundtripper for the given URL scheme.
    75  func (prt *ProxyRoundTripper) RegisterForTransportScheme(transport *http.Transport, scheme string) {
    76  	transport.RegisterProtocol(scheme, prt)
    77  }
    78  
    79  func (prt *ProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    80  	if prt.Sub == nil {
    81  		panic("An attempt was made to request file content without having" +
    82  			" the virtual filesystem initialized.")
    83  	}
    84  	return prt.Sub.RoundTrip(req)
    85  }
    86  
    87  func newHTTPResponse(status string, statusCode int, body string) *http.Response {
    88  	return &http.Response{
    89  		Proto:      "HTTP/1.0",
    90  		ProtoMajor: 1,
    91  		Header:     make(http.Header),
    92  		Close:      true,
    93  
    94  		// Parameter fields:
    95  		Status:        status,
    96  		StatusCode:    statusCode,
    97  		Body:          io.NopCloser(strings.NewReader(body)),
    98  		ContentLength: int64(len(body)),
    99  	}
   100  }
   101  
   102  // RoundTrip returns a canned error or body for the given request.
   103  func (v *CannedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   104  	full := req.URL.String()
   105  	for urlPrefix, statusCode := range v.errorURLS {
   106  		if strings.HasPrefix(full, urlPrefix) {
   107  			status := fmt.Sprintf("%d Error", statusCode)
   108  			return newHTTPResponse(status, statusCode, ""), nil
   109  		}
   110  	}
   111  	if contents, found := v.files[req.URL.Path]; found {
   112  		return newHTTPResponse("200 OK", http.StatusOK, contents), nil
   113  	}
   114  	return newHTTPResponse("404 Not Found", http.StatusNotFound, ""), nil
   115  }
   116  
   117  // NewCannedRoundTripper returns a CannedRoundTripper with the given canned
   118  // responses.
   119  func NewCannedRoundTripper(files map[string]string, errorURLs map[string]int) *CannedRoundTripper {
   120  	return &CannedRoundTripper{files, errorURLs}
   121  }