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 }