JMeter 上传固定文件时,如何修改 Content-Disposition 的 filename

背景

在进行上传文件的性能测试时,需要大批量的上传文件,测试人员不可能为每一次请求都准备一个不同的文件。所以需要支持只选择一个本地文件,但是动态修改JMeter 传给服务器的 Content-Disposition 里的 filename名称,使之每次都不唯一。

解决方法

由于JMeter上传文件参数的filename使固定从文件路径里面取的,不支持修改。那就只能通过修改jmeter ApacheJMeter_http.jar源码或者直接构造 HTTP上传 请求。本文采用第二种JSR223 脚本直接构造 HTTP上传 请求。

具体实现

setUp线程组(JSR223 Sampler)------ 文件缓存脚本

作用:读取本地文件,将文件字节缓存到JMeter全局属性(props)中,仅执行一次,供所有普通线程组复用,避免多线程重复读取本地文件,提升脚本执行效率。

groovy 复制代码
def filePath = vars.get("filePath")

// 全局缓存文件内容(只加载一次,避免多线程重复读盘)
def cacheKey = "UPLOAD_FILE_BYTES_${filePath}"
if (props.get(cacheKey) == null) {
    def file = new File(filePath)
    // 校验文件是否存在,避免脚本报错
    if (!file.exists()) {
        log.error("文件不存在:" + filePath)
        return
    }
    def fileBytes = file.bytes
    props.put(cacheKey, fileBytes)
    log.info("文件已缓存:" + filePath + ",大小:" + fileBytes.length + " 字节")
}

普通线程组(JSR223 Sampler)------ 动态修改 Content-Disposition 的 filename

groovy 复制代码
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.net.HttpURLConnection
import java.net.URL
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
import java.security.cert.X509Certificate

// ==================== 忽略SSL证书验证 ====================
TrustManager[] trustAllCerts = [
    new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { return null }
        public void checkClientTrusted(X509Certificate[] certs, String authType) { }
        public void checkServerTrusted(X509Certificate[] certs, String authType) { }
    }
]

def sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, new java.security.SecureRandom())
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory())
HttpsURLConnection.setDefaultHostnameVerifier { hostname, session -> true }
// ===================================================================

// ==================== 你的实际URL ====================
def uploadUrl = "https://xxxxxxxxxxxxxxxxxx/file/uploadBatch"  
// ==================== 请求头配置 ====================
def authorization = vars.get("authorization") ?: ""
def cookie = vars.get("cookie") ?: ""
// ==================================================

// 1. 表单参数
def params = [
    "isDeal": "0",
    "download": "1",
    "visibleScope": "",
    "knowledgeSourceList": "2",
    "themeCategoryId": "1924658089953329153",
    "organization": "1950445468057321474",
    "knowledgeId": "2054451841533865986",
    "sharePermissionMainLi": "",
    "foldersId": "",
    "graphRag": "false",
    "ruleId": "2011260510020169729",
    "targetKnowledgeId": "2054452332598784002",
    "sharePermission": "1",
    "targetFoldersId": "2054452333601222658",
    "targetFoldersName": "测试一 > 测试二 > 测试三",
    "isSelf": "0",
    "faqFile": "false"
]

// 2. 文件配置
//字段名配置
def paramName = "files"
//filename动态配置:获取用户自定义变量中的时间戳加计数器组合,也可以使用uuid
def customFileName = "test0513_${time1}_${num}.pdf"
//获取用户自定义变量中真实文件路径
def filePath = vars.get("filePath")
//配置MIME类型
def mime = "application/pdf"
//读取全局变量中存储的文件
def cacheKey = "UPLOAD_FILE_BYTES_${filePath}"
def fileBytes = props.get(cacheKey)

// 4. 构建 multipart 请求体
def boundary = "----WebKitFormBoundary" + System.currentTimeMillis() + Thread.currentThread().getId()
def lineEnd = "\r\n"
def twoHyphens = "--"

def outputStream = new ByteArrayOutputStream()
def writer = new DataOutputStream(outputStream)

