摘要
在 Web
开发过程中,文件上传功能是常见的需求。对于开发者而言,确保上传功能的稳定性和性能至关重要。
本文将带你了解如何使用ApacheBench
性能测试工具对文件上传功能进行性能测试,特别是如何按照规范构造上传文件的请求体,以便评估和提高服务器在高并发情况下的处理能力。
ApacheBench 简介
ApacheBench
(简称ab
)是 Apache
服务器自带的一个性能测试工具,它能够模拟多用户并发请求,从而评估服务器在高负载下的性能表现。若系统中未安装 Apache
服务器,可前往 Apache 官网下载和安装。更多关于 ApacheBench 的信息,请参考:ApacheBench 简介。
前置条件
假设有一个文件上传 POST
接口 http://localhost:8080/upload
,在请求体中接受 key
为 file
的文件,并返回上传的文件名和文件大小:
通过 Postman
等工具,我们可以直观地看到上传文件的请求内容:
POST /upload HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 204
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="/C:/Users/admin/Desktop/test.jpg"
Content-Type: image/jpeg
(data)
------WebKitFormBoundary7MA4YWxkTrZu0gW--
问题
当需要使用 ApacheBench
测试上传文件的 POST
接口时,ab -h
中只写到了通过 -p
参数指定 postfile
:
-p postfile File containing data to POST. Remember also to set -T
那么这个 postfile
中包含哪些内容呢?应该如何构造一个 ApacheBench
可用的 postfile
呢?
有关 multipart/form-data
的规范
RFC 7578 第4节 中关于 multipart/form-data
的定义提到:multipart/form-data
遵循 RFC 2046 第 5.1 节 中定义的多部分 MIME
数据流结构,并有一些变化,大致的结构要求如下:
- 请求头必须包含
Content-Type: multipart/form-data; boundary=边界分隔符
; - 多部分文件需要组合成一个单个的请求体,
边界分隔符
字符串需保证在整个请求体内唯一,不会出现在分割行以外的其他部分; - 请求体必须包含一个或多个部分,每部分一个实体(如:文件);
- 各部分使用 CRLF +
--
+边界分隔符
作为一个边界分割行
进行分隔,最后一部分后面使用边界分隔行
+--
表示结束;换行符均需使用 CRLF(即\r\n
,即使在非 Windows 环境中); - 每部分在边界分隔行之间,又由三部分组成:头区域、空白行、内容区域;
- 每部分头区域必须包含
Content-Disposition
头字段,类型为form-data
;同时必须包含name
参数,值为form
中的原始字段名;当内容区域表示的是文件时,还应该提供filename
参数。
更详细的内容可参考上面引用的 RFC
规范文档。
构造 postfile
下面依照规范中格式要求,构造一个只发送一个文件的 postfile
。
1. 准备 postfile 文件
- 准备要上传的文件:如:
test.jpg
; - 新建一个文本文件,命名为
postfile.txt
。
2. 确定边界分隔符
选择一个不会在文件内容中出现的字符串作为边界分隔符,例如:----WebKitFormBoundary7MA4YWxkTrZu0gW
。
3. 编写 postfile 头区域及空白行
在 postfile.txt
中写入以下内容,这些内容构成了请求体的头部信息,其中 name
应该与服务器端接收的字段名一致,filename
是要上传的文件的名称。
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test.jpg"
Content-Type: image/jpeg
依据规范描述,Content-Type 头字段非必要。
此处需注意:
因为规范要求使用CRLF
作为换行符,在非 Windows
环境中,不能直接使用文本编辑器输入上面内容,可以按如下方式通过命令构造此部分内容:
$ echo -e '------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name="file"; filename="test.jpg"\r\nContent-Type: image/jpeg\r\n\r' > postfile.txt
通过
>
、>>
向文件中添加内容时,会自动在最后追加一个 LR(\n
),所以省略最后一个\n
。
使用 cat -e
可验证换行符,每行结尾是 ^M$
代表 CRLF
换行符:
$ cat -e postfile.txt
------WebKitFormBoundary7MA4YWxkTrZu0gW^M$
Content-Disposition: form-data; name="file"; filename="test.jpg"^M$
Content-Type: image/jpeg^M$
^M$
4. 添加文件内容至 postfile 内容区域
由于文件内容通常是二进制数据,不能直接在文本编辑器中粘贴,可使用 cat
命令将文件内容追加至 postfile
中:
# 将 test.jpg 文件内容追加到 postfile.txt
$ cat test.jpg >> postfile.txt
Windows 系统中可在
git bash
中使用cat
命令。
Benchmarking file uploads 中以 base64 值作为文件数据内容进行传输的方式能够正常调用接口,但实测服务端接收到的数据内容依旧是 base64 的内容,并不是文件原始内容。故此处直接使用
cat
将文件原始数据内容直接追加到postfile
中。
5. 添加结束标记
最后以 CRLF+--
+边界分隔符
+--
标记结束:
# 将结束标记添加到 postfile.txt
$ echo -e "\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--" >> postfile.txt
因为在将文件流内容追加至
postfile.txt
文件后,已无法使用文本编辑器直接打开此文件,故继续使用命令追加文件内容,同样在 Windows 环境中可以通过git bash
使用echo
命令。
执行 ab 命令
使用以下命令执行文件上传测试:
$ ab -n 1 -c 1 -p postfile.txt -v 2 \
-T "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" \
http://localhost:8080/upload \
-v 2
-n 1
表示总共发送 1 个请求(根据实际测试需求进行调整)。-c 1
表示同时并发 1 个请求(根据实际测试需求进行调整)。-p postfile.txt
指定包含POST
数据的文件。-T "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"
指定请求的内容类型,包括之前定义的边界分隔符。-v verbosity
用于设置ApacheBench
的详细输出级别。在详细输出级别2
下,ApacheBench
会打印出警告信息和信息信息,如:请求和响应的头部信息。
执行命令后,在控制台中,可以在 LOG: header received:
消息之后找到响应状态码和响应内容。如果状态码为 200
且和预期值一致,表示服务器成功处理了请求。
在控制台中还会提供一系列关键指标,如每秒请求数、请求平均响应时间等关键指标。这些数据可以帮助分析文件上传的性能表现,并为优化提供依据。
This is ApacheBench, Version 2.3 <$Revision: 1901567 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)...INFO: POST header ==
---
POST /upload HTTP/1.0
Content-length: 24223
Content-type: multipart/form-data; boundary=---WebKitFormBoundary7MA4YWxkTrZu0gW
secret-id: 3e6bec50-3bbd-4443-b141-82aaee645cc7
secret-key: f1596671ce4d30a7847a91b8c674f43c
Host: localhost:8080
User-Agent: ApacheBench/2.3
Accept: */*
---
LOG: header received:
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 113
Date: Wed, 31 Jul 2024 13:37:18 GMT
Connection: close
File uploaded successfully: postfile.txt with size: 24072 bytes
..done
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /upload
Document Length: 113 bytes
Concurrency Level: 1
Time taken for tests: 0.056 seconds
Complete requests: 1
Failed requests: 0
Total transferred: 247 bytes
Total body sent: 24512
HTML transferred: 113 bytes
Requests per second: 17.73 [#/sec] (mean)
Time per request: 56.388 [ms] (mean)
Time per request: 56.388 [ms] (mean, across all concurrent requests)
Transfer rate: 4.28 [Kbytes/sec] received
424.51 kb/s sent
428.79 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 56 56 0.0 56 56
Waiting: 53 53 0.0 53 53
Total: 56 56 0.0 56 56
扩展 putfile
ApacheBench (ab)
同样适用于测试 PUT
接口。构造putfile
的方法与POST
接口类似,只需确保请求体的内容和头部信息符合PUT
请求的要求。
-u putfile File containing data to PUT. Remember also to set -T
附录
单个文件
构造命令:
echo -e '------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name="file"; filename="file1.jpg"\r\n\r' > postfile.txt
cat 1.jpg >> postfile.txt
echo -e "\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--" >> postfile.txt
发送请求:
ab -n 1 -c 1 -p postfile.txt -v 2 \
-T "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" \
http://localhost:8080/upload
多个文件
构造命令:
echo -e '------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name="file1"; filename="file1.jpg"\r\n\r' > postfile.txt
cat 1.jpg >> postfile.txt
echo -e "\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r" >> postfile.txt
echo -e 'Content-Disposition: form-data; name="file2"; filename="file2.zip"\r\n\r' >> postfile.txt
cat demo.zip >> postfile.txt
echo -e "\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--" >> postfile.txt
发送请求:
ab -n 1 -c 1 -p postfile.txt -v 2 \
-T "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" \
http://localhost:8080/upload2