Golang fasthttp 实践
原计划学完Golang语言HTTP客户端实践之后,就可以继续了,没想到才疏学浅,在搜资料的时候发现除了 Golang SDK 自带的 net/http,还有一个更牛的 HttpClient 实现 github.com/valyala/fasthttp,据说性能是 net/http 的 10 倍,我想可能是有点夸张了,后期我会进行测试,以正视听。
在github.com/valyala/fasthttp
用到了对象池,为了在高性能测试中减少内存的使用,fasthttp 使用了两个对象池(我只看了这俩):requestPool sync.Pool
和responsePool sync.Pool
,当然 fasthttp 也提供了正常的对象创建 API,后面我在案例中也会写到。
基础 API 演示
首先分享一下基础的用法封装:
PS:这个属于练习版本,所以没写多少注释。
package ft
import (
"encoding/json"
"fmt"
"funtester/task"
"github.com/valyala/fasthttp"
)
func FastGet(url string, args map[string]interface{}) ([]byte, error) {
uri := url + "?" + task.ToValues(args)
_, resp, err := fasthttp.Get(nil, uri)
if err != nil {
fmt.Println("请求失败:", err.Error())
return nil, err
}
return resp, err
}
func FastPostForm(url string, args map[string]interface{}) ([]byte, error) {
// 填充表单,类似于net/url
params := &fasthttp.Args{}
for s, i2 := range args {
sprintf := fmt.Sprintf("%v", i2)
params.Add(s, sprintf)
}
_, resp, err := fasthttp.Post(nil, url, params)
if err != nil {
fmt.Println("请求失败:", err.Error())
return nil, err
}
return resp, nil
}
func FastPostJson(url string, args map[string]interface{}) ([]byte, error) {
req := &fasthttp.Request{}
req.SetRequestURI(url)
marshal, _ := json.Marshal(args)
req.SetBody(marshal)
// 默认是application/x-www-form-urlencoded,其实无所谓
req.Header.SetContentType("application/json")
req.Header.SetMethod("POST")
resp := &fasthttp.Response{}
if err := fasthttp.Do(req, resp); err != nil {
fmt.Println("请求失败:", err.Error())
return nil, err
}
return resp.Body(), nil
}
其中两点主要注意:
FastGet、FastPostForm 使用的 fasthttp 提供的默认获取请求的方式,FastPostJson 使用了自定义请求和获取响应的方式
关于请求头中的 req.Header.SetContentType 方法,其实无所谓,服务端都可以解析
高性能 API 演示
下面分享使用更高的性能(基于对象池)的 API 创建请求和获取响应的方式:
package task
import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/valyala/fasthttp"
"log"
"time"
)
var FastClient fasthttp.Client = fastClient()
// FastGet 获取GET请求对象,没有进行资源回收
// @Description:
// @param url
// @param args
// @return *fasthttp.Request
func FastGet(url string, args map[string]interface{}) *fasthttp.Request {
req := fasthttp.AcquireRequest()
req.Header.SetMethod("GET")
values := ToValues(args)
req.SetRequestURI(url + "?" + values)
return req
}
// FastPostJson POST请求JSON参数,没有进行资源回收
// @Description:
// @param url
// @param args
// @return *fasthttp.Request
func FastPostJson(url string, args map[string]interface{}) *fasthttp.Request {
req := fasthttp.AcquireRequest()
// 默认是application/x-www-form-urlencoded
req.Header.SetContentType("application/json")
req.Header.SetMethod("POST")
req.SetRequestURI(url)
marshal, _ := json.Marshal(args)
req.SetBody(marshal)
return req
}
// FastPostForm POST请求表单传参,没有进行资源回收
// @Description:
// @param url
// @param args
// @return *fasthttp.Request
func FastPostForm(url string, args map[string]interface{}) *fasthttp.Request {
req := fasthttp.AcquireRequest()
// 默认是application/x-www-form-urlencoded
//req.Header.SetContentType("application/json")
req.Header.SetMethod("POST")
req.SetRequestURI(url)
marshal, _ := json.Marshal(args)
req.BodyWriter().Write([]byte(ToValues(args)))
req.BodyWriter().Write(marshal)
return req
}
// FastResponse 获取响应,保证资源回收
// @Description:
// @param request
// @return []byte
// @return error
func FastResponse(request *fasthttp.Request) ([]byte, error) {
response := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(response)
defer fasthttp.ReleaseRequest(request)
if err := FastClient.Do(request, response); err != nil {
log.Println("响应出错了")
return nil, err
}
return response.Body(), nil
}
// DoGet 发送GET请求,获取响应
// @Description:
// @param url
// @param args
// @return []byte
// @return error
func DoGet(url string, args map[string]interface{}) ([]byte, error) {
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req) // 用完需要释放资源
req.Header.SetMethod("GET")
values := ToValues(args)
req.SetRequestURI(url + "?" + values)
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp) // 用完需要释放资源
if err := FastClient.Do(req, resp); err != nil {
fmt.Println("请求失败:", err.Error())
return nil, err
}
return resp.Body(), nil
}
// fastClient 获取fast客户端
// @Description:
// @return fasthttp.Client
func fastClient() fasthttp.Client {
return fasthttp.Client{
Name: "FunTester",
NoDefaultUserAgentHeader: true,
TLSConfig: &tls.Config{InsecureSkipVerify: true},
MaxConnsPerHost: 2000,
MaxIdleConnDuration: 5 * time.Second,
MaxConnDuration: 5 * time.Second,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
MaxConnWaitTimeout: 5 * time.Second,
}
}
测试服务
用的还是 moco_FunTester 测试框架,脚本如下:
package com.mocofun.moco.main
import com.funtester.utils.ArgsUtil
import com.mocofun.moco.MocoServer
import org.apache.tools.ant.taskdefs.condition.And
class Share extends MocoServer {
static void main(String[] args) {
def util = new ArgsUtil(args)
// def server = getServerNoLog(util.getIntOrdefault(0,12345))
def server = getServer(util.getIntOrdefault(0, 12345))
server.get(both(urlStartsWith("/test"),existArgs("code"))).response("get请求")
server.post(both(urlStartsWith("/test"), existForm("fun"))).response("post请求form表单")
server.post(both(urlStartsWith("/test"), existParams("fun"))).response("post请求json表单")
server.get(urlStartsWith("/qps")).response(qps(textRes("恭喜到达QPS!"), 1))
// server.response(delay(jsonRes(getJson("Have=Fun ~ Tester !")), 1000))
server.response("Have Fun ~ Tester !")
def run = run(server)
waitForKey("fan")
run.stop()
}
}
Golang 单元测试
第一次写 Golang 单测,有点不适应,搞了很久才通。
package test
import (
"funtester/ft"
"funtester/task"
"log"
"testing"
)
const url = "http://localhost:12345/test"
func args() map[string]interface{} {
return map[string]interface{}{
"code": 32,
"fun": 32,
"msg": "324",
}
}
func TestGet(t *testing.T) {
get := task.FastGet(url, args())
res, err := task.FastResponse(get)
if err != nil {
t.Fail()
}
v := string(res)
log.Println(v)
if v != "get请求" {
t.Fail()
}
}
func TestPostJson(t *testing.T) {
post := task.FastPostJson(url, args())
res, err := task.FastResponse(post)
if err != nil {
t.Fail()
}
v := string(res)
log.Println(v)
if v != "post请求json表单" {
t.Fail()
}
}
func TestPostForm(t *testing.T) {
post := task.FastPostForm(url, args())
res, err := task.FastResponse(post)
if err != nil {
t.Fail()
}
v := string(res)
log.Println(v)
if v != "post请求form表单" {
t.Fail()
}
}
func TestGetNor(t *testing.T) {
res, err := ft.FastGet(url, args())
if err != nil {
t.Fail()
}
v := string(res)
log.Println(v)
if v != "get请求" {
t.Fail()
}
}
func TestPostJsonNor(t *testing.T) {
res, err := ft.FastPostJson(url, args())
if err != nil {
t.Fail()
}
v := string(res)
log.Println(v)
if v != "post请求json表单" {
t.Fail()
}
}
func TestPostFormNor(t *testing.T) {
res, err := ft.FastPostForm(url, args())
if err != nil {
t.Fail()
}
v := string(res)
log.Println(v)
if v != "post请求form表单" {
t.Fail()
}
}
测试报告
用的自带的控制台输出内容:
=== RUN TestGet
2021/10/18 18:56:49 get请求
--- PASS: TestGet (0.01s)
=== RUN TestPostJson
2021/10/18 18:56:49 post请求json表单
--- PASS: TestPostJson (0.00s)
=== RUN TestPostForm
2021/10/18 18:56:49 post请求form表单
--- PASS: TestPostForm (0.00s)
=== RUN TestGetNor
2021/10/18 18:56:49 get请求
--- PASS: TestGetNor (0.00s)
=== RUN TestPostJsonNor
2021/10/18 18:56:49 post请求json表单
--- PASS: TestPostJsonNor (0.00s)
=== RUN TestPostFormNor
2021/10/18 18:56:49 post请求form表单
--- PASS: TestPostFormNor (0.00s)
=== RUN TestStageJSON
Have Fun ~ Tester !
版权声明: 本文为 InfoQ 作者【FunTester】的原创文章。
原文链接:【http://xie.infoq.cn/article/827788f6aa584a67d35ade5db】。文章转载请联系作者。
FunTester
公众号:FunTester,650+原创,欢迎关注 2020.10.20 加入
Have Fun,Tester! 公众号FunTester,坚持原创文章的测试人。 FunTester测试框架作者,DCS_FunTester分布式性能测试框架作者。
评论