req v3

repository·master·Indexed 26 days ago

https://github.com/imroc/req

A powerful and intuitive HTTP client for Go featuring automatic decoding, HTTP/3 support, retries, and easy debugging. It provides advanced capabilities such as browser impersonation (Chrome, Firefox, Safari), unified error handling via OnAfterResponse, and flexible request styles including a chainable Do API. Supports Go 1.20+ depending on the version.

Tokens
15.1K
Snippets
12
Records
101
Agent score
89%

What's inside req

  1. Quick start with global wrapper methods

    master

    For quick testing, you can use the package-level functions which use a default client behind the scenes. req.DevMode() enables development mode (showing debug logs), and req.MustGet() sends a GET request. You can also use req.EnableForceHTTP1() to force the use of HTTP/1.1.

    package main
    
    import (
        "github.com/imroc/req/v3"
    )
    
    func main() {
        req.DevMode() // Treat the package name as a Client, enable development mode
        req.MustGet("https://httpbin.org/uuid") // Treat the package name as a Request, send GET request.
    
        req.EnableForceHTTP1() // Force using HTTP/1.1
        req.MustGet("https://httpbin.org/uuid")
    }
  2. Implement unified error handling with OnAfterResponse

    master

    You can set up unified logic for error handling on the client using SetCommonErrorResult and OnAfterResponse. This allows you to convert API error responses into standard Go errors automatically.

    In the OnAfterResponse callback:

    • Check resp.Err for network or unmarshal errors.
    • Use resp.ErrorResult() to access the unmarshaled error object.
    • Use resp.IsSuccessState() to check for successful status codes (200-299).
    package main
    
    import (
    	"fmt"
    	"github.com/imroc/req/v3"
    	"log"
    	"time"
    )
    
    type ErrorMessage struct {
    	Message string `json:"message"`
    }
    
    func (msg *ErrorMessage) Error() string {
    	return fmt.Sprintf("API Error: %s", msg.Message)
    }
    
    type UserInfo struct {
    	Name string `json:"name"`
    	Blog string `json:"blog"`
    }
    
    var client = req.C().
    	SetUserAgent("my-custom-client"). // Chainable client settings.
    	SetTimeout(5 * time.Second).
    	EnableDumpEachRequest().
    	SetCommonErrorResult(&ErrorMessage{}).
    	OnAfterResponse(func(client *req.Client, resp *req.Response) error {
    		if resp.Err != nil { // There is an underlying error, e.g. network error or unmarshal error.
    			return nil
    		}
    		if errMsg, ok := resp.ErrorResult().(*ErrorMessage); ok {
    			resp.Err = errMsg // Convert api error into go error
    			return nil
    		}
    		if !resp.IsSuccessState() {
    			// Neither a success response nor a error response, record details to help troubleshooting
    			resp.Err = fmt.Errorf("bad status: %s\nraw content:\n%s", resp.Status, resp.Dump())
    		}
    		return nil
    	})
    
    func main() {
    	var userInfo UserInfo
    	resp, err := client.R().
    		SetHeader("Accept", "application/vnd.github.v3+json"). // Chainable request settings
    		SetPathParam("username", "imroc").
    		SetSuccessResult(&userInfo). // Unmarshal response body into userInfo automatically if status code is between 200 and 299.
    		Get("https://api.github.com/users/{username}")
    
    	if err != nil {
    		log.Println("error:", err)
    		return
    	}
    
    	if resp.IsSuccessState() {
    		fmt.Printf("%s (%s)\n", userInfo.Name, userInfo.Blog)
    	}
    }
  3. Build an SDK with Req

    master

    When building an SDK, you can wrap *req.Client in a custom struct. Use SetCommonErrorResult to define a standard error structure and OnAfterResponse to intercept responses. In the interceptor, you can convert API-level error responses into standard Go errors by checking resp.ErrorResult() and assigning it to resp.Err.

    import (
    	"context"
    	"fmt"
    	"github.com/imroc/req/v3"
    )
    
    type ErrorMessage struct {
    	Message string `json:"message"`
    }
    
    func (msg *ErrorMessage) Error() string {
    	return fmt.Sprintf("API Error: %s", msg.Message)
    }
    
    type GithubClient struct {
    	*req.Client
    }
    
    func NewGithubClient() *GithubClient {
    	return &GithubClient{
    		Client: req.C().
    			SetBaseURL("https://api.github.com").
    			SetCommonErrorResult(&ErrorMessage{}).
    			EnableDumpEachRequest().
    			OnAfterResponse(func(client *req.Client, resp *req.Response) error {
    				if resp.Err != nil {
    					return nil
    				}
    				if errMsg, ok := resp.ErrorResult().(*ErrorMessage); ok {
    					resp.Err = errMsg
    					return nil
    				}
    				if !resp.IsSuccessState() {
    					resp.Err = fmt.Errorf("bad status: %s\nraw content:\n%s", resp.Status, resp.Dump())
    					return nil
    				}
    				return nil
    			}),
    	}
    }
    
    type UserProfile struct {
    	Name string `json:"name"`
    	Blog string `json:"blog"`
    }
    
    func (c *GithubClient) GetUserProfile_Style1(ctx context.Context, username string) (user *UserProfile, err error) {
    	_, err = c.R().
    		SetContext(ctx).
    		SetPathParam("username", username).
    		SetSuccessResult(&user).
    		Get("/users/{username}")
    	return
    }
    
    func (c *GithubClient) GetUserProfile_Style2(ctx context.Context, username string) (user *UserProfile, err error) {
    	err = c.Get("/users/{username}").
    		SetPathParam("username", username).
    		Do(ctx).
    		Into(&user)
    	return
    }
  4. Use the Do API style for requests

    master

    The 'Do API style' provides an intuitive chain of calls:

    1. Call the Client method (e.g., c.Post()) to specify the method.
    2. Use chain calls to configure the request (e.g., SetBody()).
    3. Call Do() to execute the request and return a Response.
    4. Call Response.Into(&target) to unmarshal the response body into a specific object.

    Response.Into returns an error if the request fails or unmarshalling fails. You can also use Client.SetBaseURL to set a unified base URL for all requests made by that client.

    package main
    
    import (
    	"fmt"
    	"github.com/imroc/req/v3"
    )
    
    type APIResponse struct {
    	Origin string `json:"origin"`
    	Url    string `json:"url"`
    }
    
    func main() {
    	var resp APIResponse
    	c := req.C().SetBaseURL("https://httpbin.org/post")
    	err := c.Post().
    		SetBody("hello").
    		Do().
    		Into(&resp)
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println("My IP is", resp.Origin)
    }
  5. Initialize a new Transport

    master

    Use NewTransport() or T() to create a new Transport instance. Transport is an implementation of http.RoundTripper that supports HTTP, HTTPS, and HTTP/HTTPS proxies. It is safe for concurrent use and should be reused instead of being created for every request.

    By default, it caches connections for reuse, manages HTTP/1.1 and HTTP/2, and supports auto-decoding of response bodies to UTF-8.

  6. Create a new Client

    master
    Use req.C() or req.NewClient() to initialize a new Client. The client comes with default settings including a 2-minute timeout, a memory-based cookie jar, and default middleware for parsing headers, cookies, URLs, and bodies.
  7. Initialize a Client and perform HTTP requests

    master

    Use DefaultClient() to get the global singleton client, or use C() (implied by the context of creating a new client) to instantiate a new Client. The Client provides high-level methods for all standard HTTP verbs. Each method returns a *Request object which can be further configured before execution.

    Supported HTTP methods:

    • Get(url ...string)
    • Post(url ...string)
    • Patch(url ...string)
    • Delete(url ...string)
    • Put(url ...string)
    • Head(url ...string)
    • Options(url ...string)
  8. Enable Debugging and Request Dumping

    master

    For debugging, you can dump the full content of requests and responses.

    • DevMode(): A shortcut that enables full dumping, debug logging, and tracing.
    • EnableDumpAll(): Dumps all request and response content to os.Stdout.
    • EnableDumpAllToFile(filename string): Dumps content to a specific file.
    • EnableDumpAllAsync(): Dumps content asynchronously to avoid performance impact in production.
    • EnableDumpEachRequest(): Enables dumping at the request level; content is stored in memory and can be retrieved via Response.Dump().
  9. Perform a Simple POST request

    master

    To perform a POST request, use client.R().SetBody(data).SetSuccessResult(&target).Post(url). You can use DevMode() on the client to enable debug logging. After the request, check resp.IsSuccessState() to verify the status code before accessing the unmarshaled result.

    package main
    
    import (
      "fmt"
      "github.com/imroc/req/v3"
      "log"
    )
    
    type Repo struct {
      Name string `json:"name"`
      Url  string `json:"url"`
    }
    
    type Result struct {
      Data string `json:"data"`
    }
    
    func main() {
      client := req.C().DevMode()
      var result Result
    
      resp, err := client.R().
        SetBody(&Repo{Name: "req", Url: "https://github.com/imroc/req"}).
        SetSuccessResult(&result).
        Post("https://httpbin.org/post")
      if err != nil {
        log.Fatal(err)
      }
    
      if !resp.IsSuccessState() {
        fmt.Println("bad response status:", resp.Status)
        return
      }
      fmt.Println("++++++++++++++++++++++++++++++++++++++++++++++++")
      fmt.Println("data:", result.Data)
      fmt.Println("++++++++++++++++++++++++++++++++++++++++++++++++")
    }
  10. Perform a simple GET request

    master

    In production, it is recommended to explicitly create a client using req.C() and then create a request using client.R().

    package main
    
    import (
    	"fmt"
    	"github.com/imroc/req/v3"
    	"log"
    )
    
    func main() {
    	client := req.C() // Use C() to create a client.
    	resp, err := client.R(). // Use R() to create a request.
    		Get("https://httpbin.org/uuid")
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Println(resp)
    }
  11. Perform an advanced GET request with automatic unmarshaling

    master

    You can chain settings on both the Client and the Request.

    Client settings:

    • SetUserAgent(string)
    • SetTimeout(time.Duration)

    Request settings:

    • SetHeader(key, value string)
    • SetPathParam(key, value string): Replaces path variables in the URL (e.g., {username}).
    • SetSuccessResult(interface{}): Automatically unmarshals the response body into the provided pointer if the status code is between 200 and 299.
    • SetErrorResult(interface{}): Automatically unmarshals the response body into the provided pointer if the status code is $\ge$ 400.
    • EnableDump(): Enables request-level dumping to help troubleshoot errors.
    package main
    
    import (
      "fmt"
      "github.com/imroc/req/v3"
      "log"
      "time"
    )
    
    type ErrorMessage struct {
      Message string `json:"message"`
    }
    
    type UserInfo struct {
      Name string `json:"name"`
      Blog string `json:"blog"`
    }
    
    func main() {
      client := req.C().
        SetUserAgent("my-custom-client"). // Chainable client settings.
        SetTimeout(5 * time.Second)
    
      var userInfo UserInfo
      var errMsg ErrorMessage
      resp, err := client.R().
        SetHeader("Accept", "application/vnd.github.v3+json"). // Chainable request settings.
        SetPathParam("username", "imroc"). // Replace path variable in url.
        SetSuccessResult(&userInfo). // Unmarshal response body into userInfo automatically if status code is between 200 and 299.
        SetErrorResult(&errMsg). // Unmarshal response body into errMsg automatically if status code >= 400.
        EnableDump(). // Enable dump at request level, only print dump content if there is an error or some unknown situation occurs to help troubleshoot.
        Get("https://api.github.com/users/{username}")
    
      if err != nil { // Error handling.
        log.Println("error:", err)
        log.Println("raw content:")
        log.Println(resp.Dump()) // Record raw content when error occurs.
        return
      }
    
      if resp.IsErrorState() { // Status code >= 400.
        fmt.Println(errMsg.Message) // Record error message returned.
        return
      }
    
      if resp.IsSuccessState() { // Status code is between 200 and 299.
        fmt.Printf("%s (%s)\n", userInfo.Name, userInfo.Blog)
        return
      }
    
      // Unknown status code.
      log.Println("unknown status", resp.Status)
      log.Println("raw content:")
      log.Println(resp.Dump()) // Record raw content when server returned unknown status code.
    }