github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/ioutil"
     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  func (prt *ProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    74  	if prt.Sub == nil {
    75  		panic("An attempt was made to request file content without having" +
    76  			" the virtual filesystem initialized.")
    77  	}
    78  	return prt.Sub.RoundTrip(req)
    79  }
    80  
    81  func newHTTPResponse(status string, statusCode int, body string) *http.Response {
    82  	return &http.Response{
    83  		Proto:      "HTTP/1.0",
    84  		ProtoMajor: 1,
    85  		Header:     make(http.Header),
    86  		Close:      true,
    87  
    88  		// Parameter fields:
    89  		Status:        status,
    90  		StatusCode:    statusCode,
    91  		Body:          ioutil.NopCloser(strings.NewReader(body)),
    92  		ContentLength: int64(len(body)),
    93  	}
    94  }
    95  
    96  // RoundTrip returns a canned error or body for the given request.
    97  func (v *CannedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    98  	full := req.URL.String()
    99  	for urlPrefix, statusCode := range v.errorURLS {
   100  		if strings.HasPrefix(full, urlPrefix) {
   101  			status := fmt.Sprintf("%d Error", statusCode)
   102  			return newHTTPResponse(status, statusCode, ""), nil
   103  		}
   104  	}
   105  	if contents, found := v.files[req.URL.Path]; found {
   106  		return newHTTPResponse("200 OK", http.StatusOK, contents), nil
   107  	}
   108  	return newHTTPResponse("404 Not Found", http.StatusNotFound, ""), nil
   109  }
   110  
   111  // NewCannedRoundTripper returns a CannedRoundTripper with the given canned
   112  // responses.
   113  func NewCannedRoundTripper(files map[string]string, errorURLs map[string]int) *CannedRoundTripper {
   114  	return &CannedRoundTripper{files, errorURLs}
   115  }