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

2023-11-27 18:13:27 浏览数 (1)

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

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

代码语言:javascript复制
// 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全部代码如下:

代码语言:javascript复制
// 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函数进行了优化:

代码语言:javascript复制
原代码:
// 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文件如下:

代码语言:javascript复制
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方法来实现我们的接口。

代码语言:javascript复制
// 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如下:

代码语言:javascript复制
func main() {
  log.Logger.Debug("yc:   ", config.YC)
  runService()
  log.Logger.Info("欢迎使用zap日志")
}

启动项目。然后我们使用其他接口测试工具对我们的接口进行调试。

代码语言:javascript复制
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"
    }

}

响应信息如下:

代码语言:javascript复制
{
  "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": "***全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果***"
  }
}

0 人点赞