goreq

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2021 License: MIT Imports: 9 Imported by: 2

README

goreq

中文


I have been using gorequest before, but the following pain points were found during use:

  • When using this library to call between services, it is impossible to insert some common processing logic. For example, the server usually return fields like code, and the client needs to check whether its value is validated
  • Maybe cause concurrency problems (share one agent)
  • Use short connection by default
  • Does not support hybrid responses, for example, a certain platform may return json or text, depending on the value of Conten-Type
  • Not supported retry

After looking around on github, I didn't find a library that I was satisfied with, so goreq was born.

Features

The supported features are as follows:

  • http raw request
  • serialization request
  • deserialize response
  • handling public wrapper
  • retry
  • Validating response
http raw request

The most basic usage of goreq is as follows:

    // using raw bytes and http.response to handle
    var resp http.Response
    var bodyBytes []byte
    err := Get("https://httpbin.org/get", RawResp(&resp, &bodyBytes)).Do()
    assert.NoError(t, err)
    assert.Equal(t, http.StatusOK, resp.StatusCode)
    assert.Equal(t, int(resp.ContentLength), len(bodyBytes))

In the above demo, we directly use the byte array and http.Response to accept the response. This approach is suitable for scenarios where the response structure is not fixed, such as web pages, files, etc. may be returned.

Serialized request body

goreq currently supports the serialization of request bodies in the following formats:

  • json
JsonRequest

If you need to serialize a structure into request body with json, you can use JsonReq, please refer to the following code:

  req := JsonRequest{
      String: "hello",
      Int: 666,
  }
  err := Post("https://httpbin.org/post",
    JsonReq(req)).Do()
  assert.NoError(t, err)
Deserialize response content

goreq supports deserialization of responses:

  • json
  • hybrid
JsonResponse

If the content of the response is json format, we can use goreq deserialize the content as below:

  req := JsonRequest{
    String: "hello",
    Int: 666,
  }
  respBody := JsonRequest{}
  resp := HttpBinResp{
    Json: &respBody,
  }
  err := Post("https://httpbin.org/post",
  JsonReq(req),
  JsonResp(&resp)).Do()
  assert.NoError(t, err)
  assert.Equal(t, req, respBody)
Heterogeneous response

If the response is hybrid, such as a text/html when an error occurs, and json when it is normal, we can use HybridResp to handle different situations:

err := Post("https://httpbin.org/post",
        JsonReq(req),
        HybridResp(RespHandlerPredicate{
            Predicate: func(response *http.Response) bool {
                return response.StatusCode == http.StatusOK
            },
            // if status code is 200, using JsonResp
            RespHandler: JsonResp(&ret),
        }, RespHandlerPredicate{
Predicate: func(response *http.Response) bool {
                return response.StatusCode != http.StatusOK
            },
            // if status ocde is not 200, using RawResp
            RespHandler: RawResp(nil, &bt),
        }),
    ).Do()
Handling public wrapper

Most of the time the server will have a common wrapper when returning the results, such as:

{
  "code": 0,
  "msg": "",
  "data": null,
  "req_id": "xxxx"
}

When invoking the service, in fact, Client only cares about the content in data most of the time, and only needs to care other information when api failed. In order to separate concerns, we can use RespWrapper of goreq. In addition to defining the public wrapper, another important responsibility of RespWrapper is to identify whether the current request fails. Refer to the following code:

type CountResultWrapper struct {
  Headers map[string]string `json:"headers"`
  Data string `json:"data"`
  Json interface{} `json:"json"`
  
  doValidationCallback func() int
  returnOkAfterRequests int
}

func (w *CountResultWrapper) SetData(ret interface{}) {
  w.Json = ret
}

func (w *CountResultWrapper) Validate() error {
  count := w.doValidationCallback()
  if count == w.returnOkAfterRequests {
    return nil
  }

    return fmt.Errorf("%d request is expected failed", count)
}

func TestRetryAndValidation(t *testing.T) {
  req := JsonRequest{
    String: "hello",
    Int: 666,
  }
  respData := JsonRequest{}
  
  callCount := 0
  cb := func() int {
    callCount++
    return callCount
  }

  err := Post("https://httpbin.org/post",
    JsonReq(req),
    JsonResp(&respData),
    RespWrapper(&CountResultWrapper{returnOkAfterRequests: 2, doValidationCallback: cb}),
    Retry(&RetryOpt{
    Attempts: 2,
  }),
  ).Do()
  // wrapper will ok when retry 2 times
  assert.NoError(t, err)
  assert.Equal(t, req, respData)
  
  callCount = 0
  err = Post("https://httpbin.org/post",
    JsonReq(req),
    JsonResp(&respData),
    RespWrapper(&CountResultWrapper{returnOkAfterRequests: 2, doValidationCallback: cb}),
    Retry(&RetryOpt{
        Attempts: 1,
    }),
  ).Do()
  assert.NotNil(t, err)
}

The above code shows most of the usage of goreq, including the retry and validated result as mentioned below. CountResultWrapper will return nil when it reaches a certain number of requests, other situations will fail. You can see the above example. When the configuration is retried once (equivalent to not retrying), an error will be generated. It will succeed when you configure two retries. Here are several important points:

  • When the response is returned, the Valiadte of RespWrapper will first verify whether there is a failure
  • If the request fails, you can specify goreq to retry
