计算机系统应用教程网站

网站首页 > 技术文章 正文

【测试平台系列】第一章 手撸压力机(八)- 实现testobject接口

btikc 2024-08-29 11:49:58 技术文章 14 ℃ 0 评论

上一章中我们已经启动了一个/engine/run/testObject/接口,但是我们还没有具体的实现该接口,本章我们就来实现以下该接口。

首先,我们在global目录下新建common/response.go,我们在response.go文件中定义好/engine/run/testObject/接口的响应信息。

// Package common -----------------------------
// @file      : response.go
// @author    : 被测试耽误的大厨
// @contact   : 13383088061@163.com
// @time      : 2023/7/4 18:43
// -------------------------------------------
package common

import "github.com/gin-gonic/gin"

// Response 定义一个响应体,当请求我们的接口时,返回使用
type Response struct {
        Code    int32       json:"code"    // 响应码
        Id      string      json:"id"      // 唯一id
        Message string      json:"message" // 消息
        Data    interface{} json:"data"    // 具体信息
}


/*
  返回响应信息,使用ctx.Json返回json数据
*/

func ReturnResponse(ctx *gin.Context, code int32, id string, msg string, data interface{}) {
        ctx.JSON(
                200,
                Response{
                        code,
                        id,
                        msg,
                        data,
                })
}


然后,我们在model/test_object.go中定义一个接口体接收我们发送的请求的请求及响应信息。test_object.go全部代码如下:

// Package model -----------------------------
// @file      : test_object.go
// @author    : 被测试耽误的大厨
// @contact   : 13383088061@163.com
// @time      : 2023/6/11 20:38
// -------------------------------------------
package model

// TestObjectResponse 响应信息, 用于返回给调用方
type TestObjectResponse struct {
        Name            string            json:"name"         // 对象名称
        Id              string            json:"id"           // 唯一id
        ParentId        string            json:"parent_id"    // 父id
        ObjectType      string            json:"object_type"  // 对象类型http、websocket、dubbo等
        ItemId          string            json:"item_id"      // 项目Id
        TeamId          string            json:"team_id"      // 团队Id
        SourceId        string            json:"source_id"    // 源Id
        ChannelId       string            json:"channel_id"   // 渠道Id比如YApi,postman等
        ChannelType     string            json:"channel_type" // 渠道类型
        Code            int               json:"code"
        RequestHeaders  map[string]string json:"request_headers"
        ResponseHeaders map[string]string json:"response_headers"
        Response        string            json:"response"
        RequestTime     int64             json:"request_time"
}

// TestObject 测试对象结构体
type TestObject struct {
        Name        string      json:"name"         // 对象名称
        Id          string      json:"id"           // 唯一id
        ParentId    string      json:"parent_id"    // 父id
        ObjectType  string      json:"object_type"  // 对象类型http、websocket、dubbo等
        ItemId      string      json:"item_id"      // 项目Id
        TeamId      string      json:"team_id"      // 团队Id
        SourceId    string      json:"source_id"    // 源Id
        ChannelId   string      json:"channel_id"   // 渠道Id比如YApi,postman等
        ChannelType string      json:"channel_type" // 渠道类型
        HttpRequest HttpRequest json:"http_request"
}

// Dispose 测试对象的处理函数,在go语言中 Dispose方法是TestObject对象的方法,其他对象不能使用
func (to TestObject) Dispose(response *TestObjectResponse) {
        switch to.ObjectType {
        case HTTP1: // 由于我们有个多类型,为了方便统计,我们定义好变量,直接进行比对即可
                to.HttpRequest.Request(response)
                return
        }
        return
}


上述代码中,大家可以我们把Dispose函数进行了优化:

原代码:

// Dispose 测试对象的处理函数,在go语言中 Dispose方法是TestObject对象的方法,其他对象不能使用
func (to TestObject) Dispose() {
        switch to.ObjectType {
        case HTTP1: // 由于我们有个多类型,为了方便统计,我们定义好变量,直接进行比对即可
                client.RequestHttp(to.HttpRequest)
        }
}

现代码:

// Dispose 测试对象的处理函数,在go语言中 Dispose方法是TestObject对象的方法,其他对象不能使用
func (to TestObject) Dispose(response *TestObjectResponse) {
        switch to.ObjectType {
        case HTTP1: // 由于我们有个多类型,为了方便统计,我们定义好变量,直接进行比对即可
                to.HttpRequest.Request(response)
                return
        }
        return
}

在现代码中,我们首先给Dispose函数加了个TestObjectResponse的指针参数,其次,我们将client.RequestHttp函数修改为HttpRequest.Request方法。现在,我们将client目录删除(包括http_client.go文件)。然后我们修改model/http_request.go文件如下:


package model

