wrk入门(2):发送post请求

1 引言

       wrk的命令行参数只能发送类似于get、delete这种不在请求体中带参数的http请求,如果要发送post这类请求,必须要写Lua脚本。由于wrk具有内置的LuaJIT(用于Lua的即时编译器),可以使用Lua脚本进行扩展。

       也不必担心没学过Lua不会写脚本,Lua的可读性很强,类似于Python。通过下面简单的示例,完全可以模仿出一个更高级的脚本,况且官方也写了好几个模板供我们使用。

2 脚本结构

       在写脚本之前,先研究一下脚本结构,该结构也反映了wrk的内部逻辑,有助于我们更好的理解和编写脚本。如下图:

       图示为wrk的执行流程,分为以下几个阶段:

       1. 解析IP地址。

       2. 设置线程。

       3. 执行压力测试,也被称为运行阶段。

       4. 完成测试。

       上图只是一个单线程的图示。如果是多线程,流程图会变成这样:

       此外,running(运行阶段)还可以分为三个步骤:init(初始化),request(请求)和response(响应)。

       

       总结一下:wrk一次运行过程有1.resolve IP阶段;2. setUp阶段;3. running阶段(分为init、request、reponse);4.done阶段; 在上述的几个阶段中,都有对应的变量或方法提供调用。

3 不同阶段的方法和变量

       这个小节就是介绍一下各个阶段对应的方法和一个wrk全局表,大致过一遍眼熟一下就行,配合后面的脚本示例看简直效果拔群。    

       wrk全局表,存着一些变量供后面的方法调用:

wrk = {
    scheme  = "http",
    host    = "localhost",
    port    = nil,
    method  = "GET",
    path    = "/",
    headers = {},
    body    = nil,
    thread  = <userdata>,
  }

       wrk.format方法:返回一个HTTP请求字符串,该字符串是传递的参数与wrk表中的值的合并。

function wrk.format(method, path, headers, body)

    wrk.format returns a HTTP request string containing the passed parameters
    merged with values from the wrk table.

       wrk.lookup方法:返回一个表,其中包含主机和服务对的所有已知地址。这对应于POSIX getaddrinfo()函数。

function wrk.lookup(host, service)

    wrk.lookup returns a table containing all known addresses for the host
    and service pair. This corresponds to the POSIX getaddrinfo() function.

       wrk.connect方法:如果可以连接到请求地址,则返回true,否则返回false。该地址必须是wrk.lookup方法返回的地址。

  function wrk.connect(addr)

    wrk.connect returns true if the address can be connected to, otherwise
    it returns false. The address must be one returned from wrk.lookup().

       以下全局变量是可选的,并且如果定义必须是函数,也对应着上面流程图中的各个阶段,下面将一个个介绍:

global setup    -- called during thread setup
global init     -- called when the thread is starting
global delay    -- called to get the request delay
global request  -- called to generate the HTTP request
global response -- called with HTTP response data
global done     -- called with results of run

        Setup阶段:

        设置阶段是ip地址已经被成功解析并且线程初始化完毕但是还未启动的阶段,用于将数据传递给线程。每个线程调用一次setup()并接收代表该线程的userdata对象。

        只能通过get()/ set()方法设置或获取线程运行时需要的全局变量,变量值可以是布尔值,nil,数字和字符串值或表的值。

        thread:stop()只能在线程运阶段调用。

function setup(thread)

  The setup phase begins after the target IP address has been resolved and all
  threads have been initialized but not yet started.

  setup() is called once for each thread and receives a userdata object
  representing the thread.

    thread.addr             - get or set the thread's server address
    thread:get(name)        - get the value of a global in the thread's env
    thread:set(name, value) - set the value of a global in the thread's env
    thread:stop()           - stop the thread

  Only boolean, nil, number, and string values or tables of the same may be
  transfered via get()/set() and thread:stop() can only be called while the
  thread is running.

        Running阶段:

        运行阶段从调用init()方法开始,之后循环调用request()和response()方法。

        init()每个线程初始化时调用。可以接受命令行参数,但是为了和wrk的命令行参数做区分,参数名以“--”开头。比如:

wrk -c3 -d1s -t2 -s /scripts/debug.lua http://$APP1_PRIVATE_IP:3000 -- debug true

        delay()返回延迟发送下一个请求的毫秒数。

        request()需要为每个请求返回HTTP对象。在此函数中,我们可以修改method,headers,path,和 body。可以使用wrk.format()辅助函数来生成请求对象。比如:

