Files
go-gin-api/pkg/httpclient/client.go
T
2020-12-25 21:26:56 +08:00

301 lines
6.6 KiB
Go

package httpclient
import (
"context"
"encoding/json"
"net/http"
httpURL "net/url"
"time"
"github.com/xinliangnote/go-gin-api/internal/pkg/journal"
"github.com/pkg/errors"
)
const (
// DefaultTTL 一次http请求最长执行1分钟
DefaultTTL = time.Minute
// DefaultRetryTimes 如果请求失败,最多重试3次
DefaultRetryTimes = 3
// DefaultRetryDelay 在重试前,延迟等待100毫秒
DefaultRetryDelay = time.Millisecond * 100
)
// TODO retry的code不一定正确,缺失或者多余待实际使用中修改。
func shouldRetry(ctx context.Context, httpCode int) bool {
select {
case <-ctx.Done():
return false
default:
}
switch httpCode {
case
_StatusDoReqErr, // customize
_StatusReadRespErr, // customize
http.StatusRequestTimeout,
http.StatusLocked,
http.StatusTooEarly,
http.StatusTooManyRequests,
http.StatusServiceUnavailable,
http.StatusGatewayTimeout:
return true
default:
return false
}
}
// Get get 请求
func Get(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withoutBody(http.MethodGet, url, form, options...)
}
// Delete delete 请求
func Delete(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withoutBody(http.MethodDelete, url, form, options...)
}
func withoutBody(method, url string, form httpURL.Values, options ...Option) (body []byte, err error) {
if url == "" {
return nil, errors.New("url required")
}
if len(form) > 0 {
if url, err = addFormValuesIntoURL(url, form); err != nil {
return
}
}
ts := time.Now()
opt := newOption()
defer func() {
if opt.Journal != nil {
opt.Dialog.Success = err == nil
opt.Dialog.CostSeconds = time.Since(ts).Seconds()
opt.Journal.AppendDialog(opt.Dialog)
}
}()
for _, f := range options {
f(opt)
}
opt.Header["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
if opt.Journal != nil {
opt.Header[journal.JournalHeader] = opt.Journal.ID()
}
ttl := opt.TTL
if ttl <= 0 {
ttl = DefaultTTL
}
ctx, cancel := context.WithTimeout(context.Background(), ttl)
defer cancel()
if opt.Dialog != nil {
decodedURL, _ := httpURL.QueryUnescape(url)
opt.Dialog.Request = &journal.Request{
TTL: ttl.String(),
Method: method,
DecodedURL: decodedURL,
Header: opt.Header,
}
}
retryTimes := opt.RetryTimes
if retryTimes <= 0 {
retryTimes = DefaultRetryTimes
}
retryDelay := opt.RetryDelay
if retryDelay <= 0 {
retryDelay = DefaultRetryDelay
}
var httpCode int
for k := 0; k < retryTimes; k++ {
body, httpCode, err = doHTTP(ctx, method, url, nil, opt)
if shouldRetry(ctx, httpCode) {
time.Sleep(retryDelay)
continue
}
return
}
return
}
// PostForm post form 请求
func PostForm(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withFormBody(http.MethodPost, url, form, options...)
}
// PostJSON post json 请求
func PostJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
return withJSONBody(http.MethodPost, url, raw, options...)
}
// PutForm put form 请求
func PutForm(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withFormBody(http.MethodPut, url, form, options...)
}
// PutJSON put json 请求
func PutJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
return withJSONBody(http.MethodPut, url, raw, options...)
}
// PatchFrom patch form 请求
func PatchFrom(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withFormBody(http.MethodPatch, url, form, options...)
}
// PatchJSON patch json 请求
func PatchJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
return withJSONBody(http.MethodPatch, url, raw, options...)
}
func withFormBody(method, url string, form httpURL.Values, options ...Option) (body []byte, err error) {
if url == "" {
return nil, errors.New("url required")
}
if len(form) == 0 {
return nil, errors.New("form required")
}
ts := time.Now()
opt := newOption()
defer func() {
if opt.Journal != nil {
opt.Dialog.Success = err == nil
opt.Dialog.CostSeconds = time.Since(ts).Seconds()
opt.Journal.AppendDialog(opt.Dialog)
}
}()
for _, f := range options {
f(opt)
}
opt.Header["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
if opt.Journal != nil {
opt.Header[journal.JournalHeader] = opt.Journal.ID()
}
ttl := opt.TTL
if ttl <= 0 {
ttl = DefaultTTL
}
ctx, cancel := context.WithTimeout(context.Background(), ttl)
defer cancel()
formValue := form.Encode()
if opt.Dialog != nil {
decodedURL, _ := httpURL.QueryUnescape(url)
opt.Dialog.Request = &journal.Request{
TTL: ttl.String(),
Method: method,
DecodedURL: decodedURL,
Header: opt.Header,
Body: formValue,
}
}
retryTimes := opt.RetryTimes
if retryTimes <= 0 {
retryTimes = DefaultRetryTimes
}
retryDelay := opt.RetryDelay
if retryDelay <= 0 {
retryDelay = DefaultRetryDelay
}
var httpCode int
for k := 0; k < retryTimes; k++ {
body, httpCode, err = doHTTP(ctx, method, url, []byte(formValue), opt)
if shouldRetry(ctx, httpCode) {
time.Sleep(retryDelay)
continue
}
return
}
return
}
func withJSONBody(method, url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
if url == "" {
return nil, errors.New("url required")
}
if len(raw) == 0 {
return nil, errors.New("raw required")
}
ts := time.Now()
opt := newOption()
defer func() {
if opt.Journal != nil {
opt.Dialog.Success = err == nil
opt.Dialog.CostSeconds = time.Since(ts).Seconds()
opt.Journal.AppendDialog(opt.Dialog)
}
}()
for _, f := range options {
f(opt)
}
opt.Header["Content-Type"] = "application/json; charset=utf-8"
if opt.Journal != nil {
opt.Header[journal.JournalHeader] = opt.Journal.ID()
}
ttl := opt.TTL
if ttl <= 0 {
ttl = DefaultTTL
}
ctx, cancel := context.WithTimeout(context.Background(), ttl)
defer cancel()
if opt.Dialog != nil {
decodedURL, _ := httpURL.QueryUnescape(url)
opt.Dialog.Request = &journal.Request{
TTL: ttl.String(),
Method: method,
DecodedURL: decodedURL,
Header: opt.Header,
Body: string(raw), // TODO unsafe
}
}
retryTimes := opt.RetryTimes
if retryTimes <= 0 {
retryTimes = DefaultRetryTimes
}
retryDelay := opt.RetryDelay
if retryDelay <= 0 {
retryDelay = DefaultRetryDelay
}
var httpCode int
for k := 0; k < retryTimes; k++ {
body, httpCode, err = doHTTP(ctx, method, url, raw, opt)
if shouldRetry(ctx, httpCode) {
time.Sleep(retryDelay)
continue
}
return
}
return
}