前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang开源 - go-axios (HTTP Client) 入门

Golang开源 - go-axios (HTTP Client) 入门

作者头像
猿哥
发布2019-07-10 16:17:37
2.2K0
发布2019-07-10 16:17:37
举报
文章被收录于专栏:Web技术布道师

go-axios入门

前言

日常开发中,各服务主要都是REST的形式提供接口服务,因此HTTP Client则是开发中的重中之重。 golang 中自带的HTTP Client已经能满足各类的场景,但是在使用的时候,各依赖服务的调用都基于同一模块,调整相关代码时影响较大,一些老旧系统的出错响应不规范,导致出错处理流程复杂难懂, go-axios 则由此而生。

go-axios 整体思路沿用(抄袭?) axios ,主要提供实例化的参数配置,提交数据与响应数据的 transform ,发送与响应的拦截器以及可自定义的 Adapter (用于mock测试)。

实例化配置

go-axios 不提供默认的实例,所有的调用服务都需要自己去实例化,如我们有一个调用百度服务的实例:

代码语言:javascript
复制
package main

import (
	"fmt"

	"github.com/vicanso/go-axios"
)

func main() {
	ins := axios.NewInstance(&axios.InstanceConfig{
		BaseURL: "https://www.baidu.com/",
	})
	resp, err := ins.Get("/")
	fmt.Println(err)
	fmt.Println(resp.Status)
}

压缩提交数据

一般客户端比较少提交大数据的场景,但是在内部服务间的调用,有部分场景经常需要提交大量的数据,如应用系统的统计汇总,下面的则是针对大于1KB的提交数据进行gzip压缩(还可选择snappy等更快速的压缩算法)的例子:

代码语言:javascript
复制
package main

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"math/rand"
	"net/http"
	"time"

	"github.com/vicanso/go-axios"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

// doGzip gzip
func doGzip(buf []byte, level int) ([]byte, error) {
	var b bytes.Buffer
	if level <= 0 {
		level = gzip.DefaultCompression
	}
	w, _ := gzip.NewWriterLevel(&b, level)
	_, err := w.Write(buf)
	if err != nil {
		return nil, err
	}
	w.Close()
	return b.Bytes(), nil
}

func main() {
	transformRequest := make([]axios.TransformRequest, 0)
	// 默认的transform request将提交的数据转换为字节
	transformRequest = append(transformRequest, axios.DefaultTransformRequest...)
	transformRequest = append(transformRequest, func(body interface{}, headers http.Header) (data interface{}, err error) {
		key := "Content-Encoding"
		// 已做处理的跳过
		if headers.Get(key) != "" {
			return body, nil
		}
		buf, ok := body.([]byte)
		if !ok {
			return body, nil
		}
		// 少于1KB,不压缩
		if len(buf) < 1024 {
			return body, nil
		}
		gzipBuf, err := doGzip(buf, 0)
		// 压缩失败,则不处理
		if err != nil {
			return body, nil
		}
		headers.Set(key, "gzip")
		return gzipBuf, nil
	})
	ins := axios.NewInstance(&axios.InstanceConfig{
		BaseURL:          "http://localhost:3000/",
		TransformRequest: transformRequest,
	})
	data := map[string]string{
		"account":  randStringRunes(1024),
		"password": randStringRunes(1024),
	}
	resp, err := ins.Post("/", data)
	fmt.Println(err)
	fmt.Println(resp.Status)
}

请求拦截

如果需要对某个服务停止调用,则可以在请求拦截中处理。我们在管理后台中能针对各接入的服务设置可用的时间段,方便管理,简化的示例代码如下:

代码语言:javascript
复制
package main

import (
	"errors"
	"fmt"
	"sync/atomic"
	"time"

	"github.com/vicanso/go-axios"
)

const (
	// ServiceDisabled service disalbed
	ServiceDisabled = iota
	// ServiceEnabled service enabled
	ServiceEnabled
)

func main() {
	var baiduServerStatus int32
	// 如果时间戳为偶数则设置为可用(实际定时从数据库中相关配置中更新)
	if time.Now().Unix()%2 == 0 {
		atomic.StoreInt32(&baiduServerStatus, ServiceEnabled)
	}

	ins := axios.NewInstance(&axios.InstanceConfig{
		BaseURL: "https://www.baidu.com/",
		RequestInterceptors: []axios.RequestInterceptor{
			func(config *axios.Config) (err error) {
				if atomic.LoadInt32(&baiduServerStatus) != ServiceEnabled {
					err = errors.New("service isn't enabled")
					return
				}
				return
			},
		},
	})
	resp, err := ins.Get("/")
	fmt.Println(err)
	fmt.Println(resp)
}

