上一章中我们已经启动了一个/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日志")
}
启动项目。然后我们使用其他接口测试工具对我们的接口进行调试。
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": "***全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果***"
}
}