// 4.1 添加所有表单参数
params.each { key, value ->
    if (value != null && value.toString().trim().length() > 0) {
        writer.writeBytes(twoHyphens + boundary + lineEnd)
        writer.writeBytes("Content-Disposition: form-data; name=\"${key}\"" + lineEnd)
        writer.writeBytes(lineEnd)
        writer.writeBytes(value.toString())
        writer.writeBytes(lineEnd)
    }
}

// 4.2 添加文件部分
writer.writeBytes(twoHyphens + boundary + lineEnd)
writer.writeBytes("Content-Disposition: form-data; name=\"${paramName}\"; filename=\"${customFileName}\"" + lineEnd)
writer.writeBytes("Content-Type: ${mime}" + lineEnd)
writer.writeBytes(lineEnd)
writer.write(fileBytes)
writer.writeBytes(lineEnd)

// 4.3 结束标记
writer.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd)
writer.flush()

// 5. 发送请求
def url = new URL(uploadUrl)
def connection = url.openConnection() as HttpURLConnection
connection.setRequestMethod("POST")
connection.setDoOutput(true)
connection.setDoInput(true)
connection.setUseCaches(false)

// 5.1 设置请求头
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary)
if (authorization != null && authorization.trim().length() > 0) {
    connection.setRequestProperty("Authorization", authorization)
}
if (cookie != null && cookie.trim().length() > 0) {
    connection.setRequestProperty("Cookie", cookie)
}
connection.setRequestProperty("Accept", "application/json, text/plain, */*")
connection.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9")
connection.setRequestProperty("User-Agent", "Apache JMeter")
connection.setRequestProperty("Connection", "keep-alive")

// 5.2 设置超时时间
connection.setConnectTimeout(30000)
connection.setReadTimeout(60000)

// 5.3 发送请求体
connection.outputStream.write(outputStream.toByteArray())
connection.outputStream.flush()
connection.outputStream.close()

// 6. 获取响应
def responseCode = connection.getResponseCode()
def responseMessage = connection.getResponseMessage()
def responseBody = ""
def reader = null

try {
    if (responseCode >= 200 && responseCode < 300) {
        reader = new BufferedReader(new InputStreamReader(connection.inputStream, "UTF-8"))
    } else if (connection.errorStream != null) {
        reader = new BufferedReader(new InputStreamReader(connection.errorStream, "UTF-8"))
    }
    
    if (reader != null) {
        def response = new StringBuilder()
        String line
        while ((line = reader.readLine()) != null) {
            response.append(line)
        }
        responseBody = response.toString()
    }
    
    SampleResult.setSuccessful(responseCode >= 200 && responseCode < 300)
} catch (Exception e) {
    SampleResult.setSuccessful(false)
    responseBody = "读取响应失败: ${e.message}"
} finally {
    if (reader != null) {
        try { reader.close() } catch (Exception e) { }
    }
}

//讲结果给到jmeter,以便在查看结果数据查看
SampleResult.setResponseCode(String.valueOf(responseCode))
SampleResult.setResponseMessage(responseMessage)
SampleResult.setResponseData(responseBody, "UTF-8")
//关闭
connection.disconnect()
相关推荐
qq_452396231 天前
第六篇:《JMeter逻辑控制器:循环、条件和交替执行》
android·java·jmeter
qq_452396233 天前
第四篇:《JMeter参数化:CSV数据文件与用户变量》
jmeter
qq_452396233 天前
第五篇:《JMeter关联:提取动态数据并传递给后续请求》
jmeter
弹简特4 天前
【Fiddler抓包工具】一文通关Fiddler抓包工具【附:Fiddler结合jmeter接口测试实战】
jmeter·fiddler·接口测试·抓包
测试19985 天前
性能测试方案设计的方法和思路
自动化测试·软件测试·测试工具·jmeter·测试用例·压力测试·性能测试
川石课堂软件测试5 天前
软件测试|常见面试题整理
数据库·python·jmeter·mysql·appium·postman·prometheus
这是个菜比测试5 天前
jmeter无法访问内网接口
jmeter
qq_452396235 天前
第三篇:《JMeter断言:验证接口响应正确性》
android·jmeter
是小章啊6 天前
Jmeter压测实战之HTTP_POST
网络协议·jmeter·http