refactor(通用功能): ms jmeter core代码迁移到sdk中维护
This commit is contained in:
parent
f0324e3bdb
commit
48ea53c72c
|
@ -0,0 +1,313 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.metersphere</groupId>
|
||||
<artifactId>sdk-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ms-jmeter-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
<name>ms-jmeter-core</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<description>MeterSphere jmeter core</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- webdriver -->
|
||||
<dependency>
|
||||
<groupId>io.metersphere</groupId>
|
||||
<artifactId>jmeter-plugins-webdriver</artifactId>
|
||||
<version>${jmeter-plugins-webdriver.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-nop</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<groupId>javax.servlet</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>mail</artifactId>
|
||||
<groupId>javax.mail</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>geronimo-spec-javamail</artifactId>
|
||||
<groupId>geronimo-spec</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- jmeter执行机 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- jmeter -->
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_http</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>xstream</artifactId>
|
||||
<groupId>com.thoughtworks.xstream</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>xmlgraphics-commons</artifactId>
|
||||
<version>${xmlgraphics-commons.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 2.7.2 版本在大批量执行中会造成阻塞 -->
|
||||
<dependency>
|
||||
<groupId>org.python</groupId>
|
||||
<artifactId>jython-standalone</artifactId>
|
||||
<version>${jython.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_functions</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_jdbc</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_tcp</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_java</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.metersphere</groupId>
|
||||
<artifactId>metersphere-jmeter-functions</artifactId>
|
||||
<version>${metersphere-jmeter-functions.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>${mssql-jdbc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>${oracle-database.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Zookeeper -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>${dubbo.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<groupId>org.springframework</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>${zookeeper.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- nacos -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry-nacos</artifactId>
|
||||
<version>${dubbo.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-api</artifactId>
|
||||
<version>${nacos.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
<version>${nacos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>${commons-beanutils.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.metersphere</groupId>
|
||||
<artifactId>jmeter-plugins-dubbo</artifactId>
|
||||
<version>${jmeter-plugins-dubbo.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>hessian-lite</artifactId>
|
||||
<version>${hessian-lite.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ApacheJMeter_core scope 是 runtime,这里去掉scope -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-jsr223</artifactId>
|
||||
<version>${groovy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_bolt</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_jms</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_ftp</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_junit</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_ldap</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_mail</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_components</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_native</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.brotli/dec -->
|
||||
<dependency>
|
||||
<groupId>org.brotli</groupId>
|
||||
<artifactId>dec</artifactId>
|
||||
<version>${dec.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>${org-json.version}</version>
|
||||
</dependency>
|
||||
<!-- jmeter执行机 -->
|
||||
<!--钉钉sdk-->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
|
||||
<version>${dingtalk-sdk.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>jorphan</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.github.ningyu.jmeter.plugin.dubbo.sample;
|
||||
|
||||
import io.github.ningyu.jmeter.plugin.util.*;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ConfigCenterConfig;
|
||||
import org.apache.dubbo.config.ReferenceConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.config.utils.ReferenceConfigCache;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.service.GenericService;
|
||||
import org.apache.jmeter.samplers.AbstractSampler;
|
||||
import org.apache.jmeter.samplers.Entry;
|
||||
import org.apache.jmeter.samplers.Interruptible;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jorphan.logging.LoggingManager;
|
||||
import org.apache.log.Logger;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_SERIALIZATION_DEFAULT;
|
||||
|
||||
/**
|
||||
* DubboSample
|
||||
*/
|
||||
public class DubboSample extends AbstractSampler implements Interruptible {
|
||||
|
||||
private static final Logger log = LoggingManager.getLoggerForClass();
|
||||
private static final long serialVersionUID = -6794913295411458705L;
|
||||
public static ApplicationConfig application = new ApplicationConfig("DubboSample");
|
||||
|
||||
@Override
|
||||
public SampleResult sample(Entry entry) {
|
||||
SampleResult res = new SampleResult();
|
||||
res.setSampleLabel(getName());
|
||||
//构造请求数据
|
||||
res.setSamplerData(getSampleData());
|
||||
//调用dubbo
|
||||
res.setResponseData(JsonUtils.toJson(callDubbo(res)), StandardCharsets.UTF_8.name());
|
||||
//构造响应数据
|
||||
res.setDataType(SampleResult.TEXT);
|
||||
//dubbo协议执行没有时间,现在默认执行时间为当前时间
|
||||
res.setStartTime(System.currentTimeMillis());
|
||||
res.setEndTime(System.currentTimeMillis());
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct request data
|
||||
*/
|
||||
private String getSampleData() {
|
||||
log.info("sample中的实例id" + this.toString() + ",element名称" + this.getName());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Registry Protocol: ").append(Constants.getRegistryProtocol(this)).append("\n");
|
||||
sb.append("Address: ").append(Constants.getAddress(this)).append("\n");
|
||||
sb.append("RPC Protocol: ").append(Constants.getRpcProtocol(this)).append("\n");
|
||||
sb.append("Timeout: ").append(Constants.getTimeout(this)).append("\n");
|
||||
sb.append("Version: ").append(Constants.getVersion(this)).append("\n");
|
||||
sb.append("Retries: ").append(Constants.getRetries(this)).append("\n");
|
||||
sb.append("Cluster: ").append(Constants.getCluster(this)).append("\n");
|
||||
sb.append("Group: ").append(Constants.getGroup(this)).append("\n");
|
||||
sb.append("Connections: ").append(Constants.getConnections(this)).append("\n");
|
||||
sb.append("LoadBalance: ").append(Constants.getLoadbalance(this)).append("\n");
|
||||
sb.append("Async: ").append(Constants.getAsync(this)).append("\n");
|
||||
sb.append("Interface: ").append(Constants.getInterface(this)).append("\n");
|
||||
sb.append("Method: ").append(Constants.getMethod(this)).append("\n");
|
||||
sb.append("Method Args: ").append(Constants.getMethodArgs(this).toString());
|
||||
sb.append("Attachment Args: ").append(Constants.getAttachmentArgs(this).toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private Object callDubbo(SampleResult res) {
|
||||
// This instance is heavy, encapsulating the connection to the registry and the connection to the provider,
|
||||
// so please cache yourself, otherwise memory and connection leaks may occur.
|
||||
ReferenceConfig reference = new ReferenceConfig();
|
||||
// set application
|
||||
reference.setApplication(application);
|
||||
/* registry center */
|
||||
String address = Constants.getAddress(this);
|
||||
if (StringUtils.isBlank(address)) {
|
||||
setResponseError(res, ErrorCode.MISS_ADDRESS);
|
||||
return ErrorCode.MISS_ADDRESS.getMessage();
|
||||
}
|
||||
RegistryConfig registry;
|
||||
String rpcProtocol = Constants.getRpcProtocol(this).replaceAll("://", "");
|
||||
String protocol = Constants.getRegistryProtocol(this);
|
||||
String registryGroup = Constants.getRegistryGroup(this);
|
||||
Integer registryTimeout = null;
|
||||
try {
|
||||
if (!StringUtils.isBlank(Constants.getRegistryTimeout(this))) {
|
||||
registryTimeout = Integer.valueOf(Constants.getRegistryTimeout(this));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
setResponseError(res, ErrorCode.TIMEOUT_ERROR);
|
||||
return ErrorCode.TIMEOUT_ERROR.getMessage();
|
||||
}
|
||||
if (StringUtils.isBlank(protocol) || Constants.REGISTRY_NONE.equals(protocol)) {
|
||||
//direct connection
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(Constants.getRpcProtocol(this))
|
||||
.append(Constants.getAddress(this))
|
||||
.append("/").append(Constants.getInterface(this));
|
||||
log.debug("rpc invoker url : " + sb.toString());
|
||||
reference.setUrl(sb.toString());
|
||||
} else if (Constants.REGISTRY_SIMPLE.equals(protocol)) {
|
||||
registry = new RegistryConfig();
|
||||
registry.setAddress(address);
|
||||
reference.setProtocol(rpcProtocol);
|
||||
reference.setRegistry(registry);
|
||||
} else {
|
||||
registry = new RegistryConfig();
|
||||
registry.setProtocol(protocol);
|
||||
registry.setGroup(registryGroup);
|
||||
registry.setAddress(address);
|
||||
if (registryTimeout != null) {
|
||||
registry.setTimeout(registryTimeout);
|
||||
}
|
||||
reference.setProtocol(rpcProtocol);
|
||||
reference.setRegistry(registry);
|
||||
}
|
||||
/* config center */
|
||||
try {
|
||||
String configCenterProtocol = Constants.getConfigCenterProtocol(this);
|
||||
if (!StringUtils.isBlank(configCenterProtocol)) {
|
||||
String configCenterGroup = Constants.getConfigCenterGroup(this);
|
||||
String configCenterNamespace = Constants.getConfigCenterNamespace(this);
|
||||
String configCenterAddress = Constants.getConfigCenterAddress(this);
|
||||
if (StringUtils.isBlank(configCenterAddress)) {
|
||||
setResponseError(res, ErrorCode.MISS_ADDRESS);
|
||||
return ErrorCode.MISS_ADDRESS.getMessage();
|
||||
}
|
||||
Long configCenterTimeout = null;
|
||||
try {
|
||||
if (!StringUtils.isBlank(Constants.getConfigCenterTimeout(this))) {
|
||||
configCenterTimeout = Long.valueOf(Constants.getConfigCenterTimeout(this));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
setResponseError(res, ErrorCode.TIMEOUT_ERROR);
|
||||
return ErrorCode.TIMEOUT_ERROR.getMessage();
|
||||
}
|
||||
ConfigCenterConfig configCenter = new ConfigCenterConfig();
|
||||
configCenter.setProtocol(configCenterProtocol);
|
||||
configCenter.setGroup(configCenterGroup);
|
||||
configCenter.setNamespace(configCenterNamespace);
|
||||
configCenter.setAddress(configCenterAddress);
|
||||
if (configCenterTimeout != null) {
|
||||
configCenter.setTimeout(configCenterTimeout);
|
||||
}
|
||||
reference.setConfigCenter(configCenter);
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
/* Duplicate Config */
|
||||
setResponseError(res, ErrorCode.DUPLICATE_CONFIGCENTERCONFIG);
|
||||
return ErrorCode.DUPLICATE_CONFIGCENTERCONFIG.getMessage();
|
||||
}
|
||||
try {
|
||||
// set interface
|
||||
String interfaceName = Constants.getInterface(this);
|
||||
if (StringUtils.isBlank(interfaceName)) {
|
||||
setResponseError(res, ErrorCode.MISS_INTERFACE);
|
||||
return ErrorCode.MISS_INTERFACE.getMessage();
|
||||
}
|
||||
reference.setInterface(interfaceName);
|
||||
|
||||
// set retries
|
||||
Integer retries = null;
|
||||
try {
|
||||
if (!StringUtils.isBlank(Constants.getRetries(this))) {
|
||||
retries = Integer.valueOf(Constants.getRetries(this));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
setResponseError(res, ErrorCode.RETRIES_ERROR);
|
||||
return ErrorCode.RETRIES_ERROR.getMessage();
|
||||
}
|
||||
if (retries != null) {
|
||||
reference.setRetries(retries);
|
||||
}
|
||||
|
||||
// set cluster
|
||||
String cluster = Constants.getCluster(this);
|
||||
if (!StringUtils.isBlank(cluster)) {
|
||||
reference.setCluster(Constants.getCluster(this));
|
||||
}
|
||||
|
||||
// set version
|
||||
String version = Constants.getVersion(this);
|
||||
if (!StringUtils.isBlank(version)) {
|
||||
reference.setVersion(version);
|
||||
}
|
||||
|
||||
// set timeout
|
||||
Integer timeout = null;
|
||||
try {
|
||||
if (!StringUtils.isBlank(Constants.getTimeout(this))) {
|
||||
timeout = Integer.valueOf(Constants.getTimeout(this));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
setResponseError(res, ErrorCode.TIMEOUT_ERROR);
|
||||
return ErrorCode.TIMEOUT_ERROR.getMessage();
|
||||
}
|
||||
if (timeout != null) {
|
||||
reference.setTimeout(timeout);
|
||||
}
|
||||
|
||||
// set group
|
||||
String group = Constants.getGroup(this);
|
||||
if (!StringUtils.isBlank(group)) {
|
||||
reference.setGroup(group);
|
||||
}
|
||||
|
||||
// set connections
|
||||
Integer connections = null;
|
||||
try {
|
||||
if (!StringUtils.isBlank(Constants.getConnections(this))) {
|
||||
connections = Integer.valueOf(Constants.getConnections(this));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
setResponseError(res, ErrorCode.CONNECTIONS_ERROR);
|
||||
return ErrorCode.CONNECTIONS_ERROR.getMessage();
|
||||
}
|
||||
if (connections != null) {
|
||||
reference.setConnections(connections);
|
||||
}
|
||||
|
||||
// set loadBalance
|
||||
String loadBalance = Constants.getLoadbalance(this);
|
||||
if (!StringUtils.isBlank(loadBalance)) {
|
||||
reference.setLoadbalance(loadBalance);
|
||||
}
|
||||
|
||||
// set async
|
||||
String async = Constants.getAsync(this);
|
||||
if (!StringUtils.isBlank(async)) {
|
||||
reference.setAsync(Constants.ASYNC.equals(async));
|
||||
}
|
||||
|
||||
// set generic
|
||||
reference.setGeneric(GENERIC_SERIALIZATION_DEFAULT);
|
||||
|
||||
CheckUtils.checkZookeeper(reference);
|
||||
|
||||
String methodName = Constants.getMethod(this);
|
||||
if (StringUtils.isBlank(methodName)) {
|
||||
setResponseError(res, ErrorCode.MISS_METHOD);
|
||||
return ErrorCode.MISS_METHOD.getMessage();
|
||||
}
|
||||
|
||||
// The registry's address is to generate the ReferenceConfigCache key
|
||||
ReferenceConfigCache cache = ReferenceConfigCache.getCache(Constants.getAddress(this));
|
||||
GenericService genericService = (GenericService) cache.get(reference);
|
||||
if (genericService == null) {
|
||||
setResponseError(res, ErrorCode.GENERIC_SERVICE_IS_NULL);
|
||||
return MessageFormat.format(ErrorCode.GENERIC_SERVICE_IS_NULL.getMessage(), interfaceName);
|
||||
}
|
||||
String[] parameterTypes;
|
||||
Object[] parameterValues;
|
||||
List<MethodArgument> args = Constants.getMethodArgs(this);
|
||||
List<String> parameterTypeList = new ArrayList<>();
|
||||
List<Object> parameterValuesList = new ArrayList<>();
|
||||
for (MethodArgument arg : args) {
|
||||
ClassUtils.parseParameter(parameterTypeList, parameterValuesList, arg);
|
||||
}
|
||||
parameterTypes = parameterTypeList.toArray(new String[0]);
|
||||
parameterValues = parameterValuesList.toArray(new Object[0]);
|
||||
|
||||
List<MethodArgument> attachmentArgs = Constants.getAttachmentArgs(this);
|
||||
if (!attachmentArgs.isEmpty()) {
|
||||
RpcContext.getContext().setAttachments(attachmentArgs.stream().collect(Collectors.toMap(MethodArgument::getParamType, MethodArgument::getParamValue)));
|
||||
}
|
||||
|
||||
res.sampleStart();
|
||||
Object result;
|
||||
try {
|
||||
result = genericService.$invoke(methodName, parameterTypes, parameterValues);
|
||||
res.setResponseOK();
|
||||
} catch (Exception e) {
|
||||
log.error("Exception:", e);
|
||||
if (e instanceof RpcException) {
|
||||
RpcException rpcException = (RpcException) e;
|
||||
setResponseError(res, String.valueOf(rpcException.getCode()), rpcException.getMessage());
|
||||
} else {
|
||||
setResponseError(res, ErrorCode.UNKNOWN_EXCEPTION);
|
||||
}
|
||||
result = e;
|
||||
}
|
||||
res.sampleEnd();
|
||||
cache.destroy(reference); // 清除缓存
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("UnknownException:", e);
|
||||
setResponseError(res, ErrorCode.UNKNOWN_EXCEPTION);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
public void setResponseError(SampleResult res, ErrorCode errorCode) {
|
||||
setResponseError(res, errorCode.getCode(), errorCode.getMessage());
|
||||
}
|
||||
|
||||
public void setResponseError(SampleResult res, String code, String message) {
|
||||
res.setSuccessful(false);
|
||||
res.setResponseCode(code);
|
||||
res.setResponseMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interrupt() {
|
||||
Thread.currentThread().interrupt();
|
||||
return true;
|
||||
}
|
||||
}
|
13
framework/sdk-parent/jmeter/src/main/java/io/metersphere/cache/JMeterEngineCache.java
vendored
Normal file
13
framework/sdk-parent/jmeter/src/main/java/io/metersphere/cache/JMeterEngineCache.java
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.cache;
|
||||
|
||||
import org.apache.jmeter.engine.StandardJMeterEngine;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class JMeterEngineCache {
|
||||
/**
|
||||
* 执行中的线程池
|
||||
*/
|
||||
public static ConcurrentHashMap<String, StandardJMeterEngine> runningEngine = new ConcurrentHashMap<>();
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.metersphere.constants;
|
||||
|
||||
public enum BackendListenerConstants {
|
||||
RETRY_ENABLE, TEST_ID, NAME, REPORT_ID, RUN_MODE, REPORT_TYPE, CLASS_NAME, MS_TEST_PLAN_REPORT_ID, RUN, KAFKA_CONFIG, QUEUE_ID, RUN_TYPE, EPT, MS_DEBUG
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package io.metersphere.constants;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum HttpMethodConstants {
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
OPTIONS,
|
||||
TRACE;
|
||||
|
||||
private static final Map<String, HttpMethodConstants> mappings = new HashMap(16);
|
||||
|
||||
private HttpMethodConstants() {
|
||||
}
|
||||
|
||||
public static HttpMethodConstants resolve(String method) {
|
||||
return method != null ? (HttpMethodConstants) mappings.get(method) : null;
|
||||
}
|
||||
|
||||
public boolean matches(String method) {
|
||||
return this.name().equals(method);
|
||||
}
|
||||
|
||||
static {
|
||||
HttpMethodConstants[] var0 = values();
|
||||
int var1 = var0.length;
|
||||
|
||||
for (int var2 = 0; var2 < var1; ++var2) {
|
||||
HttpMethodConstants HttpMethodConstants = var0[var2];
|
||||
mappings.put(HttpMethodConstants.name(), HttpMethodConstants);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.metersphere.constants;
|
||||
|
||||
public enum RunModeConstants {
|
||||
|
||||
SERIAL("serial"), SET_REPORT("setReport"), INDEPENDENCE("Independence"), PARALLEL("parallel"), HIS_PRO_ID("historyProjectID");
|
||||
|
||||
private String value;
|
||||
|
||||
RunModeConstants(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class BaseSystemConfigDTO {
|
||||
private String url;
|
||||
private String concurrency;
|
||||
private String prometheusHost;
|
||||
private String seleniumDockerUrl;
|
||||
private String runMode;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ErrorReportAssertionResult extends ResponseAssertionResult{
|
||||
private String errorReportMessage;
|
||||
|
||||
public ErrorReportAssertionResult(String errorReportMessage){
|
||||
this.errorReportMessage = errorReportMessage;
|
||||
this.setMessage(errorReportMessage);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import io.metersphere.constants.RunModeConstants;
|
||||
import io.metersphere.vo.BooleanPool;
|
||||
import lombok.Data;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class JmeterRunRequestDTO {
|
||||
/**
|
||||
* 队列ID
|
||||
*/
|
||||
private String queueId;
|
||||
/**
|
||||
* 执行模式/API/SCENARIO/PLAN_API等
|
||||
*/
|
||||
private String runMode;
|
||||
|
||||
/**
|
||||
* RunModeConstants.SET_REPORT : RunModeConstants.INDEPENDENCE
|
||||
*/
|
||||
private String reportType;
|
||||
/**
|
||||
* 报告ID
|
||||
*/
|
||||
private String reportId;
|
||||
/**
|
||||
* 测试计划报告ID
|
||||
*/
|
||||
private String testPlanReportId;
|
||||
|
||||
/**
|
||||
* 资源id:/场景id/用例id/接口id/测试计划场景id/测试计划用例id
|
||||
*/
|
||||
private String testId;
|
||||
|
||||
/**
|
||||
* 是否发送node节点执行
|
||||
*/
|
||||
private BooleanPool pool;
|
||||
/**
|
||||
* 资源池Id
|
||||
*/
|
||||
private String poolId;
|
||||
|
||||
/**
|
||||
* 并行/串行
|
||||
*/
|
||||
private String runType;
|
||||
/**
|
||||
* 调试
|
||||
*/
|
||||
private boolean isDebug;
|
||||
/**
|
||||
* 执行脚本
|
||||
*/
|
||||
private HashTree hashTree;
|
||||
/**
|
||||
* 并发数
|
||||
*/
|
||||
private int corePoolSize;
|
||||
|
||||
/**
|
||||
* 是否开启定时同步
|
||||
*/
|
||||
private boolean enable;
|
||||
|
||||
/**
|
||||
* KAFKA配置只用在node节点中会用到
|
||||
*/
|
||||
private Map<String, Object> kafkaConfig;
|
||||
|
||||
/**
|
||||
* 增加一个全局扩展的通传参数
|
||||
*/
|
||||
private Map<String, Object> extendedParameters;
|
||||
|
||||
/**
|
||||
* 平台服务地址只用在node节点中会用到
|
||||
*/
|
||||
private String platformUrl;
|
||||
|
||||
// 失败重试
|
||||
private boolean retryEnable;
|
||||
|
||||
// 失败重试次数
|
||||
private long retryNum;
|
||||
|
||||
public JmeterRunRequestDTO() {
|
||||
}
|
||||
|
||||
public JmeterRunRequestDTO(String testId, String reportId, String runMode, HashTree hashTree) {
|
||||
this.testId = testId;
|
||||
this.reportId = reportId;
|
||||
this.runMode = runMode;
|
||||
this.reportType = RunModeConstants.INDEPENDENCE.name();
|
||||
this.hashTree = hashTree;
|
||||
this.pool = new BooleanPool();
|
||||
this.extendedParameters = new LinkedHashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class JvmInfoDTO {
|
||||
/**
|
||||
* JVM内存的空闲空间为
|
||||
*/
|
||||
private long vmFree;
|
||||
/**
|
||||
* JVM内存已用的空间为
|
||||
*/
|
||||
private long vmUse;
|
||||
|
||||
private long vmTotal;
|
||||
/**
|
||||
* JVM总内存空间为
|
||||
*/
|
||||
private long vmMax;
|
||||
|
||||
private int totalThread;
|
||||
|
||||
private TestResourceDTO testResource;
|
||||
|
||||
public JvmInfoDTO() {
|
||||
|
||||
}
|
||||
|
||||
public JvmInfoDTO(long vmTotal, long vmFree, long vmMax, long vmUse, int totalThread) {
|
||||
this.vmFree = vmFree;
|
||||
this.vmTotal = vmTotal;
|
||||
this.vmMax = vmMax;
|
||||
this.vmUse = vmUse;
|
||||
this.totalThread = totalThread;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MsExecResponseDTO {
|
||||
|
||||
private String testId;
|
||||
|
||||
private String reportId;
|
||||
|
||||
private String runMode;
|
||||
|
||||
public MsExecResponseDTO() {
|
||||
|
||||
}
|
||||
|
||||
public MsExecResponseDTO(String testId, String reportId, String runMode) {
|
||||
this.testId = testId;
|
||||
this.reportId = reportId;
|
||||
this.runMode = runMode;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class RequestResult {
|
||||
// 请求ID
|
||||
private String id;
|
||||
|
||||
// 步骤请求唯一ID
|
||||
private String resourceId;
|
||||
|
||||
private String threadName;
|
||||
|
||||
private String name;
|
||||
|
||||
private String url;
|
||||
|
||||
private String method;
|
||||
|
||||
private String scenario;
|
||||
|
||||
private long requestSize;
|
||||
|
||||
private long startTime;
|
||||
|
||||
private long endTime;
|
||||
|
||||
private int error;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private String headers;
|
||||
|
||||
private String cookies;
|
||||
|
||||
private String body;
|
||||
|
||||
private String status;
|
||||
|
||||
private int totalAssertions = 0;
|
||||
|
||||
private int passAssertions = 0;
|
||||
|
||||
private List<RequestResult> subRequestResults = new ArrayList<>();
|
||||
|
||||
private ResponseResult responseResult = new ResponseResult();
|
||||
|
||||
public void addPassAssertions() {
|
||||
this.passAssertions++;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ResponseAssertionResult {
|
||||
|
||||
private String name;
|
||||
|
||||
private String content;
|
||||
|
||||
private String script;
|
||||
|
||||
private String message;
|
||||
|
||||
private boolean pass;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Data
|
||||
public class ResponseResult {
|
||||
|
||||
private String responseCode;
|
||||
|
||||
private String responseMessage;
|
||||
|
||||
private long responseTime;
|
||||
|
||||
private long latency;
|
||||
|
||||
private long responseSize;
|
||||
|
||||
private String headers;
|
||||
|
||||
private String body;
|
||||
|
||||
private String vars;
|
||||
|
||||
private String console;
|
||||
|
||||
private final List<ResponseAssertionResult> assertions = new ArrayList<>();
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ResultDTO {
|
||||
private List<RequestResult> requestResults;
|
||||
private String runMode;
|
||||
private String queueId;
|
||||
private String reportId;
|
||||
private String reportType;
|
||||
private String testPlanReportId;
|
||||
private String testId;
|
||||
private String runType;
|
||||
private String console;
|
||||
private String runningDebugSampler;
|
||||
// 失败重试
|
||||
private boolean retryEnable;
|
||||
/**
|
||||
* 增加一个全局扩展的通传参数
|
||||
*/
|
||||
private Map<String, Object> extendedParameters;
|
||||
|
||||
// 预留一个参数,可以放任何数据
|
||||
private Map<String, Object> arbitraryData;
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class RunModeConfigDTO {
|
||||
private String mode;
|
||||
private String reportType;
|
||||
private String reportName;
|
||||
private String reportId;
|
||||
private String testId;
|
||||
private String amassReport;
|
||||
private boolean onSampleError;
|
||||
// 失败重试
|
||||
private boolean retryEnable;
|
||||
// 失败重试次数
|
||||
private long retryNum;
|
||||
// 资源池
|
||||
private String resourcePoolId;
|
||||
private BaseSystemConfigDTO baseInfo;
|
||||
private List<JvmInfoDTO> testResources;
|
||||
/**
|
||||
* 运行环境
|
||||
*/
|
||||
private Map<String, String> envMap;
|
||||
private String environmentType;
|
||||
private String environmentGroupId;
|
||||
//ui 测试
|
||||
private String browser;
|
||||
private boolean headlessEnabled;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package io.metersphere.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class TestResourceDTO implements Serializable {
|
||||
private String id;
|
||||
|
||||
private String testResourcePoolId;
|
||||
|
||||
private String status;
|
||||
|
||||
private Long createTime;
|
||||
|
||||
private Long updateTime;
|
||||
|
||||
private String configuration;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
package io.metersphere.jmeter;
|
||||
|
||||
import io.metersphere.constants.BackendListenerConstants;
|
||||
import io.metersphere.constants.HttpMethodConstants;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.utils.JMeterVars;
|
||||
import io.metersphere.utils.JsonUtils;
|
||||
import io.metersphere.utils.ListenerUtil;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.jmeter.assertions.AssertionResult;
|
||||
import org.apache.jmeter.config.Arguments;
|
||||
import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.threads.JMeterVariables;
|
||||
import org.apache.jmeter.visualizers.backend.BackendListener;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JMeterBase {
|
||||
|
||||
private final static String THREAD_SPLIT = " ";
|
||||
|
||||
public static HashTree getHashTree(Object scriptWrapper) throws Exception {
|
||||
Field field = scriptWrapper.getClass().getDeclaredField("testPlan");
|
||||
field.setAccessible(true);
|
||||
return (HashTree) field.get(scriptWrapper);
|
||||
}
|
||||
|
||||
public static void addBackendListener(JmeterRunRequestDTO request, HashTree hashTree, String listenerClazz) {
|
||||
LoggerUtil.info("开始为报告添加BackendListener", request.getReportId());
|
||||
|
||||
BackendListener backendListener = new BackendListener();
|
||||
backendListener.setName(request.getReportId() + "_" + request.getTestId());
|
||||
Arguments arguments = new Arguments();
|
||||
arguments.addArgument(BackendListenerConstants.NAME.name(), request.getReportId() + "_" + request.getTestId());
|
||||
arguments.addArgument(BackendListenerConstants.REPORT_ID.name(), request.getReportId());
|
||||
arguments.addArgument(BackendListenerConstants.TEST_ID.name(), request.getTestId());
|
||||
arguments.addArgument(BackendListenerConstants.RUN_MODE.name(), request.getRunMode());
|
||||
arguments.addArgument(BackendListenerConstants.REPORT_TYPE.name(), request.getReportType());
|
||||
arguments.addArgument(BackendListenerConstants.MS_TEST_PLAN_REPORT_ID.name(), request.getTestPlanReportId());
|
||||
arguments.addArgument(BackendListenerConstants.CLASS_NAME.name(), listenerClazz);
|
||||
arguments.addArgument(BackendListenerConstants.QUEUE_ID.name(), request.getQueueId());
|
||||
arguments.addArgument(BackendListenerConstants.RUN_TYPE.name(), request.getRunType());
|
||||
arguments.addArgument(BackendListenerConstants.RETRY_ENABLE.name(), String.valueOf(request.isRetryEnable()));
|
||||
if (MapUtils.isNotEmpty(request.getExtendedParameters())) {
|
||||
arguments.addArgument(BackendListenerConstants.EPT.name(), JsonUtils.toJSONString(request.getExtendedParameters()));
|
||||
}
|
||||
if (request.getKafkaConfig() != null && request.getKafkaConfig().size() > 0) {
|
||||
arguments.addArgument(BackendListenerConstants.KAFKA_CONFIG.name(), JsonUtils.toJSONString(request.getKafkaConfig()));
|
||||
}
|
||||
backendListener.setArguments(arguments);
|
||||
backendListener.setClassname(listenerClazz);
|
||||
if (hashTree != null) {
|
||||
hashTree.add(hashTree.getArray()[0], backendListener);
|
||||
}
|
||||
LoggerUtil.info("报告添加BackendListener 结束", request.getTestId());
|
||||
}
|
||||
|
||||
public static RequestResult getRequestResult(SampleResult result) {
|
||||
LoggerUtil.debug("开始处理结果资源【" + result.getSampleLabel() + "】");
|
||||
|
||||
String threadName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT);
|
||||
RequestResult requestResult = new RequestResult();
|
||||
requestResult.setThreadName(threadName);
|
||||
requestResult.setId(result.getSamplerId());
|
||||
requestResult.setResourceId(result.getResourceId());
|
||||
requestResult.setName(result.getSampleLabel());
|
||||
requestResult.setUrl(result.getUrlAsString());
|
||||
requestResult.setMethod(getMethod(result));
|
||||
requestResult.setBody(result.getSamplerData());
|
||||
requestResult.setHeaders(result.getRequestHeaders());
|
||||
requestResult.setRequestSize(result.getSentBytes());
|
||||
requestResult.setStartTime(result.getStartTime());
|
||||
requestResult.setEndTime(result.getEndTime());
|
||||
requestResult.setTotalAssertions(result.getAssertionResults().length);
|
||||
requestResult.setSuccess(result.isSuccessful());
|
||||
requestResult.setError(result.getErrorCount());
|
||||
requestResult.setScenario(result.getScenario());
|
||||
if (result instanceof HTTPSampleResult) {
|
||||
HTTPSampleResult res = (HTTPSampleResult) result;
|
||||
requestResult.setCookies(res.getCookies());
|
||||
}
|
||||
|
||||
for (SampleResult subResult : result.getSubResults()) {
|
||||
requestResult.getSubRequestResults().add(getRequestResult(subResult));
|
||||
}
|
||||
ResponseResult responseResult = requestResult.getResponseResult();
|
||||
// 超过20M的文件不入库
|
||||
long size = 1024 * 1024 * 20;
|
||||
if (StringUtils.equals(ContentType.APPLICATION_OCTET_STREAM.getMimeType(), result.getContentType())
|
||||
&& StringUtils.isNotEmpty(result.getResponseDataAsString())
|
||||
&& result.getResponseDataAsString().length() > size) {
|
||||
requestResult.setBody("");
|
||||
} else {
|
||||
responseResult.setBody(result.getResponseDataAsString());
|
||||
}
|
||||
responseResult.setHeaders(result.getResponseHeaders());
|
||||
responseResult.setLatency(result.getLatency());
|
||||
responseResult.setResponseCode(result.getResponseCode());
|
||||
responseResult.setResponseSize(result.getResponseData().length);
|
||||
responseResult.setResponseTime(result.getTime());
|
||||
responseResult.setResponseMessage(result.getResponseMessage());
|
||||
JMeterVariables variables = JMeterVars.get(result.getResourceId());
|
||||
if (StringUtils.isNotEmpty(result.getExtVars())) {
|
||||
responseResult.setVars(result.getExtVars());
|
||||
JMeterVars.remove(result.getResourceId());
|
||||
} else if (variables != null && CollectionUtils.isNotEmpty(variables.entrySet())) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : variables.entrySet()) {
|
||||
builder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(builder)) {
|
||||
responseResult.setVars(builder.toString());
|
||||
}
|
||||
JMeterVars.remove(result.getResourceId());
|
||||
}
|
||||
|
||||
for (AssertionResult assertionResult : result.getAssertionResults()) {
|
||||
ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult);
|
||||
if (responseAssertionResult.isPass()) {
|
||||
requestResult.addPassAssertions();
|
||||
}
|
||||
//xpath 提取错误会添加断言错误
|
||||
if (StringUtils.isBlank(responseAssertionResult.getMessage()) ||
|
||||
(StringUtils.isNotBlank(responseAssertionResult.getName()) && !responseAssertionResult.getName().endsWith("XPath2Extractor"))
|
||||
|| (StringUtils.isNotBlank(responseAssertionResult.getContent()) && !responseAssertionResult.getContent().endsWith("XPath2Extractor"))
|
||||
) {
|
||||
responseResult.getAssertions().add(responseAssertionResult);
|
||||
}
|
||||
}
|
||||
|
||||
LoggerUtil.debug("处理结果资源【" + result.getSampleLabel() + "】结束");
|
||||
return requestResult;
|
||||
}
|
||||
|
||||
private static ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) {
|
||||
ResponseAssertionResult responseAssertionResult = null;
|
||||
|
||||
if (StringUtils.startsWith(assertionResult.getName(), "ErrorReportAssertion")) {
|
||||
responseAssertionResult = new ErrorReportAssertionResult(assertionResult.getFailureMessage());
|
||||
} else {
|
||||
responseAssertionResult = new ResponseAssertionResult();
|
||||
}
|
||||
|
||||
responseAssertionResult.setName(assertionResult.getName());
|
||||
if (StringUtils.isNotEmpty(assertionResult.getName()) && assertionResult.getName().indexOf("split==") != -1) {
|
||||
if (assertionResult.getName().indexOf("JSR223") != -1) {
|
||||
String[] array = assertionResult.getName().split("split==", 3);
|
||||
if (array.length > 2 && "JSR223".equals(array[0])) {
|
||||
responseAssertionResult.setName(array[1]);
|
||||
if (array[2].indexOf("split&&") != -1) {
|
||||
String[] content = array[2].split("split&&");
|
||||
responseAssertionResult.setContent(content[0]);
|
||||
if (content.length > 1) {
|
||||
responseAssertionResult.setScript(content[1]);
|
||||
}
|
||||
} else {
|
||||
responseAssertionResult.setContent(array[2]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String[] array = assertionResult.getName().split("split==");
|
||||
responseAssertionResult.setName(array[0]);
|
||||
StringBuffer content = new StringBuffer();
|
||||
for (int i = 1; i < array.length; i++) {
|
||||
content.append(array[i]);
|
||||
}
|
||||
responseAssertionResult.setContent(content.toString());
|
||||
}
|
||||
}
|
||||
responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError());
|
||||
if (!responseAssertionResult.isPass()) {
|
||||
responseAssertionResult.setMessage(assertionResult.getFailureMessage());
|
||||
}
|
||||
return responseAssertionResult;
|
||||
}
|
||||
|
||||
private static String getMethod(SampleResult result) {
|
||||
String body = result.getSamplerData();
|
||||
String start = "RPC Protocol: ";
|
||||
String end = "://";
|
||||
if (StringUtils.contains(body, start)) {
|
||||
String protocol = StringUtils.substringBetween(body, start, end);
|
||||
if (StringUtils.isNotEmpty(protocol)) {
|
||||
return protocol.toUpperCase();
|
||||
}
|
||||
return "DUBBO";
|
||||
} else if (StringUtils.contains(result.getResponseHeaders(), "url:jdbc")) {
|
||||
return "SQL";
|
||||
} else {
|
||||
String method = StringUtils.substringBefore(body, " ");
|
||||
for (HttpMethodConstants value : HttpMethodConstants.values()) {
|
||||
if (StringUtils.equals(method, value.name())) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return "Request";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行结果数据转化
|
||||
*
|
||||
* @param sampleResults
|
||||
* @param dto
|
||||
*/
|
||||
public static void resultFormatting(List<SampleResult> sampleResults, ResultDTO dto) {
|
||||
try {
|
||||
List<RequestResult> requestResults = new LinkedList<>();
|
||||
List<String> environmentList = new ArrayList<>();
|
||||
sampleResults.forEach(result -> {
|
||||
ListenerUtil.setVars(result);
|
||||
RequestResult requestResult = JMeterBase.getRequestResult(result);
|
||||
if (StringUtils.equals(result.getSampleLabel(), ListenerUtil.RUNNING_DEBUG_SAMPLER_NAME)) {
|
||||
String evnStr = result.getResponseDataAsString();
|
||||
environmentList.add(evnStr);
|
||||
} else {
|
||||
boolean resultNotFilterOut = ListenerUtil.checkResultIsNotFilterOut(requestResult);
|
||||
if (resultNotFilterOut) {
|
||||
if (StringUtils.isNotEmpty(requestResult.getName()) && requestResult.getName().startsWith("Transaction=")) {
|
||||
transactionFormat(requestResult.getSubRequestResults(), requestResults);
|
||||
} else {
|
||||
requestResults.add(requestResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
dto.setRequestResults(requestResults);
|
||||
ListenerUtil.setEev(dto, environmentList);
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error("JMETER-调用存储方法失败", dto.getReportId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void transactionFormat(List<RequestResult> requestResults, List<RequestResult> refRes) {
|
||||
for (RequestResult requestResult : requestResults) {
|
||||
if (CollectionUtils.isEmpty(requestResult.getSubRequestResults())) {
|
||||
refRes.add(requestResult);
|
||||
} else {
|
||||
transactionFormat(requestResult.getSubRequestResults(), refRes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package io.metersphere.jmeter;
|
||||
|
||||
import io.metersphere.cache.JMeterEngineCache;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.engine.JMeterEngineException;
|
||||
import org.apache.jmeter.engine.StandardJMeterEngine;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LocalRunner {
|
||||
private HashTree jmxTree;
|
||||
|
||||
public LocalRunner(HashTree jmxTree) {
|
||||
this.jmxTree = jmxTree;
|
||||
}
|
||||
|
||||
public LocalRunner() {
|
||||
}
|
||||
|
||||
public void run(String report) {
|
||||
StandardJMeterEngine engine = new StandardJMeterEngine();
|
||||
engine.configure(jmxTree);
|
||||
try {
|
||||
LoggerUtil.info("LocalRunner 开始执行报告",report);
|
||||
engine.runTest();
|
||||
JMeterEngineCache.runningEngine.put(report, engine);
|
||||
} catch (JMeterEngineException e) {
|
||||
engine.stopTest(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(List<String> reports) {
|
||||
if (CollectionUtils.isNotEmpty(reports)) {
|
||||
for (String report : reports) {
|
||||
StandardJMeterEngine engine = JMeterEngineCache.runningEngine.get(report);
|
||||
if (engine != null) {
|
||||
engine.stopTest();
|
||||
JMeterEngineCache.runningEngine.remove(report);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(String report) {
|
||||
if (StringUtils.isNotEmpty(report)) {
|
||||
StandardJMeterEngine engine = JMeterEngineCache.runningEngine.get(report);
|
||||
if (engine != null) {
|
||||
engine.stopTest();
|
||||
JMeterEngineCache.runningEngine.remove(report);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package io.metersphere.jmeter;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import io.metersphere.utils.JsonUtils;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
public class MsClassLoader {
|
||||
public static MsDynamicClassLoader loadJar(List<String> jarPaths) {
|
||||
if (CollectionUtils.isEmpty(jarPaths)) {
|
||||
return null;
|
||||
}
|
||||
LoggerUtil.info("开始初始化JAR[ " + JsonUtils.toJSONString(jarPaths) + " ]");
|
||||
MsDynamicClassLoader urlClassLoader = new MsDynamicClassLoader();
|
||||
jarPaths.forEach(path -> {
|
||||
try {
|
||||
if (StringUtils.isNotEmpty(path)) {
|
||||
File jarFile = new File(path);
|
||||
if (jarFile.exists()) {
|
||||
URL jarUrl = jarFile.toURI().toURL();
|
||||
urlClassLoader.addURLFile(jarUrl);
|
||||
}
|
||||
}
|
||||
LoggerUtil.info("完成初始化JAR[ " + path + " ]");
|
||||
} catch (Exception ex) {
|
||||
LoggerUtil.error("加载JAR包失败:", ex);
|
||||
}
|
||||
});
|
||||
return urlClassLoader;
|
||||
}
|
||||
|
||||
public static GroovyClassLoader getDynamic(List<String> jarPaths) {
|
||||
if (CollectionUtils.isNotEmpty(jarPaths)) {
|
||||
GroovyClassLoader dynamicClassLoader = new GroovyClassLoader();
|
||||
jarPaths.forEach(path -> {
|
||||
try {
|
||||
if (StringUtils.isNotEmpty(path)) {
|
||||
File jarFile = new File(path);
|
||||
if (jarFile.exists()) {
|
||||
dynamicClassLoader.addURL(jarFile.toURI().toURL());
|
||||
LoggerUtil.info("初始化JAR[ " + path + " ] 成功");
|
||||
} else {
|
||||
LoggerUtil.info("JAR文件:【" + path + "】已经丢失,加载失败");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LoggerUtil.error("加载JAR包失败:", ex);
|
||||
}
|
||||
});
|
||||
return dynamicClassLoader;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MsClassLoader() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package io.metersphere.jmeter;
|
||||
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class MsDynamicClassLoader extends URLClassLoader {
|
||||
public MsDynamicClassLoader() {
|
||||
super(new URL[]{}, findParentClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的文件url添加到类加载器的classpath中去,并缓存jar connection,方便以后卸载jar
|
||||
* 一个可想类加载器的classpath中添加的文件url
|
||||
*
|
||||
* @param
|
||||
*/
|
||||
public void addURLFile(URL file) {
|
||||
try {
|
||||
// 打开并缓存文件url连接
|
||||
URLConnection uc = file.openConnection();
|
||||
if (uc instanceof JarURLConnection) {
|
||||
uc.setUseCaches(true);
|
||||
((JarURLConnection) uc).getManifest();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error("Failed to cache plugin JAR file: " + file.toExternalForm());
|
||||
}
|
||||
addURL(file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 定位基于当前上下文的父类加载器
|
||||
*
|
||||
* @return 返回可用的父类加载器.
|
||||
*/
|
||||
private static ClassLoader findParentClassLoader() {
|
||||
ClassLoader parent = ClassLoader.getSystemClassLoader();
|
||||
if (parent == null) {
|
||||
parent = MsDynamicClassLoader.class.getClassLoader();
|
||||
}
|
||||
if (parent == null) {
|
||||
parent = ClassLoader.getSystemClassLoader();
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import io.metersphere.jmeter.MsClassLoader;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.functions.Function;
|
||||
import org.apache.jmeter.testelement.TestPlan;
|
||||
import org.apache.jmeter.threads.JMeterContext;
|
||||
import org.apache.jmeter.threads.JMeterContextService;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jorphan.reflect.ClassFinder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CustomizeFunctionUtil {
|
||||
public static final String CUSTOMIZE_FUNCTION = "CUSTOMIZE_FUNCTION";
|
||||
private static final String CONTAIN = "classfinder.functions.contain";
|
||||
private static final String NOT_CONTAIN = "classfinder.functions.notContain";
|
||||
public static final String JAR_PATH = "JAR_PATH";
|
||||
public static final String MS_CLASS_LOADER = "MS_CLASS_LOADER";
|
||||
|
||||
public static void initCustomizeClass(TestPlan testPlan) {
|
||||
try {
|
||||
Map<String, Class<? extends Function>> functions = new HashMap<>();
|
||||
String pathStr = testPlan.getPropertyAsString(JAR_PATH);
|
||||
JMeterContext context = JMeterContextService.getContext();
|
||||
if (StringUtils.isNotEmpty(pathStr) && context != null) {
|
||||
List<String> jarPaths = JsonUtils.parseObject(pathStr, List.class);
|
||||
if (CollectionUtils.isNotEmpty(jarPaths)) {
|
||||
// 初始化类加载器
|
||||
GroovyClassLoader loader = MsClassLoader.getDynamic(jarPaths);
|
||||
if (loader == null) {
|
||||
return;
|
||||
}
|
||||
// 所有自定义jar的类加载器
|
||||
context.getVariables().putObject(MS_CLASS_LOADER, loader);
|
||||
Thread.currentThread().setContextClassLoader(loader);
|
||||
// 获取所有自定义函数class
|
||||
List<String> classes = ClassFinder.findClassesThatExtend(
|
||||
jarPaths.toArray(String[]::new),
|
||||
new Class[]{Function.class}, true,
|
||||
JMeterUtils.getProperty(CONTAIN),
|
||||
JMeterUtils.getProperty(NOT_CONTAIN)
|
||||
);
|
||||
|
||||
for (String clazzName : classes) {
|
||||
Function tempFunc = loader.loadClass(clazzName)
|
||||
.asSubclass(Function.class)
|
||||
.getDeclaredConstructor().newInstance();
|
||||
String referenceKey = tempFunc.getReferenceKey();
|
||||
if (StringUtils.isNotEmpty(referenceKey)) { // ignore self
|
||||
functions.put(referenceKey, tempFunc.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (MapUtils.isNotEmpty(functions)) {
|
||||
context.getVariables().putObject(CUSTOMIZE_FUNCTION, functions);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Class<? extends Function>> getFunctions() {
|
||||
try {
|
||||
JMeterContext context = JMeterContextService.getContext();
|
||||
if (context != null && ObjectUtils.isNotEmpty(context.getVariables())) {
|
||||
Object function = context.getVariables().getObject(CUSTOMIZE_FUNCTION);
|
||||
if (ObjectUtils.isNotEmpty(function)) {
|
||||
return (Map<String, Class<? extends Function>>) function;
|
||||
}
|
||||
}
|
||||
return new HashMap<>();
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error(e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.metersphere.vo.Condition;
|
||||
import io.metersphere.vo.ElementCondition;
|
||||
import net.minidev.json.JSONArray;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.oro.text.regex.Pattern;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DocumentUtils {
|
||||
|
||||
public static boolean documentChecked(Object subj, String condition, ThreadLocal<DecimalFormat> decimalFormatter) {
|
||||
if (StringUtils.isNotEmpty(condition)) {
|
||||
ElementCondition elementCondition = JsonUtils.parseObject(condition, ElementCondition.class);
|
||||
boolean isTrue = true;
|
||||
if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) {
|
||||
for (Condition item : elementCondition.getConditions()) {
|
||||
String expectedValue = item.getValue() != null ? item.getValue().toString() : "";
|
||||
String resValue = objectToString(subj, decimalFormatter);
|
||||
switch (item.getKey()) {
|
||||
case "value_eq":
|
||||
isTrue = valueEquals(resValue, expectedValue);
|
||||
break;
|
||||
case "value_not_eq":
|
||||
isTrue = valueNotEquals(resValue, expectedValue);
|
||||
break;
|
||||
case "value_in":
|
||||
isTrue = StringUtils.contains(resValue, expectedValue);
|
||||
break;
|
||||
case "length_eq":
|
||||
isTrue = getLength(subj, decimalFormatter) == numberOf(item.getValue());
|
||||
break;
|
||||
case "length_not_eq":
|
||||
isTrue = getLength(subj, decimalFormatter) != numberOf(item.getValue());
|
||||
break;
|
||||
case "length_gt":
|
||||
isTrue = getLength(subj, decimalFormatter) > numberOf(item.getValue());
|
||||
break;
|
||||
case "length_lt":
|
||||
isTrue = getLength(subj, decimalFormatter) < numberOf(item.getValue());
|
||||
break;
|
||||
case "regular":
|
||||
Pattern pattern = JMeterUtils.getPatternCache().getPattern(expectedValue);
|
||||
isTrue = JMeterUtils.getMatcher().matches(resValue, pattern);
|
||||
break;
|
||||
}
|
||||
if (!isTrue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isTrue && checkType(elementCondition, subj);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean checkType(ElementCondition elementCondition, Object subj) {
|
||||
if (elementCondition.isTypeVerification()) {
|
||||
return StringUtils.equalsIgnoreCase(elementCondition.getType(), "object") || (subj != null
|
||||
&& StringUtils.equalsIgnoreCase(elementCondition.getType(), getType(subj)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String getType(Object object) {
|
||||
String type = object.getClass().getName().substring(object.getClass().getName().lastIndexOf(".") + 1);
|
||||
if (StringUtils.equalsIgnoreCase("Integer", type)) {
|
||||
return type.toLowerCase();
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, "integer", "float", "long", "double")) {
|
||||
return "number";
|
||||
} else if (StringUtils.indexOfAny(type, "Array", "List") != -1) {
|
||||
return "array";
|
||||
}
|
||||
return type.toLowerCase();
|
||||
}
|
||||
|
||||
private static boolean valueEquals(String v1, String v2) {
|
||||
try {
|
||||
Number number1 = NumberUtils.createNumber(v1);
|
||||
Number number2 = NumberUtils.createNumber(v2);
|
||||
return number1.equals(number2);
|
||||
} catch (Exception e) {
|
||||
return StringUtils.equals(v1, v2);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean valueNotEquals(String v1, String v2) {
|
||||
try {
|
||||
Number number1 = NumberUtils.createNumber(v1);
|
||||
Number number2 = NumberUtils.createNumber(v2);
|
||||
return !number1.equals(number2);
|
||||
} catch (Exception e) {
|
||||
return !StringUtils.equals(v1, v2);
|
||||
}
|
||||
}
|
||||
|
||||
public static String objectToString(Object subj, ThreadLocal<DecimalFormat> decimalFormatter) {
|
||||
String str;
|
||||
if (subj == null) {
|
||||
str = "null";
|
||||
} else if (subj instanceof Map) {
|
||||
str = new Gson().toJson(subj);
|
||||
} else if (!(subj instanceof Double) && !(subj instanceof Float)) {
|
||||
str = subj.toString();
|
||||
} else {
|
||||
str = ((DecimalFormat) decimalFormatter.get()).format(subj);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
private static int getLength(Object value) {
|
||||
if (value != null) {
|
||||
if (value instanceof List) {
|
||||
return ((List) value).size();
|
||||
}
|
||||
return value.toString().length();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int getLength(Object value, ThreadLocal<DecimalFormat> decimalFormatter) {
|
||||
if (value != null) {
|
||||
if (value instanceof Map) {
|
||||
return ((Map) value).size();
|
||||
} else if (value instanceof List) {
|
||||
return ((List) value).size();
|
||||
} else if (!(value instanceof Double) && !(value instanceof Float)) {
|
||||
return value.toString().length();
|
||||
} else {
|
||||
return ((DecimalFormat) decimalFormatter.get()).format(value).length();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static long numberOf(Object value) {
|
||||
if (value != null) {
|
||||
try {
|
||||
return Long.parseLong(value.toString());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static String arrayMatched(JSONArray value) {
|
||||
if (value.isEmpty()) {
|
||||
return "array";
|
||||
} else {
|
||||
Object[] var2 = value.toArray();
|
||||
int var3 = var2.length;
|
||||
|
||||
for (int var4 = 0; var4 < var3; ++var4) {
|
||||
Object subj = var2[var4];
|
||||
return getType(subj);
|
||||
}
|
||||
}
|
||||
return getType(value);
|
||||
}
|
||||
|
||||
public static String documentMsg(String name, Object resValue, String condition) {
|
||||
String msg = "";
|
||||
if (StringUtils.isNotEmpty(condition)) {
|
||||
ElementCondition elementCondition = JsonUtils.parseObject(condition, ElementCondition.class);
|
||||
if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) {
|
||||
for (Condition item : elementCondition.getConditions()) {
|
||||
if (StringUtils.equalsAny(item.getKey(), "value_eq", "value_not_eq", "value_in")) {
|
||||
msg = resValue != null ? resValue.toString() : "";
|
||||
} else if (StringUtils.equalsAny(item.getKey(), "length_eq", "length_not_eq", "length_gt", "length_lt")) {
|
||||
msg = "长度是:" + getLength(resValue) + "";
|
||||
} else {
|
||||
msg = resValue != null ? resValue.toString() : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!checkType(elementCondition, resValue)) {
|
||||
if (resValue instanceof JSONArray) {
|
||||
msg = " 类型:" + (arrayMatched((JSONArray) resValue)) + ", 值:" + msg;
|
||||
} else {
|
||||
msg = " 类型:" + getType(resValue) + ", 值:" + msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (StringUtils.isNotEmpty(name) ? name.split("==")[1] : "") + "校验失败,实际返回:" + msg;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.extractor.JSR223PostProcessor;
|
||||
import org.apache.jmeter.extractor.RegexExtractor;
|
||||
import org.apache.jmeter.extractor.XPath2Extractor;
|
||||
import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor;
|
||||
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
|
||||
import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler;
|
||||
import org.apache.jmeter.threads.JMeterVariables;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class JMeterVars {
|
||||
|
||||
private JMeterVars() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据和线程变量保持一致
|
||||
*/
|
||||
private static Map<String, JMeterVariables> variables = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 线程执行过程调用提取变量值
|
||||
*
|
||||
* @param testId
|
||||
* @param vars
|
||||
* @param extract
|
||||
*/
|
||||
public static void addVars(String testId, JMeterVariables vars, String extract) {
|
||||
JMeterVariables vs = variables.get(testId);
|
||||
if (vs == null) {
|
||||
vs = new JMeterVariables();
|
||||
}
|
||||
if (!StringUtils.isEmpty(extract) && vars != null) {
|
||||
List<String> extracts = Arrays.asList(extract.split(";"));
|
||||
if (CollectionUtils.isNotEmpty(extracts)) {
|
||||
for (String item : extracts) {
|
||||
String nrKey = item + "_matchNr";
|
||||
Object nr = vars.get(nrKey);
|
||||
JMeterVariables jMeterVariables = new JMeterVariables();
|
||||
if (nr != null) {
|
||||
int nrv = 0;
|
||||
try {
|
||||
nrv = Integer.valueOf(String.valueOf(nr));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (nrv > 0) {
|
||||
List<Object> data = new ArrayList<>();
|
||||
for (int i = 1; i < nrv + 1; i++) {
|
||||
data.add(vars.get(item + "_" + i));
|
||||
}
|
||||
String array = JsonUtils.toJSONString(data);
|
||||
jMeterVariables.put(item, array);
|
||||
}
|
||||
}
|
||||
if (jMeterVariables.get(item) != null) {
|
||||
vs.put(item, jMeterVariables.get(item));
|
||||
} else {
|
||||
vs.put(item, vars.get(item) == null ? "" : vars.get(item));
|
||||
}
|
||||
}
|
||||
vs.remove("TESTSTART.MS"); // 标示变量移除
|
||||
}
|
||||
}
|
||||
|
||||
variables.put(testId, vs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有请求,有提取变量的请求增加后置脚本提取变量值
|
||||
*
|
||||
* @param tree
|
||||
*/
|
||||
public static void addJSR223PostProcessor(HashTree tree) {
|
||||
for (Object key : tree.keySet()) {
|
||||
HashTree node = tree.get(key);
|
||||
if (key instanceof HTTPSamplerProxy || key instanceof DubboSample || key instanceof JDBCSampler) {
|
||||
StringJoiner extract = new StringJoiner(";");
|
||||
for (Object child : node.keySet()) {
|
||||
if (child instanceof RegexExtractor) {
|
||||
RegexExtractor regexExtractor = (RegexExtractor) child;
|
||||
extract.add(regexExtractor.getRefName());
|
||||
} else if (child instanceof XPath2Extractor) {
|
||||
XPath2Extractor regexExtractor = (XPath2Extractor) child;
|
||||
extract.add(regexExtractor.getRefName());
|
||||
} else if (child instanceof JSONPostProcessor) {
|
||||
JSONPostProcessor regexExtractor = (JSONPostProcessor) child;
|
||||
extract.add(regexExtractor.getRefNames());
|
||||
}
|
||||
}
|
||||
|
||||
if (Optional.ofNullable(extract).orElse(extract).length() > 0) {
|
||||
JSR223PostProcessor shell = new JSR223PostProcessor();
|
||||
shell.setEnabled(true);
|
||||
shell.setProperty("script", "io.metersphere.utils.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + extract.toString() + "\"" + ");");
|
||||
node.add(shell);
|
||||
}
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
addJSR223PostProcessor(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static JMeterVariables get(String key) {
|
||||
return variables.get(key);
|
||||
}
|
||||
|
||||
public static void remove(String key) {
|
||||
variables.remove(key);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.CollectionType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class JsonUtils {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
public static String toJSONString(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object parseObject(String content) {
|
||||
return parseObject(content, Object.class);
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String content, Class<T> valueType) {
|
||||
try {
|
||||
return objectMapper.readValue(content, valueType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T parseObject(InputStream src, Class<T> valueType) {
|
||||
try {
|
||||
return objectMapper.readValue(src, valueType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static List parseArray(String content) {
|
||||
return parseArray(content, Object.class);
|
||||
}
|
||||
|
||||
public static <T> List<T> parseArray(String content, Class<T> valueType) {
|
||||
CollectionType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, valueType);
|
||||
try {
|
||||
return objectMapper.readValue(content, javaType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import io.metersphere.dto.RequestResult;
|
||||
import io.metersphere.dto.ResultDTO;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.threads.JMeterVariables;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ListenerUtil {
|
||||
public static final String ERROR_LOGGING = "MsResultCollector.error_logging";
|
||||
|
||||
public static final String TEST_IS_LOCAL = "*local*";
|
||||
|
||||
public static final String SUCCESS_ONLY_LOGGING = "MsResultCollector.success_only_logging";
|
||||
|
||||
public static final String RUNNING_DEBUG_SAMPLER_NAME = "RunningDebugSampler";
|
||||
|
||||
private static final String PRE_PROCESS_SCRIPT = "PRE_PROCESSOR_ENV_";
|
||||
private static final String POST_PROCESS_SCRIPT = "POST_PROCESSOR_ENV_";
|
||||
|
||||
/**
|
||||
* 判断结果是否需要被过滤
|
||||
*
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
public static boolean checkResultIsNotFilterOut(RequestResult result) {
|
||||
boolean resultNotFilterOut = true;
|
||||
if (StringUtils.startsWithAny(result.getName(), PRE_PROCESS_SCRIPT)) {
|
||||
resultNotFilterOut = Boolean.parseBoolean(StringUtils.substring(result.getName(), PRE_PROCESS_SCRIPT.length()));
|
||||
} else if (StringUtils.startsWithAny(result.getName(), POST_PROCESS_SCRIPT)) {
|
||||
resultNotFilterOut = Boolean.parseBoolean(StringUtils.substring(result.getName(), POST_PROCESS_SCRIPT.length()));
|
||||
}
|
||||
return resultNotFilterOut;
|
||||
}
|
||||
|
||||
public static void setVars(SampleResult result) {
|
||||
if (StringUtils.isNotEmpty(result.getSampleLabel()) && result.getSampleLabel().startsWith("Transaction=")) {
|
||||
for (int i = 0; i < result.getSubResults().length; i++) {
|
||||
SampleResult subResult = result.getSubResults()[i];
|
||||
setVars(subResult);
|
||||
}
|
||||
}
|
||||
JMeterVariables variables = JMeterVars.get(result.getResourceId());
|
||||
if (variables != null && CollectionUtils.isNotEmpty(variables.entrySet())) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : variables.entrySet()) {
|
||||
builder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(builder)) {
|
||||
result.setExtVars(builder.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setEev(ResultDTO dto, List<String> environmentList) {
|
||||
dto.setArbitraryData(new HashMap<String, Object>() {{
|
||||
this.put("ENV", environmentList);
|
||||
}});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
public class LoggerUtil {
|
||||
//日志工具类
|
||||
public static final String DEBUG = "DEBUG";
|
||||
public static final String INFO = "INFO";
|
||||
public static final String WARN = "WARN";
|
||||
public static final String ERROR = "ERROR";
|
||||
|
||||
/**
|
||||
* 初始化日志
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Logger getLogger() {
|
||||
return LoggerFactory.getLogger("ms-jmeter-run-log");
|
||||
}
|
||||
|
||||
public static void writeLog(Object msg, String level) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
|
||||
if (DEBUG.equals(level)) {
|
||||
if (logger != null && logger.isDebugEnabled()) {
|
||||
logger.debug(LoggerUtil.getMsg(msg));
|
||||
}
|
||||
} else if (INFO.equals(level)) {
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.info(LoggerUtil.getMsg(msg));
|
||||
}
|
||||
} else if (WARN.equals(level)) {
|
||||
if (logger != null && logger.isWarnEnabled()) {
|
||||
logger.warn(LoggerUtil.getMsg(msg));
|
||||
}
|
||||
} else if (ERROR.equals(level)) {
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error(LoggerUtil.getMsg(msg));
|
||||
}
|
||||
} else {
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(Object msg) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.info(LoggerUtil.getMsg(msg));
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(Object msg, Object o1) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.info(LoggerUtil.getMsg(msg), o1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(String msg, String reportId) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.info(LoggerUtil.getLogMethod() + "[REPORT-ID: " + reportId + "] " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(String msg, String reportId, Object o1) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.info(LoggerUtil.getLogMethod() + "[REPORT-ID: " + reportId + "] " + msg, o1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(Object msg, Object o1, Object o2) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.info(LoggerUtil.getMsg(msg), o1, o2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void info(Object msg, Object[] obj) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.info(LoggerUtil.getMsg(msg), obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug(Object msg) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isDebugEnabled()) {
|
||||
logger.debug(LoggerUtil.getMsg(msg));
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug(Object msg, Object o) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isDebugEnabled()) {
|
||||
logger.debug(LoggerUtil.getMsg(msg), o);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug(Object msg, Object o1, Object o2) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isDebugEnabled()) {
|
||||
logger.debug(LoggerUtil.getMsg(msg), o1, o2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug(Object msg, Object[] obj) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isDebugEnabled()) {
|
||||
logger.debug(LoggerUtil.getMsg(msg), obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static void warn(Object msg) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isWarnEnabled()) {
|
||||
logger.warn(LoggerUtil.getMsg(msg));
|
||||
}
|
||||
}
|
||||
|
||||
public static void warn(Object msg, Object o) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isWarnEnabled()) {
|
||||
logger.warn(LoggerUtil.getMsg(msg), o);
|
||||
}
|
||||
}
|
||||
|
||||
public static void warn(Object msg, Object o1, Object o2) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isWarnEnabled()) {
|
||||
logger.warn(LoggerUtil.getMsg(msg), o1, o2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void warn(Object msg, Object[] obj) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isWarnEnabled()) {
|
||||
logger.warn(LoggerUtil.getMsg(msg), obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(Object msg) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error(LoggerUtil.getMsg(msg));// 并追加方法名称
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(Throwable e) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error(LoggerUtil.getMsg(e), e);// 同时打印错误堆栈信息
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(Object msg, Object o) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error(LoggerUtil.getMsg(msg), o);
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(String msg, String reportId, Object o1) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isInfoEnabled()) {
|
||||
logger.error(LoggerUtil.getLogMethod() + "[REPORT-ID: " + reportId + "] " + msg, o1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(Object msg, Object o1, Object o2) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error(LoggerUtil.getMsg(msg), o1, o2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(Object msg, Object[] obj) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error(LoggerUtil.getMsg(msg), obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(Object msg, Throwable ex) {
|
||||
Logger logger = LoggerUtil.getLogger();
|
||||
if (logger != null && logger.isErrorEnabled()) {
|
||||
logger.error(LoggerUtil.getMsg(msg), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getMsg(Object msg, Throwable ex) {
|
||||
String str = "";
|
||||
|
||||
if (msg != null) {
|
||||
str = LoggerUtil.getLogMethod() + "[" + msg.toString() + "]";
|
||||
} else {
|
||||
str = LoggerUtil.getLogMethod() + "[null]";
|
||||
}
|
||||
if (ex != null) {
|
||||
str += "[" + ex.getMessage() + "]";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static String getMsg(Object msg) {
|
||||
return LoggerUtil.getMsg(msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到调用类名称
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String getLogClass() {
|
||||
String str = "";
|
||||
|
||||
StackTraceElement[] stack = (new Throwable()).getStackTrace();
|
||||
if (stack.length > 3) {
|
||||
StackTraceElement ste = stack[3];
|
||||
str = ste.getClassName();// 类名称
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到调用方法名称
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String getLogMethod() {
|
||||
String str = "";
|
||||
|
||||
StackTraceElement[] stack = (new Throwable()).getStackTrace();
|
||||
if (stack.length > 4) {
|
||||
StackTraceElement ste = stack[4];
|
||||
str = "Method[" + ste.getMethodName() + "]";// 方法名称
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public static String toString(Throwable e) {
|
||||
StringWriter sw = null;
|
||||
PrintWriter pw = null;
|
||||
try {
|
||||
sw = new StringWriter();
|
||||
pw = new PrintWriter(sw);
|
||||
//将出错的栈信息输出到printWriter中
|
||||
e.printStackTrace(pw);
|
||||
pw.flush();
|
||||
sw.flush();
|
||||
} finally {
|
||||
if (sw != null) {
|
||||
try {
|
||||
sw.close();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (pw != null) {
|
||||
pw.close();
|
||||
}
|
||||
}
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
public static String getExceptionDetailsToStr(Exception e) {
|
||||
StringBuilder sb = new StringBuilder(e.toString());
|
||||
StackTraceElement[] stackElements = e.getStackTrace();
|
||||
for (StackTraceElement stackTraceElement : stackElements) {
|
||||
sb.append(stackTraceElement.toString());
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package io.metersphere.utils;
|
||||
|
||||
import io.metersphere.dto.RequestResult;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 重试报告处理util
|
||||
*/
|
||||
public class RetryResultUtil {
|
||||
public final static String RETRY = "MsRetry_";
|
||||
public final static String RETRY_CN = "重试";
|
||||
public final static String RETRY_FIRST_CN = "首次";
|
||||
public final static String MS_CLEAR_LOOPS_VAR = "MS_CLEAR_LOOPS_VAR_";
|
||||
public final static int RETRY_RES_NUM = 11;
|
||||
|
||||
/**
|
||||
* 合并掉重试结果;保留最后十次重试结果
|
||||
*
|
||||
* @param results
|
||||
*/
|
||||
public static void mergeRetryResults(List<RequestResult> results) {
|
||||
if (CollectionUtils.isNotEmpty(results)) {
|
||||
Map<String, List<RequestResult>> resultMap = results.stream().collect(Collectors.groupingBy(RequestResult::getResourceId));
|
||||
List<RequestResult> list = new LinkedList<>();
|
||||
resultMap.forEach((k, v) -> {
|
||||
if (CollectionUtils.isNotEmpty(v)) {
|
||||
// 校验是否含重试结果
|
||||
List<RequestResult> isRetryResults = v
|
||||
.stream()
|
||||
.filter(c -> StringUtils.isNotEmpty(c.getName()) && c.getName().startsWith(RETRY))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(isRetryResults)) {
|
||||
// 取最后执行的10 条
|
||||
if (v.size() > 10) {
|
||||
Collections.sort(v, Comparator.comparing(RequestResult::getResourceId));
|
||||
RequestResult sampleResult = v.get(0);
|
||||
List<RequestResult> topTens = v.subList(v.size() - RETRY_RES_NUM, v.size());
|
||||
topTens.set(0, sampleResult);
|
||||
assembleName(topTens);
|
||||
list.addAll(topTens);
|
||||
} else {
|
||||
assembleName(v);
|
||||
list.addAll(v);
|
||||
}
|
||||
} else {
|
||||
// 成功的结果
|
||||
list.addAll(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
results.clear();
|
||||
results.addAll(list);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assembleName(List<RequestResult> list) {
|
||||
// 名称排序处理
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
list.get(i).setName(list.get(i).getName().replaceAll(RETRY, RETRY_CN));
|
||||
if (list.get(i).getName().endsWith("_")) {
|
||||
list.get(i).setName(list.get(i).getName().substring(0, list.get(i).getName().length() - 1));
|
||||
}
|
||||
if (i == 0) {
|
||||
list.get(i).setName(StringUtils.isNotEmpty(list.get(i).getName())
|
||||
? RETRY_FIRST_CN + "_" + list.get(i).getName() : RETRY_FIRST_CN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<SampleResult> clearLoops(List<SampleResult> results) {
|
||||
if (CollectionUtils.isNotEmpty(results)) {
|
||||
return results.stream().filter(sampleResult ->
|
||||
StringUtils.isNotEmpty(sampleResult.getSampleLabel())
|
||||
&& !sampleResult.getSampleLabel().startsWith(MS_CLEAR_LOOPS_VAR))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.metersphere.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BooleanPool {
|
||||
private boolean isPool;
|
||||
private boolean isK8s;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.metersphere.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Condition {
|
||||
private String key;
|
||||
private Object value;
|
||||
|
||||
public Condition() {
|
||||
|
||||
}
|
||||
|
||||
public Condition(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package io.metersphere.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ElementCondition {
|
||||
private boolean include;
|
||||
private boolean typeVerification;
|
||||
private boolean arrayVerification;
|
||||
private String type;
|
||||
|
||||
List<Condition> conditions;
|
||||
|
||||
public ElementCondition() {
|
||||
|
||||
}
|
||||
|
||||
public ElementCondition(boolean include, boolean typeVerification, boolean arrayVerification, List<Condition> conditions) {
|
||||
this.include = include;
|
||||
this.typeVerification = typeVerification;
|
||||
this.arrayVerification = arrayVerification;
|
||||
this.conditions = conditions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter;
|
||||
|
||||
// N.B. this must only use standard Java packages
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main class for JMeter - sets up initial classpath and the loader.
|
||||
*/
|
||||
public final class NewDriver {
|
||||
|
||||
private static final String CLASSPATH_SEPARATOR = File.pathSeparator;
|
||||
|
||||
private static final String OS_NAME = System.getProperty("os.name");// $NON-NLS-1$
|
||||
|
||||
private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH);
|
||||
|
||||
private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$
|
||||
|
||||
private static final String JMETER_LOGFILE_SYSTEM_PROPERTY = "jmeter.logfile";// $NON-NLS-1$
|
||||
|
||||
private static final String HEADLESS_MODE_PROPERTY = "java.awt.headless";// $NON-NLS-1$
|
||||
/**
|
||||
* The class loader to use for loading JMeter classes.
|
||||
*/
|
||||
private static final DynamicClassLoader loader;
|
||||
|
||||
/**
|
||||
* The directory JMeter is installed in.
|
||||
*/
|
||||
private static final String JMETER_INSTALLATION_DIRECTORY;
|
||||
|
||||
private static final List<Exception> EXCEPTIONS_IN_INIT = new ArrayList<>();
|
||||
|
||||
// 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题
|
||||
public static void setContextClassLoader() {
|
||||
Thread.currentThread().setContextClassLoader(loader);
|
||||
}
|
||||
|
||||
public static void loaderClass(String name) {
|
||||
try {
|
||||
loader.loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
final List<URL> jars = new ArrayList<>();
|
||||
final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH);
|
||||
|
||||
// Find JMeter home dir from the initial classpath
|
||||
String tmpDir;
|
||||
|
||||
//只从 jmeter.home 加载
|
||||
tmpDir = System.getProperty("jmeter.home", "");// Allow override $NON-NLS-1$ $NON-NLS-2$
|
||||
if (tmpDir.length() == 0) {
|
||||
File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$
|
||||
tmpDir = userDir.getAbsoluteFile().getParent();
|
||||
}
|
||||
if (tmpDir == null) {
|
||||
tmpDir = System.getenv("JMETER_HOME");
|
||||
}
|
||||
JMETER_INSTALLATION_DIRECTORY = tmpDir;
|
||||
|
||||
/*
|
||||
* Does the system support UNC paths? If so, may need to fix them up
|
||||
* later
|
||||
*/
|
||||
boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$
|
||||
|
||||
// Add standard jar locations to initial classpath
|
||||
StringBuilder classpath = new StringBuilder();
|
||||
File[] libDirs = new File[]{new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$
|
||||
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$
|
||||
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$
|
||||
for (File libDir : libDirs) {
|
||||
File[] libJars = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
||||
if (libJars == null) {
|
||||
new Throwable("Could not access " + libDir).printStackTrace(); // NOSONAR No logging here
|
||||
continue;
|
||||
}
|
||||
Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars
|
||||
for (File libJar : libJars) {
|
||||
try {
|
||||
String s = libJar.getPath();
|
||||
|
||||
// Fix path to allow the use of UNC URLs
|
||||
if (usesUNC) {
|
||||
if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$
|
||||
s = "\\\\" + s;// $NON-NLS-1$
|
||||
} else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$
|
||||
s = "//" + s;// $NON-NLS-1$
|
||||
}
|
||||
} // usesUNC
|
||||
|
||||
jars.add(new File(s).toURI().toURL());// See Java bug 4496398
|
||||
classpath.append(CLASSPATH_SEPARATOR);
|
||||
classpath.append(s);
|
||||
} catch (MalformedURLException e) { // NOSONAR
|
||||
EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:" + libJar.getAbsolutePath(), e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ClassFinder needs the classpath
|
||||
System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString());
|
||||
loader = createClassLoader(jars);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static DynamicClassLoader createClassLoader(List<URL> jars) {
|
||||
return java.security.AccessController.doPrivileged(
|
||||
(java.security.PrivilegedAction<DynamicClassLoader>) () ->
|
||||
new DynamicClassLoader(jars.toArray(new URL[jars.size()]), Thread.currentThread().getContextClassLoader())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent instantiation.
|
||||
*/
|
||||
private NewDriver() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an array of jar files located in a directory.
|
||||
* Jar files located in sub directories will not be added.
|
||||
*
|
||||
* @param dir to search for the jar files.
|
||||
*/
|
||||
private static File[] listJars(File dir) {
|
||||
if (dir.isDirectory()) {
|
||||
return dir.listFiles((f, name) -> {
|
||||
if (name.endsWith(".jar")) {// $NON-NLS-1$
|
||||
File jar = new File(f, name);
|
||||
return jar.isFile() && jar.canRead();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return new File[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a URL to the loader classpath only; does not update the system classpath.
|
||||
*
|
||||
* @param path to be added.
|
||||
* @throws MalformedURLException when <code>path</code> points to an invalid url
|
||||
*/
|
||||
public static void addURL(String path) throws MalformedURLException {
|
||||
File furl = new File(path);
|
||||
loader.addURL(furl.toURI().toURL()); // See Java bug 4496398
|
||||
File[] jars = listJars(furl);
|
||||
for (File jar : jars) {
|
||||
loader.addURL(jar.toURI().toURL()); // See Java bug 4496398
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a URL to the loader classpath only; does not update the system
|
||||
* classpath.
|
||||
*
|
||||
* @param url The {@link URL} to add to the classpath
|
||||
*/
|
||||
public static void addURL(URL url) {
|
||||
loader.addURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory or jar to the loader and system classpaths.
|
||||
*
|
||||
* @param path to add to the loader and system classpath
|
||||
* @throws MalformedURLException if <code>path</code> can not be transformed to a valid
|
||||
* {@link URL}
|
||||
*/
|
||||
public static void addPath(String path) throws MalformedURLException {
|
||||
File file = new File(path);
|
||||
// Ensure that directory URLs end in "/"
|
||||
if (file.isDirectory() && !path.endsWith("/")) {// $NON-NLS-1$
|
||||
file = new File(path + "/");// $NON-NLS-1$
|
||||
}
|
||||
loader.addURL(file.toURI().toURL()); // See Java bug 4496398
|
||||
StringBuilder sb = new StringBuilder(System.getProperty(JAVA_CLASS_PATH));
|
||||
sb.append(CLASSPATH_SEPARATOR);
|
||||
sb.append(path);
|
||||
File[] jars = listJars(file);
|
||||
for (File jar : jars) {
|
||||
loader.addURL(jar.toURI().toURL()); // See Java bug 4496398
|
||||
sb.append(CLASSPATH_SEPARATOR);
|
||||
sb.append(jar.getPath());
|
||||
}
|
||||
|
||||
// ClassFinder needs this
|
||||
System.setProperty(JAVA_CLASS_PATH, sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory where JMeter is installed. This is the absolute path
|
||||
* name.
|
||||
*
|
||||
* @return the directory where JMeter is installed.
|
||||
*/
|
||||
public static String getJMeterDir() {
|
||||
return JMETER_INSTALLATION_DIRECTORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main program which actually runs JMeter.
|
||||
*
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (!EXCEPTIONS_IN_INIT.isEmpty()) {
|
||||
System.err.println("Configuration error during init, see exceptions:" + exceptionsToString(EXCEPTIONS_IN_INIT)); // NOSONAR Intentional System.err use
|
||||
} else {
|
||||
Thread.currentThread().setContextClassLoader(loader);
|
||||
|
||||
|
||||
setLoggingProperties(args);
|
||||
|
||||
try {
|
||||
// Only set property if it has not been set explicitely
|
||||
if (System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) {
|
||||
System.setProperty(HEADLESS_MODE_PROPERTY, "true");
|
||||
}
|
||||
Class<?> initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$
|
||||
Object instance = initialClass.getDeclaredConstructor().newInstance();
|
||||
Method startup = initialClass.getMethod("start", new Class[]{new String[0].getClass()});// $NON-NLS-1$
|
||||
startup.invoke(instance, new Object[]{args});
|
||||
} catch (Throwable e) { // NOSONAR We want to log home directory in case of exception
|
||||
e.printStackTrace(); // NOSONAR No logger at this step
|
||||
System.err.println("JMeter home directory was detected as: " + JMETER_INSTALLATION_DIRECTORY); // NOSONAR Intentional System.err use
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param exceptionsInInit List of {@link Exception}
|
||||
* @return String
|
||||
*/
|
||||
private static String exceptionsToString(List<Exception> exceptionsInInit) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Exception exception : exceptionsInInit) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||
exception.printStackTrace(printWriter); // NOSONAR
|
||||
builder.append(stringWriter.toString())
|
||||
.append("\r\n");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* Set logging related system properties.
|
||||
*/
|
||||
private static void setLoggingProperties(String[] args) {
|
||||
String jmLogFile = getCommandLineArgument(args, 'j', "jmeterlogfile");// $NON-NLS-1$ $NON-NLS-2$
|
||||
|
||||
if (jmLogFile != null && !jmLogFile.isEmpty()) {
|
||||
jmLogFile = replaceDateFormatInFileName(jmLogFile);
|
||||
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, jmLogFile);// $NON-NLS-1$
|
||||
} else if (System.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY) == null) {// $NON-NLS-1$
|
||||
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, "jmeter.log");// $NON-NLS-1$ $NON-NLS-2$
|
||||
}
|
||||
|
||||
String jmLogConf = getCommandLineArgument(args, 'i', "jmeterlogconf");// $NON-NLS-1$ $NON-NLS-2$
|
||||
File logConfFile = null;
|
||||
|
||||
if (jmLogConf != null && !jmLogConf.isEmpty()) {
|
||||
logConfFile = new File(jmLogConf);
|
||||
} else if (System.getProperty("log4j.configurationFile") == null) {// $NON-NLS-1$
|
||||
logConfFile = new File("log4j2.xml");// $NON-NLS-1$
|
||||
if (!logConfFile.isFile()) {
|
||||
logConfFile = new File(JMETER_INSTALLATION_DIRECTORY, "bin" + File.separator + "log4j2.xml");// $NON-NLS-1$ $NON-NLS-2$
|
||||
}
|
||||
}
|
||||
|
||||
if (logConfFile != null) {
|
||||
System.setProperty("log4j.configurationFile", logConfFile.toURI().toString());// $NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldBeHeadless(String[] args) {
|
||||
for (String arg : args) {
|
||||
if ("-n".equals(arg) || "-s".equals(arg) || "-g".equals(arg)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find command line argument option value by the id and name.
|
||||
*/
|
||||
private static String getCommandLineArgument(String[] args, int id, String name) {
|
||||
final String shortArgName = "-" + ((char) id);// $NON-NLS-1$
|
||||
final String longArgName = "--" + name;// $NON-NLS-1$
|
||||
|
||||
String value = null;
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if ((shortArgName.equals(args[i]) && i < args.length - 1)
|
||||
|| longArgName.equals(args[i])) {
|
||||
if (!args[i + 1].startsWith("-")) {// $NON-NLS-1$
|
||||
value = args[i + 1];
|
||||
}
|
||||
break;
|
||||
} else if (!shortArgName.equals(args[i]) && args[i].startsWith(shortArgName)) {
|
||||
value = args[i].substring(shortArgName.length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the fileName contains at least one set of paired single-quotes, reformat using DateFormat
|
||||
*/
|
||||
private static String replaceDateFormatInFileName(String fileName) {
|
||||
try {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
final Instant date = Instant.now();
|
||||
int fromIndex = 0;
|
||||
int begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$
|
||||
int end;
|
||||
|
||||
String format;
|
||||
DateTimeFormatter dateFormat;
|
||||
|
||||
while (begin != -1) {
|
||||
builder.append(fileName.substring(fromIndex, begin));
|
||||
|
||||
fromIndex = begin + 1;
|
||||
end = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$
|
||||
if (end == -1) {
|
||||
throw new IllegalArgumentException("Invalid pairs of single-quotes in the file name: " + fileName);// $NON-NLS-1$
|
||||
}
|
||||
|
||||
format = fileName.substring(begin + 1, end);
|
||||
dateFormat = DateTimeFormatter.ofPattern(format).withZone(ZoneId.systemDefault());
|
||||
builder.append(dateFormat.format(date));
|
||||
|
||||
fromIndex = end + 1;
|
||||
begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$
|
||||
}
|
||||
|
||||
if (fromIndex < fileName.length() - 1) {
|
||||
builder.append(fileName.substring(fromIndex));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("Error replacing date format in file name:" + fileName + ", error:" + ex.getMessage()); // NOSONAR
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package org.apache.jmeter.assertions;
|
||||
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
|
||||
public class ErrorReportAssertion extends ResponseAssertion{
|
||||
@Override
|
||||
public AssertionResult getResult(SampleResult response) {
|
||||
AssertionResult result = super.getResult(response);
|
||||
String faliurMessage = result.getFailureMessage();
|
||||
if(result.isError() || result.isFailure()){
|
||||
faliurMessage = faliurMessage+" Final result is error";
|
||||
} else {
|
||||
faliurMessage = faliurMessage+" Final result is success";
|
||||
}
|
||||
result.setError(false);
|
||||
result.setFailure(false);
|
||||
result.setFailureMessage(faliurMessage);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.assertions;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import io.metersphere.utils.DocumentUtils;
|
||||
import net.minidev.json.JSONArray;
|
||||
import net.minidev.json.JSONObject;
|
||||
import net.minidev.json.JSONValue;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.testelement.AbstractTestElement;
|
||||
import org.apache.jmeter.testelement.ThreadListener;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.oro.text.regex.Pattern;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This is main class for JSONPath Assertion which verifies assertion on
|
||||
* previous sample result using JSON path expression
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener {
|
||||
private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class);
|
||||
private static final long serialVersionUID = 2L;
|
||||
public static final String JSONPATH = "JSON_PATH";
|
||||
public static final String EXPECTEDVALUE = "EXPECTED_VALUE";
|
||||
public static final String JSONVALIDATION = "JSONVALIDATION";
|
||||
public static final String EXPECT_NULL = "EXPECT_NULL";
|
||||
public static final String INVERT = "INVERT";
|
||||
public static final String ISREGEX = "ISREGEX";
|
||||
|
||||
private static final boolean USE_JAVA_REGEX = !JMeterUtils.getPropDefault(
|
||||
"jmeter.regex.engine", "oro").equalsIgnoreCase("oro");
|
||||
|
||||
private static ThreadLocal<DecimalFormat> decimalFormatter =
|
||||
ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat);
|
||||
|
||||
private static DecimalFormat createDecimalFormat() {
|
||||
DecimalFormat decimalFormatter = new DecimalFormat("#.#");
|
||||
decimalFormatter.setMaximumFractionDigits(340); // java.text.DecimalFormat.DOUBLE_FRACTION_DIGITS == 340
|
||||
decimalFormatter.setMinimumFractionDigits(1);
|
||||
return decimalFormatter;
|
||||
}
|
||||
|
||||
public String getOption() {
|
||||
return getPropertyAsString("ASS_OPTION");
|
||||
}
|
||||
|
||||
public String getElementCondition() {
|
||||
return getPropertyAsString("ElementCondition");
|
||||
}
|
||||
|
||||
public String getJsonPath() {
|
||||
return getPropertyAsString(JSONPATH);
|
||||
}
|
||||
|
||||
public void setJsonPath(String jsonPath) {
|
||||
setProperty(JSONPATH, jsonPath);
|
||||
}
|
||||
|
||||
public String getExpectedValue() {
|
||||
return getPropertyAsString(EXPECTEDVALUE);
|
||||
}
|
||||
|
||||
public void setExpectedValue(String expectedValue) {
|
||||
setProperty(EXPECTEDVALUE, expectedValue);
|
||||
}
|
||||
|
||||
public void setJsonValidationBool(boolean jsonValidation) {
|
||||
setProperty(JSONVALIDATION, jsonValidation);
|
||||
}
|
||||
|
||||
public void setExpectNull(boolean val) {
|
||||
setProperty(EXPECT_NULL, val);
|
||||
}
|
||||
|
||||
public boolean isExpectNull() {
|
||||
return getPropertyAsBoolean(EXPECT_NULL);
|
||||
}
|
||||
|
||||
public boolean isJsonValidationBool() {
|
||||
return getPropertyAsBoolean(JSONVALIDATION);
|
||||
}
|
||||
|
||||
public void setInvert(boolean invert) {
|
||||
setProperty(INVERT, invert);
|
||||
}
|
||||
|
||||
public boolean isInvert() {
|
||||
return getPropertyAsBoolean(INVERT);
|
||||
}
|
||||
|
||||
public void setIsRegex(boolean flag) {
|
||||
setProperty(ISREGEX, flag);
|
||||
}
|
||||
|
||||
public boolean isUseRegex() {
|
||||
return getPropertyAsBoolean(ISREGEX, true);
|
||||
}
|
||||
|
||||
private void doAssert(String jsonString) {
|
||||
Object value = JsonPath.read(jsonString, getJsonPath());
|
||||
|
||||
if (!isJsonValidationBool()) {
|
||||
if (value instanceof JSONArray) {
|
||||
JSONArray arrayValue = (JSONArray) value;
|
||||
if (arrayValue.isEmpty() && !JsonPath.isPathDefinite(getJsonPath())) {
|
||||
throw new IllegalStateException("JSONPath is indefinite and the extracted Value is an empty Array." +
|
||||
" Please use an assertion value, to be sure to get a correct result. " + getExpectedValue());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof JSONArray) {
|
||||
if (arrayMatched((JSONArray) value)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if ((isExpectNull() && value == null)
|
||||
|| isEquals(value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isExpectNull()) {
|
||||
throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value));
|
||||
} else {
|
||||
String msg = "";
|
||||
if (this.isUseRegex()) {
|
||||
msg = "Value expected to match regexp '%s', but it did not match: '%s'";
|
||||
} else if (StringUtils.isNotEmpty(getOption()) && !this.isEquals(value)) {
|
||||
switch (getOption()) {
|
||||
case "CONTAINS":
|
||||
msg = "Value contains to be '%s', but found '%s'";
|
||||
break;
|
||||
case "NOT_CONTAINS":
|
||||
msg = "Value not contains to be '%s', but found '%s'";
|
||||
break;
|
||||
case "EQUALS":
|
||||
msg = "Value equals to be '%s', but found '%s'";
|
||||
break;
|
||||
case "NOT_EQUALS":
|
||||
msg = "Value not equals to be '%s', but found '%s'";
|
||||
break;
|
||||
case "GT":
|
||||
msg = "Value > '%s', but found '%s'";
|
||||
break;
|
||||
case "LT":
|
||||
msg = "Value < '%s', but found '%s'";
|
||||
break;
|
||||
case "DOCUMENT":
|
||||
msg = DocumentUtils.documentMsg(this.getName(), value, this.getElementCondition());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
msg = "Value expected to be '%s', but found '%s'";
|
||||
}
|
||||
throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean arrayMatched(JSONArray value) {
|
||||
|
||||
List<Boolean> result = new ArrayList<>();
|
||||
for (Object subj : value.toArray()) {
|
||||
if (!StringUtils.equals(getOption(), "NOT_CONTAINS")) {
|
||||
if (subj == null || isEquals(subj)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
result.add(isEquals(subj));
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(result) && StringUtils.equals(getOption(), "NOT_CONTAINS")) {
|
||||
if (result.stream().filter(item -> item == true).collect(Collectors.toList()).size() == result.size()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return isEquals(value);
|
||||
}
|
||||
|
||||
private boolean isGt(String v1, String v2) {
|
||||
try {
|
||||
BigDecimal value1 = new BigDecimal(v1);
|
||||
BigDecimal value2 = new BigDecimal(v2);
|
||||
return value1.compareTo(value2) > 0;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLt(String v1, String v2) {
|
||||
try {
|
||||
BigDecimal value1 = new BigDecimal(v1);
|
||||
BigDecimal value2 = new BigDecimal(v2);
|
||||
return value1.compareTo(value2) < 0;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEquals(Object subj) {
|
||||
String str = DocumentUtils.objectToString(subj, decimalFormatter);
|
||||
if (isUseRegex()) {
|
||||
if (USE_JAVA_REGEX) {
|
||||
return JMeterUtils.compilePattern(getExpectedValue()).matcher(str).matches();
|
||||
} else {
|
||||
Pattern pattern = JMeterUtils.getPatternCache().getPattern(getExpectedValue());
|
||||
return JMeterUtils.getMatcher().matches(str, pattern);
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.isNotEmpty(getOption())) {
|
||||
boolean refFlag = false;
|
||||
switch (getOption()) {
|
||||
case "CONTAINS":
|
||||
refFlag = str.contains(getExpectedValue());
|
||||
break;
|
||||
case "NOT_CONTAINS":
|
||||
refFlag = !str.contains(getExpectedValue());
|
||||
break;
|
||||
case "EQUALS":
|
||||
refFlag = valueEquals(str, getExpectedValue());
|
||||
break;
|
||||
case "NOT_EQUALS":
|
||||
refFlag = valueNotEquals(str, getExpectedValue());
|
||||
break;
|
||||
case "GT":
|
||||
refFlag = isGt(str, getExpectedValue());
|
||||
break;
|
||||
case "LT":
|
||||
refFlag = isLt(str, getExpectedValue());
|
||||
break;
|
||||
case "DOCUMENT":
|
||||
refFlag = DocumentUtils.documentChecked(subj, this.getElementCondition(), decimalFormatter);
|
||||
break;
|
||||
}
|
||||
return refFlag;
|
||||
}
|
||||
Object expected = JSONValue.parse(getExpectedValue());
|
||||
return Objects.equals(expected, subj);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean valueEquals(String v1, String v2) {
|
||||
try {
|
||||
Number number1 = NumberUtils.createNumber(v1);
|
||||
Number number2 = NumberUtils.createNumber(v2);
|
||||
return number1.equals(number2);
|
||||
} catch (Exception e) {
|
||||
return StringUtils.equals(v1, v2);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean valueNotEquals(String v1, String v2) {
|
||||
try {
|
||||
Number number1 = NumberUtils.createNumber(v1);
|
||||
Number number2 = NumberUtils.createNumber(v2);
|
||||
return !number1.equals(number2);
|
||||
} catch (Exception e) {
|
||||
return !StringUtils.equals(v1, v2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssertionResult getResult(SampleResult samplerResult) {
|
||||
AssertionResult result = new AssertionResult(getName());
|
||||
String responseData = samplerResult.getResponseDataAsString();
|
||||
if (responseData.isEmpty()) {
|
||||
return result.setResultForNull();
|
||||
}
|
||||
|
||||
result.setFailure(false);
|
||||
result.setFailureMessage("");
|
||||
|
||||
if (!isInvert()) {
|
||||
try {
|
||||
doAssert(responseData);
|
||||
} catch (Exception e) {
|
||||
log.debug("Assertion failed", e);
|
||||
result.setFailure(true);
|
||||
result.setFailureMessage(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
doAssert(responseData);
|
||||
result.setFailure(true);
|
||||
if (isJsonValidationBool()) {
|
||||
if (isExpectNull()) {
|
||||
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches null");
|
||||
} else {
|
||||
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches " + getExpectedValue());
|
||||
}
|
||||
} else {
|
||||
result.setFailureMessage("Failed that JSONPath not exists: " + getJsonPath());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Assertion failed, as expected", e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String objectToString(Object subj) {
|
||||
String str;
|
||||
if (subj == null) {
|
||||
str = "null";
|
||||
} else if (subj instanceof Map) {
|
||||
//noinspection unchecked
|
||||
str = new JSONObject((Map<String, ?>) subj).toJSONString();
|
||||
} else if (subj instanceof Double || subj instanceof Float) {
|
||||
str = decimalFormatter.get().format(subj);
|
||||
} else {
|
||||
str = subj.toString();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadStarted() {
|
||||
// nothing to do on thread start
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadFinished() {
|
||||
decimalFormatter.remove();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.assertions;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.jayway.jsonpath.Predicate;
|
||||
import io.metersphere.utils.DocumentUtils;
|
||||
import net.minidev.json.JSONArray;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.testelement.AbstractTestElement;
|
||||
import org.apache.jmeter.testelement.ThreadListener;
|
||||
import org.json.JSONObject;
|
||||
import org.json.XML;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.StringReader;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Checks if the result is a well-formed XML content using {@link XMLReader}
|
||||
*
|
||||
*/
|
||||
public class XMLAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener {
|
||||
private static final Logger log = LoggerFactory.getLogger(XMLAssertion.class);
|
||||
private static ThreadLocal<DecimalFormat> decimalFormatter = ThreadLocal.withInitial(XMLAssertion::createDecimalFormat);
|
||||
|
||||
private static final long serialVersionUID = 242L;
|
||||
|
||||
public String getXmlPath() {
|
||||
return this.getPropertyAsString("XML_PATH");
|
||||
}
|
||||
|
||||
public String getExpectedValue() {
|
||||
return this.getPropertyAsString("EXPECTED_VALUE");
|
||||
}
|
||||
|
||||
public String getCondition() {
|
||||
return getPropertyAsString("ElementCondition");
|
||||
}
|
||||
|
||||
private static DecimalFormat createDecimalFormat() {
|
||||
DecimalFormat decimalFormatter = new DecimalFormat("#.#");
|
||||
decimalFormatter.setMaximumFractionDigits(340);
|
||||
decimalFormatter.setMinimumFractionDigits(1);
|
||||
return decimalFormatter;
|
||||
}
|
||||
|
||||
// one builder for all requests in a thread
|
||||
private static final ThreadLocal<XMLReader> XML_READER = new ThreadLocal<XMLReader>() {
|
||||
@Override
|
||||
protected XMLReader initialValue() {
|
||||
try {
|
||||
XMLReader reader = SAXParserFactory.newInstance()
|
||||
.newSAXParser()
|
||||
.getXMLReader();
|
||||
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
return reader;
|
||||
} catch (SAXException | ParserConfigurationException e) {
|
||||
log.error("Error initializing XMLReader in XMLAssertion", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the result of the Assertion.
|
||||
* Here it checks whether the Sample data is XML.
|
||||
* If so an AssertionResult containing a FailureMessage will be returned.
|
||||
* Otherwise the returned AssertionResult will reflect the success of the Sample.
|
||||
*/
|
||||
@Override
|
||||
public AssertionResult getResult(SampleResult response) {
|
||||
// no error as default
|
||||
AssertionResult result = new AssertionResult(getName());
|
||||
String resultData = response.getResponseDataAsString();
|
||||
if (resultData.length() == 0) {
|
||||
return result.setResultForNull();
|
||||
}
|
||||
result.setFailure(false);
|
||||
XMLReader builder = XML_READER.get();
|
||||
if (builder != null) {
|
||||
try {
|
||||
builder.setErrorHandler(new LogErrorHandler());
|
||||
builder.parse(new InputSource(new StringReader(resultData)));
|
||||
try {
|
||||
JSONObject xmlJSONObj = XML.toJSONObject(resultData);
|
||||
String jsonPrettyPrintString = xmlJSONObj.toString(4);
|
||||
doAssert(jsonPrettyPrintString);
|
||||
} catch (Exception e) {
|
||||
result.setError(true);
|
||||
result.setFailure(true);
|
||||
result.setFailureMessage(e.getMessage());
|
||||
}
|
||||
} catch (SAXException | IOException e) {
|
||||
result.setError(true);
|
||||
result.setFailure(true);
|
||||
result.setFailureMessage(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
result.setError(true);
|
||||
result.setFailureMessage("Cannot initialize XMLReader in element:" + getName() + ", check jmeter.log file");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private void doAssert(String jsonString) {
|
||||
Object value = JsonPath.read(jsonString, this.getXmlPath(), new Predicate[0]);
|
||||
if (value instanceof JSONArray) {
|
||||
if (this.arrayMatched((JSONArray) value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!this.isEquals(value)) {
|
||||
String msg = DocumentUtils.documentMsg(this.getName(), value, this.getCondition());
|
||||
throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isEquals(Object subj) {
|
||||
String str = DocumentUtils.objectToString(subj, decimalFormatter);
|
||||
return DocumentUtils.documentChecked(str, this.getCondition(), decimalFormatter);
|
||||
}
|
||||
|
||||
private boolean arrayMatched(JSONArray value) {
|
||||
if (value.isEmpty() && "[]".equals(getExpectedValue())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Object subj : value.toArray()) {
|
||||
if (subj == null || isEquals(subj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return isEquals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadStarted() {
|
||||
// nothing to do on thread start
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadFinished() {
|
||||
XML_READER.remove();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.config;
|
||||
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.engine.event.LoopIterationEvent;
|
||||
import org.apache.jmeter.engine.event.LoopIterationListener;
|
||||
import org.apache.jmeter.engine.util.NoConfigMerge;
|
||||
import org.apache.jmeter.gui.GUIMenuSortOrder;
|
||||
import org.apache.jmeter.gui.TestElementMetadata;
|
||||
import org.apache.jmeter.save.CSVSaveService;
|
||||
import org.apache.jmeter.services.FileServer;
|
||||
import org.apache.jmeter.testbeans.TestBean;
|
||||
import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer;
|
||||
import org.apache.jmeter.testelement.property.JMeterProperty;
|
||||
import org.apache.jmeter.testelement.property.StringProperty;
|
||||
import org.apache.jmeter.threads.JMeterContext;
|
||||
import org.apache.jmeter.threads.JMeterVariables;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jorphan.util.JMeterStopThreadException;
|
||||
import org.apache.jorphan.util.JOrphanUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.io.IOException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* Read lines from a file and split int variables.
|
||||
*
|
||||
* The iterationStart() method is used to set up each set of values.
|
||||
*
|
||||
* By default, the same file is shared between all threads
|
||||
* (and other thread groups, if they use the same file name).
|
||||
*
|
||||
* The shareMode can be set to:
|
||||
* <ul>
|
||||
* <li>All threads - default, as described above</li>
|
||||
* <li>Current thread group</li>
|
||||
* <li>Current thread</li>
|
||||
* <li>Identifier - all threads sharing the same identifier</li>
|
||||
* </ul>
|
||||
*
|
||||
* The class uses the FileServer alias mechanism to provide the different share modes.
|
||||
* For all threads, the file alias is set to the file name.
|
||||
* Otherwise, a suffix is appended to the filename to make it unique within the required context.
|
||||
* For current thread group, the thread group identityHashcode is used;
|
||||
* for individual threads, the thread hashcode is used as the suffix.
|
||||
* Or the user can provide their own suffix, in which case the file is shared between all
|
||||
* threads with the same suffix.
|
||||
*
|
||||
*/
|
||||
@GUIMenuSortOrder(1)
|
||||
@TestElementMetadata(labelResource = "displayName")
|
||||
public class CSVDataSet extends ConfigTestElement
|
||||
implements TestBean, LoopIterationListener, NoConfigMerge {
|
||||
private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class);
|
||||
|
||||
private static final long serialVersionUID = 233L;
|
||||
|
||||
private static final String EOFVALUE = // value to return at EOF
|
||||
JMeterUtils.getPropDefault("csvdataset.eofstring", "<EOF>"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
private transient String filename;
|
||||
|
||||
private transient String fileEncoding;
|
||||
|
||||
private transient String variableNames;
|
||||
|
||||
private transient String delimiter;
|
||||
|
||||
private transient boolean quoted;
|
||||
|
||||
private transient boolean recycle = true;
|
||||
|
||||
private transient boolean stopThread;
|
||||
|
||||
private transient String[] vars;
|
||||
|
||||
private transient String alias;
|
||||
|
||||
private transient String shareMode;
|
||||
|
||||
private boolean firstLineIsNames = false;
|
||||
|
||||
private boolean ignoreFirstLine = false;
|
||||
|
||||
private final static String THREAD_SPLIT = " ";
|
||||
|
||||
private Object readResolve() {
|
||||
recycle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the setProperty method in order to convert
|
||||
* the original String shareMode property.
|
||||
* This used the locale-dependent display value, so caused
|
||||
* problems when the language was changed.
|
||||
* If the "shareMode" value matches a resource value then it is converted
|
||||
* into the resource key.
|
||||
* To reduce the need to look up resources, we only attempt to
|
||||
* convert values with spaces in them, as these are almost certainly
|
||||
* not variables (and they are definitely not resource keys).
|
||||
*/
|
||||
@Override
|
||||
public void setProperty(JMeterProperty property) {
|
||||
if (!(property instanceof StringProperty)) {
|
||||
super.setProperty(property);
|
||||
return;
|
||||
}
|
||||
|
||||
final String propName = property.getName();
|
||||
if (!"shareMode".equals(propName)) {
|
||||
super.setProperty(property);
|
||||
return;
|
||||
}
|
||||
|
||||
final String propValue = property.getStringValue();
|
||||
if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation
|
||||
try {
|
||||
final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass());
|
||||
final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE);
|
||||
for (String resKey : CSVDataSetBeanInfo.getShareTags()) {
|
||||
if (propValue.equals(rb.getString(resKey))) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale());
|
||||
}
|
||||
((StringProperty) property).setValue(resKey); // reset the value
|
||||
super.setProperty(property);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// This could perhaps be a variable name
|
||||
log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale());
|
||||
} catch (IntrospectionException e) {
|
||||
LoggerUtil.error("Could not find BeanInfo; cannot translate shareMode entries", e);
|
||||
}
|
||||
}
|
||||
super.setProperty(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void iterationStart(LoopIterationEvent iterEvent) {
|
||||
FileServer server = FileServer.getFileServer();
|
||||
final JMeterContext context = getThreadContext();
|
||||
String delim = getDelimiter();
|
||||
if ("\\t".equals(delim)) { // $NON-NLS-1$
|
||||
delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$
|
||||
} else if (delim.isEmpty()) {
|
||||
log.debug("Empty delimiter, will use ','");
|
||||
delim = ",";
|
||||
}
|
||||
if (vars == null) {
|
||||
initVars(server, context, delim);
|
||||
}
|
||||
|
||||
// TODO: fetch this once as per vars above?
|
||||
JMeterVariables threadVars = context.getVariables();
|
||||
String[] lineValues = {};
|
||||
try {
|
||||
if (getQuotedData()) {
|
||||
lineValues = server.getParsedLine(alias, recycle,
|
||||
firstLineIsNames || ignoreFirstLine, delim.charAt(0));
|
||||
} else {
|
||||
String line = server.readLine(alias, recycle,
|
||||
firstLineIsNames || ignoreFirstLine);
|
||||
lineValues = JOrphanUtils.split(line, delim, false);
|
||||
}
|
||||
for (int a = 0; a < vars.length && a < lineValues.length; a++) {
|
||||
threadVars.put(vars[a], lineValues[a]);
|
||||
}
|
||||
} catch (IOException e) { // treat the same as EOF
|
||||
LoggerUtil.error(e.toString());
|
||||
}
|
||||
if (lineValues.length == 0) {// i.e. EOF
|
||||
if (getStopThread()) {
|
||||
throw new JMeterStopThreadException("End of file:" + getFilename() + " detected for CSV DataSet:"
|
||||
+ getName() + " configured with stopThread:" + getStopThread() + ", recycle:" + getRecycle());
|
||||
}
|
||||
for (String var : vars) {
|
||||
threadVars.put(var, EOFVALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initVars(FileServer server, final JMeterContext context, String delim) {
|
||||
String fileName = getFilename().trim();
|
||||
LoggerUtil.info("初始化csv内容:" + fileName);
|
||||
setAlias(context, fileName);
|
||||
final String names = getVariableNames();
|
||||
if (StringUtils.isEmpty(names)) {
|
||||
String header = server.reserveFile(fileName, getFileEncoding(), alias, true);
|
||||
try {
|
||||
vars = CSVSaveService.csvSplitString(header, delim.charAt(0));
|
||||
firstLineIsNames = true;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Could not split CSV header line from file:" + fileName, e);
|
||||
}
|
||||
} else {
|
||||
server.reserveFile(fileName, getFileEncoding(), alias, ignoreFirstLine);
|
||||
vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$
|
||||
}
|
||||
trimVarNames(vars);
|
||||
}
|
||||
|
||||
private void setAlias(final JMeterContext context, String alias) {
|
||||
String mode = getShareMode();
|
||||
int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode);
|
||||
String threadName = StringUtils.substringBeforeLast(context.getThread().getThreadName(), THREAD_SPLIT);
|
||||
switch (modeInt) {
|
||||
case CSVDataSetBeanInfo.SHARE_ALL:
|
||||
this.alias = alias;
|
||||
break;
|
||||
case CSVDataSetBeanInfo.SHARE_GROUP:
|
||||
this.alias = alias + "@" + "GROUP_" + threadName + "@" + System.identityHashCode(context.getThreadGroup());
|
||||
break;
|
||||
case CSVDataSetBeanInfo.SHARE_THREAD:
|
||||
this.alias = alias + "@" + threadName + "@" + System.identityHashCode(context.getThread());
|
||||
break;
|
||||
default:
|
||||
this.alias = alias + "@" + mode; // user-specified key
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* trim content of array varNames
|
||||
* @param varsNames
|
||||
*/
|
||||
private void trimVarNames(String[] varsNames) {
|
||||
for (int i = 0; i < varsNames.length; i++) {
|
||||
varsNames[i] = varsNames[i].trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the filename.
|
||||
*/
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param filename The filename to set.
|
||||
*/
|
||||
public void setFilename(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the file encoding.
|
||||
*/
|
||||
public String getFileEncoding() {
|
||||
return fileEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileEncoding
|
||||
* The fileEncoding to set.
|
||||
*/
|
||||
public void setFileEncoding(String fileEncoding) {
|
||||
this.fileEncoding = fileEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the variableNames.
|
||||
*/
|
||||
public String getVariableNames() {
|
||||
return variableNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param variableNames
|
||||
* The variableNames to set.
|
||||
*/
|
||||
public void setVariableNames(String variableNames) {
|
||||
this.variableNames = variableNames;
|
||||
}
|
||||
|
||||
public String getDelimiter() {
|
||||
return delimiter;
|
||||
}
|
||||
|
||||
public void setDelimiter(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
public boolean getQuotedData() {
|
||||
return quoted;
|
||||
}
|
||||
|
||||
public void setQuotedData(boolean quoted) {
|
||||
this.quoted = quoted;
|
||||
}
|
||||
|
||||
public boolean getRecycle() {
|
||||
return recycle;
|
||||
}
|
||||
|
||||
public void setRecycle(boolean recycle) {
|
||||
this.recycle = recycle;
|
||||
}
|
||||
|
||||
public boolean getStopThread() {
|
||||
return stopThread;
|
||||
}
|
||||
|
||||
public void setStopThread(boolean value) {
|
||||
this.stopThread = value;
|
||||
}
|
||||
|
||||
public String getShareMode() {
|
||||
return shareMode;
|
||||
}
|
||||
|
||||
public void setShareMode(String value) {
|
||||
this.shareMode = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ignoreFirstLine
|
||||
*/
|
||||
public boolean isIgnoreFirstLine() {
|
||||
return ignoreFirstLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ignoreFirstLine the ignoreFirstLine to set
|
||||
*/
|
||||
public void setIgnoreFirstLine(boolean ignoreFirstLine) {
|
||||
this.ignoreFirstLine = ignoreFirstLine;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.config;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.gui.TestElementMetadata;
|
||||
import org.apache.jmeter.testbeans.TestBean;
|
||||
import org.apache.jmeter.testelement.TestStateListener;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jmeter.util.SSLManager;
|
||||
import org.apache.jorphan.util.JMeterStopTestException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Configure Keystore
|
||||
*/
|
||||
@TestElementMetadata(labelResource = "displayName")
|
||||
public class KeystoreConfig extends ConfigTestElement implements TestBean, TestStateListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger log = LoggerFactory.getLogger(KeystoreConfig.class);
|
||||
|
||||
private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$
|
||||
private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$
|
||||
|
||||
private String startIndex;
|
||||
private String endIndex;
|
||||
private String preload;
|
||||
private String clientCertAliasVarName;
|
||||
|
||||
public KeystoreConfig() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testEnded() {
|
||||
testEnded(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testEnded(String host) {
|
||||
log.info("Destroying Keystore");
|
||||
SSLManager.getInstance().destroyKeystore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testStarted() {
|
||||
testStarted(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testStarted(String host) {
|
||||
String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context");
|
||||
if (StringUtils.isEmpty(reuseSSLContext) || "true".equals(reuseSSLContext)) {
|
||||
log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used");
|
||||
}
|
||||
int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0);
|
||||
int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, -1);
|
||||
|
||||
if (!StringUtils.isEmpty(this.startIndex)) {
|
||||
try {
|
||||
startIndexAsInt = Integer.parseInt(this.startIndex);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Failed parsing startIndex: {}, will default to: {}, error message: {}", this.startIndex,
|
||||
startIndexAsInt, e, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(this.endIndex)) {
|
||||
try {
|
||||
endIndexAsInt = Integer.parseInt(this.endIndex);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Failed parsing endIndex: {}, will default to: {}, error message: {}", this.endIndex,
|
||||
endIndexAsInt, e, e);
|
||||
}
|
||||
}
|
||||
if (endIndexAsInt != -1 && startIndexAsInt > endIndexAsInt) {
|
||||
throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index");
|
||||
}
|
||||
log.info(
|
||||
"Configuring Keystore with (preload: '{}', startIndex: {}, endIndex: {}, clientCertAliasVarName: '{}')",
|
||||
preload, startIndexAsInt, endIndexAsInt, clientCertAliasVarName);
|
||||
// 加载认证文件
|
||||
String path = this.getPropertyAsString("MS-KEYSTORE-FILE-PATH");
|
||||
String password = this.getPropertyAsString("MS-KEYSTORE-FILE-PASSWORD");
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(new File(path));
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload),
|
||||
startIndexAsInt,
|
||||
endIndexAsInt,
|
||||
clientCertAliasVarName, in, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the endIndex
|
||||
*/
|
||||
public String getEndIndex() {
|
||||
return endIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param endIndex the endIndex to set
|
||||
*/
|
||||
public void setEndIndex(String endIndex) {
|
||||
this.endIndex = endIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the startIndex
|
||||
*/
|
||||
public String getStartIndex() {
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startIndex the startIndex to set
|
||||
*/
|
||||
public void setStartIndex(String startIndex) {
|
||||
this.startIndex = startIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the preload
|
||||
*/
|
||||
public String getPreload() {
|
||||
return preload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param preload the preload to set
|
||||
*/
|
||||
public void setPreload(String preload) {
|
||||
this.preload = preload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientCertAliasVarName
|
||||
*/
|
||||
public String getClientCertAliasVarName() {
|
||||
return clientCertAliasVarName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientCertAliasVarName the clientCertAliasVarName to set
|
||||
*/
|
||||
public void setClientCertAliasVarName(String clientCertAliasVarName) {
|
||||
this.clientCertAliasVarName = clientCertAliasVarName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.engine;
|
||||
|
||||
import io.metersphere.utils.CustomizeFunctionUtil;
|
||||
import org.apache.jmeter.config.Arguments;
|
||||
import org.apache.jmeter.engine.util.ValueReplacer;
|
||||
import org.apache.jmeter.functions.InvalidVariableException;
|
||||
import org.apache.jmeter.reporters.ResultCollector;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jmeter.testelement.TestPlan;
|
||||
import org.apache.jmeter.threads.JMeterContextService;
|
||||
import org.apache.jmeter.threads.JMeterVariables;
|
||||
import org.apache.jmeter.visualizers.backend.Backend;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.apache.jorphan.collections.HashTreeTraverser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class to replace function and variable references in the test tree.
|
||||
*
|
||||
*/
|
||||
public class PreCompiler implements HashTreeTraverser {
|
||||
private static final Logger log = LoggerFactory.getLogger(PreCompiler.class);
|
||||
|
||||
private final ValueReplacer replacer;
|
||||
|
||||
// Used by both StandardJMeterEngine and ClientJMeterEngine.
|
||||
// In the latter case, only ResultCollectors are updated,
|
||||
// as only these are relevant to the client, and updating
|
||||
// other elements causes all sorts of problems.
|
||||
private final boolean isClientSide; // skip certain processing for remote tests
|
||||
|
||||
private JMeterVariables clientSideVariables;
|
||||
|
||||
public PreCompiler() {
|
||||
replacer = new ValueReplacer();
|
||||
isClientSide = false;
|
||||
}
|
||||
|
||||
public PreCompiler(boolean remote) {
|
||||
replacer = new ValueReplacer();
|
||||
isClientSide = remote;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void addNode(Object node, HashTree subTree) {
|
||||
if(isClientSide) {
|
||||
if(node instanceof ResultCollector || node instanceof Backend) {
|
||||
try {
|
||||
replacer.replaceValues((TestElement) node);
|
||||
} catch (InvalidVariableException e) {
|
||||
log.error("invalid variables in node {}", ((TestElement)node).getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (node instanceof TestPlan) {
|
||||
this.clientSideVariables = createVars((TestPlan)node);
|
||||
}
|
||||
|
||||
if (node instanceof Arguments) {
|
||||
// Don't store User Defined Variables in the context for client side
|
||||
Map<String, String> args = createArgumentsMap((Arguments) node);
|
||||
clientSideVariables.putAll(args);
|
||||
}
|
||||
|
||||
} else {
|
||||
if(node instanceof TestElement) {
|
||||
try {
|
||||
replacer.replaceValues((TestElement) node);
|
||||
} catch (InvalidVariableException e) {
|
||||
log.error("invalid variables in node {}", ((TestElement)node).getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (node instanceof TestPlan) {
|
||||
JMeterVariables vars = createVars((TestPlan)node);
|
||||
JMeterContextService.getContext().setVariables(vars);
|
||||
// 加载自定义函数
|
||||
CustomizeFunctionUtil.initCustomizeClass((TestPlan) node);
|
||||
}
|
||||
|
||||
if (node instanceof Arguments) {
|
||||
Map<String, String> args = createArgumentsMap((Arguments) node);
|
||||
JMeterContextService.getContext().getVariables().putAll(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Map of Arguments
|
||||
* @param arguments {@link Arguments}
|
||||
* @return {@link Map}
|
||||
*/
|
||||
private Map<String, String> createArgumentsMap(Arguments arguments) {
|
||||
arguments.setRunningVersion(true);
|
||||
Map<String, String> args = arguments.getArgumentsAsMap();
|
||||
replacer.addVariables(args);
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create variables for testPlan
|
||||
* @param testPlan {@link JMeterVariables}
|
||||
* @return {@link JMeterVariables}
|
||||
*/
|
||||
private JMeterVariables createVars(TestPlan testPlan) {
|
||||
testPlan.prepareForPreCompile(); //A hack to make user-defined variables in the testplan element more dynamic
|
||||
Map<String, String> args = testPlan.getUserDefinedVariables();
|
||||
replacer.setUserDefinedVariables(args);
|
||||
JMeterVariables vars = new JMeterVariables();
|
||||
vars.putAll(args);
|
||||
return vars;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void subtractNode() {
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void processPath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientSideVariables
|
||||
*/
|
||||
public JMeterVariables getClientSideVariables() {
|
||||
return clientSideVariables;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.engine.util;
|
||||
|
||||
import io.metersphere.utils.CustomizeFunctionUtil;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.jmeter.functions.Function;
|
||||
import org.apache.jmeter.functions.InvalidVariableException;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.samplers.Sampler;
|
||||
import org.apache.jmeter.threads.JMeterContext;
|
||||
import org.apache.jmeter.threads.JMeterContextService;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jorphan.reflect.ClassFinder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CompoundFunction.
|
||||
*/
|
||||
public class CompoundVariable implements Function {
|
||||
private static final Logger log = LoggerFactory.getLogger(CompoundVariable.class);
|
||||
|
||||
private String rawParameters;
|
||||
|
||||
private static final FunctionParser functionParser = new FunctionParser();
|
||||
|
||||
// Created during class init; not modified thereafter
|
||||
private static final Map<String, Class<? extends Function>> functions = new HashMap<>();
|
||||
|
||||
private boolean hasFunction;
|
||||
private boolean isDynamic;
|
||||
|
||||
private String permanentResults;
|
||||
|
||||
// Type is ArrayList, so we can use ArrayList#clone
|
||||
private ArrayList<Object> compiledComponents = new ArrayList<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
final String contain = // Classnames must contain this string [.functions.]
|
||||
JMeterUtils.getProperty("classfinder.functions.contain"); // $NON-NLS-1$
|
||||
final String notContain = // Classnames must not contain this string [.gui.]
|
||||
JMeterUtils.getProperty("classfinder.functions.notContain"); // $NON-NLS-1$
|
||||
if (contain != null) {
|
||||
log.info("Note: Function class names must contain the string: '{}'", contain);
|
||||
}
|
||||
if (notContain != null) {
|
||||
log.info("Note: Function class names must not contain the string: '{}'", notContain);
|
||||
}
|
||||
|
||||
List<String> classes = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(),
|
||||
new Class[]{Function.class}, true, contain, notContain);
|
||||
for (String clazzName : classes) {
|
||||
Function tempFunc = Class.forName(clazzName)
|
||||
.asSubclass(Function.class)
|
||||
.getDeclaredConstructor().newInstance();
|
||||
String referenceKey = tempFunc.getReferenceKey();
|
||||
if (referenceKey.length() > 0) { // ignore self
|
||||
functions.put(referenceKey, tempFunc.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
if (functions.isEmpty()) {
|
||||
log.warn("Did not find any functions");
|
||||
} else {
|
||||
log.debug("Function count: {}", functions.size());
|
||||
}
|
||||
} catch (Exception err) {
|
||||
log.error("Exception occurred in static initialization of CompoundVariable.", err);
|
||||
}
|
||||
}
|
||||
|
||||
public CompoundVariable() {
|
||||
hasFunction = false;
|
||||
}
|
||||
|
||||
public CompoundVariable(String parameters) {
|
||||
this();
|
||||
try {
|
||||
setParameters(parameters);
|
||||
} catch (InvalidVariableException e) {
|
||||
// TODO should level be more than debug ?
|
||||
log.debug("Invalid variable: {}", parameters, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String execute() {
|
||||
if (isDynamic || permanentResults == null) {
|
||||
JMeterContext context = JMeterContextService.getContext();
|
||||
SampleResult previousResult = context.getPreviousResult();
|
||||
Sampler currentSampler = context.getCurrentSampler();
|
||||
return execute(previousResult, currentSampler);
|
||||
}
|
||||
return permanentResults; // $NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the retrieval of the original String prior to it being compiled.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getRawParameters() {
|
||||
return rawParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String execute(SampleResult previousResult, Sampler currentSampler) {
|
||||
if (compiledComponents == null || compiledComponents.isEmpty()) {
|
||||
return ""; // $NON-NLS-1$
|
||||
}
|
||||
|
||||
StringBuilder results = new StringBuilder();
|
||||
for (Object item : compiledComponents) {
|
||||
if (item instanceof Function) {
|
||||
try {
|
||||
results.append(((Function) item).execute(previousResult, currentSampler));
|
||||
} catch (InvalidVariableException e) {
|
||||
// TODO should level be more than debug ?
|
||||
log.debug("Invalid variable: {}", item, e);
|
||||
}
|
||||
} else if (item instanceof SimpleVariable) {
|
||||
results.append(((SimpleVariable) item).toString());
|
||||
} else {
|
||||
results.append(item);
|
||||
}
|
||||
}
|
||||
if (!isDynamic) {
|
||||
permanentResults = results.toString();
|
||||
}
|
||||
return results.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // clone will produce correct type
|
||||
public CompoundVariable getFunction() {
|
||||
CompoundVariable func = new CompoundVariable();
|
||||
func.compiledComponents = (ArrayList<Object>) compiledComponents.clone();
|
||||
func.rawParameters = rawParameters;
|
||||
func.hasFunction = hasFunction;
|
||||
func.isDynamic = isDynamic;
|
||||
return func;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<String> getArgumentDesc() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
// TODO should this also clear isDynamic, rawParameters, permanentResults?
|
||||
hasFunction = false;
|
||||
compiledComponents.clear();
|
||||
}
|
||||
|
||||
public void setParameters(String parameters) throws InvalidVariableException {
|
||||
this.rawParameters = parameters;
|
||||
if (parameters == null || parameters.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
compiledComponents = functionParser.compileString(parameters);
|
||||
if (compiledComponents.size() > 1 || !(compiledComponents.get(0) instanceof String)) {
|
||||
hasFunction = true;
|
||||
}
|
||||
permanentResults = null; // To be calculated and cached on first execution
|
||||
isDynamic = false;
|
||||
for (Object item : compiledComponents) {
|
||||
if (item instanceof Function || item instanceof SimpleVariable) {
|
||||
isDynamic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Object getNamedFunction(String functionName) throws InvalidVariableException {
|
||||
if (functions.containsKey(functionName)) {
|
||||
try {
|
||||
return functions.get(functionName).getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
log.error("Exception occurred while instantiating a function: {}", functionName, e); // $NON-NLS-1$
|
||||
throw new InvalidVariableException(e);
|
||||
}
|
||||
}
|
||||
Map<String, Class<? extends Function>> customizeFunctions = CustomizeFunctionUtil.getFunctions();
|
||||
if (MapUtils.isNotEmpty(customizeFunctions) && customizeFunctions.containsKey(functionName)) {
|
||||
try {
|
||||
log.info("ms custom function handling", functionName);
|
||||
return CustomizeFunctionUtil.getFunctions().get(functionName).getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
log.error("Exception occurred while instantiating a function: {}", functionName, e); // $NON-NLS-1$
|
||||
throw new InvalidVariableException(e);
|
||||
}
|
||||
}
|
||||
return new SimpleVariable(functionName);
|
||||
}
|
||||
|
||||
// For use by FunctionHelper
|
||||
public static Class<? extends Function> getFunctionClass(String className) {
|
||||
return functions.get(className);
|
||||
}
|
||||
|
||||
// For use by FunctionHelper
|
||||
public static String[] getFunctionNames() {
|
||||
return functions.keySet().toArray(new String[functions.size()]);
|
||||
}
|
||||
|
||||
public boolean hasFunction() {
|
||||
return hasFunction;
|
||||
}
|
||||
|
||||
// Dummy methods needed by Function interface
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getReferenceKey() {
|
||||
return ""; // $NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.protocol.http.sampler;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.engine.event.LoopIterationEvent;
|
||||
import org.apache.jmeter.protocol.http.util.HTTPConstants;
|
||||
import org.apache.jmeter.samplers.Interruptible;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
/**
|
||||
* Proxy class that dispatches to the appropriate HTTP sampler.
|
||||
* <p>
|
||||
* This class is stored in the test plan, and holds all the configuration settings.
|
||||
* The actual implementation is created at run-time, and is passed a reference to this class
|
||||
* so it can get access to all the settings stored by HTTPSamplerProxy.
|
||||
*/
|
||||
public final class HTTPSamplerProxy extends HTTPSamplerBase implements Interruptible {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String HTTP_PREFIX = HTTPConstants.PROTOCOL_HTTP+"://"; // $NON-NLS-1$
|
||||
private static final String HTTPS_PREFIX = HTTPConstants.PROTOCOL_HTTPS+"://"; // $NON-NLS-1$
|
||||
|
||||
private static final String QRY_PFX = "?"; // $NON-NLS-1$
|
||||
|
||||
private static final String QRY_SEP = "&"; // $NON-NLS-1$
|
||||
|
||||
private transient HTTPAbstractImpl impl;
|
||||
|
||||
public HTTPSamplerProxy() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method used to initialise the implementation.
|
||||
*
|
||||
* @param impl the implementation to use.
|
||||
*/
|
||||
public HTTPSamplerProxy(String impl) {
|
||||
super();
|
||||
setImplementation(impl);
|
||||
}
|
||||
|
||||
protected String toExternalForm(URL u) {
|
||||
int len = u.getProtocol().length() + 1;
|
||||
if (u.getAuthority() != null && u.getAuthority().length() > 0)
|
||||
len += 2 + u.getAuthority().length();
|
||||
if (u.getPath() != null) {
|
||||
len += u.getPath().length();
|
||||
}
|
||||
if (u.getQuery() != null) {
|
||||
len += 1 + u.getQuery().length();
|
||||
}
|
||||
if (u.getRef() != null)
|
||||
len += 1 + u.getRef().length();
|
||||
|
||||
StringBuffer result = new StringBuffer(len);
|
||||
result.append(u.getProtocol());
|
||||
result.append(":");
|
||||
if (u.getAuthority() != null && u.getAuthority().length() > 0) {
|
||||
result.append("//");
|
||||
result.append(u.getAuthority());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(u.getPath())) {
|
||||
int index = 0;
|
||||
for (int i = 0; i < u.getPath().length(); i++) {
|
||||
char ch = u.getPath().charAt(i);
|
||||
if (String.valueOf(ch).equals("/")) {
|
||||
index++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.append("/" + u.getPath().substring(index));
|
||||
}
|
||||
if (u.getQuery() != null) {
|
||||
result.append('?');
|
||||
result.append(u.getQuery());
|
||||
}
|
||||
if (u.getRef() != null) {
|
||||
result.append("#");
|
||||
result.append(u.getRef());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirect, int depth) {
|
||||
// When Retrieve Embedded resources + Concurrent Pool is used
|
||||
// as the instance of Proxy is cloned, we end up with impl being null
|
||||
// testIterationStart will not be executed but it's not a problem for 51380 as it's download of resources
|
||||
// so SSL context is to be reused
|
||||
if (impl == null) { // Not called from multiple threads, so this is OK
|
||||
try {
|
||||
impl = HTTPSamplerFactory.getImplementation(getImplementation(), this);
|
||||
} catch (Exception ex) {
|
||||
return errorResult(ex, new HTTPSampleResult());
|
||||
}
|
||||
}
|
||||
try {
|
||||
String url = toExternalForm(u);
|
||||
if (StringUtils.isNotEmpty(url) && url.startsWith("http:/http")) {
|
||||
url = url.substring(6);
|
||||
}
|
||||
u = new URL(url);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
return impl.sample(u, method, areFollowingRedirect, depth);
|
||||
}
|
||||
|
||||
// N.B. It's not possible to forward threadStarted() to the implementation class.
|
||||
// This is because Config items are not processed until later, and HTTPDefaults may define the implementation
|
||||
|
||||
@Override
|
||||
public void threadFinished() {
|
||||
if (impl != null) {
|
||||
impl.threadFinished(); // Forward to sampler
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interrupt() {
|
||||
if (impl != null) {
|
||||
return impl.interrupt(); // Forward to sampler
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent)
|
||||
*/
|
||||
@Override
|
||||
public void testIterationStart(LoopIterationEvent event) {
|
||||
if (impl != null) {
|
||||
impl.notifyFirstSampleAfterLoopRestart();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写getUrl方法, 防止获取的domain是完整的地址
|
||||
* 重新切割domain,重新赋值给domain和protocol
|
||||
* @return
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
String path = this.getPath();
|
||||
if (path.startsWith("//")) {
|
||||
path.replaceFirst("/" ,"");
|
||||
}
|
||||
// Hack to allow entire URL to be provided in host field
|
||||
if (path.startsWith(HTTP_PREFIX)
|
||||
|| path.startsWith(HTTPS_PREFIX)) {
|
||||
return new URL(path);
|
||||
}
|
||||
String domain = getDomain();
|
||||
String protocol = null;
|
||||
if (domain.startsWith(HTTP_PREFIX) || domain.startsWith(HTTPS_PREFIX)) {
|
||||
try {
|
||||
URL url = new URL(domain);
|
||||
domain = url.getHost();
|
||||
protocol = url.getProtocol();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
protocol = StringUtils.isNotEmpty(protocol) ? protocol : getProtocol();
|
||||
String method = getMethod();
|
||||
StringBuilder pathAndQuery = new StringBuilder(100);
|
||||
if (PROTOCOL_FILE.equalsIgnoreCase(protocol)) {
|
||||
domain = null; // allow use of relative file URLs
|
||||
} else {
|
||||
// HTTP URLs must be absolute, allow file to be relative
|
||||
if (!path.startsWith("/")) { // $NON-NLS-1$
|
||||
pathAndQuery.append('/'); // $NON-NLS-1$
|
||||
}
|
||||
}
|
||||
pathAndQuery.append(path);
|
||||
|
||||
// Add the query string if it is a HTTP GET or DELETE request
|
||||
if (HTTPConstants.GET.equals(method)
|
||||
|| HTTPConstants.DELETE.equals(method)
|
||||
|| HTTPConstants.OPTIONS.equals(method)) {
|
||||
// Get the query string encoded in specified encoding
|
||||
// If no encoding is specified by user, we will get it
|
||||
// encoded in UTF-8, which is what the HTTP spec says
|
||||
String queryString = getQueryString(getContentEncoding());
|
||||
if (queryString.length() > 0) {
|
||||
if (path.contains(QRY_PFX)) {// Already contains a prefix
|
||||
pathAndQuery.append(QRY_SEP);
|
||||
} else {
|
||||
pathAndQuery.append(QRY_PFX);
|
||||
}
|
||||
pathAndQuery.append(queryString);
|
||||
}
|
||||
}
|
||||
// If default port for protocol is used, we do not include port in URL
|
||||
if (isProtocolDefaultPort()) {
|
||||
return new URL(protocol, domain, pathAndQuery.toString());
|
||||
}
|
||||
return new URL(protocol, domain, getPort(), pathAndQuery.toString());
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.protocol.tcp.sampler;
|
||||
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Sample TCPClient implementation.
|
||||
* Reads data until the defined EOL byte is reached.
|
||||
* If there is no EOL byte defined, then reads until
|
||||
* the end of the stream is reached.
|
||||
* The EOL byte is defined by the property "tcp.eolByte".
|
||||
*/
|
||||
public class MsTCPClientImpl extends TCPClientImpl {
|
||||
private static final Logger log = LoggerFactory.getLogger(MsTCPClientImpl.class);
|
||||
|
||||
private static final int EOL_INT = JMeterUtils.getPropDefault("tcp.eolByte", 1000); // $NON-NLS-1$
|
||||
|
||||
public void write(OutputStream os, String s , String charset) throws IOException{
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("WriteS: {}", showEOL(s));
|
||||
}
|
||||
os.write(s.getBytes(charset));
|
||||
os.flush();
|
||||
}
|
||||
|
||||
public void write(OutputStream os, InputStream is , String charset) throws IOException{
|
||||
byte[] buff = new byte[512];
|
||||
while(is.read(buff) > 0){
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("WriteIS: {}", showEOL(new String(buff, charset)));
|
||||
}
|
||||
os.write(buff);
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
private String showEOL(final String input) {
|
||||
StringBuilder sb = new StringBuilder(input.length()*2);
|
||||
for(int i=0; i < input.length(); i++) {
|
||||
char ch = input.charAt(i);
|
||||
if (ch < ' ') {
|
||||
sb.append('[');
|
||||
sb.append((int)ch);
|
||||
sb.append(']');
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String read(InputStream is, SampleResult sampleResult, String charset) throws ReadException {
|
||||
ByteArrayOutputStream w = new ByteArrayOutputStream();
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
int x;
|
||||
boolean first = true;
|
||||
while ((x = is.read(buffer)) > -1) {
|
||||
if (first) {
|
||||
sampleResult.latencyEnd();
|
||||
first = false;
|
||||
}
|
||||
w.write(buffer, 0, x);
|
||||
if (useEolByte && (buffer[x - 1] == eolByte)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// do we need to close byte array (or flush it?)
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("Read: {}\n{}", w.size(), w.toString(charset));
|
||||
}
|
||||
return w.toString(charset);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ReadException("Error decoding bytes from server with " + charset + ", bytes read: " + w.size(),
|
||||
e, "<Read bytes with bad encoding>");
|
||||
} catch (IOException e) {
|
||||
String decodedBytes;
|
||||
try {
|
||||
decodedBytes = w.toString(charset);
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
// we should never get here, as it would have crashed earlier
|
||||
decodedBytes = "<Read bytes with bad encoding>";
|
||||
}
|
||||
throw new ReadException("Error reading from server, bytes read: " + w.size(), e, decodedBytes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.protocol.tcp.sampler;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.config.ConfigTestElement;
|
||||
import org.apache.jmeter.samplers.AbstractSampler;
|
||||
import org.apache.jmeter.samplers.Entry;
|
||||
import org.apache.jmeter.samplers.Interruptible;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jmeter.testelement.ThreadListener;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A sampler which understands Tcp requests.
|
||||
*
|
||||
*/
|
||||
public class TCPSampler extends AbstractSampler implements ThreadListener, Interruptible {
|
||||
private static final long serialVersionUID = 280L;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TCPSampler.class);
|
||||
|
||||
private static final Set<String> APPLIABLE_CONFIG_CLASSES = new HashSet<>(
|
||||
Arrays.asList(
|
||||
"org.apache.jmeter.config.gui.LoginConfigGui",
|
||||
"org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui",
|
||||
"org.apache.jmeter.config.gui.SimpleConfigGui"
|
||||
));
|
||||
|
||||
public static final String SERVER = "TCPSampler.server"; //$NON-NLS-1$
|
||||
|
||||
public static final String PORT = "TCPSampler.port"; //$NON-NLS-1$
|
||||
|
||||
public static final String FILENAME = "TCPSampler.filename"; //$NON-NLS-1$
|
||||
|
||||
public static final String CLASSNAME = "TCPSampler.classname";//$NON-NLS-1$
|
||||
|
||||
public static final String NODELAY = "TCPSampler.nodelay"; //$NON-NLS-1$
|
||||
|
||||
public static final String TIMEOUT = "TCPSampler.timeout"; //$NON-NLS-1$
|
||||
|
||||
public static final String TIMEOUT_CONNECT = "TCPSampler.ctimeout"; //$NON-NLS-1$
|
||||
|
||||
public static final String REQUEST = "TCPSampler.request"; //$NON-NLS-1$
|
||||
|
||||
public static final String RE_USE_CONNECTION = "TCPSampler.reUseConnection"; //$NON-NLS-1$
|
||||
public static final boolean RE_USE_CONNECTION_DEFAULT = true;
|
||||
|
||||
public static final String CLOSE_CONNECTION = "TCPSampler.closeConnection"; //$NON-NLS-1$
|
||||
public static final boolean CLOSE_CONNECTION_DEFAULT = false;
|
||||
|
||||
public static final String SO_LINGER = "TCPSampler.soLinger"; //$NON-NLS-1$
|
||||
|
||||
public static final String EOL_BYTE = "TCPSampler.EolByte"; //$NON-NLS-1$
|
||||
|
||||
private static final String TCPKEY = "TCP"; //$NON-NLS-1$ key for HashMap
|
||||
|
||||
private static final String ERRKEY = "ERR"; //$NON-NLS-1$ key for HashMap
|
||||
|
||||
// the response is scanned for these strings
|
||||
private static final String STATUS_PREFIX = JMeterUtils.getPropDefault("tcp.status.prefix", ""); //$NON-NLS-1$
|
||||
|
||||
private static final String STATUS_SUFFIX = JMeterUtils.getPropDefault("tcp.status.suffix", ""); //$NON-NLS-1$
|
||||
|
||||
private static final String STATUS_PROPERTIES = JMeterUtils.getPropDefault("tcp.status.properties", ""); //$NON-NLS-1$
|
||||
|
||||
private static final Properties STATUS_PROPS = new Properties();
|
||||
|
||||
private static final String PROTO_PREFIX = "org.apache.jmeter.protocol.tcp.sampler."; //$NON-NLS-1$
|
||||
|
||||
private static final boolean HAVE_STATUS_PROPS;
|
||||
|
||||
//设置tcp编码
|
||||
public static String charset = "UTF-8";
|
||||
public void setCharset(String charset) {
|
||||
this.setProperty("CHARSET", charset);
|
||||
}
|
||||
public String getCharset() {
|
||||
return getPropertyAsString("CHARSET");
|
||||
}
|
||||
static {
|
||||
boolean hsp = false;
|
||||
log.debug("Status prefix={}, suffix={}, properties={}",
|
||||
STATUS_PREFIX, STATUS_SUFFIX, STATUS_PROPERTIES); //$NON-NLS-1$
|
||||
if (STATUS_PROPERTIES.length() > 0) {
|
||||
File f = new File(STATUS_PROPERTIES);
|
||||
try (FileInputStream fis = new FileInputStream(f)){
|
||||
STATUS_PROPS.load(fis);
|
||||
log.debug("Successfully loaded properties"); //$NON-NLS-1$
|
||||
hsp = true;
|
||||
} catch (FileNotFoundException e) {
|
||||
log.debug("Property file {} not found", STATUS_PROPERTIES); //$NON-NLS-1$
|
||||
} catch (IOException e) {
|
||||
log.debug("Error reading property file {} error {}", STATUS_PROPERTIES, e.toString()); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
HAVE_STATUS_PROPS = hsp;
|
||||
}
|
||||
|
||||
/** the cache of TCP Connections */
|
||||
// KEY = TCPKEY or ERRKEY, Entry= Socket or String
|
||||
private static final ThreadLocal<Map<String, Object>> tp =
|
||||
ThreadLocal.withInitial(HashMap::new);
|
||||
|
||||
private transient TCPClient protocolHandler;
|
||||
|
||||
private transient boolean firstSample; // Are we processing the first sample?
|
||||
|
||||
private transient volatile Socket currentSocket; // used for handling interrupt
|
||||
|
||||
public TCPSampler() {
|
||||
log.debug("Created {}", this); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private String getError() {
|
||||
Map<String, Object> cp = tp.get();
|
||||
return (String) cp.get(ERRKEY);
|
||||
}
|
||||
|
||||
private Socket getSocket(String socketKey) {
|
||||
Map<String, Object> cp = tp.get();
|
||||
Socket con = null;
|
||||
if (isReUseConnection()) {
|
||||
con = (Socket) cp.get(socketKey);
|
||||
if (con != null) {
|
||||
log.debug("{} Reusing connection {}", this, con); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
if (con == null) {
|
||||
// Not in cache, so create new one and cache it
|
||||
try {
|
||||
closeSocket(socketKey); // Bug 44910 - close previous socket (if any)
|
||||
SocketAddress sockaddr = new InetSocketAddress(getServer(), getPort());
|
||||
con = new Socket(); // NOSONAR socket is either cache in ThreadLocal for reuse and closed at end of thread or closed here
|
||||
if (getPropertyAsString(SO_LINGER,"").length() > 0){
|
||||
con.setSoLinger(true, getSoLinger());
|
||||
}
|
||||
con.connect(sockaddr, getConnectTimeout());
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("Created new connection {}", con); //$NON-NLS-1$
|
||||
}
|
||||
cp.put(socketKey, con);
|
||||
} catch (UnknownHostException e) {
|
||||
log.warn("Unknown host for {}", getLabel(), e);//$NON-NLS-1$
|
||||
cp.put(ERRKEY, e.toString());
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
log.warn("Could not create socket for {}", getLabel(), e); //$NON-NLS-1$
|
||||
cp.put(ERRKEY, e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// (re-)Define connection params - Bug 50977
|
||||
try {
|
||||
con.setSoTimeout(getTimeout());
|
||||
con.setTcpNoDelay(getNoDelay());
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("{} Timeout={}, NoDelay={}", this, getTimeout(), getNoDelay()); //$NON-NLS-1$
|
||||
}
|
||||
} catch (SocketException se) {
|
||||
log.warn("Could not set timeout or nodelay for {}", getLabel(), se); //$NON-NLS-1$
|
||||
cp.put(ERRKEY, se.toString());
|
||||
}
|
||||
return con;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String socket key in cache Map
|
||||
*/
|
||||
private String getSocketKey() {
|
||||
return TCPKEY+"#"+getServer()+"#"+getPort()+"#"+getUsername()+"#"+getPassword();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return getPropertyAsString(ConfigTestElement.USERNAME);
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return getPropertyAsString(ConfigTestElement.PASSWORD);
|
||||
}
|
||||
|
||||
public void setServer(String newServer) {
|
||||
this.setProperty(SERVER, newServer);
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return getPropertyAsString(SERVER);
|
||||
}
|
||||
|
||||
public boolean isReUseConnection() {
|
||||
return getPropertyAsBoolean(RE_USE_CONNECTION, RE_USE_CONNECTION_DEFAULT);
|
||||
}
|
||||
|
||||
public void setCloseConnection(String close) {
|
||||
this.setProperty(CLOSE_CONNECTION, close, "");
|
||||
}
|
||||
|
||||
public boolean isCloseConnection() {
|
||||
return getPropertyAsBoolean(CLOSE_CONNECTION, CLOSE_CONNECTION_DEFAULT);
|
||||
}
|
||||
|
||||
public void setSoLinger(String soLinger) {
|
||||
this.setProperty(SO_LINGER, soLinger, "");
|
||||
}
|
||||
|
||||
public int getSoLinger() {
|
||||
return getPropertyAsInt(SO_LINGER);
|
||||
}
|
||||
|
||||
public void setEolByte(String eol) {
|
||||
this.setProperty(EOL_BYTE, eol, "");
|
||||
}
|
||||
|
||||
public int getEolByte() {
|
||||
return getPropertyAsInt(EOL_BYTE);
|
||||
}
|
||||
|
||||
|
||||
public void setPort(String newFilename) {
|
||||
this.setProperty(PORT, newFilename);
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return getPropertyAsInt(PORT);
|
||||
}
|
||||
|
||||
public void setFilename(String newFilename) {
|
||||
this.setProperty(FILENAME, newFilename);
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return getPropertyAsString(FILENAME);
|
||||
}
|
||||
|
||||
public void setRequestData(String newRequestData) {
|
||||
this.setProperty(REQUEST, newRequestData);
|
||||
}
|
||||
|
||||
public String getRequestData() {
|
||||
return getPropertyAsString(REQUEST);
|
||||
}
|
||||
|
||||
public void setTimeout(String newTimeout) {
|
||||
this.setProperty(TIMEOUT, newTimeout);
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return getPropertyAsInt(TIMEOUT);
|
||||
}
|
||||
|
||||
public void setConnectTimeout(String newTimeout) {
|
||||
this.setProperty(TIMEOUT_CONNECT, newTimeout, "");
|
||||
}
|
||||
|
||||
public int getConnectTimeout() {
|
||||
return getPropertyAsInt(TIMEOUT_CONNECT, 0);
|
||||
}
|
||||
|
||||
public boolean getNoDelay() {
|
||||
return getPropertyAsBoolean(NODELAY);
|
||||
}
|
||||
|
||||
public void setClassname(String classname) {
|
||||
this.setProperty(CLASSNAME, classname, ""); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
public String getClassname() {
|
||||
String clazz = getPropertyAsString(CLASSNAME,"");
|
||||
if (clazz==null || clazz.length()==0){
|
||||
clazz = JMeterUtils.getPropDefault("tcp.handler", "MsClientImpl"); //$NON-NLS-1$ $NON-NLS-2$
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted string label describing this sampler Example output:
|
||||
* Tcp://Tcp.nowhere.com/pub/README.txt
|
||||
*
|
||||
* @return a formatted string label describing this sampler
|
||||
*/
|
||||
public String getLabel() {
|
||||
return "tcp://" + this.getServer() + ":" + this.getPort();//$NON-NLS-1$ $NON-NLS-2$
|
||||
}
|
||||
|
||||
private Class<?> getClass(String className) {
|
||||
Class<?> c = null;
|
||||
try {
|
||||
c = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
try {
|
||||
c = Class.forName(PROTO_PREFIX + className, false, Thread.currentThread().getContextClassLoader());
|
||||
} catch (ClassNotFoundException e1) {
|
||||
log.error("Could not find protocol class '{}'", className); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
return c;
|
||||
|
||||
}
|
||||
|
||||
private TCPClient getProtocol() {
|
||||
TCPClient tcpClient = null;
|
||||
Class<?> javaClass = getClass(getClassname());
|
||||
if (javaClass == null){
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
tcpClient = (TCPClient) javaClass.getDeclaredConstructor().newInstance();
|
||||
if (getPropertyAsString(EOL_BYTE, "").length()>0){
|
||||
tcpClient.setEolByte(getEolByte());
|
||||
log.info("Using eolByte={}", getEolByte());
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("{} Created: {}@{}", this, getClassname(), Integer.toHexString(tcpClient.hashCode())); //$NON-NLS-1$
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("{} Exception creating: {} ", this, getClassname(), e); //$NON-NLS-1$
|
||||
}
|
||||
return tcpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleResult sample(Entry e)// Entry tends to be ignored ...
|
||||
{
|
||||
if (firstSample) { // Do stuff we cannot do as part of threadStarted()
|
||||
initSampling();
|
||||
firstSample=false;
|
||||
}
|
||||
final boolean reUseConnection = isReUseConnection();
|
||||
final boolean closeConnection = isCloseConnection();
|
||||
String socketKey = getSocketKey();
|
||||
if (log.isDebugEnabled()){
|
||||
log.debug(getLabel() + " " + getFilename() + " " + getUsername() + " " + getPassword());
|
||||
}
|
||||
SampleResult res = new SampleResult();
|
||||
boolean isSuccessful = false;
|
||||
res.setSampleLabel(getName());// Use the test element name for the label
|
||||
String sb = "Host: " + getServer() +
|
||||
" Port: " + getPort() + "\n" +
|
||||
"Reuse: " + reUseConnection +
|
||||
" Close: " + closeConnection + "\n[" +
|
||||
"SOLINGER: " + getSoLinger() +
|
||||
" EOL: " + getEolByte() +
|
||||
" noDelay: " + getNoDelay() +
|
||||
"]";
|
||||
res.setSamplerData(sb);
|
||||
res.sampleStart();
|
||||
try {
|
||||
Socket sock;
|
||||
try {
|
||||
sock = getSocket(socketKey);
|
||||
} finally {
|
||||
res.connectEnd();
|
||||
}
|
||||
if (sock == null) {
|
||||
res.setResponseCode("500"); //$NON-NLS-1$
|
||||
res.setResponseMessage(getError());
|
||||
} else if (protocolHandler == null){
|
||||
res.setResponseCode("500"); //$NON-NLS-1$
|
||||
res.setResponseMessage("Protocol handler not found");
|
||||
} else {
|
||||
currentSocket = sock;
|
||||
InputStream is = sock.getInputStream();
|
||||
OutputStream os = sock.getOutputStream();
|
||||
String req = getRequestData();
|
||||
// TODO handle filenames
|
||||
res.setSamplerData(req);
|
||||
String in = null;
|
||||
//替换原来的编码
|
||||
if (protocolHandler instanceof MsTCPClientImpl) {
|
||||
((MsTCPClientImpl) protocolHandler).write(os, req , getCharset());
|
||||
in = ((MsTCPClientImpl) protocolHandler).read(is, res , getCharset());
|
||||
} else {
|
||||
protocolHandler.write(os, req);
|
||||
in = protocolHandler.read(is, res);
|
||||
}
|
||||
isSuccessful = setupSampleResult(res, in, null, protocolHandler);
|
||||
}
|
||||
} catch (ReadException ex) {
|
||||
log.error("", ex);
|
||||
isSuccessful=setupSampleResult(res, ex.getPartialResponse(), ex,protocolHandler);
|
||||
closeSocket(socketKey);
|
||||
} catch (Exception ex) {
|
||||
log.error("", ex);
|
||||
isSuccessful=setupSampleResult(res, "", ex, protocolHandler);
|
||||
closeSocket(socketKey);
|
||||
} finally {
|
||||
currentSocket = null;
|
||||
// Calculate response time
|
||||
res.sampleEnd();
|
||||
|
||||
// Set if we were successful or not
|
||||
res.setSuccessful(isSuccessful);
|
||||
|
||||
if (!reUseConnection || closeConnection) {
|
||||
closeSocket(socketKey);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills SampleResult object
|
||||
* @param sampleResult {@link SampleResult}
|
||||
* @param readResponse Response read until error occurred
|
||||
* @param exception Source exception
|
||||
* @param protocolHandler {@link TCPClient}
|
||||
* @return boolean if sample is considered as successful
|
||||
*/
|
||||
private boolean setupSampleResult(SampleResult sampleResult,
|
||||
String readResponse,
|
||||
Exception exception,
|
||||
TCPClient protocolHandler) {
|
||||
//替换原来的编码
|
||||
sampleResult.setResponseData(readResponse,
|
||||
protocolHandler != null ? this.getCharset() : null);
|
||||
sampleResult.setDataType(SampleResult.TEXT);
|
||||
if(exception==null) {
|
||||
sampleResult.setResponseCodeOK();
|
||||
sampleResult.setResponseMessage("OK"); //$NON-NLS-1$
|
||||
} else {
|
||||
sampleResult.setResponseCode("500"); //$NON-NLS-1$
|
||||
sampleResult.setResponseMessage(exception.toString()); //$NON-NLS-1$
|
||||
}
|
||||
boolean isSuccessful = exception == null;
|
||||
// Reset the status code if the message contains one
|
||||
if (!StringUtils.isEmpty(readResponse) && STATUS_PREFIX.length() > 0) {
|
||||
int i = readResponse.indexOf(STATUS_PREFIX);
|
||||
int j = readResponse.indexOf(STATUS_SUFFIX, i + STATUS_PREFIX.length());
|
||||
if (i != -1 && j > i) {
|
||||
String rc = readResponse.substring(i + STATUS_PREFIX.length(), j);
|
||||
sampleResult.setResponseCode(rc);
|
||||
isSuccessful = isSuccessful && checkResponseCode(rc);
|
||||
if (HAVE_STATUS_PROPS) {
|
||||
sampleResult.setResponseMessage(STATUS_PROPS.getProperty(rc, "Status code not found in properties")); //$NON-NLS-1$
|
||||
} else {
|
||||
sampleResult.setResponseMessage("No status property file");
|
||||
}
|
||||
} else {
|
||||
sampleResult.setResponseCode("999"); //$NON-NLS-1$
|
||||
sampleResult.setResponseMessage("Status value not found");
|
||||
isSuccessful = false;
|
||||
}
|
||||
}
|
||||
return isSuccessful;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rc response code
|
||||
* @return whether this represents success or not
|
||||
*/
|
||||
private boolean checkResponseCode(String rc) {
|
||||
int responseCode = Integer.parseInt(rc);
|
||||
return responseCode >= 400 && responseCode <= 599;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadStarted() {
|
||||
log.debug("Thread Started"); //$NON-NLS-1$
|
||||
firstSample = true;
|
||||
}
|
||||
|
||||
// Cannot do this as part of threadStarted() because the Config elements have not been processed.
|
||||
private void initSampling() {
|
||||
protocolHandler = getProtocol();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Using Protocol Handler: {}", //$NON-NLS-1$
|
||||
protocolHandler == null ? "NONE" : protocolHandler.getClass().getName()); //$NON-NLS-1$
|
||||
}
|
||||
if (protocolHandler != null){
|
||||
protocolHandler.setupTest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close socket of current sampler
|
||||
*/
|
||||
private void closeSocket(String socketKey) {
|
||||
Map<String, Object> cp = tp.get();
|
||||
Socket con = (Socket) cp.remove(socketKey);
|
||||
if (con != null) {
|
||||
log.debug("{} Closing connection {}", this, con); //$NON-NLS-1$
|
||||
try {
|
||||
con.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing socket {}", e); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void threadFinished() {
|
||||
log.debug("Thread Finished"); //$NON-NLS-1$
|
||||
tearDown();
|
||||
if (protocolHandler != null){
|
||||
protocolHandler.teardownTest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all connections, clears Map and remove thread local Map
|
||||
*/
|
||||
private void tearDown() {
|
||||
Map<String, Object> cp = tp.get();
|
||||
cp.forEach((k, v) -> {
|
||||
if(k.startsWith(TCPKEY)) {
|
||||
try {
|
||||
((Socket)v).close();
|
||||
} catch (IOException e) {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
});
|
||||
cp.clear();
|
||||
tp.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractSampler#applies(ConfigTestElement)
|
||||
*/
|
||||
@Override
|
||||
public boolean applies(ConfigTestElement configElement) {
|
||||
String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue();
|
||||
return APPLIABLE_CONFIG_CLASSES.contains(guiClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interrupt() {
|
||||
Optional<Socket> sock = Optional.ofNullable(currentSocket); // fetch in case gets nulled later
|
||||
if (sock.isPresent()) {
|
||||
try {
|
||||
sock.get().close();
|
||||
} catch (IOException ignored) {
|
||||
// NOOP
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.reporters;
|
||||
|
||||
import org.apache.jmeter.samplers.SampleEvent;
|
||||
import org.apache.jmeter.samplers.SampleListener;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.testelement.OnErrorTestElement;
|
||||
import org.apache.jmeter.threads.JMeterContext.TestLogicalAction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* ResultAction - take action based on the status of the last Result
|
||||
*
|
||||
*/
|
||||
public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener {
|
||||
|
||||
private static final long serialVersionUID = 242L;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ResultAction.class);
|
||||
|
||||
/**
|
||||
* Constructor is initially called once for each occurrence in the test plan
|
||||
* For GUI, several more instances are created Then clear is called at start
|
||||
* of test Called several times during test startup The name will not
|
||||
* necessarily have been set at this point.
|
||||
*/
|
||||
public ResultAction() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine the sample(s) and take appropriate action
|
||||
*
|
||||
* @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent)
|
||||
*/
|
||||
@Override
|
||||
public void sampleOccurred(SampleEvent e) {
|
||||
SampleResult s = e.getResult();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ResultStatusHandler {} for {} OK? {}", getName(), s.getSampleLabel(), s.isSuccessful());
|
||||
}
|
||||
if (!s.isSuccessful()) {
|
||||
if (isStopTestNow()) {
|
||||
s.setStopTestNow(true);
|
||||
} else if (isStopTest()) {
|
||||
s.setStopTest(true);
|
||||
} else if (isStopThread()) {
|
||||
s.setStopThread(true);
|
||||
} else if (isStartNextThreadLoop()) {
|
||||
s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_THREAD);
|
||||
} else if (isStartNextIterationOfCurrentLoop()) {
|
||||
s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_CURRENT_LOOP);
|
||||
} else if (isBreakCurrentLoop()) {
|
||||
s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP);
|
||||
}
|
||||
} else {
|
||||
if (getErrorAction() == 1000) {
|
||||
s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void sampleStarted(SampleEvent e) {
|
||||
// not used
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void sampleStopped(SampleEvent e) {
|
||||
// not used
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,613 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.services;
|
||||
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.io.input.BOMInputStream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.gui.JMeterFileFilter;
|
||||
import org.apache.jmeter.save.CSVSaveService;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jorphan.util.JOrphanUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* This class provides thread-safe access to files, and to
|
||||
* provide some simplifying assumptions about where to find files and how to
|
||||
* name them. For instance, putting supporting files in the same directory as
|
||||
* the saved test plan file allows users to refer to the file with just it's
|
||||
* name - this FileServer class will find the file without a problem.
|
||||
* Eventually, I want all in-test file access to be done through here, with the
|
||||
* goal of packaging up entire test plans as a directory structure that can be
|
||||
* sent via rmi to remote servers (currently, one must make sure the remote
|
||||
* server has all support files in a relative-same location) and to package up
|
||||
* test plans to execute on unknown boxes that only have Java installed.
|
||||
*/
|
||||
public class FileServer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FileServer.class);
|
||||
|
||||
/**
|
||||
* The default base used for resolving relative files, i.e.<br/>
|
||||
* {@code System.getProperty("user.dir")}
|
||||
*/
|
||||
private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Default base prefix: {@value}
|
||||
*/
|
||||
private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$
|
||||
|
||||
private static final String BASE_PREFIX =
|
||||
JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$
|
||||
BASE_PREFIX_DEFAULT);
|
||||
|
||||
private File base;
|
||||
|
||||
private final Map<String, FileEntry> files = new HashMap<>();
|
||||
|
||||
private static final FileServer server = new FileServer();
|
||||
|
||||
// volatile needed to ensure safe publication
|
||||
private volatile String scriptName;
|
||||
|
||||
// Cannot be instantiated
|
||||
private FileServer() {
|
||||
base = new File(DEFAULT_BASE);
|
||||
log.info("Default base='{}'", DEFAULT_BASE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the singleton instance of the server.
|
||||
*/
|
||||
public static FileServer getFileServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current base to DEFAULT_BASE.
|
||||
*/
|
||||
public synchronized void resetBase() {
|
||||
checkForOpenFiles();
|
||||
base = new File(DEFAULT_BASE);
|
||||
log.info("Reset base to '{}'", base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current base directory for relative file names from the provided path.
|
||||
* If the path does not refer to an existing directory, then its parent is used.
|
||||
* Normally the provided path is a file, so using the parent directory is appropriate.
|
||||
*
|
||||
* @param basedir the path to set, or {@code null} if the GUI is being cleared
|
||||
* @throws IllegalStateException if files are still open
|
||||
*/
|
||||
public synchronized void setBasedir(String basedir) {
|
||||
checkForOpenFiles(); // TODO should this be called if basedir == null?
|
||||
if (basedir != null) {
|
||||
File newBase = new File(basedir);
|
||||
if (!newBase.isDirectory()) {
|
||||
newBase = newBase.getParentFile();
|
||||
}
|
||||
base = newBase;
|
||||
log.info("Set new base='{}'", base);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current base directory for relative file names from the provided script file.
|
||||
* The parameter is assumed to be the path to a JMX file, so the base directory is derived
|
||||
* from its parent.
|
||||
*
|
||||
* @param scriptPath the path of the script file; must be not be {@code null}
|
||||
* @throws IllegalStateException if files are still open
|
||||
* @throws IllegalArgumentException if scriptPath parameter is null
|
||||
*/
|
||||
public synchronized void setBaseForScript(File scriptPath) {
|
||||
if (scriptPath == null) {
|
||||
throw new IllegalArgumentException("scriptPath must not be null");
|
||||
}
|
||||
setScriptName(scriptPath.getName());
|
||||
// getParentFile() may not work on relative paths
|
||||
setBase(scriptPath.getAbsoluteFile().getParentFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current base directory for relative file names.
|
||||
*
|
||||
* @param jmxBase the path of the script file base directory, cannot be null
|
||||
* @throws IllegalStateException if files are still open
|
||||
* @throws IllegalArgumentException if {@code basepath} is null
|
||||
*/
|
||||
public synchronized void setBase(File jmxBase) {
|
||||
if (jmxBase == null) {
|
||||
throw new IllegalArgumentException("jmxBase must not be null");
|
||||
}
|
||||
checkForOpenFiles();
|
||||
base = jmxBase;
|
||||
log.info("Set new base='{}'", base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are entries in use.
|
||||
* <p>
|
||||
* Caller must ensure that access to the files map is single-threaded as
|
||||
* there is a window between checking the files Map and clearing it.
|
||||
*
|
||||
* @throws IllegalStateException if there are any entries still in use
|
||||
*/
|
||||
private void checkForOpenFiles() throws IllegalStateException {
|
||||
if (filesOpen()) { // checks for entries in use
|
||||
throw new IllegalStateException("Files are still open, cannot change base directory");
|
||||
}
|
||||
files.clear(); // tidy up any unused entries
|
||||
}
|
||||
|
||||
public synchronized String getBaseDir() {
|
||||
return base.getAbsolutePath();
|
||||
}
|
||||
|
||||
public static String getDefaultBase() {
|
||||
return DEFAULT_BASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the relative path from DEFAULT_BASE to the current base,
|
||||
* which must be the same as or a child of the default.
|
||||
*
|
||||
* @return the relative path, or {@code "."} if the path cannot be determined
|
||||
*/
|
||||
public synchronized File getBaseDirRelative() {
|
||||
// Must first convert to absolute path names to ensure parents are available
|
||||
File parent = new File(DEFAULT_BASE).getAbsoluteFile();
|
||||
File f = base.getAbsoluteFile();
|
||||
ArrayDeque<String> l = new ArrayDeque<>();
|
||||
while (f != null) {
|
||||
if (f.equals(parent)) {
|
||||
if (l.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
File rel = new File(l.pop());
|
||||
while (!l.isEmpty()) {
|
||||
rel = new File(rel, l.pop());
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
l.push(f.getName());
|
||||
f = f.getParentFile();
|
||||
}
|
||||
return new File(".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an association between a filename and a File inputOutputObject,
|
||||
* and stores it for later use - unless it is already stored.
|
||||
*
|
||||
* @param filename - relative (to base) or absolute file name (must not be null)
|
||||
*/
|
||||
public void reserveFile(String filename) {
|
||||
reserveFile(filename,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an association between a filename and a File inputOutputObject,
|
||||
* and stores it for later use - unless it is already stored.
|
||||
*
|
||||
* @param filename - relative (to base) or absolute file name (must not be null)
|
||||
* @param charsetName - the character set encoding to use for the file (may be null)
|
||||
*/
|
||||
public void reserveFile(String filename, String charsetName) {
|
||||
reserveFile(filename, charsetName, filename, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an association between a filename and a File inputOutputObject,
|
||||
* and stores it for later use - unless it is already stored.
|
||||
*
|
||||
* @param filename - relative (to base) or absolute file name (must not be null)
|
||||
* @param charsetName - the character set encoding to use for the file (may be null)
|
||||
* @param alias - the name to be used to access the object (must not be null)
|
||||
*/
|
||||
public void reserveFile(String filename, String charsetName, String alias) {
|
||||
reserveFile(filename, charsetName, alias, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an association between a filename and a File inputOutputObject,
|
||||
* and stores it for later use - unless it is already stored.
|
||||
*
|
||||
* @param filename - relative (to base) or absolute file name (must not be null or empty)
|
||||
* @param charsetName - the character set encoding to use for the file (may be null)
|
||||
* @param alias - the name to be used to access the object (must not be null)
|
||||
* @param hasHeader true if the file has a header line describing the contents
|
||||
* @return the header line; may be null
|
||||
* @throws IllegalArgumentException if header could not be read or filename is null or empty
|
||||
*/
|
||||
public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) {
|
||||
if (filename == null || filename.isEmpty()) {
|
||||
throw new IllegalArgumentException("Filename must not be null or empty");
|
||||
}
|
||||
if (alias == null) {
|
||||
throw new IllegalArgumentException("Alias must not be null");
|
||||
}
|
||||
FileEntry fileEntry = files.get(alias);
|
||||
if (fileEntry == null) {
|
||||
fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName);
|
||||
if (filename.equals(alias)) {
|
||||
log.info("Stored: {}", filename);
|
||||
} else {
|
||||
log.info("Stored: {} Alias: {}", filename, alias);
|
||||
}
|
||||
files.put(alias, fileEntry);
|
||||
if (hasHeader) {
|
||||
try {
|
||||
fileEntry.headerLine = readLine(alias, false);
|
||||
if (fileEntry.headerLine == null) {
|
||||
fileEntry.exception = new EOFException("File is empty: " + fileEntry.file);
|
||||
}
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
fileEntry.exception = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasHeader && fileEntry.headerLine == null) {
|
||||
throw new IllegalArgumentException("Could not read file header line for file " + filename,
|
||||
fileEntry.exception);
|
||||
}
|
||||
return fileEntry.headerLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves file name into {@link File} instance.
|
||||
* When filename is not absolute and not found from current working dir,
|
||||
* it tries to find it under current base directory
|
||||
* @param filename original file name
|
||||
* @return {@link File} instance
|
||||
*/
|
||||
private File resolveFileFromPath(String filename) {
|
||||
File f = new File(filename);
|
||||
if (f.isAbsolute() || f.exists()) {
|
||||
return f;
|
||||
} else {
|
||||
return new File(base, filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next line of the named file, recycle by default.
|
||||
*
|
||||
* @param filename the filename or alias that was used to reserve the file
|
||||
* @return String containing the next line in the file
|
||||
* @throws IOException when reading of the file fails, or the file was not reserved properly
|
||||
*/
|
||||
public String readLine(String filename) throws IOException {
|
||||
return readLine(filename, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next line of the named file, first line is name to false
|
||||
*
|
||||
* @param filename the filename or alias that was used to reserve the file
|
||||
* @param recycle - should file be restarted at EOF?
|
||||
* @return String containing the next line in the file (null if EOF reached and not recycle)
|
||||
* @throws IOException when reading of the file fails, or the file was not reserved properly
|
||||
*/
|
||||
public String readLine(String filename, boolean recycle) throws IOException {
|
||||
return readLine(filename, recycle, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next line of the named file
|
||||
*
|
||||
* @param filename the filename or alias that was used to reserve the file
|
||||
* @param recycle - should file be restarted at EOF?
|
||||
* @param ignoreFirstLine - Ignore first line
|
||||
* @return String containing the next line in the file (null if EOF reached and not recycle)
|
||||
* @throws IOException when reading of the file fails, or the file was not reserved properly
|
||||
*/
|
||||
public synchronized String readLine(String filename, boolean recycle,
|
||||
boolean ignoreFirstLine) throws IOException {
|
||||
FileEntry fileEntry = files.get(filename);
|
||||
if (fileEntry != null) {
|
||||
if (fileEntry.inputOutputObject == null) {
|
||||
fileEntry.inputOutputObject = createBufferedReader(fileEntry);
|
||||
} else if (!(fileEntry.inputOutputObject instanceof Reader)) {
|
||||
throw new IOException("File " + filename + " already in use");
|
||||
}
|
||||
BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject;
|
||||
String line = reader.readLine();
|
||||
if (line == null && recycle) {
|
||||
reader.close();
|
||||
reader = createBufferedReader(fileEntry);
|
||||
fileEntry.inputOutputObject = reader;
|
||||
if (ignoreFirstLine) {
|
||||
// read first line and forget
|
||||
reader.readLine();//NOSONAR
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
log.debug("Read:{}", line);
|
||||
return line;
|
||||
}
|
||||
throw new IOException("File never reserved: "+filename);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param alias the file name or alias
|
||||
* @param recycle whether the file should be re-started on EOF
|
||||
* @param ignoreFirstLine whether the file contains a file header which will be ignored
|
||||
* @param delim the delimiter to use for parsing
|
||||
* @return the parsed line, will be empty if the file is at EOF
|
||||
* @throws IOException when reading of the aliased file fails, or the file was not reserved properly
|
||||
*/
|
||||
public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException {
|
||||
BufferedReader reader = getReader(alias, recycle, ignoreFirstLine);
|
||||
return CSVSaveService.csvReadFile(reader, delim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return BufferedReader handling close if EOF reached and recycle is true
|
||||
* and ignoring first line if ignoreFirstLine is true
|
||||
*
|
||||
* @param alias String alias
|
||||
* @param recycle Recycle at eof
|
||||
* @param ignoreFirstLine Ignore first line
|
||||
* @return {@link BufferedReader}
|
||||
*/
|
||||
private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException {
|
||||
FileEntry fileEntry = files.get(alias);
|
||||
if (fileEntry != null) {
|
||||
BufferedReader reader;
|
||||
if (fileEntry.inputOutputObject == null) {
|
||||
reader = createBufferedReader(fileEntry);
|
||||
fileEntry.inputOutputObject = reader;
|
||||
if (ignoreFirstLine) {
|
||||
// read first line and forget
|
||||
reader.readLine(); //NOSONAR
|
||||
}
|
||||
} else if (!(fileEntry.inputOutputObject instanceof Reader)) {
|
||||
throw new IOException("File " + alias + " already in use");
|
||||
} else {
|
||||
reader = (BufferedReader) fileEntry.inputOutputObject;
|
||||
if (recycle) { // need to check if we are at EOF already
|
||||
reader.mark(1);
|
||||
int peek = reader.read();
|
||||
if (peek == -1) { // already at EOF
|
||||
reader.close();
|
||||
reader = createBufferedReader(fileEntry);
|
||||
fileEntry.inputOutputObject = reader;
|
||||
if (ignoreFirstLine) {
|
||||
// read first line and forget
|
||||
reader.readLine(); //NOSONAR
|
||||
}
|
||||
} else { // OK, we still have some data, restore it
|
||||
reader.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
return reader;
|
||||
} else {
|
||||
throw new IOException("File never reserved: "+alias);
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException {
|
||||
if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) {
|
||||
throw new IllegalArgumentException("File " + fileEntry.file.getName() + " must exist and be readable");
|
||||
}
|
||||
BOMInputStream fis = new BOMInputStream(Files.newInputStream(fileEntry.file.toPath())); //NOSONAR
|
||||
InputStreamReader isr = null;
|
||||
// If file encoding is specified, read using that encoding, otherwise use default platform encoding
|
||||
String charsetName = fileEntry.charSetEncoding;
|
||||
if (!JOrphanUtils.isBlank(charsetName)) {
|
||||
isr = new InputStreamReader(fis, charsetName);
|
||||
} else if (fis.hasBOM()) {
|
||||
isr = new InputStreamReader(fis, fis.getBOM().getCharsetName());
|
||||
} else {
|
||||
@SuppressWarnings("DefaultCharset") final InputStreamReader withPlatformEncoding = new InputStreamReader(fis);
|
||||
isr = withPlatformEncoding;
|
||||
}
|
||||
return new BufferedReader(isr);
|
||||
}
|
||||
|
||||
public synchronized void write(String filename, String value) throws IOException {
|
||||
FileEntry fileEntry = files.get(filename);
|
||||
if (fileEntry != null) {
|
||||
if (fileEntry.inputOutputObject == null) {
|
||||
fileEntry.inputOutputObject = createBufferedWriter(fileEntry);
|
||||
} else if (!(fileEntry.inputOutputObject instanceof Writer)) {
|
||||
throw new IOException("File " + filename + " already in use");
|
||||
}
|
||||
BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject;
|
||||
log.debug("Write:{}", value);
|
||||
writer.write(value);
|
||||
} else {
|
||||
throw new IOException("File never reserved: "+filename);
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException {
|
||||
OutputStream fos = Files.newOutputStream(fileEntry.file.toPath());
|
||||
OutputStreamWriter osw;
|
||||
// If file encoding is specified, write using that encoding, otherwise use default platform encoding
|
||||
String charsetName = fileEntry.charSetEncoding;
|
||||
if (!JOrphanUtils.isBlank(charsetName)) {
|
||||
osw = new OutputStreamWriter(fos, charsetName);
|
||||
} else {
|
||||
@SuppressWarnings("DefaultCharset") final OutputStreamWriter withPlatformEncoding = new OutputStreamWriter(fos);
|
||||
osw = withPlatformEncoding;
|
||||
}
|
||||
return new BufferedWriter(osw);
|
||||
}
|
||||
|
||||
public synchronized void closeFiles() throws IOException {
|
||||
for (Map.Entry<String, FileEntry> me : files.entrySet()) {
|
||||
closeFile(me.getKey(), me.getValue() );
|
||||
}
|
||||
files.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据线程名字清理掉当前线程缓存的csv
|
||||
*
|
||||
* @param name 线程组名称
|
||||
*/
|
||||
public synchronized void closeCsv(String name) {
|
||||
try {
|
||||
if (StringUtils.isNotEmpty(name)) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (Iterator<String> iterator = files.keySet().iterator(); iterator.hasNext(); ) {
|
||||
String key = iterator.next();
|
||||
if (key.contains(name)) {
|
||||
FileEntry fileEntry = files.get(key);
|
||||
closeFile(name, fileEntry);
|
||||
list.add(key);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(list)) {
|
||||
for (String key : list) {
|
||||
files.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
LoggerUtil.info("在处理中的CSV数量:" + files.size());
|
||||
} catch (Exception e) {
|
||||
LoggerUtil.error("关闭CSV异常:" + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
public int fileSize() {
|
||||
return files.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name or alias of the file to be closed
|
||||
* @throws IOException when closing of the aliased file fails
|
||||
*/
|
||||
public synchronized void closeFile(String name) throws IOException {
|
||||
FileEntry fileEntry = files.get(name);
|
||||
closeFile(name, fileEntry);
|
||||
}
|
||||
|
||||
private void closeFile(String name, FileEntry fileEntry) throws IOException {
|
||||
if (fileEntry != null && fileEntry.inputOutputObject != null) {
|
||||
log.info("Close: {}", name);
|
||||
fileEntry.inputOutputObject.close();
|
||||
fileEntry.inputOutputObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
boolean filesOpen() { // package access for test code only
|
||||
return files.values().stream()
|
||||
.anyMatch(fileEntry -> fileEntry.inputOutputObject != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method will get a random file in a base directory
|
||||
* <p>
|
||||
* TODO hey, not sure this method belongs here.
|
||||
* FileServer is for thread safe File access relative to current test's base directory.
|
||||
*
|
||||
* @param basedir name of the directory in which the files can be found
|
||||
* @param extensions array of allowed extensions, if <code>null</code> is given,
|
||||
* any file be allowed
|
||||
* @return a random File from the <code>basedir</code> that matches one of
|
||||
* the extensions
|
||||
*/
|
||||
public File getRandomFile(String basedir, String[] extensions) {
|
||||
File input = null;
|
||||
if (basedir != null) {
|
||||
File src = new File(basedir);
|
||||
File[] lfiles = src.listFiles(new JMeterFileFilter(extensions));
|
||||
if (lfiles != null) {
|
||||
// lfiles cannot be null as it has been checked before
|
||||
int count = lfiles.length;
|
||||
input = lfiles[ThreadLocalRandom.current().nextInt(count)];
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link File} instance for provided file path,
|
||||
* resolve file location relative to base dir or script dir when needed
|
||||
*
|
||||
* @param path original path to file, maybe relative
|
||||
* @return {@link File} instance
|
||||
*/
|
||||
public File getResolvedFile(String path) {
|
||||
reserveFile(path);
|
||||
return files.get(path).file;
|
||||
}
|
||||
|
||||
private static class FileEntry {
|
||||
private String headerLine;
|
||||
private Throwable exception;
|
||||
private final File file;
|
||||
private Closeable inputOutputObject;
|
||||
private final String charSetEncoding;
|
||||
|
||||
FileEntry(File f, Closeable o, String e) {
|
||||
file = f;
|
||||
inputOutputObject = o;
|
||||
charSetEncoding = e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a file name that may be relative to the base directory. If the
|
||||
* name begins with the value of the JMeter property
|
||||
* "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is
|
||||
* assumed to be relative to the basename.
|
||||
*
|
||||
* @param relativeName
|
||||
* filename that should be checked for
|
||||
* <code>jmeter.save.saveservice.base_prefix</code>
|
||||
* @return the updated filename
|
||||
*/
|
||||
public static String resolveBaseRelativeName(String relativeName) {
|
||||
if (relativeName.startsWith(BASE_PREFIX)) {
|
||||
String newName = relativeName.substring(BASE_PREFIX.length());
|
||||
return new File(getFileServer().getBaseDir(), newName).getAbsolutePath();
|
||||
}
|
||||
return relativeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JMX Script name
|
||||
* @since 2.6
|
||||
*/
|
||||
public String getScriptName() {
|
||||
return scriptName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scriptName Script name
|
||||
* @since 2.6
|
||||
*/
|
||||
public void setScriptName(String scriptName) {
|
||||
this.scriptName = scriptName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.testelement;
|
||||
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import org.apache.jmeter.NewDriver;
|
||||
import org.apache.jmeter.config.Arguments;
|
||||
import org.apache.jmeter.services.FileServer;
|
||||
import org.apache.jmeter.testelement.property.BooleanProperty;
|
||||
import org.apache.jmeter.testelement.property.JMeterProperty;
|
||||
import org.apache.jmeter.testelement.property.TestElementProperty;
|
||||
import org.apache.jmeter.threads.AbstractThreadGroup;
|
||||
import org.apache.jorphan.util.JOrphanUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TestPlan extends AbstractTestElement implements Serializable, TestStateListener {
|
||||
private static final long serialVersionUID = 234L;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestPlan.class);
|
||||
|
||||
//+ JMX field names - do not change values
|
||||
private static final String FUNCTIONAL_MODE = "TestPlan.functional_mode"; //$NON-NLS-1$
|
||||
|
||||
private static final String USER_DEFINED_VARIABLES = "TestPlan.user_defined_variables"; //$NON-NLS-1$
|
||||
|
||||
private static final String SERIALIZE_THREADGROUPS = "TestPlan.serialize_threadgroups"; //$NON-NLS-1$
|
||||
|
||||
private static final String CLASSPATHS = "TestPlan.user_define_classpath"; //$NON-NLS-1$
|
||||
|
||||
private static final String TEARDOWN_ON_SHUTDOWN = "TestPlan.tearDown_on_shutdown"; //$NON-NLS-1$
|
||||
|
||||
//- JMX field names
|
||||
|
||||
private static final String CLASSPATH_SEPARATOR = ","; //$NON-NLS-1$
|
||||
|
||||
private static final String BASEDIR = "basedir";
|
||||
private transient List<AbstractThreadGroup> threadGroups = new ArrayList<>();
|
||||
|
||||
// There's only 1 test plan, so can cache the mode here
|
||||
private static volatile boolean functionalMode = false;
|
||||
|
||||
public TestPlan() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TestPlan(String name) {
|
||||
setName(name);
|
||||
}
|
||||
|
||||
// create transient item
|
||||
protected Object readResolve() {
|
||||
threadGroups = new ArrayList<>();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void prepareForPreCompile() {
|
||||
getVariables().setRunningVersion(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the functional mode property
|
||||
*
|
||||
* @return functional mode
|
||||
*/
|
||||
public boolean isFunctionalMode() {
|
||||
return getPropertyAsBoolean(FUNCTIONAL_MODE);
|
||||
}
|
||||
|
||||
public void setUserDefinedVariables(Arguments vars) {
|
||||
setProperty(new TestElementProperty(USER_DEFINED_VARIABLES, vars));
|
||||
}
|
||||
|
||||
public JMeterProperty getUserDefinedVariablesAsProperty() {
|
||||
return getProperty(USER_DEFINED_VARIABLES);
|
||||
}
|
||||
|
||||
public String getBasedir() {
|
||||
return getPropertyAsString(BASEDIR);
|
||||
}
|
||||
|
||||
// Does not appear to be used yet
|
||||
public void setBasedir(String b) {
|
||||
setProperty(BASEDIR, b);
|
||||
}
|
||||
|
||||
public Arguments getArguments() {
|
||||
return getVariables();
|
||||
}
|
||||
|
||||
public Map<String, String> getUserDefinedVariables() {
|
||||
Arguments args = getVariables();
|
||||
return args.getArgumentsAsMap();
|
||||
}
|
||||
|
||||
private Arguments getVariables() {
|
||||
Arguments args = (Arguments) getProperty(USER_DEFINED_VARIABLES).getObjectValue();
|
||||
if (args == null) {
|
||||
args = new Arguments();
|
||||
setUserDefinedVariables(args);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public void setFunctionalMode(boolean funcMode) {
|
||||
setProperty(new BooleanProperty(FUNCTIONAL_MODE, funcMode));
|
||||
setGlobalFunctionalMode(funcMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set JMeter in functional mode
|
||||
*
|
||||
* @param funcMode boolean functional mode
|
||||
*/
|
||||
private static void setGlobalFunctionalMode(boolean funcMode) {
|
||||
functionalMode = funcMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the static copy of the functional mode
|
||||
*
|
||||
* @return mode
|
||||
*/
|
||||
public static boolean getFunctionalMode() {
|
||||
return functionalMode;
|
||||
}
|
||||
|
||||
public void setSerialized(boolean serializeTGs) {
|
||||
setProperty(new BooleanProperty(SERIALIZE_THREADGROUPS, serializeTGs));
|
||||
}
|
||||
|
||||
public void setTearDownOnShutdown(boolean tearDown) {
|
||||
setProperty(TEARDOWN_ON_SHUTDOWN, tearDown, false);
|
||||
}
|
||||
|
||||
public boolean isTearDownOnShutdown() {
|
||||
return getPropertyAsBoolean(TEARDOWN_ON_SHUTDOWN, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the classpath for the test plan. If the classpath is made up from
|
||||
* more then one path, the parts must be separated with
|
||||
* {@link TestPlan#CLASSPATH_SEPARATOR}.
|
||||
*
|
||||
* @param text the classpath to be set
|
||||
*/
|
||||
public void setTestPlanClasspath(String text) {
|
||||
setProperty(CLASSPATHS, text);
|
||||
}
|
||||
|
||||
public void setTestPlanClasspathArray(String[] text) {
|
||||
StringBuilder cat = new StringBuilder();
|
||||
for (int idx = 0; idx < text.length; idx++) {
|
||||
if (idx > 0) {
|
||||
cat.append(CLASSPATH_SEPARATOR);
|
||||
}
|
||||
cat.append(text[idx]);
|
||||
}
|
||||
this.setTestPlanClasspath(cat.toString());
|
||||
}
|
||||
|
||||
public String[] getTestPlanClasspathArray() {
|
||||
return JOrphanUtils.split(this.getTestPlanClasspath(), CLASSPATH_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the classpath
|
||||
*
|
||||
* @return classpath
|
||||
*/
|
||||
public String getTestPlanClasspath() {
|
||||
return getPropertyAsString(CLASSPATHS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the serialize threadgroups property
|
||||
*
|
||||
* @return serialized setting
|
||||
*/
|
||||
public boolean isSerialized() {
|
||||
return getPropertyAsBoolean(SERIALIZE_THREADGROUPS);
|
||||
}
|
||||
|
||||
public void addParameter(String name, String value) {
|
||||
getVariables().addArgument(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestElement(TestElement tg) {
|
||||
super.addTestElement(tg);
|
||||
if (tg instanceof AbstractThreadGroup && !isRunningVersion()) {
|
||||
addThreadGroup((AbstractThreadGroup) tg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a feature to the AbstractThreadGroup attribute of the TestPlan object.
|
||||
*
|
||||
* @param group the feature to be added to the AbstractThreadGroup attribute
|
||||
*/
|
||||
public void addThreadGroup(AbstractThreadGroup group) {
|
||||
threadGroups.add(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void testEnded() {
|
||||
LoggerUtil.info("Test plan " + this.getName() + "test end");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void testEnded(String host) {
|
||||
testEnded();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void testStarted() {
|
||||
LoggerUtil.info("测试计划开始执行:" + this.getName());
|
||||
if (getBasedir() != null && getBasedir().length() > 0) {
|
||||
try {
|
||||
FileServer.getFileServer().setBasedir(FileServer.getFileServer().getBaseDir() + getBasedir());
|
||||
} catch (IllegalStateException e) {
|
||||
log.error("Failed to set file server base dir with {}", getBasedir(), e);
|
||||
}
|
||||
}
|
||||
// we set the classpath
|
||||
String[] paths = this.getTestPlanClasspathArray();
|
||||
for (String path : paths) {
|
||||
try {
|
||||
NewDriver.addURL(path);
|
||||
log.info("added {} to classpath", path);
|
||||
} catch (MalformedURLException e) {
|
||||
// TODO Should we continue the test or fail ?
|
||||
log.error("Error adding {} to classpath", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void testStarted(String host) {
|
||||
testStarted();
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.util;
|
||||
|
||||
import org.apache.jmeter.testbeans.TestBean;
|
||||
|
||||
import javax.script.ScriptEngineFactory;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 解决JSR233加载 ScriptEngineFactory 空指针问题
|
||||
*/
|
||||
public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport {
|
||||
|
||||
private static final String[] LANGUAGE_TAGS;
|
||||
|
||||
/**
|
||||
* Will be removed in next version following 3.2
|
||||
* @deprecated use {@link JSR223BeanInfoSupport#getLanguageNames()}
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String[][] LANGUAGE_NAMES; // NOSONAR Kept for backward compatibility
|
||||
|
||||
private static final String[][] CONSTANT_LANGUAGE_NAMES;
|
||||
|
||||
static {
|
||||
Map<String, ScriptEngineFactory> nameMap = new HashMap<>();
|
||||
ScriptEngineManager sem = new ScriptEngineManager();
|
||||
final List<ScriptEngineFactory> engineFactories = sem.getEngineFactories();
|
||||
for(ScriptEngineFactory fact : engineFactories){
|
||||
List<String> names = fact.getNames();
|
||||
for(String shortName : names) {
|
||||
if (shortName != null) {
|
||||
nameMap.put(shortName.toLowerCase(Locale.ENGLISH), fact);
|
||||
}
|
||||
}
|
||||
}
|
||||
LANGUAGE_TAGS = nameMap.keySet().toArray(new String[nameMap.size()]);
|
||||
Arrays.sort(LANGUAGE_TAGS);
|
||||
CONSTANT_LANGUAGE_NAMES = new String[nameMap.size()][2];
|
||||
int i = 0;
|
||||
for (Map.Entry<String, ScriptEngineFactory> me : nameMap.entrySet()) {
|
||||
final String key = me.getKey();
|
||||
CONSTANT_LANGUAGE_NAMES[i][0] = key;
|
||||
final ScriptEngineFactory fact = me.getValue();
|
||||
CONSTANT_LANGUAGE_NAMES[i++][1] = key +
|
||||
" (" // $NON-NLS-1$
|
||||
+ fact.getLanguageName() + " " + fact.getLanguageVersion() // $NON-NLS-1$
|
||||
+ " / " // $NON-NLS-1$
|
||||
+ fact.getEngineName() + " " + fact.getEngineVersion() // $NON-NLS-1$
|
||||
+ ")"; // $NON-NLS-1$
|
||||
}
|
||||
|
||||
LANGUAGE_NAMES = getLanguageNames(); // NOSONAR Kept for backward compatibility
|
||||
}
|
||||
|
||||
private static final ResourceBundle NAME_BUNDLE = new ListResourceBundle() {
|
||||
@Override
|
||||
protected Object[][] getContents() {
|
||||
return CONSTANT_LANGUAGE_NAMES;
|
||||
}
|
||||
};
|
||||
|
||||
protected JSR223BeanInfoSupport(Class<? extends TestBean> beanClass) {
|
||||
super(beanClass, LANGUAGE_TAGS, NAME_BUNDLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String array of 2 columns array containing Script engine short name / Script Language details
|
||||
*/
|
||||
public static final String[][] getLanguageNames() {
|
||||
return CONSTANT_LANGUAGE_NAMES.clone();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.util;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import io.metersphere.utils.CustomizeFunctionUtil;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.collections4.map.LRUMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.samplers.Sampler;
|
||||
import org.apache.jmeter.testelement.TestStateListener;
|
||||
import org.apache.jmeter.threads.JMeterContext;
|
||||
import org.apache.jmeter.threads.JMeterContextService;
|
||||
import org.apache.jmeter.threads.JMeterVariables;
|
||||
import org.apache.jorphan.util.JOrphanUtils;
|
||||
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.script.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Base class for JSR223 Test elements
|
||||
*/
|
||||
public abstract class JSR223TestElement extends ScriptingTestElement
|
||||
implements Serializable, TestStateListener {
|
||||
private static final long serialVersionUID = 233L;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JSR223TestElement.class);
|
||||
/**
|
||||
* Cache of compiled scripts
|
||||
*/
|
||||
private static final Map<String, CompiledScript> compiledScriptsCache =
|
||||
Collections.synchronizedMap(
|
||||
new LRUMap<>(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100)));
|
||||
|
||||
/**
|
||||
* If not empty then script in ScriptText will be compiled and cached
|
||||
*/
|
||||
private String cacheKey = "";
|
||||
|
||||
/**
|
||||
* md5 of the script, used as an unique key for the cache
|
||||
*/
|
||||
private String scriptMd5 = null;
|
||||
|
||||
/**
|
||||
* Initialization On Demand Holder pattern
|
||||
*/
|
||||
private static class LazyHolder {
|
||||
private LazyHolder() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static final ScriptEngineManager INSTANCE = new ScriptEngineManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ScriptEngineManager singleton
|
||||
*/
|
||||
public static ScriptEngineManager getInstance() {
|
||||
return LazyHolder.INSTANCE;
|
||||
}
|
||||
|
||||
protected JSR223TestElement() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link ScriptEngine} for language defaulting to groovy if language is not set
|
||||
* @throws ScriptException when no {@link ScriptEngine} could be found
|
||||
*/
|
||||
protected ScriptEngine getScriptEngine() throws ScriptException {
|
||||
String lang = getScriptLanguageWithDefault();
|
||||
ScriptEngine scriptEngine = getInstance().getEngineByName(lang);
|
||||
if (scriptEngine == null) {
|
||||
throw new ScriptException("Cannot find engine named: '" + lang + "', ensure you set language field in JSR223 Test Element: " + getName());
|
||||
}
|
||||
|
||||
return scriptEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return script language or DEFAULT_SCRIPT_LANGUAGE if none is set
|
||||
*/
|
||||
private String getScriptLanguageWithDefault() {
|
||||
String lang = getScriptLanguage();
|
||||
if (StringUtils.isNotEmpty(lang)) {
|
||||
return lang;
|
||||
}
|
||||
return DEFAULT_SCRIPT_LANGUAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate variables to be passed to scripts
|
||||
*
|
||||
* @param bindings Bindings
|
||||
*/
|
||||
protected void populateBindings(Bindings bindings) {
|
||||
final String label = getName();
|
||||
final String fileName = getFilename();
|
||||
final String scriptParameters = getParameters();
|
||||
// Use actual class name for log
|
||||
final Logger elementLogger = LoggerFactory.getLogger(getClass().getName() + "." + getName());
|
||||
bindings.put("log", elementLogger); // $NON-NLS-1$ (this name is fixed)
|
||||
bindings.put("Label", label); // $NON-NLS-1$ (this name is fixed)
|
||||
bindings.put("FileName", fileName); // $NON-NLS-1$ (this name is fixed)
|
||||
bindings.put("Parameters", scriptParameters); // $NON-NLS-1$ (this name is fixed)
|
||||
String[] args = JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$
|
||||
bindings.put("args", args); // $NON-NLS-1$ (this name is fixed)
|
||||
// Add variables for access to context and variables
|
||||
JMeterContext jmctx = JMeterContextService.getContext();
|
||||
|
||||
bindings.put("ctx", jmctx); // $NON-NLS-1$ (this name is fixed)
|
||||
JMeterVariables vars = jmctx.getVariables();
|
||||
bindings.put("vars", vars); // $NON-NLS-1$ (this name is fixed)
|
||||
Properties props = JMeterUtils.getJMeterProperties();
|
||||
bindings.put("props", props); // $NON-NLS-1$ (this name is fixed)
|
||||
// For use in debugging:
|
||||
bindings.put("OUT", System.out); // NOSONAR $NON-NLS-1$ (this name is fixed)
|
||||
|
||||
// Most subclasses will need these:
|
||||
Sampler sampler = jmctx.getCurrentSampler();
|
||||
bindings.put("sampler", sampler); // $NON-NLS-1$ (this name is fixed)
|
||||
SampleResult prev = jmctx.getPreviousResult();
|
||||
bindings.put("prev", prev); // $NON-NLS-1$ (this name is fixed)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will run inline script or file script with special behaviour for file script:
|
||||
* - If ScriptEngine implements Compilable script will be compiled and cached
|
||||
* - If not if will be run
|
||||
*
|
||||
* @param scriptEngine ScriptEngine
|
||||
* @param pBindings {@link Bindings} might be null
|
||||
* @return Object returned by script
|
||||
* @throws IOException when reading the script fails
|
||||
* @throws ScriptException when compiling or evaluation of the script fails
|
||||
*/
|
||||
protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings pBindings)
|
||||
throws IOException, ScriptException {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
// 设置自定义类加载器
|
||||
Object dynamicClassLoader = JMeterContextService.getContext().getVariables().getObject(CustomizeFunctionUtil.MS_CLASS_LOADER);
|
||||
if (dynamicClassLoader != null) {
|
||||
GroovyClassLoader classLoader = (GroovyClassLoader) dynamicClassLoader;
|
||||
if (scriptEngine instanceof GroovyScriptEngineImpl) {
|
||||
GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine;
|
||||
groovyScriptEngine.setClassLoader(classLoader);
|
||||
} else {
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
}
|
||||
}
|
||||
|
||||
Bindings bindings = pBindings;
|
||||
if (bindings == null) {
|
||||
bindings = scriptEngine.createBindings();
|
||||
}
|
||||
populateBindings(bindings);
|
||||
File scriptFile = new File(getFilename());
|
||||
// Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws
|
||||
// "java.lang.Error: unimplemented"
|
||||
boolean supportsCompilable = false;
|
||||
try {
|
||||
if (!StringUtils.isEmpty(getFilename())) {
|
||||
if (scriptFile.exists() && scriptFile.canRead()) {
|
||||
if (supportsCompilable) {
|
||||
String newCacheKey = getScriptLanguage() + "#" + // $NON-NLS-1$
|
||||
scriptFile.getAbsolutePath() + "#" + // $NON-NLS-1$
|
||||
scriptFile.lastModified();
|
||||
CompiledScript compiledScript = compiledScriptsCache.get(newCacheKey);
|
||||
if (compiledScript == null) {
|
||||
synchronized (compiledScriptsCache) {
|
||||
compiledScript = compiledScriptsCache.get(newCacheKey);
|
||||
if (compiledScript == null) {
|
||||
try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) {
|
||||
compiledScript = ((Compilable) scriptEngine).compile(fileReader);
|
||||
compiledScriptsCache.put(newCacheKey, compiledScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return compiledScript.eval(bindings);
|
||||
} else {
|
||||
try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) {
|
||||
return scriptEngine.eval(fileReader, bindings);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new ScriptException("Script file '" + scriptFile.getAbsolutePath()
|
||||
+ "' does not exist or is unreadable for element:" + getName());
|
||||
}
|
||||
} else if (!StringUtils.isEmpty(getScript())) {
|
||||
if (supportsCompilable &&
|
||||
!ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) {
|
||||
computeScriptMD5();
|
||||
CompiledScript compiledScript = compiledScriptsCache.get(this.scriptMd5);
|
||||
if (compiledScript == null) {
|
||||
synchronized (compiledScriptsCache) {
|
||||
compiledScript = compiledScriptsCache.get(this.scriptMd5);
|
||||
if (compiledScript == null) {
|
||||
compiledScript = ((Compilable) scriptEngine).compile(getScript());
|
||||
compiledScriptsCache.put(this.scriptMd5, compiledScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return compiledScript.eval(bindings);
|
||||
} else {
|
||||
return scriptEngine.eval(getScript(), bindings);
|
||||
}
|
||||
} else {
|
||||
throw new ScriptException("Both script file and script text are empty for element:" + getName());
|
||||
}
|
||||
} catch (ScriptException ex) {
|
||||
Throwable rootCause = ex.getCause();
|
||||
if (isStopCondition(rootCause)) {
|
||||
throw (RuntimeException) ex.getCause();
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
} finally {
|
||||
// 恢复原始类加载器
|
||||
Thread.currentThread().setContextClassLoader(loader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean true if element is not compilable or if compilation succeeds
|
||||
* @throws IOException if script is missing
|
||||
* @throws ScriptException if compilation fails
|
||||
*/
|
||||
public boolean compile()
|
||||
throws ScriptException, IOException {
|
||||
String lang = getScriptLanguageWithDefault();
|
||||
ScriptEngine scriptEngine = getInstance().getEngineByName(lang);
|
||||
boolean supportsCompilable = scriptEngine instanceof Compilable
|
||||
&& !"bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName()); // NOSONAR // $NON-NLS-1$
|
||||
if (!supportsCompilable) {
|
||||
return true;
|
||||
}
|
||||
if (!StringUtils.isEmpty(getScript())) {
|
||||
try {
|
||||
((Compilable) scriptEngine).compile(getScript());
|
||||
return true;
|
||||
} catch (ScriptException e) { // NOSONAR
|
||||
logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
File scriptFile = new File(getFilename());
|
||||
try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) {
|
||||
try {
|
||||
((Compilable) scriptEngine).compile(fileReader);
|
||||
return true;
|
||||
} catch (ScriptException e) { // NOSONAR
|
||||
logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* compute MD5 if it is null
|
||||
*/
|
||||
private void computeScriptMD5() {
|
||||
// compute the md5 of the script if needed
|
||||
if (scriptMd5 == null) {
|
||||
scriptMd5 = DigestUtils.md5Hex(getScript());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cacheKey
|
||||
*/
|
||||
public String getCacheKey() {
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cacheKey the cacheKey to set
|
||||
*/
|
||||
public void setCacheKey(String cacheKey) {
|
||||
this.cacheKey = cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.jmeter.testelement.TestStateListener#testStarted()
|
||||
*/
|
||||
@Override
|
||||
public void testStarted() {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void testStarted(String host) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.jmeter.testelement.TestStateListener#testEnded()
|
||||
*/
|
||||
@Override
|
||||
public void testEnded() {
|
||||
testEnded("");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void testEnded(String host) {
|
||||
compiledScriptsCache.clear();
|
||||
this.scriptMd5 = null;
|
||||
}
|
||||
|
||||
public String getScriptLanguage() {
|
||||
return scriptLanguage;
|
||||
}
|
||||
|
||||
public void setScriptLanguage(String s) {
|
||||
scriptLanguage = s;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.jmeter.util;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.jmeter.gui.GuiPackage;
|
||||
import org.apache.jmeter.util.keystore.JmeterKeyStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
/**
|
||||
* The SSLManager handles the KeyStore information for JMeter. Basically, it
|
||||
* handles all the logic for loading and initializing all the JSSE parameters
|
||||
* and selecting the alias to authenticate against if it is available.
|
||||
* SSLManager will try to automatically select the client certificate for you,
|
||||
* but if it can't make a decision, it will pop open a dialog asking you for
|
||||
* more information.
|
||||
* <p>
|
||||
* TODO? - N.B. does not currently allow the selection of a client certificate.
|
||||
*
|
||||
*/
|
||||
public abstract class SSLManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(SSLManager.class);
|
||||
|
||||
private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$
|
||||
|
||||
private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ NOSONAR no hard coded password
|
||||
|
||||
public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$
|
||||
|
||||
private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$
|
||||
|
||||
private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$
|
||||
|
||||
/** Singleton instance of the manager */
|
||||
private static SSLManager manager;
|
||||
|
||||
private static final boolean IS_SSL_SUPPORTED = true;
|
||||
|
||||
/** Cache the KeyStore instance */
|
||||
private JmeterKeyStore keyStore;
|
||||
|
||||
/** Cache the TrustStore instance - null if no truststore name was provided */
|
||||
private KeyStore trustStore = null;
|
||||
// Have we yet tried to load the truststore?
|
||||
private volatile boolean truststoreLoaded=false;
|
||||
|
||||
/** Have the password available */
|
||||
protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD);
|
||||
|
||||
private int keystoreAliasStartIndex;
|
||||
|
||||
private int keystoreAliasEndIndex;
|
||||
|
||||
private String clientCertAliasVarName;
|
||||
|
||||
/**
|
||||
* Resets the SSLManager so that we can create a new one with a new keystore
|
||||
*/
|
||||
public static synchronized void reset() {
|
||||
SSLManager.manager = null;
|
||||
}
|
||||
|
||||
public abstract void setContext(HttpURLConnection conn);
|
||||
|
||||
/**
|
||||
* Default implementation of setting the Provider
|
||||
*
|
||||
* @param provider
|
||||
* the provider to use
|
||||
*/
|
||||
protected void setProvider(Provider provider) {
|
||||
if (null != provider) {
|
||||
Security.addProvider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized JmeterKeyStore getKeyStore() {
|
||||
if (null == this.keyStore) {
|
||||
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided
|
||||
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
|
||||
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
|
||||
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
|
||||
try {
|
||||
this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName);
|
||||
log.info("KeyStore created OK");
|
||||
} catch (Exception e) {
|
||||
this.keyStore = null;
|
||||
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// The string 'NONE' is used for the keystore location when using PKCS11
|
||||
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE
|
||||
if ("NONE".equalsIgnoreCase(fileName)) {
|
||||
retryLoadKeys(null, false);
|
||||
log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount());
|
||||
} else {
|
||||
File initStore = new File(fileName);
|
||||
if (fileName.length() > 0 && initStore.exists()) {
|
||||
retryLoadKeys(initStore, true);
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Total of {} aliases loaded OK from keystore {}",
|
||||
keyStore.getAliasCount(), fileName);
|
||||
}
|
||||
} else {
|
||||
log.warn("Keystore file not found, loading empty keystore");
|
||||
this.defaultpw = ""; // Ensure not null
|
||||
this.keyStore.load(null, "");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Problem loading keystore: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("JmeterKeyStore type: {}", this.keyStore.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
return this.keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens and initializes the KeyStore. If the password for the KeyStore is
|
||||
* not set, this method will prompt you to enter it. Unfortunately, there is
|
||||
* no PasswordEntryField available from JOptionPane.
|
||||
*
|
||||
* @return the configured {@link JmeterKeyStore}
|
||||
*/
|
||||
protected synchronized JmeterKeyStore getKeyStore(InputStream is,String password) {
|
||||
if (null == this.keyStore) {
|
||||
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided
|
||||
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
|
||||
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
|
||||
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
|
||||
try {
|
||||
this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName);
|
||||
log.info("KeyStore created OK");
|
||||
} catch (Exception e) {
|
||||
this.keyStore = null;
|
||||
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// The string 'NONE' is used for the keystore location when using PKCS11
|
||||
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE
|
||||
if ("NONE".equalsIgnoreCase(fileName)) {
|
||||
retryLoadKeys(null, false);
|
||||
log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount());
|
||||
} else {
|
||||
File initStore = new File(fileName);
|
||||
if (fileName.length() > 0 && initStore.exists()) {
|
||||
retryLoadKeys(initStore, true);
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Total of {} aliases loaded OK from keystore {}",
|
||||
keyStore.getAliasCount(), fileName);
|
||||
}
|
||||
} else {
|
||||
log.warn("Keystore file not found, loading empty keystore");
|
||||
this.defaultpw = ""; // Ensure not null
|
||||
this.keyStore.load(is, password);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Can't load keystore '{}'. Wrong password?", fileName, e);
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
log.error("Can't recover keys from keystore '{}'", fileName, e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
log.error("Problem finding the correct algorithm while loading keys from keystore '{}'", fileName, e);
|
||||
} catch (CertificateException e) {
|
||||
log.error("Problem with one of the certificates/keys in keystore '{}'", fileName, e);
|
||||
} catch (KeyStoreException e) {
|
||||
log.error("Problem loading keystore: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("JmeterKeyStore type: {}", this.keyStore.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
return this.keyStore;
|
||||
}
|
||||
|
||||
private void retryLoadKeys(File initStore, boolean allowEmptyPassword) throws NoSuchAlgorithmException,
|
||||
CertificateException, KeyStoreException, UnrecoverableKeyException {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
String password = getPassword();
|
||||
if (!allowEmptyPassword) {
|
||||
Validate.notNull(password, "Password for keystore must not be null");
|
||||
}
|
||||
try {
|
||||
if (initStore == null) {
|
||||
this.keyStore.load(null, password);
|
||||
} else {
|
||||
try (InputStream fis = new FileInputStream(initStore)) {
|
||||
this.keyStore.load(fis, password);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
log.warn("Could not load keystore '{}'. Wrong password for keystore?", initStore, e);
|
||||
}
|
||||
this.defaultpw = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The password can be defined as a property; this dialogue is provided to allow it
|
||||
* to be entered at run-time.
|
||||
*/
|
||||
private String getPassword() {
|
||||
String password = this.defaultpw;
|
||||
if (null == password) {
|
||||
final GuiPackage guiInstance = GuiPackage.getInstance();
|
||||
// if (guiInstance != null) {
|
||||
// JPanel panel = new JPanel(new MigLayout("fillx, wrap 2", "[][fill, grow]"));
|
||||
// JLabel passwordLabel = new JLabel("Password: ");
|
||||
// JPasswordField pwf = new JPasswordField(64);
|
||||
// pwf.setEchoChar('*');
|
||||
// passwordLabel.setLabelFor(pwf);
|
||||
// panel.add(passwordLabel);
|
||||
// panel.add(pwf);
|
||||
// int choice = JOptionPane.showConfirmDialog(guiInstance.getMainFrame(), panel,
|
||||
// JMeterUtils.getResString("ssl_pass_prompt"), JOptionPane.OK_CANCEL_OPTION,
|
||||
// JOptionPane.PLAIN_MESSAGE);
|
||||
// if (choice == JOptionPane.OK_OPTION) {
|
||||
// char[] pwchars = pwf.getPassword();
|
||||
// this.defaultpw = new String(pwchars);
|
||||
// Arrays.fill(pwchars, '*');
|
||||
// }
|
||||
// System.setProperty(KEY_STORE_PASSWORD, this.defaultpw);
|
||||
// password = this.defaultpw;
|
||||
// }
|
||||
} else {
|
||||
log.warn("No password provided, and no GUI present so cannot prompt");
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens and initializes the TrustStore.
|
||||
*
|
||||
* There are 3 possibilities:
|
||||
* <ul>
|
||||
* <li>no truststore name provided, in which case the default Java truststore
|
||||
* should be used</li>
|
||||
* <li>truststore name is provided, and loads OK</li>
|
||||
* <li>truststore name is provided, but is not found or does not load OK, in
|
||||
* which case an empty
|
||||
* truststore is created</li>
|
||||
* </ul>
|
||||
* If the KeyStore object cannot be created, then this is currently treated the
|
||||
* same as if no truststore name was provided.
|
||||
*
|
||||
* @return
|
||||
* {@code null} when Java truststore should be used.
|
||||
* Otherwise the truststore, which may be empty if the file could not be
|
||||
* loaded.
|
||||
*
|
||||
*/
|
||||
protected KeyStore getTrustStore() {
|
||||
if (!truststoreLoaded) {
|
||||
|
||||
truststoreLoaded=true;// we've tried ...
|
||||
|
||||
String fileName = System.getProperty(SSL_TRUST_STORE);
|
||||
if (fileName == null) {
|
||||
return null;
|
||||
}
|
||||
log.info("TrustStore Location: {}", fileName);
|
||||
|
||||
try {
|
||||
this.trustStore = KeyStore.getInstance("JKS");
|
||||
log.info("TrustStore created OK, Type: JKS");
|
||||
} catch (Exception e) {
|
||||
this.trustStore = null;
|
||||
throw new RuntimeException("Problem creating truststore: "+e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
File initStore = new File(fileName);
|
||||
|
||||
if (initStore.exists()) {
|
||||
try (InputStream fis = new FileInputStream(initStore)) {
|
||||
this.trustStore.load(fis, null);
|
||||
log.info("Truststore loaded OK from file");
|
||||
}
|
||||
} else {
|
||||
log.warn("Truststore file not found, loading empty truststore");
|
||||
this.trustStore.load(null, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return this.trustStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected Constructor to remove the possibility of directly instantiating
|
||||
* this object. Create the SSLContext, and wrap all the X509KeyManagers with
|
||||
* our X509KeyManager so that we can choose our alias.
|
||||
*/
|
||||
protected SSLManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Static accessor for the SSLManager object. The SSLManager is a singleton.
|
||||
*
|
||||
* @return the singleton {@link SSLManager}
|
||||
*/
|
||||
public static synchronized SSLManager getInstance() {
|
||||
if (null == SSLManager.manager) {
|
||||
SSLManager.manager = new JsseSSLManager(null);
|
||||
}
|
||||
|
||||
return SSLManager.manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether SSL is supported or not.
|
||||
*
|
||||
* @return flag whether SSL is supported
|
||||
*/
|
||||
public static boolean isSSLSupported() {
|
||||
return SSLManager.IS_SSL_SUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Keystore
|
||||
*
|
||||
* @param preload
|
||||
* flag whether the keystore should be opened within this method,
|
||||
* or the opening should be delayed
|
||||
* @param startIndex
|
||||
* first index to consider for a key
|
||||
* @param endIndex
|
||||
* last index to consider for a key
|
||||
* @param clientCertAliasVarName
|
||||
* name of the default key, if empty the first key will be used
|
||||
* as default key
|
||||
*/
|
||||
public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName,InputStream is,String password) {
|
||||
this.keystoreAliasStartIndex = startIndex;
|
||||
this.keystoreAliasEndIndex = endIndex;
|
||||
this.clientCertAliasVarName = clientCertAliasVarName;
|
||||
if(preload) {
|
||||
keyStore = getKeyStore(is,password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy Keystore
|
||||
*/
|
||||
public synchronized void destroyKeystore() {
|
||||
keyStore=null;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -19,6 +19,7 @@
|
|||
<module>domain</module>
|
||||
<module>sdk</module>
|
||||
<module>xpack-interface</module>
|
||||
<module>jmeter</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
|
Loading…
Reference in New Issue