设计模式那些事(3)——使用建造者模式封装go的http库
在日常开发中经常会用到http库来发送请求,就将常用的请求方法封装了一个库,灵活又好用。
这里用到了建造者模式,类似的使用场景都可以这样来实现:
具体实现
package requests
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
const (
contentType = "Content-Type"
)
type HttpSend struct {
ctx context.Context // 上下文
client *http.Client // 客户端
request *http.Request // 请求体
url, httpProxy string // 请求路径
getValue interface{} // get请求体
postValue interface{} // post请求体
postFormValue url.Values // form-data post请求体
header map[string]string // header
timeout time.Duration // 超时时间
tryNum int // 重试次数
doNum int // 已重试次数
intervalTime time.Duration // 重试间隔时间
err error // 错误
}
// 使用建造者模式初始化HttpClient
func NewHttpClient(ctx context.Context, options ...HttpOptions) *HttpSend {
client := new(HttpSend)
client.ctx = ctx
for i := 0; i < len(options); i++ {
options[i](client)
}
client.NewClient(ctx)
if client.timeout != 0 {
client.client.Timeout = client.timeout
}
return client
}
// 实例化HttpClient
func (h *HttpSend) NewClient(ctx context.Context) {
h.client = new(http.Client)
if h.httpProxy != "" {
var proxyUrl *url.URL
proxyUrl, h.err = url.Parse(h.httpProxy)
if h.err != nil {
log.Errorf(ctx, fmt.Sprintf("初始化httpClient-httpProxy解析错误, error:%s", h.err))
}
h.client.Transport = &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
}
}
}
// 包装了重试的do方法
func (h *HttpSend) HttpDo() []byte {
var resp *http.Response
var body []byte
resp, h.err = h.client.Do(h.request)
if h.err == nil {
log.Infof(h.ctx, fmt.Sprintf("HttpDo,resp:%+v, error:%+v", resp, h.err))
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
body, h.err = ioutil.ReadAll(resp.Body)
} else {
h.err = errors.New(resp.Status)
}
} else {
for h.doNum < h.tryNum {
h.doNum++
log.Errorf(h.ctx, fmt.Sprintf("HttpDo,url:%s,resp:%+v,重试次数:%d,error:%s", h.url, resp, h.doNum, h.err))
time.Sleep(h.intervalTime)
h.NewClient(h.ctx)
body = h.HttpDo()
return body
}
}
return body
}
// 发送get请求
func (h *HttpSend) HttpGet() ([]byte, error) {
if h.err != nil {
return []byte{}, h.err
}
defer func(t time.Time) {
log.Info(h.ctx, fmt.Sprintf("took %v/s", time.Since(t).Seconds()))
}(time.Now())
log.Info(h.ctx, fmt.Sprintf("url:%s,request:%s", h.url, h.url))
h.request, h.err = http.NewRequest(http.MethodGet, h.url, nil)
if h.err != nil {
return []byte{}, h.err
}
h.setHeader()
var body []byte
body = h.HttpDo()
log.Infof(h.ctx, fmt.Sprintf("url:%s,response:%s,error:%s", h.url, string(body), h.err))
return body, h.err
}
// 发送json参数的get请求
func (h *HttpSend) HttpGetWithJson() ([]byte, error) {
if h.err != nil {
return []byte{}, h.err
}
getValue, err := json.Marshal(h.getValue)
if err != nil {
log.Errorf(h.ctx, fmt.Sprintf("err:%s", err))
return nil, err
}
log.Infof(h.ctx, fmt.Sprintf("url:%s,request:%+v", h.url, h.getValue))
h.request, h.err = http.NewRequest(http.MethodGet, h.url, strings.NewReader(string(getValue)))
if _, ok := h.header[contentType]; !ok {
h.request.Header.Set(contentType, "application/json")
}
h.setHeader()
var body []byte
body = h.HttpDo()
log.Infof(h.ctx, fmt.Sprintf("url:%s,response:%s,error:%s", h.url, string(body), h.err))
return body, h.err
}
// 发送json格式的post请求
func (h *HttpSend) HttpPost() ([]byte, error) {
if h.err != nil {
return []byte{}, h.err
}
postValue, err := json.Marshal(h.postValue)
if err != nil {
log.Errorf(h.ctx, fmt.Sprintf("err:%s", err))
return nil, err
}
log.Infof(h.ctx, fmt.Sprintf("url:%s,request:%s", h.url, h.postValue))
h.request, h.err = http.NewRequest(http.MethodPost, h.url, strings.NewReader(string(postValue)))
if _, ok := h.header[contentType]; !ok {
h.request.Header.Set(contentType, "application/json")
}
h.setHeader()
var body []byte
body = h.HttpDo()
log.Infof(h.ctx, fmt.Sprintf("url:%s,response:%s", h.url, string(body)))
return body, h.err
}
// 发送form-data的post请求
func (h *HttpSend) HttpPostForm() ([]byte, error) {
if h.err != nil {
return []byte{}, h.err
}
log.Infof(h.ctx, fmt.Sprintf("url:%s,postValue:%+v", h.url, h.postFormValue))
h.request, h.err = http.NewRequest(http.MethodPost, h.url, strings.NewReader(h.postFormValue.Encode()))
if h.err != nil {
return []byte{}, h.err
}
h.request.Header.Set(contentType, "application/x-www-form-urlencoded")
var body []byte
body = h.HttpDo()
log.Infof(h.ctx, fmt.Sprintf("send post success,body:%s,h.err:%s", string(body), h.err))
return body, h.err
}
// 设置header
func (h *HttpSend) setHeader() {
for k, v := range h.header {
h.request.Header.Set(k, v)
}
}
type HttpOptions func(*HttpSend)
// 设置请求路径
func UrlOptions(url string) HttpOptions {
return func(client *HttpSend) {
client.url = url
}
}
// 设置post请求体
func PostValueOptions(postValue interface{}) HttpOptions {
return func(client *HttpSend) {
client.postValue = postValue
}
}
// 设置form-data格式的post请求体
func PostFormValueOptions(postFormValue url.Values) HttpOptions {
return func(client *HttpSend) {
client.postFormValue = postFormValue
}
}
// 设置get请求体
func GetValueOptions(getValue interface{}) HttpOptions {
return func(client *HttpSend) {
client.getValue = getValue
}
}
// 设置超时时间
func TimeOptions(timeout time.Duration) HttpOptions {
return func(client *HttpSend) {
client.timeout = timeout
}
}
// 设置代理
func ProxyOptions(proxy string) HttpOptions {
return func(client *HttpSend) {
client.httpProxy = proxy
}
}
// 设置请求头
func HeaderOptions(header map[string]string) HttpOptions {
return func(client *HttpSend) {
client.header = header
}
}
// 设置重试参数
func TryOptions(tryNum int, intervalTime time.Duration) HttpOptions {
return func(client *HttpSend) {
client.tryNum = tryNum
client.intervalTime = intervalTime
}
}
使用
data, err := requests.NewHttpClient(
ctx,
requests.UrlOptions(xxxUrl),
requests.PostValueOptions(postParams),
requests.TryOptions(2, time.Second*3)).
HttpPost()
被 1 篇内容引用
推荐阅读
TCP协议是如何保证数据的可靠传输的
一个数据包,从聊天框里发出,消息会从聊天软件所在的用户空间拷贝到内核空间的发送缓冲区(send buffer),数据包在传输层添加一个TCP头部、在网络层添加一个IP首部,进入到数据链路层添加一个首部和尾部,将其...
爆裂Gopher阅读 200
前端如何入门 Go 语言
类比法是一种学习方法,它是通过将新知识与已知知识进行比较,从而加深对新知识的理解。在学习 Go 语言的过程中,我发现,通过类比已有的前端知识,可以更好地理解 Go 语言的特性。
robin赞 21阅读 2.9k评论 3
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。
机器铃砍菜刀赞 22阅读 55.1k评论 1
年度最佳【golang】map详解
这篇文章主要讲 map 的赋值、删除、查询、扩容的具体执行过程,仍然是从底层的角度展开。结合源码,看完本文一定会彻底明白 map 底层原理。
去去1002赞 14阅读 11k评论 2
年度最佳【golang】GMP调度详解
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底的. 这篇文章将通过分析...
去去1002赞 13阅读 11.2k评论 4
【已结束】SegmentFault 思否技术征文丨浅谈 Go 语言框架
亲爱的开发者们:我们的 11 月技术征文如期而来,这次主题围绕 「 Go 」 语言,欢迎大家来参与分享~征文时间11 月 4 日 - 11 月 27 日 23:5911 月 28 日 18:00 前发布中奖名单参与条件新老思否作者均可参加征文...
SegmentFault思否赞 11阅读 4.7k评论 11
【Go微服务】开发gRPC总共分三步
之前我也有写过RPC相关的文章:《 Go RPC入门指南:RPC的使用边界在哪里?如何实现跨语言调用?》,详细介绍了RPC是什么,使用边界在哪里?并且用Go和php举例,实现了跨语言调用。不了解RPC的同学建议先读这篇文...
王中阳Go赞 8阅读 3.7k评论 6
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。