Documentation
¶
Overview ¶
Package requests is a convenience wrapper around net/http to make it faster and easier to build requests and custom transports.
Example ¶
package main import ( "context" "fmt" "strings" "github.com/carlmjohnson/requests" ) func main() { // Simple GET into a string var s string err := requests. URL("http://example.com"). ToString(&s). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to example.com:", err) } fmt.Println(strings.Contains(s, "Example Domain")) }
Output: true
Example (GetJSON) ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) func main() { // GET a JSON object id := 1 var post placeholder err := requests. URL("https://jsonplaceholder.typicode.com"). Pathf("/posts/%d", id). ToJSON(&post). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to jsonplaceholder.typicode.com:", err) } fmt.Println(post.Title) } type placeholder struct { ID int `json:"id,omitempty"` Title string `json:"title"` Body string `json:"body"` UserID int `json:"userId"` }
Output: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
Example (PostJSON) ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) func main() { // POST a JSON object and parse the response var res placeholder req := placeholder{ Title: "foo", Body: "baz", UserID: 1, } err := requests. URL("/posts"). Host("jsonplaceholder.typicode.com"). BodyJSON(&req). ToJSON(&res). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to jsonplaceholder.typicode.com:", err) } fmt.Println(res) } type placeholder struct { ID int `json:"id,omitempty"` Title string `json:"title"` Body string `json:"body"` UserID int `json:"userId"` }
Output: {101 foo baz 1}
Example (QueryParam) ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { c := 4 // Set a query parameter var params postman err := requests. URL("https://postman-echo.com/get?a=1&b=2"). Param("b", "3"). ParamInt("c", c). ToJSON(¶ms). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Println(params.Args) }
Output: map[a:1 b:3 c:4]
Index ¶
- Variables
- func HasStatusErr(err error, codes ...int) bool
- func NewCookieJar() http.CookieJar
- type BodyGetter
- type Builder
- func (rb *Builder) Accept(contentTypes string) *Builder
- func (rb *Builder) AddValidator(h ResponseHandler) *Builder
- func (rb *Builder) BasicAuth(username, password string) *Builder
- func (rb *Builder) Bearer(token string) *Builder
- func (rb *Builder) Body(src BodyGetter) *Builder
- func (rb *Builder) BodyBytes(b []byte) *Builder
- func (rb *Builder) BodyFile(name string) *Builder
- func (rb *Builder) BodyForm(data url.Values) *Builder
- func (rb *Builder) BodyJSON(v any) *Builder
- func (rb *Builder) BodyReader(r io.Reader) *Builder
- func (rb *Builder) BodyWriter(f func(w io.Writer) error) *Builder
- func (rb *Builder) CacheControl(directive string) *Builder
- func (rb *Builder) CheckContentType(cts ...string) *Builder
- func (rb *Builder) CheckPeek(n int, f func([]byte) error) *Builder
- func (rb *Builder) CheckStatus(acceptStatuses ...int) *Builder
- func (rb *Builder) Client(cl *http.Client) *Builder
- func (rb *Builder) Clone() *Builder
- func (rb *Builder) Config(cfgs ...Config) *Builder
- func (rb *Builder) ContentType(ct string) *Builder
- func (rb *Builder) Cookie(name, value string) *Builder
- func (rb *Builder) CopyHeaders(h map[string][]string) *Builder
- func (rb *Builder) Delete() *Builder
- func (rb *Builder) Do(req *http.Request) (err error)
- func (rb *Builder) Fetch(ctx context.Context) (err error)
- func (rb *Builder) Handle(h ResponseHandler) *Builder
- func (rb *Builder) Head() *Builder
- func (rb *Builder) Header(key string, values ...string) *Builder
- func (rb *Builder) Host(host string) *Builder
- func (rb *Builder) Hostf(format string, a ...any) *Builder
- func (rb *Builder) Method(method string) *Builder
- func (rb *Builder) OnError(h ErrorHandler) *Builder
- func (rb *Builder) OnValidatorError(h ResponseHandler) *Builder
- func (rb *Builder) Param(key string, values ...string) *Builder
- func (rb *Builder) ParamInt(key string, value int) *Builder
- func (rb *Builder) Patch() *Builder
- func (rb *Builder) Path(path string) *Builder
- func (rb *Builder) Pathf(format string, a ...any) *Builder
- func (rb *Builder) Put() *Builder
- func (rb *Builder) Request(ctx context.Context) (req *http.Request, err error)
- func (rb *Builder) Scheme(scheme string) *Builder
- func (rb *Builder) ToBytesBuffer(buf *bytes.Buffer) *Builder
- func (rb *Builder) ToFile(name string) *Builder
- func (rb *Builder) ToHeaders(h map[string][]string) *Builder
- func (rb *Builder) ToJSON(v any) *Builder
- func (rb *Builder) ToString(sp *string) *Builder
- func (rb *Builder) ToWriter(w io.Writer) *Builder
- func (rb *Builder) Transport(rt http.RoundTripper) *Builder
- func (rb *Builder) UserAgent(s string) *Builder
- type CheckRedirectPolicy
- type Config
- type ErrorHandler
- type ErrorKind
- type ErrorKindError
- type ResponseError
- type ResponseHandler
- func ChainHandlers(handlers ...ResponseHandler) ResponseHandler
- func CheckContentType(cts ...string) ResponseHandler
- func CheckPeek(n int, f func([]byte) error) ResponseHandler
- func CheckStatus(acceptStatuses ...int) ResponseHandler
- func CopyHeaders(h map[string][]string) ResponseHandler
- func ToBufioReader(f func(r *bufio.Reader) error) ResponseHandler
- func ToBufioScanner(f func(r *bufio.Scanner) error) ResponseHandler
- func ToBytesBuffer(buf *bytes.Buffer) ResponseHandler
- func ToFile(name string) ResponseHandler
- func ToHTML(n *html.Node) ResponseHandler
- func ToJSON(v any) ResponseHandler
- func ToString(sp *string) ResponseHandler
- func ToWriter(w io.Writer) ResponseHandler
- type RoundTripFunc
- type Transport
- func Caching(rt http.RoundTripper, basepath string) Transport
- func PermitURLTransport(rt http.RoundTripper, regex string) Transport
- func Record(rt http.RoundTripper, basepath string) Transport
- func Replay(basepath string) Transport
- func ReplayFS(fsys fs.FS) Transport
- func ReplayString(rawResponse string) Transport
- func UserAgentTransport(rt http.RoundTripper, s string) Transport
Examples ¶
- Package
- Package (GetJSON)
- Package (PostJSON)
- Package (QueryParam)
- Builder.Bearer
- Builder.BodyBytes
- Builder.BodyFile
- Builder.BodyForm
- Builder.BodyReader
- Builder.BodyWriter
- Builder.CheckContentType
- Builder.CheckPeek
- Builder.CheckStatus
- Builder.Config
- Builder.CopyHeaders
- Builder.Header
- Builder.OnError
- Builder.Path
- Builder.ToBytesBuffer
- Builder.ToFile
- Builder.ToHeaders
- Builder.ToWriter
- Builder.Transport
- CheckRedirectPolicy
- HasStatusErr
- MaxFollow
- NewCookieJar
- PermitURLTransport
- ReplayFS
- ReplayString
- RoundTripFunc
- ToBufioReader
- ToBufioScanner
- ToHTML
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidHandled = errors.New("handled recovery from invalid response")
var ToHeaders = CopyHeaders
ToHeaders is an alias for backwards compatibility.
Functions ¶
func HasStatusErr ¶
HasStatusErr returns true if err is a ResponseError caused by any of the codes given.
Example ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) func main() { err := requests. URL("http://example.com/404"). CheckStatus(200). Fetch(context.Background()) if requests.HasStatusErr(err, 404) { fmt.Println("got a 404") } }
Output: got a 404
func NewCookieJar ¶ added in v0.21.10
NewCookieJar returns a cookie jar using the standard public suffix list.
Example ¶
package main import ( "context" "fmt" "net/http" "net/url" "github.com/carlmjohnson/requests" ) func main() { // Create a client that preserve cookies between requests myClient := *http.DefaultClient myClient.Jar = requests.NewCookieJar() // Use the client to make a request err := requests. URL("http://httpbin.org/cookies/set/chocolate/chip"). Client(&myClient). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to httpbin.org:", err) } // Now check that cookies we got for _, cookie := range myClient.Jar.Cookies(&url.URL{ Scheme: "http", Host: "httpbin.org", }) { fmt.Println(cookie) } // And we'll see that they're reused on subsequent requests var cookies struct { Cookies map[string]string } err = requests. URL("http://httpbin.org/cookies"). Client(&myClient). ToJSON(&cookies). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to httpbin.org:", err) } fmt.Println(cookies) // And we can manually add our own cookie values // without overriding existing ones err = requests. URL("http://httpbin.org/cookies"). Client(&myClient). Cookie("oatmeal", "raisin"). ToJSON(&cookies). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to httpbin.org:", err) } fmt.Println(cookies) }
Output: chocolate=chip {map[chocolate:chip]} {map[chocolate:chip oatmeal:raisin]}
Types ¶
type BodyGetter ¶
type BodyGetter = func() (io.ReadCloser, error)
BodyGetter provides a Builder with a source for a request body.
func BodyBytes ¶
func BodyBytes(b []byte) BodyGetter
BodyBytes is a BodyGetter that returns the provided raw bytes.
func BodyFile ¶ added in v0.22.0
func BodyFile(name string) BodyGetter
BodyFile is a BodyGetter that reads the provided file path.
func BodyForm ¶
func BodyForm(data url.Values) BodyGetter
BodyForm is a BodyGetter that builds an encoded form body.
func BodyJSON ¶
func BodyJSON(v any) BodyGetter
BodyJSON is a BodyGetter that marshals a JSON object.
func BodyReader ¶
func BodyReader(r io.Reader) BodyGetter
BodyReader is a BodyGetter that returns an io.Reader.
func BodyWriter ¶ added in v0.21.11
func BodyWriter(f func(w io.Writer) error) BodyGetter
BodyWriter is a BodyGetter that pipes writes into a request body.
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder is a convenient way to build, send, and handle HTTP requests. Builder has a fluent API with methods returning a pointer to the same struct, which allows for declaratively describing a request by method chaining.
Builder can be thought of as having the following phases:
Set the base URL for a request with requests.URL then customize it with Scheme, Host, Hostf, Path, Pathf, and Param.
Set the method for a request with Method or use the Delete, Patch, and Put methods. By default, requests without a body are GET and those with a body are POST.
Set headers with Header or set conventional header keys with Accept, CacheControl, ContentType, Cookie, UserAgent, BasicAuth, and Bearer.
Set the http.Client to use for a request with Client and/or set an http.RoundTripper with Transport.
Set the body of the request, if any, with Body or use built in BodyBytes, BodyFile, BodyForm, BodyJSON, BodyReader, or BodyWriter.
Add a response validator to the Builder with AddValidator or use the built in CheckStatus, CheckContentType, CopyHeaders, and Peek.
Set a handler for a response with Handle or use the built in ToHeaders, ToJSON, ToString, ToBytesBuffer, or ToWriter.
Fetch creates an http.Request with Request and sends it via the underlying http.Client with Do.
Config can be used to set several options on a Builder at once.
In many cases, it will be possible to set most options for an API endpoint in a Builder at the package or struct level and then call Clone in a function to add request specific details for the URL, parameters, headers, body, or handler. The zero value of Builder is usable.
func (*Builder) AddValidator ¶
func (rb *Builder) AddValidator(h ResponseHandler) *Builder
AddValidator adds a response validator to the Builder. Adding a validator disables DefaultValidator. To disable all validation, just add nil.
func (*Builder) Bearer ¶ added in v0.21.4
Bearer sets the Authorization header to a bearer token.
Example ¶
package main import ( "context" "fmt" "net/http" "github.com/carlmjohnson/requests" ) func main() { // We get a 401 response if no bearer token is provided err := requests. URL("http://httpbin.org/bearer"). CheckStatus(http.StatusUnauthorized). Fetch(context.Background()) if err != nil { fmt.Println("problem with httpbin:", err) } // But our response is accepted when we provide a bearer token var res struct { Authenticated bool Token string } err = requests. URL("http://httpbin.org/bearer"). Bearer("whatever"). ToJSON(&res). Fetch(context.Background()) if err != nil { fmt.Println("problem with httpbin:", err) } fmt.Println(res.Authenticated) fmt.Println(res.Token) }
Output: true whatever
func (*Builder) Body ¶ added in v0.21.11
func (rb *Builder) Body(src BodyGetter) *Builder
Body sets the BodyGetter to use to build the body of a request. The provided BodyGetter is used as an http.Request.GetBody func. It implicitly sets method to POST.
func (*Builder) BodyBytes ¶
BodyBytes sets the Builder's request body to b.
Example ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { // Post a raw body var data postman err := requests. URL("https://postman-echo.com/post"). BodyBytes([]byte(`hello, world`)). ContentType("text/plain"). ToJSON(&data). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Println(data.Data) }
Output: hello, world
func (*Builder) BodyFile ¶ added in v0.22.0
BodyFile sets the Builder's request body to read from the given file path.
Example ¶
package main import ( "context" "fmt" "log" "os" "path/filepath" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { // Make a file to read from dir, err := os.MkdirTemp("", "body_file_*") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) // clean up exampleFilename := filepath.Join(dir, "example.txt") exampleContent := `hello, world` if err = os.WriteFile(exampleFilename, []byte(exampleContent), 0644); err != nil { log.Fatal(err) } // Post a raw file var data postman err = requests. URL("https://postman-echo.com/post"). BodyFile(exampleFilename). ContentType("text/plain"). ToJSON(&data). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Println(data.Data) }
Output: hello, world
func (*Builder) BodyForm ¶
BodyForm sets the Builder's request body to the encoded form. It also sets the ContentType to "application/x-www-form-urlencoded".
Example ¶
package main import ( "context" "fmt" "net/url" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { // Submit form values var echo postman err := requests. URL("https://postman-echo.com/put"). Put(). BodyForm(url.Values{ "hello": []string{"world"}, }). ToJSON(&echo). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Println(echo.JSON) }
Output: map[hello:world]
func (*Builder) BodyJSON ¶
BodyJSON sets the Builder's request body to the marshaled JSON. It also sets ContentType to "application/json".
func (*Builder) BodyReader ¶
BodyReader sets the Builder's request body to r.
Example ¶
package main import ( "context" "fmt" "log" "os" "path/filepath" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { // temp file creation boilerplate dir, err := os.MkdirTemp("", "body_reader_*") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) // clean up exampleFilename := filepath.Join(dir, "example.txt") exampleContent := `hello, world` if err := os.WriteFile(exampleFilename, []byte(exampleContent), 0644); err != nil { log.Fatal(err) } // suppose there is some io.Reader you want to stream from f, err := os.Open(exampleFilename) if err != nil { log.Fatal(err) } defer f.Close() // send the raw file to server var echo postman err = requests. URL("https://postman-echo.com/post"). ContentType("text/plain"). BodyReader(f). ToJSON(&echo). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Println(echo.Data) }
Output: hello, world
func (*Builder) BodyWriter ¶ added in v0.21.11
BodyWriter pipes writes from w to the Builder's request body.
Example ¶
package main import ( "context" "encoding/csv" "fmt" "io" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { var echo postman err := requests. URL("https://postman-echo.com/post"). ContentType("text/plain"). BodyWriter(func(w io.Writer) error { cw := csv.NewWriter(w) cw.Write([]string{"col1", "col2"}) cw.Write([]string{"val1", "val2"}) cw.Flush() return cw.Error() }). ToJSON(&echo). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Printf("%q\n", echo.Data) }
Output: "col1,col2\nval1,val2\n"
func (*Builder) CacheControl ¶ added in v0.21.4
CacheControl sets the client-side Cache-Control directive for a request.
func (*Builder) CheckContentType ¶ added in v0.21.2
CheckContentType adds a validator for the content type header of a response.
Example ¶
package main import ( "context" "errors" "fmt" "github.com/carlmjohnson/requests" ) func main() { // Expect a specific status code err := requests. URL("https://jsonplaceholder.typicode.com"). Pathf("/posts/%d", 1). CheckContentType("application/bison"). Fetch(context.Background()) if err != nil { if re := new(requests.ResponseError); errors.As(err, &re) { fmt.Println("content-type was", re.Header.Get("Content-Type")) } } }
Output: content-type was application/json; charset=utf-8
func (*Builder) CheckPeek ¶ added in v0.21.11
CheckPeek adds a validator that peeks at the first n bytes of a response body.
Example ¶
package main import ( "context" "fmt" "strings" "github.com/carlmjohnson/requests" ) func main() { // Check that a response has a doctype const doctype = "<!doctype html>" var s string err := requests. URL("http://example.com"). CheckPeek(len(doctype), func(b []byte) error { if string(b) != doctype { return fmt.Errorf("missing doctype: %q", b) } return nil }). ToString(&s). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to example.com:", err) } fmt.Println( // Final result still has the prefix strings.HasPrefix(s, doctype), // And the full body strings.HasSuffix(s, "</html>\n"), ) }
Output: true true
func (*Builder) CheckStatus ¶
CheckStatus adds a validator for status code of a response.
Example ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) func main() { // Expect a specific status code err := requests. URL("https://jsonplaceholder.typicode.com"). Pathf("/posts/%d", 9001). CheckStatus(404). CheckContentType("application/json"). Fetch(context.Background()) if err != nil { fmt.Println("should be a 404:", err) } else { fmt.Println("OK") } }
Output: OK
func (*Builder) Client ¶
Client sets the http.Client to use for requests. If nil, it uses http.DefaultClient.
func (*Builder) Config ¶ added in v0.21.11
Config allows Builder to be extended by functions that set several options at once.
Example ¶
package main import ( "compress/gzip" "context" "fmt" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { var echo postman err := requests. URL("https://postman-echo.com/post"). ContentType("text/plain"). Config(requests.GzipConfig( gzip.DefaultCompression, func(gw *gzip.Writer) error { _, err := gw.Write([]byte(`hello, world`)) return err })). ToJSON(&echo). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Println(echo.Data) }
Output: hello, world
func (*Builder) ContentType ¶
ContentType sets the Content-Type header on a request.
func (*Builder) Cookie ¶ added in v0.22.0
Cookie adds a cookie to a request. Unlike other headers, adding a cookie does not overwrite existing values.
func (*Builder) CopyHeaders ¶ added in v0.22.3
CopyHeaders adds a validator which copies the response headers to h. Note that because CopyHeaders adds a validator, the DefaultValidator is disabled and must be added back manually if status code validation is desired.
Example ¶
package main import ( "context" "fmt" "net/http" "strings" "github.com/carlmjohnson/requests" ) func main() { // Get headers while also getting body var s string headers := http.Header{} err := requests. URL("http://example.com"). CopyHeaders(headers). // CopyHeaders disables status validation, so add it back CheckStatus(http.StatusOK). ToString(&s). Fetch(context.Background()) if err != nil { fmt.Println("problem with example.com:", err) } fmt.Println(headers.Get("Etag")) fmt.Println(strings.Contains(s, "Example Domain")) }
Output: "3147526947+gzip" true
func (*Builder) Do ¶
Do calls the underlying http.Client and validates and handles any resulting response. The response body is closed after all validators and the handler run.
func (*Builder) Handle ¶
func (rb *Builder) Handle(h ResponseHandler) *Builder
Handle sets the response handler for a Builder. To use multiple handlers, use ChainHandlers.
func (*Builder) Header ¶
Header sets a header on a request. It overwrites the existing values of a key.
Example ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) // Examples with the Postman echo server type postman struct { Args map[string]string `json:"args"` Data string `json:"data"` Headers map[string]string `json:"headers"` JSON map[string]string `json:"json"` } func main() { // Set headers var headers postman err := requests. URL("https://postman-echo.com/get"). UserAgent("bond/james-bond"). BasicAuth("bondj", "007!"). ContentType("secret"). Header("martini", "shaken"). ToJSON(&headers). Fetch(context.Background()) if err != nil { fmt.Println("problem with postman:", err) } fmt.Println(headers.Headers["user-agent"]) fmt.Println(headers.Headers["authorization"]) fmt.Println(headers.Headers["content-type"]) fmt.Println(headers.Headers["martini"]) }
Output: bond/james-bond Basic Ym9uZGo6MDA3IQ== secret shaken
func (*Builder) OnError ¶
func (rb *Builder) OnError(h ErrorHandler) *Builder
OnError adds an ErrorHandler to run if any part of building, validating, or handling a request fails. ErrorHandlers are run in reverse order and may modify the error returned to the caller and other ErrorHandlers.
Example ¶
package main import ( "context" "fmt" "net/http" "strings" "github.com/carlmjohnson/requests" ) func main() { logError := func(kind requests.ErrorKind, err error, req *http.Request, res *http.Response) error { url := "<no url>" if req != nil { url = req.URL.String() } resCode := "---" if res != nil { resCode = res.Status } fmt.Printf("[error] kind=%q url=%q status=%q message=%q\n", kind, url, resCode, err) return err } var ( body string errBody string ) // All errors are sent to logErr. // If we fail validation because the response is a 404, // we send the body to errBody instead of body for separate // processing. err := requests. URL("http://example.com/404"). ToString(&body). OnError(logError). OnValidatorError( requests.ToString(&errBody)). Fetch(context.Background()) if err == requests.ErrInvalidHandled { fmt.Println("got errBody:", strings.Contains(errBody, "Example Domain")) } else if err != nil { fmt.Println("unknown error", err) } fmt.Println("got body:", strings.Contains(body, "Example Domain")) }
Output: [error] kind="ErrorKindValidator" url="http://example.com/404" status="404 Not Found" message="handled recovery from invalid response" got errBody: true got body: false
func (*Builder) OnValidatorError ¶
func (rb *Builder) OnValidatorError(h ResponseHandler) *Builder
OnValidatorError calls OnError by converting h with ValidatorHandler into an ErrorHandler. It only runs if the Builder encounters a validation error.
func (*Builder) Param ¶
Param sets a query parameter on a request. It overwrites the existing values of a key.
func (*Builder) Path ¶
Path joins a path to a request per the path joining rules of RFC 3986. If the path begins with /, it overrides any existing path. If the path begins with ./ or ../, the final path will be rewritten in its absolute form when creating a request.
Example ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" ) type placeholder struct { ID int `json:"id,omitempty"` Title string `json:"title"` Body string `json:"body"` UserID int `json:"userId"` } func main() { // Add an ID to a base path id := 1 var post placeholder err := requests. URL("https://jsonplaceholder.typicode.com/posts/"). // inherits path /posts from baseurl Pathf("%d", id). // URL is now https://jsonplaceholder.typicode.com/posts/1 ToJSON(&post). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to jsonplaceholder.typicode.com:", err) } fmt.Println(post.ID) }
Output: 1
func (*Builder) Pathf ¶ added in v0.21.2
Pathf calls Path with fmt.Sprintf.
Note that for security reasons, you must not use %s with a user provided string!
func (*Builder) Scheme ¶ added in v0.21.7
Scheme sets the scheme for a request. It overrides the URL function.
func (*Builder) ToBytesBuffer ¶
ToBytesBuffer sets the Builder to write the response body to the provided bytes.Buffer.
Example ¶
package main import ( "bytes" "context" "fmt" "strings" "github.com/carlmjohnson/requests" ) func main() { // Simple GET into a buffer var buf bytes.Buffer err := requests. URL("http://example.com"). ToBytesBuffer(&buf). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to example.com:", err) } fmt.Println(strings.Contains(buf.String(), "Example Domain")) }
Output: true
func (*Builder) ToFile ¶ added in v0.21.13
ToFile sets the Builder to write the response body to the given file name. The file and its parent directories are created automatically. For more advanced use cases, use ToWriter.
Example ¶
package main import ( "context" "fmt" "log" "os" "path/filepath" "github.com/carlmjohnson/requests" ) func main() { dir, err := os.MkdirTemp("", "to_file_*") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) // clean up exampleFilename := filepath.Join(dir, "example.txt") err = requests. URL("http://example.com"). ToFile(exampleFilename). Fetch(context.Background()) if err != nil { log.Fatal(err) } stat, err := os.Stat(exampleFilename) if err != nil { log.Fatal(err) } fmt.Printf("file is %d bytes\n", stat.Size()) }
Output: file is 1256 bytes
func (*Builder) ToHeaders ¶ added in v0.22.0
ToHeaders sets the method to HEAD and adds a handler which copies the response headers to h. To just copy headers, see Builder.CopyHeaders.
Example ¶
package main import ( "context" "fmt" "net/http" "github.com/carlmjohnson/requests" ) func main() { // Send a HEAD request and look at headers headers := http.Header{} err := requests. URL("http://example.com"). ToHeaders(headers). Fetch(context.Background()) if err != nil { fmt.Println("problem with example.com:", err) } fmt.Println(headers.Get("Etag")) }
Output: "3147526947"
func (*Builder) ToString ¶
ToString sets the Builder to write the response body to the provided string pointer.
func (*Builder) ToWriter ¶ added in v0.21.7
ToWriter sets the Builder to copy the response body into w.
Example ¶
package main import ( "context" "fmt" "log" "os" "github.com/carlmjohnson/requests" ) func main() { f, err := os.CreateTemp("", "*.to_writer.html") if err != nil { log.Fatal(err) } defer os.Remove(f.Name()) // clean up // suppose there is some io.Writer you want to stream to err = requests. URL("http://example.com"). ToWriter(f). Fetch(context.Background()) if err != nil { log.Fatal(err) } if err = f.Close(); err != nil { log.Fatal(err) } stat, err := os.Stat(f.Name()) if err != nil { log.Fatal(err) } fmt.Printf("file is %d bytes\n", stat.Size()) }
Output: file is 1256 bytes
func (*Builder) Transport ¶ added in v0.22.0
func (rb *Builder) Transport(rt http.RoundTripper) *Builder
Transport sets the http.RoundTripper to use for requests. If set, it makes a shallow copy of the http.Client before modifying it.
Example ¶
package main import ( "context" "fmt" "io" "net/http" "strings" "github.com/carlmjohnson/requests" ) func main() { const text = "Hello, from transport!" var myCustomTransport requests.RoundTripFunc = func(req *http.Request) (res *http.Response, err error) { res = &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(text)), } return } var s string err := requests. URL("x://transport.example"). Transport(myCustomTransport). ToString(&s). Fetch(context.Background()) if err != nil { fmt.Println("transport failed:", err) } fmt.Println(s == text) // true }
Output: true
type CheckRedirectPolicy ¶ added in v0.21.10
CheckRedirectPolicy is a function suitable for use as CheckRedirect on an http.Client.
Example ¶
package main import ( "context" "fmt" "net/http" "github.com/carlmjohnson/requests" ) func main() { cl := *http.DefaultClient cl.CheckRedirect = requests.NoFollow if err := requests. URL("https://httpbingo.org/redirect/1"). Client(&cl). CheckStatus(http.StatusFound). Handle(func(res *http.Response) error { fmt.Println("Status", res.Status) fmt.Println("From", res.Request.URL) fmt.Println("To", res.Header.Get("Location")) return nil }). Fetch(context.Background()); err != nil { panic(err) } }
Output: Status 302 Found From https://httpbingo.org/redirect/1 To /get
var NoFollow CheckRedirectPolicy = MaxFollow(0)
NoFollow is a CheckRedirectPolicy that does not follow redirects.
func MaxFollow ¶ added in v0.21.10
func MaxFollow(n int) CheckRedirectPolicy
MaxFollow returns a CheckRedirectPolicy that follows a maximum of n redirects.
Example ¶
package main import ( "context" "fmt" "net/http" "github.com/carlmjohnson/requests" ) func main() { cl := *http.DefaultClient cl.CheckRedirect = requests.MaxFollow(1) if err := requests. URL("https://httpbingo.org/redirect/2"). Client(&cl). CheckStatus(http.StatusFound). Handle(func(res *http.Response) error { fmt.Println("Status", res.Status) fmt.Println("From", res.Request.URL) fmt.Println("To", res.Header.Get("Location")) return nil }). Fetch(context.Background()); err != nil { panic(err) } }
Output: Status 302 Found From https://httpbingo.org/relative-redirect/1 To /get
type Config ¶ added in v0.21.11
type Config = func(rb *Builder)
Config allows Builder to be extended by setting several options at once. For example, a Config might set a Body and its ContentType.
type ErrorHandler ¶
ErrorHandler is a function accepted by Builder.OnError. Note that the error, request, and response may all be nil depending on the error encountered and the effect of prior handlers.
func ValidatorHandler ¶ added in v0.23.1
func ValidatorHandler(h ResponseHandler) ErrorHandler
ValidatorHandler converts a ResponseHandler into an ErrorHandler for invalid responses. The ResponseHandler only runs if the ErrorHandler encounters a validation error. If the ResponseHandler succeeds, ErrInvalidHandled is returned.
type ErrorKind ¶ added in v0.23.1
type ErrorKind int8
ErrorKind indicates where an error was returned in the process of building, validating, and handling a request.
const ( ErrorKindUnknown ErrorKind = iota // Not a Builder error ErrorKindURL // error building URL ErrorKindBodyGet // error getting request body ErrorKindMethod // request method was invalid ErrorKindContext // request context was nil ErrorKindConnect // error connecting ErrorKindValidator // validator error ErrorKindHandler // handler error )
Enum values for type ErrorKind
type ErrorKindError ¶
ErrorKindError is an error that can return its underlying ErrorKind. Errors returned by Builder conform to ErrorKindError.
type ResponseError ¶ added in v0.21.11
ResponseError is the error type produced by CheckStatus and CheckContentType.
func (*ResponseError) Error ¶ added in v0.21.11
func (se *ResponseError) Error() string
Error fulfills the error interface.
type ResponseHandler ¶
ResponseHandler is used to validate or handle the response to a request.
var DefaultValidator ResponseHandler = CheckStatus( http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNonAuthoritativeInfo, http.StatusNoContent, )
DefaultValidator is the validator applied by Builder unless otherwise specified.
func ChainHandlers ¶
func ChainHandlers(handlers ...ResponseHandler) ResponseHandler
ChainHandlers allows for the composing of validators or response handlers.
func CheckContentType ¶ added in v0.21.2
func CheckContentType(cts ...string) ResponseHandler
CheckContentType validates that a response has one of the given content type headers.
func CheckPeek ¶ added in v0.21.11
func CheckPeek(n int, f func([]byte) error) ResponseHandler
CheckPeek wraps the body of a response in a bufio.Reader and gives f a peek at the first n bytes for validation.
func CheckStatus ¶
func CheckStatus(acceptStatuses ...int) ResponseHandler
CheckStatus validates the response has an acceptable status code.
func CopyHeaders ¶ added in v0.22.3
func CopyHeaders(h map[string][]string) ResponseHandler
CopyHeaders copies the response headers to h.
func ToBufioReader ¶
func ToBufioReader(f func(r *bufio.Reader) error) ResponseHandler
ToBufioReader takes a callback which wraps the response body in a bufio.Reader.
Example ¶
package main import ( "bufio" "context" "fmt" "io" "strings" "github.com/carlmjohnson/requests" ) func main() { // read a response line by line for a sentinel found := false err := requests. URL("http://example.com"). Handle(requests.ToBufioReader(func(r *bufio.Reader) error { var err error for s := ""; err == nil; { if strings.Contains(s, "Example Domain") { found = true return nil } // read one line from response s, err = r.ReadString('\n') } if err == io.EOF { return nil } return err })). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to example.com:", err) } fmt.Println(found) }
Output: true
func ToBufioScanner ¶ added in v0.21.9
func ToBufioScanner(f func(r *bufio.Scanner) error) ResponseHandler
ToBufioScanner takes a callback which wraps the response body in a bufio.Scanner.
Example ¶
package main import ( "bufio" "bytes" "context" "fmt" "github.com/carlmjohnson/requests" ) func main() { // read a response line by line for a sentinel found := false needle := []byte("Example Domain") err := requests. URL("http://example.com"). Handle(requests.ToBufioScanner(func(s *bufio.Scanner) error { // read one line at time from response for s.Scan() { if bytes.Contains(s.Bytes(), needle) { found = true return nil } } return s.Err() })). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to example.com:", err) } fmt.Println(found) }
Output: true
func ToBytesBuffer ¶
func ToBytesBuffer(buf *bytes.Buffer) ResponseHandler
ToBytesBuffer writes the response body to the provided bytes.Buffer.
func ToFile ¶ added in v0.21.13
func ToFile(name string) ResponseHandler
ToFile writes the response body at the provided file path. The file and its parent directories are created automatically.
func ToHTML ¶ added in v0.21.7
func ToHTML(n *html.Node) ResponseHandler
ToHTML parses the page with x/net/html.Parse.
Example ¶
package main import ( "context" "fmt" "github.com/carlmjohnson/requests" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) func main() { var doc html.Node err := requests. URL("http://example.com"). Handle(requests.ToHTML(&doc)). Fetch(context.Background()) if err != nil { fmt.Println("could not connect to example.com:", err) } var f func(*html.Node) f = func(n *html.Node) { if n.DataAtom == atom.A { for _, attr := range n.Attr { if attr.Key == "href" { fmt.Println("link:", attr.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { f(c) } } f(&doc) }
Output: link: https://www.iana.org/domains/example
func ToString ¶
func ToString(sp *string) ResponseHandler
ToString writes the response body to the provided string pointer.
func ToWriter ¶ added in v0.21.7
func ToWriter(w io.Writer) ResponseHandler
ToWriter copies the response body to w.
type RoundTripFunc ¶ added in v0.21.3
RoundTripFunc is an adaptor to use a function as an http.RoundTripper.
Example ¶
package main import ( "context" "fmt" "net/http" "github.com/carlmjohnson/requests" ) func main() { // Wrap an underlying transport in order to add request middleware var logTripper requests.RoundTripFunc = func(req *http.Request) (res *http.Response, err error) { fmt.Printf("req [%s] %s\n", req.Method, req.URL) res, err = http.DefaultClient.Transport.RoundTrip(req) if err != nil { fmt.Printf("res [error] %s %s\n", err, req.URL) } else { fmt.Printf("res [%s] %s\n", res.Status, req.URL) } return } err := requests. URL("http://example.com"). Transport(logTripper). Fetch(context.Background()) if err != nil { fmt.Println("something went wrong:", err) } }
Output: req [GET] http://example.com res [200 OK] http://example.com
type Transport ¶ added in v0.22.0
type Transport = http.RoundTripper
Transport is an alias of http.RoundTripper for documentation purposes.
func Caching ¶ added in v0.22.0
func Caching(rt http.RoundTripper, basepath string) Transport
Caching returns an http.RoundTripper that attempts to read its responses from text files in basepath. If the response is absent, it caches the result of issuing the request with rt in basepath. Requests are named according to a hash of their contents. Responses are named according to the request that made them.
func PermitURLTransport ¶ added in v0.21.11
func PermitURLTransport(rt http.RoundTripper, regex string) Transport
PermitURLTransport returns a wrapped http.RoundTripper that rejects any requests whose URL doesn't match the provided regular expression string.
PermitURLTransport will panic if the regexp does not compile.
Example ¶
package main import ( "context" "fmt" "net/http" "strings" "github.com/carlmjohnson/requests" ) func main() { // Wrap an existing transport or use nil for http.DefaultTransport baseTrans := http.DefaultClient.Transport trans := requests.PermitURLTransport(baseTrans, `^http://example\.com/?`) var s string if err := requests. URL("http://example.com"). Transport(trans). ToString(&s). Fetch(context.Background()); err != nil { panic(err) } fmt.Println(strings.Contains(s, "Example Domain")) if err := requests. URL("http://unauthorized.example.com"). Transport(trans). ToString(&s). Fetch(context.Background()); err != nil { fmt.Println(err) // unauthorized subdomain not allowed } }
Output: true Get "http://unauthorized.example.com": requested URL not permitted by regexp: ^http://example\.com/?
func Record ¶ added in v0.21.3
func Record(rt http.RoundTripper, basepath string) Transport
Record returns an http.RoundTripper that writes out its requests and their responses to text files in basepath. Requests are named according to a hash of their contents. Responses are named according to the request that made them.
func Replay ¶ added in v0.21.3
Replay returns an http.RoundTripper that reads its responses from text files in basepath. Responses are looked up according to a hash of the request.
func ReplayFS ¶ added in v0.21.5
ReplayFS returns an http.RoundTripper that reads its responses from text files in the fs.FS. Responses are looked up according to a hash of the request. Response file names may optionally be prefixed with comments for better human organization.
Example ¶
package main import ( "context" "fmt" "net/http" "testing/fstest" "github.com/carlmjohnson/requests" ) func main() { fsys := fstest.MapFS{ "fsys.example - MKIYDwjs.res.txt": &fstest.MapFile{ Data: []byte(`HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Date: Mon, 24 May 2021 18:48:50 GMT An example response.`), }, } var s string const expected = `An example response.` if err := requests. URL("http://fsys.example"). Client(&http.Client{ Transport: requests.ReplayFS(fsys), }). ToString(&s). Fetch(context.Background()); err != nil { panic(err) } fmt.Println(s == expected) }
Output: true
func ReplayString ¶ added in v0.21.8
ReplayString returns an http.RoundTripper that always responds with a request built from rawResponse. It is intended for use in one-off tests.
Example ¶
package main import ( "context" "fmt" "net/http" "github.com/carlmjohnson/requests" ) func main() { const res = `HTTP/1.1 200 OK An example response.` var s string const expected = `An example response.` if err := requests. URL("http://response.example"). Client(&http.Client{ Transport: requests.ReplayString(res), }). ToString(&s). Fetch(context.Background()); err != nil { panic(err) } fmt.Println(s == expected) }
Output: true
func UserAgentTransport ¶ added in v0.21.10
func UserAgentTransport(rt http.RoundTripper, s string) Transport
UserAgentTransport returns a wrapped http.RoundTripper that sets the User-Agent header on requests to s.