1 // Copyright 2018 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package httpreplay provides an API for recording and replaying traffic 16 // from HTTP-based Google API clients. 17 // 18 // To record: 19 // 1. Call NewRecorder to get a Recorder. 20 // 2. Use its Client method to obtain an HTTP client to use when making API calls. 21 // 3. Close the Recorder when you're done. That will save the log of interactions 22 // to the file you provided to NewRecorder. 23 // 24 // To replay: 25 // 1. Call NewReplayer with the same filename you used to record to get a Replayer. 26 // 2. Call its Client method and use the client to make the same API calls. 27 // You will get back the recorded responses. 28 // 3. Close the Replayer when you're done. 29 // 30 // This package is EXPERIMENTAL and is subject to change or removal without notice. 31 // It requires Go version 1.8 or higher. 32 package httpreplay 33 34 // TODO(jba): add examples. 35 36 import ( 37 "context" 38 "net/http" 39 40 "cloud.google.com/go/httpreplay/internal/proxy" 41 "google.golang.org/api/option" 42 htransport "google.golang.org/api/transport/http" 43 ) 44 45 // A Recorder records HTTP interactions. 46 type Recorder struct { 47 proxy *proxy.Proxy 48 } 49 50 // NewRecorder creates a recorder that writes to filename. The file will 51 // also store initial state that can be retrieved to configure replay. 52 // 53 // You must call Close on the Recorder to ensure that all data is written. 54 func NewRecorder(filename string, initial []byte) (*Recorder, error) { 55 p, err := proxy.ForRecording(filename, 0) 56 if err != nil { 57 return nil, err 58 } 59 p.Initial = initial 60 return &Recorder{proxy: p}, nil 61 } 62 63 // RemoveRequestHeaders will remove request headers matching patterns from the log, 64 // and skip matching them during replay. 65 // 66 // Pattern is taken literally except for *, which matches any sequence of characters. 67 func (r *Recorder) RemoveRequestHeaders(patterns ...string) { 68 r.proxy.RemoveRequestHeaders(patterns) 69 } 70 71 // ClearHeaders will replace the value of request and response headers that match 72 // any of the patterns with CLEARED, on both recording and replay. 73 // Use ClearHeaders when the header information is secret or may change from run to 74 // run, but you still want to verify that the headers are being sent and received. 75 // 76 // Pattern is taken literally except for *, which matches any sequence of characters. 77 func (r *Recorder) ClearHeaders(patterns ...string) { 78 r.proxy.ClearHeaders(patterns) 79 } 80 81 // RemoveQueryParams will remove URL query parameters matching patterns from the log, 82 // and skip matching them during replay. 83 // 84 // Pattern is taken literally except for *, which matches any sequence of characters. 85 func (r *Recorder) RemoveQueryParams(patterns ...string) { 86 r.proxy.RemoveQueryParams(patterns) 87 } 88 89 // ClearQueryParams will replace the value of URL query parametrs that match any of 90 // the patterns with CLEARED, on both recording and replay. 91 // Use ClearQueryParams when the parameter information is secret or may change from 92 // run to run, but you still want to verify that it are being sent. 93 // 94 // Pattern is taken literally except for *, which matches any sequence of characters. 95 func (r *Recorder) ClearQueryParams(patterns ...string) { 96 r.proxy.ClearQueryParams(patterns) 97 } 98 99 // Client returns an http.Client to be used for recording. Provide authentication options 100 // like option.WithTokenSource as you normally would, or omit them to use Application Default 101 // Credentials. 102 func (r *Recorder) Client(ctx context.Context, opts ...option.ClientOption) (*http.Client, error) { 103 return proxyClient(ctx, r.proxy, opts...) 104 } 105 106 func proxyClient(ctx context.Context, p *proxy.Proxy, opts ...option.ClientOption) (*http.Client, error) { 107 trans, err := htransport.NewTransport(ctx, p.Transport(), opts...) 108 if err != nil { 109 return nil, err 110 } 111 return &http.Client{Transport: trans}, nil 112 } 113 114 // Close closes the Recorder and saves the log file. 115 func (r *Recorder) Close() error { 116 return r.proxy.Close() 117 } 118 119 // A Replayer replays previously recorded HTTP interactions. 120 type Replayer struct { 121 proxy *proxy.Proxy 122 } 123 124 // NewReplayer creates a replayer that reads from filename. 125 func NewReplayer(filename string) (*Replayer, error) { 126 p, err := proxy.ForReplaying(filename, 0) 127 if err != nil { 128 return nil, err 129 } 130 return &Replayer{proxy: p}, nil 131 } 132 133 // Client returns an HTTP client for replaying. The client does not need to be 134 // configured with credentials for authenticating to a server, since it never 135 // contacts a real backend. 136 func (r *Replayer) Client(ctx context.Context) (*http.Client, error) { 137 return proxyClient(ctx, r.proxy, option.WithoutAuthentication()) 138 } 139 140 // Initial returns the initial state saved by the Recorder. 141 func (r *Replayer) Initial() []byte { 142 return r.proxy.Initial 143 } 144 145 // IgnoreHeader will not use h when matching requests. 146 func (r *Replayer) IgnoreHeader(h string) { 147 r.proxy.IgnoreHeader(h) 148 } 149 150 // Close closes the replayer. 151 func (r *Replayer) Close() error { 152 return r.proxy.Close() 153 } 154 155 // DebugHeaders helps to determine whether a header should be ignored. 156 // When true, if requests have the same method, URL and body but differ 157 // in a header, the first mismatched header is logged. 158 func DebugHeaders() { 159 proxy.DebugHeaders = true 160 } 161 162 // Supported reports whether httpreplay is supported in the current version of Go. 163 // For Go 1.8 and above, the answer is true. 164 func Supported() bool { return true } 165