到这里,大家已经知道如何通过一个web服务,来构建我们的压力机的逻辑了。上一篇我们已经通过/engin/run/testObject/接口完成了,我们对一次接口请求的调用。算是大家对这种设计思想有了初步的了解。但是我们只是完成了初步的逻辑处理,这一篇我们继续进行优化。
将我们的一些代码封装到函数和方法中,这样我们看来代码可读性更好。如果发现bug,也可以更好的进行追踪。
我们首先将header\query\body\cookie结构体进行一下变更。
// Header header
type Header struct {
IsEnable bool `json:"is_enable"` // 是否启用
Field string `json:"field"` // 字段名称
Value string `json:"value"` // 字段值
FieldType string `json:"field_type"` // 字段类型
}
// Query query
type Query struct {
IsEnable bool `json:"is_enable"` // 是否启用
Field string `json:"field"`
Value string `json:"value"`
FieldType string `json:"field_type"`
}
// Cookie cookie
type Cookie struct {
IsEnable bool `json:"is_enable"` // 是否启用
Field string `json:"field"`
Value string `json:"value"`
FieldType string `json:"field_type"`
}
// Body body
type Body struct {
Type string `json:"type"`
Raw string `json:"raw"`
Parameter []*VarForm `json:"parameter"`
}
// VarForm 参数表
type VarForm struct {
IsEnable bool `json:"is_enable" bson:"is_enable"`
Type string `json:"type" bson:"type"`
FileBase64 []string `json:"fileBase64"`
Key string `json:"key" bson:"key"`
Value interface{} `json:"value" bson:"value"`
NotNull int64 `json:"not_null" bson:"not_null"`
Description string `json:"description" bson:"description"`
FieldType string `json:"field_type" bson:"field_type"`
}
在global目录下新建constant/constant.go存放我们使用的常量。
// Package constant -----------------------------
// @file : constant.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/7/5 18:30
// -------------------------------------------
package constant
// http请求body的类型, 由于http的body有很多类型,所以我们定义使用常量定义好这些类型
const (
NoneMode = "none"
FormMode = "multipart/form-data"
UrlencodeMode = "application/x-www-form-urlencoded"
JsonMode = "application/json"
XmlMode = "application/xml"
JSMode = "application/javascript"
PlainMode = "text/plain"
HtmlMode = "text/html"
)
然后在http_request.go文件如新增如下的HttpRequest对象的方法
// 设置url
func (hr *HttpRequest) urlInit(req *fasthttp.Request) {
// 去除url两端的空格
hr.Url = strings.TrimSpace(hr.Url)
// 判断url是否以http或者https开头,如果不是,则给url加上http://
if !strings.HasPrefix(hr.Url, "http") && !strings.HasPrefix(hr.Url, "https") {
hr.Url = fmt.Sprintf("%s%s", "http://", hr.Url)
}
// 添加该请求的http的url
req.SetRequestURI(hr.Url)
}
// 设置method
func (hr *HttpRequest) methodInit(req *fasthttp.Request) {
// 将method转换为大写,如get->GET
hr.Method = strings.ToUpper(hr.Method)
// 添加该请求的http方法:get、post、delete、update等等
req.Header.SetMethod(hr.Method)
}
// 设置header
func (hr *HttpRequest) headerInit(req *fasthttp.Request) {
for _, header := range hr.Headers {
// 该跳header没有启用,则跳过
if !header.IsEnable {
continue
}
// 去除header的key、value中两端的空格
header.Field = strings.TrimSpace(header.Field)
header.Value = strings.TrimSpace(header.Value)
if strings.EqualFold(header.Field, "host") {
// 由于在header中设置host不生效,所以需要强制设置生效
req.SetHost(header.Value)
req.UseHostHeader = true
} else {
req.Header.Add(header.Field, header.Value)
}
}
}
// 设置query
func (hr *HttpRequest) queryInit(req *fasthttp.Request) {
// 如果query不为空则设置query
urlQuery := req.URI().QueryArgs()
for _, query := range hr.Querys {
if !query.IsEnable {
continue
}
// 去除query的key中两端空格
query.Field = strings.TrimSpace(query.Field)
// 如果url中已经包含了该query的key则跳过
if strings.Contains(hr.Url, query.Field+"=") {
continue
}
// 去除query的value中两端空格
query.Value = strings.TrimSpace(query.Value)
queryBy := []byte(query.Value)
urlQuery.AddBytesV(query.Field, queryBy)
hr.Url += fmt.Sprintf("&%s=%s", query.Field, query.Value)
}
}
// 设置cookie
func (hr *HttpRequest) cookieInit(req *fasthttp.Request) {
// 设置cookie
for _, cookie := range hr.Cookies {
if !cookie.IsEnable {
continue
}
// 去除header的key、value中两端的空格
cookie.Field = strings.TrimSpace(cookie.Field)
cookie.Value = strings.TrimSpace(cookie.Value)
req.Header.SetCookie(cookie.Field, cookie.Value)
}
}
// 设置body
func (hr *HttpRequest) bodyInit(req *fasthttp.Request) {
if hr.Body == nil {
return
}
switch hr.Body.Type {
case constant.JSMode, constant.JsonMode, constant.HtmlMode, constant.PlainMode, constant.XmlMode:
req.Header.SetContentType(hr.Body.Type)
req.SetBody([]byte(hr.Body.Raw))
}
}
修改HttpRequest对象的Request方法如下:
func (hr *HttpRequest) Request(response *TestObjectResponse) {
// 使用fasthttp 协程池
// 新建一个http请求
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)
// 新建一个http响应接受服务端的返回
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp)
// 新建一个http的客户端, newHttpClient是一个方法,在下面
client := newHttpClient(hr.HttpClientSettings)
// 设置请求方法
hr.methodInit(req)
// 设置header
hr.headerInit(req)
// 设置query
hr.queryInit(req)
// 设置cookie
hr.cookieInit(req)
// 设置url
hr.urlInit(req)
// 设置body
hr.bodyInit(req)
// 记录开始时间
startTime := time.Now()
// 开始请求
err := client.Do(req, resp)
// 计算响应时间差值
requestTime := time.Since(startTime)
response.RequestTime = requestTime.Milliseconds()
response.Code = resp.StatusCode()
if err != nil {
response.Response = err.Error()
return
}
response.Response = string(resp.Body())
}
目前http_request.go文件全部代码如下:
package model
import (
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/valyala/fasthttp"
"io/ioutil"
"kitchen-engine/global/constant"
"strings"
"time"
)
// HttpRequest http请求的结构
type HttpRequest struct {
Url string `json:"url"` // 接口uri
Method string `json:"method"` // 接口方法,Get Post Update...
Headers []Header `json:"headers"` // 接口请求头
Querys []Query `json:"querys"` // get请求时的url
Cookies []Cookie `json:"cookies"` // cookie
Body *Body `json:"body"` // 请求提
HttpClientSettings HttpClientSettings `json:"http_client_settings"` // http客户端配置
}
// 设置url
func (hr *HttpRequest) urlInit(req *fasthttp.Request) {
// 去除url两端的空格
hr.Url = strings.TrimSpace(hr.Url)
// 判断url是否以http或者https开头,如果不是,则给url加上http://
if !strings.HasPrefix(hr.Url, "http") || !strings.HasPrefix(hr.Url, "https") {
hr.Url = fmt.Sprintf("%s%s", "https://", hr.Url)
}
// 添加该请求的http的url
req.SetRequestURI(hr.Url)
}
// 设置method
func (hr *HttpRequest) methodInit(req *fasthttp.Request) {
// 将method转换为大写,如get->GET
hr.Method = strings.ToUpper(hr.Method)
// 添加该请求的http方法:get、post、delete、update等等
req.Header.SetMethod(hr.Method)
}
// 设置header
func (hr *HttpRequest) headerInit(req *fasthttp.Request) {
for _, header := range hr.Headers {
// 该跳header没有启用,则跳过
if !header.IsEnable {
continue
}
// 去除header的key、value中两端的空格
header.Field = strings.TrimSpace(header.Field)
header.Value = strings.TrimSpace(header.Value)
if strings.EqualFold(header.Field, "host") {
// 由于在header中设置host不生效,所以需要强制设置生效
req.SetHost(header.Value)
req.UseHostHeader = true
} else {
req.Header.Add(header.Field, header.Value)
}
}
}
// 设置query
func (hr *HttpRequest) queryInit(req *fasthttp.Request) {
// 如果query不为空则设置query
urlQuery := req.URI().QueryArgs()
for _, query := range hr.Querys {
if !query.IsEnable {
continue
}
// 去除query的key中两端空格
query.Field = strings.TrimSpace(query.Field)
// 如果url中已经包含了该query的key则跳过
if strings.Contains(hr.Url, query.Field+"=") {
continue
}
// 去除query的value中两端空格
query.Value = strings.TrimSpace(query.Value)
queryBy := []byte(query.Value)
urlQuery.AddBytesV(query.Field, queryBy)
hr.Url += fmt.Sprintf("&%s=%s", query.Field, query.Value)
}
}
// 设置cookie
func (hr *HttpRequest) cookieInit(req *fasthttp.Request) {
// 设置cookie
for _, cookie := range hr.Cookies {
if !cookie.IsEnable {
continue
}
// 去除header的key、value中两端的空格
cookie.Field = strings.TrimSpace(cookie.Field)
cookie.Value = strings.TrimSpace(cookie.Value)
req.Header.SetCookie(cookie.Field, cookie.Value)
}
}
// 设置body
func (hr *HttpRequest) bodyInit(req *fasthttp.Request) {
if hr.Body == nil {
return
}
switch hr.Body.Type {
case constant.JSMode, constant.JsonMode, constant.HtmlMode, constant.PlainMode, constant.XmlMode:
req.Header.SetContentType(hr.Body.Type)
req.SetBody([]byte(hr.Body.Raw))
}
}
func (hr *HttpRequest) Request(response *TestObjectResponse) {
// 使用fasthttp 协程池
// 新建一个http请求
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)
// 新建一个http响应接受服务端的返回
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp)
// 新建一个http的客户端, newHttpClient是一个方法,在下面
client := newHttpClient(hr.HttpClientSettings)
// 设置请求方法
hr.methodInit(req)
// 设置header
hr.headerInit(req)
// 设置query
hr.queryInit(req)
// 设置cookie
hr.cookieInit(req)
// 设置url
hr.urlInit(req)
// 设置body
hr.bodyInit(req)
// 记录开始时间
startTime := time.Now()
// 开始请求
err := client.Do(req, resp)
// 计算响应时间差值
requestTime := time.Since(startTime)
response.RequestTime = requestTime.Milliseconds()
response.Code = resp.StatusCode()
if err != nil {
response.Response = err.Error()
return
}
response.Response = string(resp.Body())
}
// 新建一个http客户端
func newHttpClient(httpClientSettings HttpClientSettings) (httpClient *fasthttp.Client) {
// tls验证,关闭验证
tr := &tls.Config{
InsecureSkipVerify: true,
}
// 新建指针类型的客户端
httpClient = &fasthttp.Client{}
if httpClientSettings.Name != "" {
httpClient.Name = httpClientSettings.Name
}
if httpClientSettings.NoDefaultUserAgentHeader == true {
httpClient.NoDefaultUserAgentHeader = true
}
// 如果最大连接数不为0,将设置此数
if httpClientSettings.MaxConnsPerHost != 0 {
httpClient.MaxConnsPerHost = httpClientSettings.MaxConnsPerHost
}
// url不按照标准输出,按照原样输出
if httpClientSettings.DisablePathNormalizing == true {
httpClient.DisablePathNormalizing = true
}
// 请求头不按标准格式传输
if httpClientSettings.DisableHeaderNamesNormalizing == true {
httpClient.DisableHeaderNamesNormalizing = true
}
// 如果此时间不为0,那么将设置此时间。keep-alive维持此时长后将关闭。时间单位为毫秒
if httpClientSettings.MaxConnDuration != 0 {
httpClient.MaxConnDuration = time.Duration(httpClientSettings.MaxConnDuration) * time.Millisecond
}
if httpClientSettings.ReadTimeout != 0 {
httpClient.ReadTimeout = time.Duration(httpClientSettings.ReadTimeout) * time.Millisecond
}
if httpClientSettings.WriteTimeout != 0 {
httpClient.WriteTimeout = time.Duration(httpClientSettings.WriteTimeout) * time.Millisecond
}
// 该连接如果空闲的话,在此时间后断开。
if httpClientSettings.MaxIdleConnDuration != 0 {
httpClient.MaxIdleConnDuration = time.Duration(httpClientSettings.MaxIdleConnDuration) * time.Millisecond
}
//
httpsTls := httpClientSettings.AdvancedOptions.Tls
// 如果开启认证
if httpsTls.IsVerify {
// switch条件选择语句,如果认证类型为0:则表示双向认证,如果是1:则表示为单向认证
switch httpsTls.VerifyType {
case 0: // 开启双向验证
tr.InsecureSkipVerify = false
// 如果密钥文件为空则跳出switch语句
if httpsTls.CaCert == "" {
break
}
// 生成一个cert对象池
caCertPool := x509.NewCertPool()
if caCertPool == nil {
fmt.Println("生成CertPool失败!")
break
}
// 读取认证文件,读出后为字节
key, err := ioutil.ReadFile(httpsTls.CaCert)
// 如果读取错误,则跳出switch语句
if err != nil {
fmt.Println("打开密钥文件失败: ", err.Error())
break
}
// 将认证文件添加到cert池中
ok := caCertPool.AppendCertsFromPEM(key)
// 如果添加失败则跳出switch语句
if !ok {
fmt.Println("密钥文件错误,生成失败!!!")
break
}
// 将认证信息添加到客户端认证结构体
tr.ClientCAs = caCertPool
case 1: // 开启单向验证,客户端验证服务端密钥
tr.InsecureSkipVerify = false
}
}
fmt.Println("tr: ", tr.InsecureSkipVerify)
// 客户端认证配置项
httpClient.TLSConfig = tr
return
}
// Header header
type Header struct {
IsEnable bool `json:"is_enable"` // 是否启用
Field string `json:"field"` // 字段名称
Value string `json:"value"` // 字段值
FieldType string `json:"field_type"` // 字段类型
}
// Query query
type Query struct {
IsEnable bool `json:"is_enable"` // 是否启用
Field string `json:"field"`
Value string `json:"value"`
FieldType string `json:"field_type"`
}
// Cookie cookie
type Cookie struct {
IsEnable bool `json:"is_enable"` // 是否启用
Field string `json:"field"`
Value string `json:"value"`
FieldType string `json:"field_type"`
}
// Body body
type Body struct {
Type string `json:"type"`
Raw string `json:"raw"`
Parameter []*VarForm `json:"parameter"`
}
// VarForm 参数表
type VarForm struct {
IsEnable bool `json:"is_enable" bson:"is_enable"`
Type string `json:"type" bson:"type"`
FileBase64 []string `json:"fileBase64"`
Key string `json:"key" bson:"key"`
Value interface{} `json:"value" bson:"value"`
NotNull int64 `json:"not_null" bson:"not_null"`
Description string `json:"description" bson:"description"`
FieldType string `json:"field_type" bson:"field_type"`
}
type HttpClientSettings struct {
// 客户端的名称,在header中的user-agent使用,通常我们默认就好
Name string `json:"name"`
// 默认为flase,表示User-Agent使用fasthttp的默认值
NoDefaultUserAgentHeader bool `json:"no_default_user_agent_header"`
// 每台主机可以建立的最大连接数。如果没有设置,则使用DefaultMaxConnsPerHost。
MaxConnsPerHost int `json:"max_conns_per_host"`
// 空闲的保持连接在此持续时间之后关闭。默认情况下,在DefaultMaxIdleConnDuration之后关闭空闲连接。
// 该连接如果空闲的话,在此时间后断开。
MaxIdleConnDuration int64 `json:"max_idle_conn_duration"`
// Keep-alive连接在此持续时间后关闭。默认情况下,连接时间是不限制的。
MaxConnDuration int `json:"max_conn_duration"`
// 默认情况下,响应读取超时时间是不限制的。
ReadTimeout int64 `json:"read_timeout"`
// 默认情况下,请求写超时时间不受限制。
WriteTimeout int64 `json:"write_timeout"`
// 请求头是否按标准格式传输
DisableHeaderNamesNormalizing bool `json:"disable_header_names_normalizing"`
// url路径是按照原样输出,还是按照规范化输出。默认按照规范化输出
DisablePathNormalizing bool `json:"disable_path_normalizing"`
AdvancedOptions AdvancedOptions `json:"advanced_options"` // 高级选项
}
// AdvancedOptions 高级选项
type AdvancedOptions struct {
Tls Tls `json:"tls"` // 验证设置
}
// Tls tls认证结构体
type Tls struct {
IsVerify bool `json:"is_verify"` // 是否开启验证,默认不开启,开启后需要上传密钥文件
VerifyType int32 `json:"verify_type"` // 认证类型:0表示双向认证;1表示单向认证;默认为0
CaCert string `json:"ca_cert"` // 密钥文件
}
好了,现在我们简单对http请求做了一些优化,下一步,我们开始将单个接口组成按业务流程组成场景进行使用。