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 }