import (
        "crypto/tls"
        "crypto/x509"
        "fmt"
        "github.com/valyala/fasthttp"
        "io/ioutil"
        "kitchen-engine/global/log"
        "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               string             json:"body"                 // 请求提
        HttpClientSettings HttpClientSettings json:"http_client_settings" // http客户端配置
}

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)

        // 添加该请求的http方法:get、post、delete、update等等
        req.Header.SetMethod(hr.Method)

        // 设置header
        for _, header := range hr.Headers {
                if strings.EqualFold(header.Field, "host") {
                        // 由于在header中设置host不生效,所以需要强制设置生效
                        req.SetHost(header.Value)
                        req.UseHostHeader = true
                } else {
                        req.Header.Add(header.Field, header.Value)
                }

        }

        // 设置cookie
        for _, cookie := range hr.Cookies {
                req.Header.SetCookie(cookie.Field, cookie.Value)
        }

        // 如果query不为空则设置query
        urlQuery := req.URI().QueryArgs()
        for _, query := range hr.Querys {
                if !strings.Contains(hr.Url, query.Field) {
                        queryBy := []byte(query.Value)
                        urlQuery.AddBytesV(query.Field, queryBy)
                        hr.Url += fmt.Sprintf("&%s=%s", query.Field, query.Value)
                }
        }

        req.SetBody([]byte(hr.Body))
        // 添加该请求的http的url
        req.SetRequestURI(hr.Url)

        // 记录开始时间
        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
        }
        log.Logger.Debug("resp:    ", string(resp.Body()))
        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 {
        Field     string json:"field"      // 字段名称
        Value     string json:"value"      // 字段值
        FieldType string json:"field_type" // 字段类型
}

// Query query
type Query struct {
        Field     string json:"field"
        Value     string json:"value"
        FieldType string json:"field_type"
}

// Cookie cookie
type Cookie struct {
        Field     string json:"field"
        Value     string json:"value"
        FieldType string json:"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"     // 密钥文件
}

再在我们项

目的根目录新建service/api.go,我们定义RunTestObject方法来实现我们的接口。

// Package service -----------------------------
// @file      : api.go
// @author    : 被测试耽误的大厨
// @contact   : 13383088061@163.com
// @time      : 2023/7/4 18:05
// -------------------------------------------
package service

import (
        "encoding/json"
        "fmt"
        "github.com/gin-gonic/gin"
        "github.com/google/uuid"
        "kitchen-engine/global/common"
        "kitchen-engine/global/log"
        "kitchen-engine/model"
        "net/http"
)


/*
        RunTestObject 实现run/testObject/接口
*/
func RunTestObject(c *gin.Context) {

        // 声明一个TO对象
        var testObject model.TestObject

        // 接收json格式的请求数据
        err := c.ShouldBindJSON(&testObject)
        id := uuid.New().String()
        // 如果请求格式错误
        if err != nil {
                log.Logger.Error("请求数据格式错误", err.Error())
                common.ReturnResponse(c, http.StatusBadRequest, id, "请求数据格式错误!", err.Error())
                return
        }

        // 使用json包解析以下TO对象, 解析出来为[]byte类型
        requestJson, _ := json.Marshal(testObject)
        // 打印以下日志, 使用fmt.Sprintf包格式花数据,%s 表示string(requestJson)为字符串类型,如果不确定类型,可以使用%v表示
        log.Logger.Debug(fmt.Sprintf("测试对象: %s", string(requestJson)))

        response := model.TestObjectResponse{
                Name:        testObject.Name,
                Id:          testObject.Id,
                ParentId:    testObject.ParentId,
                ObjectType:  testObject.ObjectType,
                ItemId:      testObject.ItemId,
                SourceId:    testObject.SourceId,
                ChannelId:   testObject.ChannelId,
                ChannelType: testObject.ChannelType,
        }

        // 开始处理TO
        testObject.Dispose(&response)
        // 返回响应消息
        common.ReturnResponse(c, http.StatusOK, id, "请求成功!", response)
        return
}


main.go如下:

func main() {
        log.Logger.Debug("yc:   ", config.YC)
        runService()
        log.Logger.Info("欢迎使用zap日志")
}


启动项目。然后我们使用apipost对我们的接口进行调试。

method: POST
url: http:127.0.0.1:8003/engine/run/testObject/
body: 
{
    "name": "百度",
    "id": "12312312",
    "parent_id": "",
    "object_type": "HTTP1.1",
    "item_id": "",
    "http_request": {
        "url": "http://www.baidu.com",
        "method": "GET"
    }

}


响应信息如下:

{
        "code": 200,
        "id": "b531a6db-ba65-4bb4-b9cb-fb252a210996",
        "message": "请求成功!",
        "data": {
                "name": "百度",
                "id": "12312312",
                "parent_id": "",
                "object_type": "HTTP1.1",
                "item_id": "",
                "team_id": "",
                "source_id": "",
                "channel_id": "",
                "channel_type": "",
                "code": 200,
                "request_headers": null,
                "response_headers": null,
                "response": "<!DOCTYPE html><!--STATUS OK--><html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\"><meta content=\"always\" name=\"referrer\"><meta name=\"theme-color\" content=\"#ffffff\"><meta name=\"description\" content=\"全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。\">"
      }
}

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表