
# PerfTest插件开发样例
本章节介绍PerfTest插件开发样例，包含以下：
- [自定义请求样例]
- [自定义函数样例]
 #### 自定义请求样例
自定义请求Go样例：
```
package main
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"strings"
"sync"
)
var (
trs  = sync.Map{}
jars = sync.Map{}
)
func getTr(addr string) *http.Transport {
if v, ok := trs.Load(addr); ok {
return v.(*http.Transport)
} else {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
MaxConnsPerHost:     5000,
MaxIdleConnsPerHost: 5000,
}
v, _ = trs.LoadOrStore(addr, tr)
return v.(*http.Transport)
}
}
func getJar(goroutineId string) *cookiejar.Jar {
if v, ok := jars.Load(goroutineId); ok {
return v.(*cookiejar.Jar)
} else {
jar, _ := cookiejar.New(nil)
v, _ = jars.LoadOrStore(goroutineId, jar)
return v.(*cookiejar.Jar)
}
}
func outgoingLength(req *http.Request) int64 {
if req.Body == nil || req.Body == http.NoBody {
return 0
}
if req.ContentLength != 0 {
return req.ContentLength
}
return -1
}
func redirectBehavior(reqMethod string, resp *http.Response, req *http.Request) (redirectMethod string, shouldRedirect, includeBody bool) {
switch resp.StatusCode {
case 301, 302, 303:
redirectMethod = reqMethod
shouldRedirect = true
includeBody = false
if reqMethod != "GET" && reqMethod != "HEAD" {
redirectMethod = "GET"
}
case 307, 308:
redirectMethod = reqMethod
shouldRedirect = true
includeBody = true
if resp.Header.Get("Location") == "" {
shouldRedirect = false
break
}
if req.GetBody == nil && outgoingLength(req) != 0 {
shouldRedirect = false
}
}
return redirectMethod, shouldRedirect, includeBody
}
func Do(params map[string]string) interface{} {
parent := acquireResult(params[InnerVarName])
sub := parent
var (
err           error
redirectCount int
req           *http.Request
res           *http.Response
)
defer func() {
if p := recover(); p != nil {
sub.ResponseCode = 500
sub.Success = false
sub.FailureMessage = fmt.Sprintf("%v", sub)
} else if err != nil {
sub.ResponseCode = 500
sub.Success = false
sub.FailureMessage = err.Error()
}
}()
redirect := params["redirect"]
for {
// process params
url, method, body := params["url"], params["method"], params["body"]
sub.Url, sub.Method, sub.RequestBody = url, method, body
sub.SentBytes = len(body)
// create request
req, err = http.NewRequest(method, url, strings.NewReader(body))
if err != nil {
return parent
}
// load cookies
jar := getJar(params[InnerVarGoroutineId])
cookies := jar.Cookies(req.URL)
for _, c := range cookies {
req.AddCookie(c)
}
// write headers
if params["headers"] != "" {
headers := map[string][]string{}
if err = json.Unmarshal([]byte(params["headers"]), &headers); err != nil {
return parent
}
for k, vs := range headers {
for _, v := range vs {
req.Header.Add(k, v)
}
}
}
for k, v := range req.Header {
sub.RequestHeader[k] = strings.Join(v, ",")
}
// do request
sub.begin()
res, err = getTr(fmt.Sprintf("%s://%s", req.URL.Scheme, req.URL.Host)).RoundTrip(req)
sub.end()
if err != nil {
return parent
}
var b []byte
b, err = ioutil.ReadAll(res.Body)
if err != nil {
_ = res.Body.Close()
return parent
}
// store cookies
jar.SetCookies(req.URL, res.Cookies())
for k, v := range res.Header {
sub.ResponseHeader[k] = strings.Join(v, ",")
}
sub.ResponseBody = string(b)
sub.ReceivedBytes = len(b)
sub.ResponseCode = res.StatusCode
_ = res.Body.Close()
if redirect != "on" {
break
}
if redirectMethod, shouldRedirect, includeBody := redirectBehavior(method, res, req); shouldRedirect {
redirectCount++
if redirectCount > 10 {
err = errors.New("stopped after 10 redirects")
return parent
}
params["url"] = res.Header.Get("Location")
params["method"] = redirectMethod
if !includeBody {
params["body"] = ""
}
parent = acquireResult(params[InnerVarName])
sub = parent
continue
}
break
}
return parent
}
```
自定义请求JSON样例：
```
{
  "name": "Http 1.0",
  "plugin": "http.so",
  "desc": "Http plugin supports http 1.0 test.",
  "funcs": [
    {
      "name": "Do",
      "type": "req",
      "desc": "This is the entry of http request.",
      "params": [
        {
          "name": "url",
          "default": "",
          "range": [],
          "desc": "The url of http request",
          "required": true
        },
        {
          "name": "headers",
          "default": "{\"Content-Type\": [\"text/plain;utf-8\"]}",
          "range": [],
          "desc": "The headers of http request",
          "required": true
        },
        {
          "name": "method",
          "default": "GET",
          "range": [
            "GET",
            "HEAD",
            "POST",
            "PUT",
            "PATCH",
            "DELETE",
            "CONNECT",
            "OPTIONS",
            "TRACE"
          ],
          "desc": "The method of http request",
          "required": true
        },
        {
          "name": "body",
          "default": "",
          "range": [],
          "desc": "The body of http request",
          "required": true
        },
        {
          "name": "redirect",
          "default": "on",
          "range": [
            "on",
            "off"
          ],
          "desc": "If enable auto redirect",
          "required": true
        }
      ]
    }
  ]
}
```
 #### 自定义函数样例