性能统计

go-axios 可启用性能跟踪,包括DNS,TCP连接,首字节等各时间点的统计指标,可在 ResponseInterceptor 中获取这些指标写入统计数据库,示例如下:

代码语言:javascript
复制
package main

import (
	"fmt"

	"github.com/vicanso/go-axios"
)

var (
	aslant = axios.NewInstance(&axios.InstanceConfig{
		BaseURL:     "https://aslant.site/",
		// 启用性能跟踪
		EnableTrace: true,
		ResponseInterceptors: []axios.ResponseInterceptor{
			httpStats,
		},
	})
)

func httpStats(resp *axios.Response) (err error) {
	stats := make(map[string]interface{})
	config := resp.Config
	stats["url"] = config.URL
	stats["status"] = resp.Status

	ht := config.HTTPTrace
	if ht != nil {
		stats["timeline"] = config.HTTPTrace.Stats()
		stats["addr"] = ht.Addr
		stats["reused"] = ht.Reused
	}
	// 可以将相应的记录写入统计数据
	fmt.Println(stats)
	return nil
}

func main() {
	resp, err := aslant.Get("/")
	fmt.Println(err)
	fmt.Println(resp.Status)
}

出错转换

我们的REST服务出错是返回的HTTP状态码为4xx,5xx,而axios默认只为请求出错时才会返回Error,因此我们需要针对各服务将出错的响应直接转换为相应的Error,简化编码流程,也保证针对出错的正常处理(因为开发者有时会只判断Error,而未判断状态码),示例如下:

代码语言:javascript
复制
package main

import (
	"errors"
	"fmt"

	"github.com/vicanso/go-axios"

	jsoniter "github.com/json-iterator/go"
)

var (
	standardJSON = jsoniter.ConfigCompatibleWithStandardLibrary
)
var (
	aslant = axios.NewInstance(&axios.InstanceConfig{
		BaseURL: "https://ip.aslant.site/",
		ResponseInterceptors: []axios.ResponseInterceptor{
			convertResponseToError,
		},
	})
)

// convertResponseToError convert http response(4xx, 5xx) to error
func convertResponseToError(resp *axios.Response) (err error) {
	if resp.Status >= 400 {
		// 我们标准的响应出错消息记录至message中
		message := standardJSON.Get(resp.Data, "message").ToString()
		if message == "" {
			message = "Unknown Error"
		}
		// 也可自定义出错类
		err = errors.New(message)
	}
	return
}

func main() {
	_, err := aslant.Get("/ip-locations/json/123")
	fmt.Println(err)
}

Mock测试

系统依赖于各种服务,最需要处理的就是如何在测试中不受其它系统的影响,因为需要简单易用的mock方式,示例如下:

代码语言:javascript
复制
package main

import (
	"fmt"

	"github.com/vicanso/go-axios"
)

type (
	// UserInfo user info
	UserInfo struct {
		Account string `json:"account,omitempty"`
		Name    string `json:"name,omitempty"`
	}
)

var (
	aslant = axios.NewInstance(&axios.InstanceConfig{
		BaseURL: "https://aslant.site/",
	})
)

// getUserInfo get user info from aslant.site
func getUserInfo() (userInfo *UserInfo, err error) {
	resp, err := aslant.Get("/users/me")
	if err != nil {
		return
	}
	userInfo = new(UserInfo)
	err = resp.JSON(userInfo)
	if err != nil {
		return
	}
	return
}

// mockUserInfo mock user info
func mockUserInfo(data []byte) (done func()) {
	originalAdapter := aslant.Config.Adapter
	aslant.Config.Adapter = func(config *axios.Config) (resp *axios.Response, err error) {
		resp = &axios.Response{
			Data:   data,
			Status: 200,
		}
		return
	}

	done = func() {
		aslant.Config.Adapter = originalAdapter
	}
	return
}

func main() {
	mockUserInfo([]byte(`{"account":"tree", "name":"tree.xie"}`))
	userInfo, err := getUserInfo()
	fmt.Println(err)
	fmt.Println(userInfo)
}

小结

go-axios 的总体实现较为简单,总体上还是依赖于 http.Client ,更新详细的文档可至github上查阅,如果使用中有任何疑问,欢迎提issue。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP技术大全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • go-axios入门
  • 前言
  • 实例化配置
  • 压缩提交数据
  • 请求拦截
  • 性能统计
  • 出错转换
  • Mock测试
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档