return wrk.format(method, path, headers, body)

        response()在响应返回时调用。需要status(http响应状态),headers(http响应头),body(http响应体)三个参数,解析响应头和响应体非常消耗计算机资源,因此,如果调用init()方法之后全局变量response是nil,wrk将不会解析响应头和响应体。

Running

  function init(args)
  function delay()
  function request()
  function response(status, headers, body)

  The running phase begins with a single call to init(), followed by
  a call to request() and response() for each request cycle.

  The init() function receives any extra command line arguments for the
  script which must be separated from wrk arguments with "--".

  delay() returns the number of milliseconds to delay sending the next
  request.

  request() returns a string containing the HTTP request. Building a new
  request each time is expensive, when testing a high performance server
  one solution is to pre-generate all requests in init() and do a quick
  lookup in request().

  response() is called with the HTTP response status, headers, and body.
  Parsing the headers and body is expensive, so if the response global is
  nil after the call to init() wrk will ignore the headers and body.

        Done阶段:

        当所有请求都已经完成并且请求结果已经被统计时调用。

        done()函数接收一个包含结果数据的表(表是lua内置数据类型),以及两个统计对象:每个请求的延迟时间(以微秒为单位)、每个线程的请求速率(以每秒请求数为单位)。Done函数内,可以使用以下属性:

function done(summary, latency, requests)

  The done() function receives a table containing result data, and two
  statistics objects representing the per-request latency and per-thread
  request rate. Duration and latency are microsecond values and rate is
  measured in requests per second.

latency.min
-- minimum value seen 延时最小值 latency.max -- maximum value seen 延时最大值 latency.mean -- average value seen 延时平均值 latency.stdev -- standard deviation 延时标准差 latency:percentile(99.0) -- 99th percentile value 第99%的值 latency[i] -- raw value and count 请求i的原始延迟数据 summary = { duration = N, -- run duration in microseconds 运行持续时间(以微秒为单位) requests = N, -- total completed requests 完成的请求总数 bytes = N, -- total bytes received 接受的总字节数 errors = { connect = N, -- total socket connection errors socket连接错误的总数目 read = N, -- total socket read errors socket读取错误的总数目 write = N, -- total socket write errors socket写入错误的总数目 status = N, -- total HTTP status codes > 399 HTTp状态码大于399的总数目 timeout = N -- total request timeouts 所有请求的超时时间总和 } }

4 Post请求示例

        首先,在wrk1(博主本地虚拟机)上创建脚本文件夹和文件

[root@iQOO-Z1 pyy]# mkdir scripts
[root@iQOO-Z1 pyy]# vi scripts/post.lua

        添加以下内容到post.lua:

wrk.method = "POST"
wrk.body = '{"username":"admin","password":"123456"}'
wrk.headers["Content-Type"] = "application/json"

response = function(status, headers, body)
print(body) --调试用,正式测试时需要关闭,因为解析response非常消耗资源
end

        这个脚本非常简单,仅仅修改了全局wrk对象属性和重载了response()方法,response()只是打印了一下响应体内容。使用带有wrk的Lua脚本只需将文件路径附加到-s参数后面即可:

# -v 数据卷挂载,将本地目录与wrk容器内目录共享; `pwd` 代表当前目录
docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c1 -t1 -d1s -s /scripts/post.lua http://xxx.xx.xx.xx:port/url

        运行结果如下,可以看见已经登录成功并且返回了用户token:

        上面实现了post请求,但是还有提升效率的方法,比如每次测试都要输入一遍命令未免有点繁琐,所以可以考虑写一个简单的shell脚本来更快捷的进行测试。

        创建shell文件并输入命令代码,如下:

[root@iQOO-Z1 pyy]# vi login_test.sh

# content
docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c$1 -t$2 -d$3 -s /scripts/post.lua http://xx.x.xx.xx:port/login

[root@iQOO-Z1 pyy]# chmod +x login_test.sh
[root@iQOO-Z1 pyy]# ./login_test.sh 1 1 1s

        运行结果:

        查看更多脚本模板(可能需要FQ) 

        Lua语法练习     

        未完待续........

posted @ 2021-01-31 16:44  章土  阅读(3786)  评论(0编辑  收藏  举报