Retry

For the retry example, refer to the above codes.

Validating response

Here are two ways to validate response:

  • Validating in the wrapper: you can implement RespWrapper to do it
  • Validating http status code: you can set ExpectedStatusCodes to do it.If the http status code is not in this range, an error will be generated.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	DefaultClient    = http.DefaultClient
	DefaultTransport = http.DefaultTransport
)

Functions

This section is empty.

Types

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

func Delete

func Delete(url string, ops ...AgentOp) *Agent

Delete start a request with DELETE

func Get

func Get(url string, ops ...AgentOp) *Agent

Get start a request with GET

func Patch

func Patch(url string, ops ...AgentOp) *Agent

Patch start a request with PATCH

func Post

func Post(url string, ops ...AgentOp) *Agent

Post start a request with POST

func Put

func Put(url string, ops ...AgentOp) *Agent

Put start a request with PUT

func (*Agent) Do

func (a *Agent) Do() error

func (*Agent) Ops

func (a *Agent) Ops(ops ...AgentOp) *Agent

type AgentOp

type AgentOp interface {
	InitialAgent(*Agent) error
}

func JsonReq

func JsonReq(reqBody interface{}) AgentOp

type AgentOpFunc

type AgentOpFunc func(agent *Agent) error

func Context

func Context(ctx context.Context) AgentOpFunc

func CustomRespHandler

func CustomRespHandler(handler RespHandler) AgentOpFunc

CustomRespHandler specify a custom RespHandler

func ExpectedStatusCodes

func ExpectedStatusCodes(codes []int) AgentOpFunc

func RespWrapper

func RespWrapper(wrapper Wrapper) AgentOpFunc

func Retry

func Retry(opt *RetryOpt) AgentOpFunc

func SetHeader

func SetHeader(header http.Header) AgentOpFunc

func (AgentOpFunc) InitialAgent

func (f AgentOpFunc) InitialAgent(agent *Agent) error

type HttpCodeErr

type HttpCodeErr struct {
	StatusCode          int
	ExpectedStatusCodes []int

	ReadBodyErr error
	Body        []byte
}

func NewHttpCodeErr

func NewHttpCodeErr(expectedCodes []int, resp *http.Response) *HttpCodeErr

func (*HttpCodeErr) Error

func (e *HttpCodeErr) Error() string

type HybridHandler

type HybridHandler struct {
	// contains filtered or unexported fields
}

RawRespHandler is a wrapper to implement AgentOp and RespHandler

func HybridResp

func HybridResp(predicate ...RespHandlerPredicate) *HybridHandler

HybridResp can handle hybrid response such as Json and Raw you can use RespHandlerPredicate to indicate when use which resp handler with Predicate

func (*HybridHandler) HandleResponse

func (h *HybridHandler) HandleResponse(resp *http.Response, respWrapper Wrapper) error

func (*HybridHandler) InitialAgent

func (h *HybridHandler) InitialAgent(a *Agent) error

type JsonRespHandler

type JsonRespHandler struct {
	// contains filtered or unexported fields
}

JsonRespHandler is a wrapper to implement AgentOp and RespHandler

func JsonResp

func JsonResp(ret interface{}) *JsonRespHandler

JsonResp use to handler json response, ret must be a ptr

func (*JsonRespHandler) HandleResponse

func (h *JsonRespHandler) HandleResponse(resp *http.Response, respWrapper Wrapper) error

func (*JsonRespHandler) InitialAgent

func (h *JsonRespHandler) InitialAgent(a *Agent) error

type RawRespHandler

type RawRespHandler struct {
	// contains filtered or unexported fields
}

RawRespHandler is a wrapper to implement AgentOp and RespHandler

func RawResp

func RawResp(resp *http.Response, bs *[]byte) *RawRespHandler

RawResp use http.Response and []byes to accept response

func (*RawRespHandler) HandleResponse

func (h *RawRespHandler) HandleResponse(resp *http.Response, respWrapper Wrapper) error

func (*RawRespHandler) InitialAgent

func (h *RawRespHandler) InitialAgent(a *Agent) error

type ReqPreHandler

type ReqPreHandler interface {
	PreHandleRequest(req *http.Request) (*http.Request, error)
}

type ReqPreHandlerFunc

type ReqPreHandlerFunc func(req *http.Request) (*http.Request, error)

func (ReqPreHandlerFunc) PreHandleRequest

func (f ReqPreHandlerFunc) PreHandleRequest(req *http.Request) (*http.Request, error)

type RespHandler

type RespHandler interface {
	HandleResponse(resp *http.Response, respWrapper Wrapper) error
}

RespHandler you can implement some special cases TIPS: Usually JsonResp, RawResp and HybridResp handle most situations

type RespHandlerPredicate

type RespHandlerPredicate struct {
	Predicate   func(response *http.Response) bool
	RespHandler RespHandler
}

type RetryOpt

type RetryOpt struct {
	// the max delay of interval
	MaxDelay time.Duration
	// RetryAppError indicate if retry when "RespWrapper validate failed"
	RetryAppError bool
	Attempts      int
}

type Wrapper

type Wrapper interface {
	SetData(ret interface{})
	Validate() error
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL