Documentation ¶
Overview ¶
Package recorder provides an HTTP record/replay transport.
The primary use-case is for tests where HTTP requests are sent and can be replayed without needing to reach out to the network. The Recorder is configurable to always perform request, never perform requests, or auto, where requests are performed when no existing entry exists.
Example ¶
package main import ( "fmt" "log" "net/http" "net/http/httputil" "github.com/akupila/recorder" ) func main() { // Create a new recorder. // Data will be saved in testdata/example.yml rec := recorder.New("testdata/example") // Create HTTP client with recorder transport cli := &http.Client{ Transport: rec, } // Perform a request resp, err := cli.Get("https://jsonplaceholder.typicode.com/posts/1") if err != nil { log.Fatal(err) } // Response is only done if required b, err := httputil.DumpResponse(resp, true) if err != nil { log.Fatal(err) } fmt.Println(string(b)) }
Output:
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Filter ¶
type Filter func(entry *Entry)
A Filter modifies the entry before it is saved to disk.
Filters are applied after the actual request, with the primary purpose being to remove sensitive data from the saved file.
Example (Custom) ¶
package main import ( "log" "net/http" "github.com/akupila/recorder" ) func main() { rec := recorder.New("testdata/request-header", func(e *recorder.Entry) { // Modify e.Request and e.Response }) cli := &http.Client{ Transport: rec, } _, err := cli.Get("https://example.com") if err != nil { log.Fatal(err) } }
Output:
func RemoveRequestHeader ¶
RemoveRequestHeader removes a header with the given name from the request. The name of the header is case-sensitive.
Example ¶
package main import ( "log" "net/http" "strings" "github.com/akupila/recorder" ) func main() { rec := recorder.New("testdata/request-header", recorder.RemoveRequestHeader("Authorization")) cli := &http.Client{ Transport: rec, } req, _ := http.NewRequest("https://example.com", "application/json", strings.NewReader("{}")) req.Header.Add("Authorization", "abcdef") _, err := cli.Do(req) if err != nil { log.Fatal(err) } // Authorization header is not saved to disk }
Output:
func RemoveResponseHeader ¶
RemoveResponseHeader removes a header with the given name from the response. The name of the header is case-sensitive.
Example ¶
package main import ( "log" "net/http" "github.com/akupila/recorder" ) func main() { rec := recorder.New("testdata/secret", recorder.RemoveResponseHeader("Set-Cookie")) cli := &http.Client{ Transport: rec, } _, err := cli.Get("https://example.com") if err != nil { log.Fatal(err) } // The saved file will not contain the Set-Cookie header that was set by the server. }
Output:
type Mode ¶
type Mode int
Mode controls the mode of the recorder.
const ( // Auto reads requests from disk if a recording exists. If one does not // exist, the request is performed and results saved to disk. Auto Mode = iota // ReplayOnly only allows replaying from disk without network traffic. // If a recorded session does not exist, NoRequestError is returned. ReplayOnly // Record records all traffic even if an existing entry exists. // The new requests & responses overwrite any existing ones. Record // Passthrough disables the recorder and passes through all traffic // directly to client. Responses are not recorded to disk but can be // retrieved from the with Lookup(). Passthrough )
Possible values:
type NoRequestError ¶
NoRequestError is returned when the recorder mode is ReplayOnly and a corresponding entry is not found for the current request.
Because the error is returned from the transport, it may be wrapped.
Example ¶
package main import ( "log" "net/http" "net/url" "github.com/akupila/recorder" ) func main() { rec := recorder.New("notfound") // Disallow network traffic so this returns an error. rec.Mode = recorder.ReplayOnly cli := &http.Client{Transport: rec} if _, err := cli.Get("https://example.com"); err != nil { uerr, ok := err.(*url.Error) if !ok { log.Fatal("Error is not *url.Error") } _, ok = uerr.Err.(recorder.NoRequestError) if ok { // Recorded entry was not found. } } }
Output:
func (NoRequestError) Error ¶
func (e NoRequestError) Error() string
Error implements the error interface.
type OncePerCall ¶ added in v0.2.0
type OncePerCall struct {
// contains filtered or unexported fields
}
OncePerCall is a Selector that selects entries based on the method and URL, but it will only select any given entry at most once.
type Recorder ¶
type Recorder struct { // Filename to use for saved entries. A .yml extension is added if not set. // Any subdirectories are created if needed. // // Required if mode is not Passthrough. Filename string // Mode to use. Default mode is Auto. Mode Mode // Filters to apply before saving to disk. // Filters are executed in the order specified. Filters []Filter // Transport to use for real request. // If nil, http.DefaultTransport is used. Transport http.RoundTripper // An optional Select function may be specified to control which recorded // Entry is selected to respond to a given request. If nil, the default // selection is used that picks the first recorded response with a matching // method and url. Selector Selector // contains filtered or unexported fields }
Recorder wraps a http.RoundTripper by recording requests that go through it.
When recording, any observed requests are written to disk after response. In case previous entries were recorded for the same endpoint, the file is overwritten on first request.
func (*Recorder) Lookup ¶
Lookup returns an existing entry matching the given method and url.
The method and url are case-insensitive.
Returns false if no such entry exists.
func (*Recorder) RoundTrip ¶
RoundTrip implements http.RoundTripper and does the actual request.
The behavior depends on the mode set:
Auto: If an existing entry exists, the response from the entry is returned. ReplayOnly: Returns a previously recorded response. Returns NoRequestError if an entry is found for the request. Record: Always send real request and record the response. If an existing entry is found, it is overwritten. Passthrough: The request is passed through to the underlying transport.
Attempting to set another mode will cause a panic.
type Request ¶
type Request struct { Method string `yaml:"method"` URL string `yaml:"url"` Headers map[string]string `yaml:"headers,omitempty"` Body string `yaml:"body,omitempty"` }
A Request is a recorded outgoing request.
The headers are flattened to a simple key-value map. The underlying request may contain multiple value for each key but in practice this is not very common and working with a simple key-value map is much more convenient.
type Response ¶
type Response struct { StatusCode int `yaml:"status_code"` Headers map[string]string `yaml:"headers,omitempty"` Body string `yaml:"body,omitempty"` }
A Response is a recorded incoming response.
The headers are flattened to a simple key-value map. The underlying request may contain multiple value for each key but in practice this is not very common and working with a simple key-value map is much more convenient.