自定义函数Go样例：
```
package main
import (
"fmt"
"math/rand"
"strconv"
)
func RandomNum(params map[string]string) (s string) {
var err error
defer func() {
if r := recover(); r != nil {
s = fmt.Sprintf("%v", r)
} else if err != nil {
s = err.Error()
}
}()
min, err := strconv.ParseInt(params["min"], 10, 64)
if err != nil {
return
}
max, err := strconv.ParseInt(params["max"], 10, 64)
if err != nil {
return
}
if min > max {
min, max = max, min
}
return strconv.FormatInt(min+rand.Int63n(max-min+1), 10)
}
var (
charSet = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
)
func RandomText(params map[string]string) (s string) {
var err error
defer func() {
if r := recover(); r != nil {
s = fmt.Sprintf("%v", r)
} else if err != nil {
s = err.Error()
}
}()
length, err := strconv.ParseInt(params["length"], 10, 64)
if err != nil {
return
}
if length == 0 {
return ""
}
set := []rune(params["charset"])
if len(set) == 0 {
set = charSet
}
n := len(set)
rs := make([]rune, length)
for i := int64(0); i < length; i++ {
rs[i] = set[rand.Intn(n)]
}
return string(rs)
}
```
自定义函数JSON样例：
```
{
  "name": "Random",
  "plugin": "random.so",
  "desc": "Random plugin supports generating random value.",
  "funcs": [
    {
      "name": "RandomNum",
      "type": "func",
      "desc": "RandomNum generates random number.",
      "params": [
        {
          "name": "min",
          "default": "0",
          "range": [],
          "desc": "The maximum of random number.",
          "required": true
        },
        {
          "name": "max",
          "default": "100",
          "range": [],
          "desc": "The maximum of random number.",
          "required": true
        }
      ]
    },
    {
      "name": "RandomText",
      "type": "func",
      "desc": "RandomText generates random text of a specified length.",
      "params": [
        {
          "name": "length",
          "default": "32",
          "range": [],
          "desc": "The length of random text.",
          "required": true
        },
        {
          "name": "charset",
          "default": "",
          "range": [],
          "desc": "Random text is generated randomly from the specified character set. if empty, default char set is 0-9 a-z A-Z",
          "required": false
        }
      ]
    }
  ]
}
```
