# req **Repository Path**: fufuok/req ## Basic Information - **Project Name**: req - **Description**: Simple Go HTTP client with Black Magic (forked from imroc/req) - **Primary Language**: Go - **License**: MIT - **Default Branch**: fix-retry-afterResponse - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-03-08 - **Last Updated**: 2022-05-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

Req

Simple Go HTTP client with Black Magic (Less code and More efficiency).

Build Status Code Coverage Go Report Card License GitHub Releases Mentioned in Awesome Go

## News Brand-New version v3 is released, which is completely rewritten, bringing revolutionary innovations and many superpowers, try and enjoy :) If you want to use the older version, check it out on [v1 branch](https://github.com/imroc/req/tree/v1). > v2 is a transitional version, due to some breaking changes were introduced during optmize user experience ## Table of Contents * [Features](#Features) * [Get Started](#Get-Started) * [Debugging - Dump/Log/Trace](#Debugging) * [Quick HTTP Test](#Test) * [HTTP2 and HTTP1](#HTTP2-HTTP1) * [URL Path and Query Parameter](#Param) * [Form Data](#Form) * [Header and Cookie](#Header-Cookie) * [Body and Marshal/Unmarshal](#Body) * [Custom Certificates](#Cert) * [Basic Auth and Bearer Token](#Auth) * [Download and Upload](#Download-Upload) * [Auto-Decode](#AutoDecode) * [Request and Response Middleware](#Middleware) * [Redirect Policy](#Redirect) * [Proxy](#Proxy) * [Unix Socket](#Unix) * [Retry](#Retry) * [TODO List](#TODO) * [License](#License) ## Features * Simple and chainable methods for both client-level and request-level settings, and the request-level setting takes precedence if both are set. * Powerful and convenient debug utilites, including debug logs, performance traces, and even dump the complete request and response content (see [Debugging - Dump/Log/Trace](#Debugging)). * Easy making HTTP test with code instead of tools like curl or postman, `req` provide global wrapper methods and `MustXXX` to test API with minimal code (see [Quick HTTP Test](#Test)). * Works fine with both `HTTP/2` and `HTTP/1.1`, which `HTTP/2` is preferred by default if server support, and you can also force `HTTP/1.1` if you want (see [HTTP2 and HTTP1](#HTTP2-HTTP1)). * Detect the charset of response body and decode it to utf-8 automatically to avoid garbled characters by default (see [Auto-Decode](#AutoDecode)). * Automatic marshal and unmarshal for JSON and XML content type and fully customizable (see [Body and Marshal/Unmarshal](#Body)). * Exportable `Transport`, easy to integrate with existing `http.Client`, debug APIs with minimal code change. * Easy [Download and Upload](#Download-Upload). * Easy set header, cookie, path parameter, query parameter, form data, basic auth, bearer token for both client and request level. * Easy set timeout, proxy, certs, redirect policy, cookie jar, compression, keepalives etc for client. * Support middleware before request sent and after got response (see [Request and Response Middleware](#Middleware)). ## Get Started **Install** ``` sh go get github.com/imroc/req/v3 ``` **Import** ```go import "github.com/imroc/req/v3" ``` **Basic Usage** ```go // For test, you can create and send a request with the global default // client, use DevMode to see all details, try and suprise :) req.DevMode() req.Get("https://api.github.com/users/imroc") // Create and send a request with the custom client and settings client := req.C(). // Use C() to create a client SetUserAgent("my-custom-client"). // Chainable client settings SetTimeout(5 * time.Second). DevMode() resp, err := client.R(). // Use R() to create a request SetHeader("Accept", "application/vnd.github.v3+json"). // Chainable request settings SetPathParam("username", "imroc"). SetQueryParam("page", "1"). SetResult(&result). Get("https://api.github.com/users/{username}/repos") ``` **Videos** * [Get Started With Req](https://www.youtube.com/watch?v=k47i0CKBVrA) (English, Youtube) * [快速上手 req](https://www.bilibili.com/video/BV1Xq4y1b7UR) (Chinese, BiliBili) **API Reference** Checkout [Quick API Reference](docs/api.md) for a brief and categorized list of the core APIs, for a more detailed and complete list of APIs, please refer to [GoDoc](https://pkg.go.dev/github.com/imroc/req/v3). **Examples** Checkout more examples below or runnable examples in the [examples](examples) direcotry. ## Debugging - Dump/Log/Trace **Dump the Content** ```go // Enable dump at client level, which will dump for all requests, // including all content of request and response and output // to stdout by default. client := req.C().EnableDumpAll() client.R().Get("https://httpbin.org/get") /* Output :authority: httpbin.org :method: GET :path: /get :scheme: https user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 accept-encoding: gzip :status: 200 date: Wed, 26 Jan 2022 06:39:20 GMT content-type: application/json content-length: 372 server: gunicorn/19.9.0 access-control-allow-origin: * access-control-allow-credentials: true { "args": {}, "headers": { "Accept-Encoding": "gzip", "Host": "httpbin.org", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36", "X-Amzn-Trace-Id": "Root=1-61f0ec98-5958c02662de26e458b7672b" }, "origin": "103.7.29.30", "url": "https://httpbin.org/get" } */ // Customize dump settings with predefined and convenient settings at client level. client.EnableDumpAllWithoutBody(). // Only dump the header of request and response EnableDumpAllAsync(). // Dump asynchronously to improve performance EnableDumpAllToFile("reqdump.log") // Dump to file without printing it out // Send request to see the content that have been dumped client.R().Get(url) // Enable dump with fully customized settings at client level. opt := &req.DumpOptions{ Output: os.Stdout, RequestHeader: true, ResponseBody: true, RequestBody: false, ResponseHeader: false, Async: false, } client.SetCommonDumpOptions(opt).EnableDumpAll() client.R().Get("https://httpbin.org/get") // Change settings dynamiclly opt.ResponseBody = false client.R().Get("https://httpbin.org/get") // You can also enable dump at request level, which will not override client-level dumping, // dump to the internal buffer and will not print it out by default, you can call `Response.Dump()` // to get the dump result and print only if you want to, typically used in production, only record // the content of the request when the request is abnormal to help us troubleshoot problems. resp, err := client.R().EnableDump().SetBody("test body").Post("https://httpbin.org/post") if err != nil { fmt.Println("err:", err) if resp.Dump() != "" { fmt.Println("raw content:") fmt.Println(resp.Dump()) } return } if !resp.IsSuccess() { // Status code not beetween 200 and 299 fmt.Println("bad status:", resp.Status) fmt.Println("raw content:") fmt.Println(resp.Dump()) return } // Similarly, also support to customize dump settings with the predefined and convenient settings at request level. resp, err = client.R().EnableDumpWithoutRequest().SetBody("test body").Post("https://httpbin.org/post") // ... resp, err = client.R().SetDumpOptions(opt).EnableDump().SetBody("test body").Post("https://httpbin.org/post") ``` **Enable DebugLog for Deeper Insights** ```go // Logging is enabled by default, but only output the warning and error message. // Use `EnableDebugLog` to enable debug level logging. client := req.C().EnableDebugLog() client.R().Get("http://baidu.com/s?wd=req") /* Output 2022/01/26 15:46:29.279368 DEBUG [req] GET http://baidu.com/s?wd=req 2022/01/26 15:46:29.469653 DEBUG [req] charset iso-8859-1 detected in Content-Type, auto-decode to utf-8 2022/01/26 15:46:29.469713 DEBUG [req] GET http://www.baidu.com/s?wd=req ... */ // SetLogger with nil to disable all log client.SetLogger(nil) // Or customize the logger with your own implementation. client.SetLogger(logger) ``` **Enable Trace to Analyze Performance** ```go // Enable trace at request level client := req.C() resp, err := client.R().EnableTrace().Get("https://api.github.com/users/imroc") if err != nil { log.Fatal(err) } trace := resp.TraceInfo() // Use `resp.Request.TraceInfo()` to avoid unnecessary struct copy in production. fmt.Println(trace.Blame()) // Print out exactly where the http request is slowing down. fmt.Println("----------") fmt.Println(trace) // Print details /* Output the request total time is 2.562416041s, and costs 1.289082208s from connection ready to server respond frist byte -------- TotalTime : 2.562416041s DNSLookupTime : 445.246375ms TCPConnectTime : 428.458µs TLSHandshakeTime : 825.888208ms FirstResponseTime : 1.289082208s ResponseTime : 1.712375ms IsConnReused: : false RemoteAddr : 98.126.155.187:443 */ // Enable trace at client level client.EnableTraceAll() resp, err = client.R().Get(url) // ... ``` **DevMode** If you want to enable all debug features (dump, debug log and tracing), just call `DevMode()`: ```go client := req.C().DevMode() client.R().Get("https://imroc.cc") ``` ## Quick HTTP Test **Test with Global Wrapper Methods** `req` wrap methods of both `Client` and `Request` with global methods, which is delegated to the default client behind the scenes, so you can just treat the package name `req` as a Client or Request to test quickly without create one explicitly. ```go // Call the global methods just like the Client's method, // so you can treat package name `req` as a Client, and // you don't need to create any client explicitly. req.SetTimeout(5 * time.Second). SetCommonBasicAuth("imroc", "123456"). SetCommonHeader("Accept", "text/xml"). SetUserAgent("my api client"). DevMode() // Call the global method just like the Request's method, // which will create request automatically using the default // client, so you can treat package name `req` as a Request, // and you don't need to create any request and client explicitly. req.SetQueryParam("page", "2"). SetHeader("Accept", "application/json"). // Override client level settings at request level. Get("https://httpbin.org/get") ``` **Test with MustXXX** Use `MustXXX` to ignore error handling during test, make it possible to complete a complex test with just one line of code: ```go fmt.Println(req.DevMode().R().MustGet("https://imroc.cc").TraceInfo()) ``` ## HTTP2 and HTTP1 Req works fine both with `HTTP/2` and `HTTP/1.1`, `HTTP/2` is preferred by default if server support, which is negotiated by TLS handshake. You can force using `HTTP/1.1` if you want. ```go client := req.C().EnableForceHTTP1().EnableDumpAllWithoutBody() client.R().MustGet("https://httpbin.org/get") /* Output GET /get HTTP/1.1 Host: httpbin.org User-Agent: req/v3 (https://github.com/imroc/req) Accept-Encoding: gzip HTTP/1.1 200 OK Date: Tue, 08 Feb 2022 02:30:18 GMT Content-Type: application/json Content-Length: 289 Connection: keep-alive Server: gunicorn/19.9.0 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true */ ``` And also you can force using `HTTP/2` if you want, will return error if server does not support: ```go client := req.C().EnableForceHTTP2() client.R().MustGet("https://baidu.com") /* Output panic: Get "https://baidu.com": server does not support http2, you can use http/1.1 which is supported */ ``` ## URL Path and Query Parameter **Path Parameter** Use `SetPathParam` or `SetPathParams` to replace variable in the url path: ```go client := req.C().DevMode() client.R(). SetPathParam("owner", "imroc"). // Set a path param, which will replace the vairable in url path SetPathParams(map[string]string{ // Set multiple path params at once "repo": "req", "path": "README.md", }).Get("https://api.github.com/repos/{owner}/{repo}/contents/{path}") // path parameter will replace path variable in the url /* Output 2022/01/23 14:43:59.114592 DEBUG [req] GET https://api.github.com/repos/imroc/req/contents/README.md ... */ // You can also set the common PathParam for every request on client client.SetCommonPathParam(k1, v1).SetCommonPathParams(pathParams) resp1, err := client.Get(url1) ... resp2, err := client.Get(url2) ... ``` **Query Parameter** Use `SetQueryParam`, `SetQueryParams` or `SetQueryString` to append url query parameter: ```go client := req.C().DevMode() // Set query parameter at request level. client.R(). SetQueryParam("a", "a"). // Set a query param, which will be encoded as query parameter in url SetQueryParams(map[string]string{ // Set multiple query params at once "b": "b", "c": "c", }).SetQueryString("d=d&e=e"). // Set query params as a raw query string Get("https://api.github.com/repos/imroc/req/contents/README.md?x=x") /* Output 2022/01/23 14:43:59.114592 DEBUG [req] GET https://api.github.com/repos/imroc/req/contents/README.md?x=x&a=a&b=b&c=c&d=d&e=e ... */ // You can also set the query parameter at client level. client.SetCommonQueryParam(k, v). SetCommonQueryParams(queryParams). SetCommonQueryString(queryString). resp1, err := client.Get(url1) ... resp2, err := client.Get(url2) ... // Add query parameter with multiple values at request level. client.R().AddQueryParam("key", "value1").AddQueryParam("key", "value2").Get("https://httpbin.org/get") /* Output 2022/02/05 08:49:26.260780 DEBUG [req] GET https://httpbin.org/get?key=value1&key=value2 ... */ // Multiple values also supported at client level. client.AddCommonQueryParam("key", "value1").AddCommonQueryParam("key", "value2") ``` ## Form Data ```go client := req.C().EnableDumpAllWithoutResponse() client.R().SetFormData(map[string]string{ "username": "imroc", "blog": "https://imroc.cc", }).Post("https://httpbin.org/post") /* Output :authority: httpbin.org :method: POST :path: /post :scheme: https content-type: application/x-www-form-urlencoded accept-encoding: gzip user-agent: req/v2 (https://github.com/imroc/req) blog=https%3A%2F%2Fimroc.cc&username=imroc */ // Multi value form data v := url.Values{ "multi": []string{"a", "b", "c"}, } client.R().SetFormDataFromValues(v).Post("https://httpbin.org/post") /* Output :authority: httpbin.org :method: POST :path: /post :scheme: https content-type: application/x-www-form-urlencoded accept-encoding: gzip user-agent: req/v2 (https://github.com/imroc/req) multi=a&multi=b&multi=c */ // You can also set form data in client level client.SetCommonFormData(m) client.SetCommonFormDataFromValues(v) ``` > `GET`, `HEAD`, and `OPTIONS` requests ignores form data by default ## Header and Cookie **Set Header** ```go // Let's dump the header to see what's going on client := req.C().EnableForceHTTP1().EnableDumpAllWithoutResponse() // Send a request with multiple headers and cookies client.R(). SetHeader("Accept", "application/json"). // Set one header SetHeaders(map[string]string{ // Set multiple headers at once "My-Custom-Header": "My Custom Value", "User": "imroc", }).Get("https://httpbin.org/get") /* Output GET /get HTTP/1.1 Host: httpbin.org User-Agent: req/v3 (https://github.com/imroc/req) Accept: application/json My-Custom-Header: My Custom Value User: imroc Accept-Encoding: gzip */ // You can also set the common header and cookie for every request on client. client.SetCommonHeader(header).SetCommonHeaders(headers) resp1, err := client.R().Get(url1) ... resp2, err := client.R().Get(url2) ... ``` **Set Cookie** ```go // Let's dump the header to see what's going on client := req.C().EnableForceHTTP1().EnableDumpAllWithoutResponse() // Send a request with multiple headers and cookies client.R(). SetCookies( &http.Cookie{ Name: "testcookie1", Value: "testcookie1 value", Path: "/", Domain: "baidu.com", MaxAge: 36000, HttpOnly: false, Secure: true, }, &http.Cookie{ Name: "testcookie2", Value: "testcookie2 value", Path: "/", Domain: "baidu.com", MaxAge: 36000, HttpOnly: false, Secure: true, }, ).Get("https://httpbin.org/get") /* Output GET /get HTTP/1.1 Host: httpbin.org User-Agent: req/v3 (https://github.com/imroc/req) Cookie: testcookie1="testcookie1 value"; testcookie2="testcookie2 value" Accept-Encoding: gzip */ // You can also set the common cookie for every request on client. client.SetCommonCookies(cookie1, cookie2, cookie3) resp1, err := client.R().Get(url1) ... resp2, err := client.R().Get(url2) ``` You can also customize the CookieJar: ```go // Set your own http.CookieJar implementation client.SetCookieJar(jar) // Set to nil to disable CookieJar client.SetCookieJar(nil) ``` ## Body and Marshal/Unmarshal **Request Body** ```go // Create a client that dump request client := req.C().EnableDumpAllWithoutResponse() // SetBody accepts string, []byte, io.Reader, use type assertion to // determine the data type of body automatically. client.R().SetBody("test").Post("https://httpbin.org/post") /* Output :authority: httpbin.org :method: POST :path: /post :scheme: https accept-encoding: gzip user-agent: req/v2 (https://github.com/imroc/req) test */ // If it cannot determine, like map and struct, then it will wait // and marshal to JSON or XML automatically according to the `Content-Type` // header that have been set before or after, default to json if not set. type User struct { Name string `json:"name"` Email string `json:"email"` } user := &User{Name: "imroc", Email: "roc@imroc.cc"} client.R().SetBody(user).Post("https://httpbin.org/post") /* Output :authority: httpbin.org :method: POST :path: /post :scheme: https content-type: application/json; charset=utf-8 accept-encoding: gzip user-agent: req/v2 (https://github.com/imroc/req) {"name":"imroc","email":"roc@imroc.cc"} */ // You can use more specific methods to avoid type assertions and improves performance, client.R().SetBodyJsonString(`{"username": "imroc"}`).Post("https://httpbin.org/post") /* :authority: httpbin.org :method: POST :path: /post :scheme: https content-type: application/json; charset=utf-8 accept-encoding: gzip user-agent: req/v2 (https://github.com/imroc/req) {"username": "imroc"} */ // Marshal body and set `Content-Type` automatically without any guess cient.R().SetBodyXmlMarshal(user).Post("https://httpbin.org/post") /* Output :authority: httpbin.org :method: POST :path: /post :scheme: https content-type: text/xml; charset=utf-8 accept-encoding: gzip user-agent: req/v2 (https://github.com/imroc/req) imrocroc@imroc.cc */ ``` **Response Body** ```go // Define success body struct type User struct { Name string `json:"name"` Blog string `json:"blog"` } // Define error body struct type ErrorMessage struct { Message string `json:"message"` } // Create a client and dump body to see details client := req.C().EnableDumpAllWithoutHeader() // Send a request and unmarshal result automatically according to // response `Content-Type` user := &User{} errMsg := &ErrorMessage{} resp, err := client.R(). SetResult(user). // Set success result SetError(errMsg). // Set error result Get("https://api.github.com/users/imroc") if err != nil { log.Fatal(err) } fmt.Println("----------") if resp.IsSuccess() { // status `code >= 200 and <= 299` is considered as success // Must have been marshaled to user if no error returned before fmt.Printf("%s's blog is %s\n", user.Name, user.Blog) } else if resp.IsError() { // status `code >= 400` is considered as error // Must have been marshaled to errMsg if no error returned before fmt.Println("got error:", errMsg.Message) } else { log.Fatal("unknown http status:", resp.Status) } /* Output {"login":"imroc","id":7448852,"node_id":"MDQ6VXNlcjc0NDg4NTI=","avatar_url":"https://avatars.githubusercontent.com/u/7448852?v=4","gravatar_id":"","url":"https://api.github.com/users/imroc","html_url":"https://github.com/imroc","followers_url":"https://api.github.com/users/imroc/followers","following_url":"https://api.github.com/users/imroc/following{/other_user}","gists_url":"https://api.github.com/users/imroc/gists{/gist_id}","starred_url":"https://api.github.com/users/imroc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/imroc/subscriptions","organizations_url":"https://api.github.com/users/imroc/orgs","repos_url":"https://api.github.com/users/imroc/repos","events_url":"https://api.github.com/users/imroc/events{/privacy}","received_events_url":"https://api.github.com/users/imroc/received_events","type":"User","site_admin":false,"name":"roc","company":"Tencent","blog":"https://imroc.cc","location":"China","email":null,"hireable":true,"bio":"I'm roc","twitter_username":"imrocchan","public_repos":129,"public_gists":0,"followers":362,"following":151,"created_at":"2014-04-30T10:50:46Z","updated_at":"2022-01-24T23:32:53Z"} ---------- roc's blog is https://imroc.cc */ // Or you can also unmarshal response later if resp.IsSuccess() { err = resp.Unmarshal(user) if err != nil { log.Fatal(err) } fmt.Printf("%s's blog is %s\n", user.Name, user.Blog) } else { fmt.Println("bad response:", resp) } // Also, you can get the raw response and Unmarshal by yourself yaml.Unmarshal(resp.Bytes()) ``` **Customize JSON&XML Marshal/Unmarshal** ```go // Example of registering json-iterator import jsoniter "github.com/json-iterator/go" json := jsoniter.ConfigCompatibleWithStandardLibrary client := req.C(). SetJsonMarshal(json.Marshal). SetJsonUnmarshal(json.Unmarshal) // Similarly, XML functions can also be customized client.SetXmlMarshal(xmlMarshalFunc).SetXmlUnmarshal(xmlUnmarshalFunc) ``` **Disable Auto-Read Response Body** Response body will be read into memory if it's not a download request by default, you can disable it if you want (normally you don't need to do this). ```go client.DisableAutoReadResponse() resp, err := client.R().Get(url) if err != nil { log.Fatal(err) } io.Copy(dst, resp.Body) ``` ## Custom Certificates ```go client := req.R() // Set root cert and client cert from file path client.SetRootCertsFromFile("/path/to/root/certs/pemFile1.pem", "/path/to/root/certs/pemFile2.pem", "/path/to/root/certs/pemFile3.pem"). // Set root cert from one or more pem files SetCertFromFile("/path/to/client/certs/client.pem", "/path/to/client/certs/client.key") // Set client cert and key cert file // You can also set root cert from string client.SetRootCertFromString("-----BEGIN CERTIFICATE-----XXXXXX-----END CERTIFICATE-----") // And set client cert with cert1, err := tls.LoadX509KeyPair("/path/to/client/certs/client.pem", "/path/to/client/certs/client.key") if err != nil { log.Fatalf("ERROR client certificate: %s", err) } // ... // you can add more certs if you want client.SetCerts(cert1, cert2, cert3) ``` ## Basic Auth and Bearer Token ```go client := req.C() // Set basic auth for all request client.SetCommonBasicAuth("imroc", "123456") // Set bearer token for all request client.SetCommonBearerAuthToken("MDc0ZTg5YmU4Yzc5MjAzZGJjM2ZiMzkz") // Set basic auth for a request, will override client's basic auth setting. client.R().SetBasicAuth("myusername", "mypassword").Get("https://api.example.com/profile") // Set bearer token for a request, will override client's bearer token setting. client.R().SetBearerToken("NGU1ZWYwZDJhNmZhZmJhODhmMjQ3ZDc4").Get("https://api.example.com/profile") ``` ## Download and Upload **Download** ```go // Create a client with default download direcotry client := req.C().SetOutputDirectory("/path/to/download").EnableDumpAllWithoutResponseBody() // Download to relative file path, this will be downloaded // to /path/to/download/test.jpg client.R().SetOutputFile("test.jpg").Get(url) // Download to absolute file path, ignore the output directory // setting from Client client.R().SetOutputFile("/tmp/test.jpg").Get(url) // You can also save file to any `io.WriteCloser` file, err := os.Create("/tmp/test.jpg") if err != nil { fmt.Println(err) return } client.R().SetOutput(file).Get(url) ``` **Multipart Upload** ```go client := req.C().EnableDumpAllWithoutRequestBody() // Request body contains unreadable binary, do not dump client.R().SetFile("pic", "test.jpg"). // Set form param name and filename SetFile("pic", "/path/to/roc.png"). // Multiple files using the same form param name SetFiles(map[string]string{ // Set multiple files using map "exe": "test.exe", "src": "main.go", }). SetFormData(map[string]string{ // Set form data while uploading "name": "imroc", "email": "roc@imroc.cc", }). SetFromDataFromValues(values). // You can also set form data using `url.Values` Post("http://127.0.0.1:8888/upload") // You can also use io.Reader to upload avatarImgFile, _ := os.Open("avatar.png") client.R().SetFileReader("avatar", "avatar.png", avatarImgFile).Post(url) */ ``` ## Auto-Decode `Req` detect the charset of response body and decode it to utf-8 automatically to avoid garbled characters by default. Its principle is to detect `Content-Type` header at first, if it's not the text content type (json, xml, html and so on), `req` will not try to decode. If it is, then `req` will try to find the charset information. And `req` also will try to sniff the body's content to determine the charset if the charset information is not included in the header, if sniffed out and not utf-8, then decode it to utf-8 automatically, and `req` will not try to decode if the charset is not sure, just leave the body untouched. You can also disable it if you don't need or care a lot about performance: ```go client.DisableAutoDecode() ``` And also you can make some customization: ```go // Try to auto-detect and decode all content types (some server may return incorrect Content-Type header) client.SetAutoDecodeAllContentType() // Only auto-detect and decode content which `Content-Type` header contains "html" or "json" client.SetAutoDecodeContentType("html", "json") // Or you can customize the function to determine whether to decode fn := func(contentType string) bool { if regexContentType.MatchString(contentType) { return true } return false } client.SetAutoDecodeContentTypeFunc(fn) ``` ## Request and Response Middleware ```go client := req.C() // Registering Request Middleware client.OnBeforeRequest(func(c *req.Client, r *req.Request) error { // You can access Client and current Request object to do something // as you need return nil // return nil if it is success }) // Registering Response Middleware client.OnAfterResponse(func(c *req.Client, r *req.Response) error { // You can access Client and current Response object to do something // as you need return nil // return nil if it is success }) ``` ## Redirect Policy ```go client := req.C().EnableDumpAllWithoutResponse() client.SetRedirectPolicy( // Only allow up to 5 redirects req.MaxRedirectPolicy(5), // Only allow redirect to same domain. // e.g. redirect "www.imroc.cc" to "imroc.cc" is allowed, but "google.com" is not req.SameDomainRedirectPolicy(), ) client.SetRedirectPolicy( // Only *.google.com/google.com and *.imroc.cc/imroc.cc is allowed to redirect req.AllowedDomainRedirectPolicy("google.com", "imroc.cc"), // Only allow redirect to same host. // e.g. redirect "www.imroc.cc" to "imroc.cc" is not allowed, only "www.imroc.cc" is allowed req.SameHostRedirectPolicy(), ) // All redirect is not allowd client.SetRedirectPolicy(req.NoRedirectPolicy()) // Or customize the redirect with your own implementation client.SetRedirectPolicy(func(req *http.Request, via []*http.Request) error { // ... }) ``` ## Proxy `Req` use proxy `http.ProxyFromEnvironment` by default, which will read the `HTTP_PROXY/HTTPS_PROXY/http_proxy/https_proxy` environment variable, and setup proxy if environment variable is been set. You can customize it if you need: ```go // Set proxy from proxy url client.SetProxyURL("http://myproxy:8080") // Custmize the proxy function with your own implementation client.SetProxy(func(request *http.Request) (*url.URL, error) { // ... }) // Disable proxy client.SetProxy(nil) ``` ## Unix Socket ```go client := req.C() client.SetUnixSocket("/var/run/custom.sock") client.SetBaseURL("http://example.local") resp, err := client.R().Get("/index.html") ``` ## Retry You can enable retry for all requests at client-level (check the full list of client-level retry settings around [here](./docs/api.md#Retry-Client)): ```go client := req.C() // Enable retry and set the maximum retry count. client.SetCommonRetryCount(3). // Set the retry sleep interval with a commonly used algorithm: capped exponential backoff with jitter (https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/). client.SetCommonRetryBackoffInterval(1 * time.Second, 5 * time.Second) // Set the retry to sleep fixed interval of 2 seconds. client.SetCommonRetryFixedInterval(2 * time.Seconds) // Set the retry to use a custom retry interval algorithm. client.SetCommonRetryInterval(func(resp *req.Response, attempt int) time.Duration { // Sleep seconds from "Retry-After" response header if it is present and correct (https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html). if resp.Response != nil { ra := resp.Header.Get("Retry-After") if ra != "" { after, err := strconv.Atoi(ra) if err == nil { return time.Duration(after) * time.Second } } } return 2 * time.Second // Otherwise sleep 2 seconds }) // Add a retry hook which will be executed before a retry. client.AddCommonRetryHook(func(resp *req.Response, err error){ req := resp.Request.RawRequest fmt.Println("Retry request:", req.Method, req.URL) }) // Add a retry condition which determines whether the request should retry. client.AddCommonRetryCondition(func(resp *req.Response, err error) bool { return err != nil }) // Add another retry condition client.AddCommonRetryCondition(func(resp *req.Response, err error) bool { return resp.StatusCode == http.StatusTooManyRequests }) ``` You can also override retry settings at request-level (check the full list of request-level retry settings around [here](./docs/api.md#Retry-Request)): ```go client.R(). SetRetryCount(2). SetRetryInterval(intervalFunc). SetRetryHook(hookFunc1). // Unlike add, set will remove all other retry hooks which is added before at both request and client level. AddRetryHook(hookFunc2). SetRetryCondition(conditionFunc1). // Similarly, this will remove all other retry conditions which is added before at both request and client level. AddRetryCondition(conditionFunc2) ``` ## TODO List * [ ] Wrap more transport settings into client. * [ ] Support h2c. * [ ] Design a logo. * [ ] Support HTTP3. ## License `Req` released under MIT license, refer [LICENSE](LICENSE) file.