1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
13
// CannedRoundTripper can be used to provide canned "http" responses without
14
// actually starting an HTTP server.
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
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
42
var _ http.RoundTripper = (*CannedRoundTripper)(nil)
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.
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.
60
var _ http.RoundTripper = (*ProxyRoundTripper)(nil)
62
// RegisterForScheme registers a ProxyRoundTripper as the default roundtripper
63
// for the given URL scheme.
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)
73
func (prt *ProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
75
panic("An attempt was made to request file content without having" +
76
" the virtual filesystem initialized.")
78
return prt.Sub.RoundTrip(req)
81
func newHTTPResponse(status string, statusCode int, body string) *http.Response {
82
return &http.Response{
85
Header: make(http.Header),
90
StatusCode: statusCode,
91
Body: ioutil.NopCloser(strings.NewReader(body)),
92
ContentLength: int64(len(body)),
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
105
if contents, found := v.files[req.URL.Path]; found {
106
return newHTTPResponse("200 OK", http.StatusOK, contents), nil
108
return newHTTPResponse("404 Not Found", http.StatusNotFound, ""), nil
111
// NewCannedRoundTripper returns a CannedRoundTripper with the given canned
113
func NewCannedRoundTripper(files map[string]string, errorURLs map[string]int) *CannedRoundTripper {
114
return &CannedRoundTripper{files, errorURLs}