JMeter自定義取樣器外掛開發

火顏發表於2021-04-19

JMeter自定義取樣器外掛開發

1. 簡介

JMeter支援外掛機制,只需要將打包好的jar包放到lib/ext/下面,JMeter就會動態的載入符合要求的外掛。

  • 要擴充套件UI的話,擴充套件的Java類的包名必須是.gui.
  • 同樣的擴充套件函式的Java類的包名必須是.function.

2. 需求簡介

本文演示的是如何自定義一個https 的取樣器。

ssl的配置以引數的形式傳給JMeter。

3.成品展示

成功展示

失敗展示

4. 準備開發環境

新建Maven專案。

4.1 準備pom檔案

<?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>
    <groupId>org.example</groupId>
    <artifactId>httpsSampler</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jmeter-version>3.0</jmeter-version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_core</artifactId>
            <version>${jmeter-version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_java</artifactId>
            <version>${jmeter-version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <defaultGoal>install</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>assemble-all</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

4.2 新建Java的GUI類

新建 org.apache.jmeter.protocol.https.control.gui 類,並繼承org.apache.jmeter.samplers.gui.AbstractSamplerGui

需要重寫幾個函式。

/**gui顯示的sample的名稱**/
public String getStaticLabel()
public String getLabelResource()
//這個方法用於把介面的資料移到Sampler中。
public void modifyTestElement(TestElement testElement)
//介面與Sampler之間的資料交換
public void configure(TestElement el)
//該方法會在reset新介面的時候呼叫,這裡可以填入介面控制元件中需要顯示的一些預設的值(就是預設顯示值)
public void clearGui()
//該方法建立一個新的Sampler,然後將介面中的資料設定到這個新的Sampler例項中。
public TestElement createTestElement()

全程式碼

package org.apache.jmeter.protocol.https.control.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.apache.jmeter.gui.util.JSyntaxTextArea;
import org.apache.jmeter.gui.util.JTextScrollPane;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.protocol.https.sampler.HttpsSampler;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;

import org.apache.jorphan.gui.JLabeledTextField;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

//這個註解必須要有
@SuppressWarnings("deprecation")
public class HttpsSamplerUI extends AbstractSamplerGui {

    private static final long serialVersionUID = 1L;
    private static Logger log = LoggingManager.getLoggerForClass();

    private final JLabeledTextField sslVersionField = new JLabeledTextField("SSL版本");
    private final JLabeledTextField cipherField = new JLabeledTextField("密碼套件");
    private final JLabeledTextField twoWayField = new JLabeledTextField("雙向");
    private final JLabeledTextField caCertField = new JLabeledTextField("CA證照");
    private final JLabeledTextField clientCertField = new JLabeledTextField("客戶端證照");
    private final JLabeledTextField clientP12Field = new JLabeledTextField("客戶端私鑰");

    private final JLabeledTextField requestsStringField = new JLabeledTextField("請求資訊");



    private final JSyntaxTextArea textMessage = new JSyntaxTextArea(10, 50);
    // private final JLabel textArea = new JLabel(JMeterUtils.getResString("kafka.message", "Message"));
    private final JLabel textArea = new JLabel("Message");
    private final JTextScrollPane textPanel = new JTextScrollPane(textMessage);
    public HttpsSamplerUI(){
        super();
        this.init();

    }

    private void init(){
        log.info("Initializing the UI.");
        setLayout(new BorderLayout());
        setBorder(makeBorder());

        add(makeTitlePanel(), BorderLayout.NORTH);
        JPanel mainPanel = new VerticalPanel();
        add(mainPanel, BorderLayout.CENTER);

        JPanel DPanel = new JPanel();
        DPanel.setLayout(new GridLayout(4, 2));
        DPanel.add(sslVersionField);
        DPanel.add(cipherField);
        DPanel.add(twoWayField);
        DPanel.add(caCertField);
        DPanel.add(clientCertField);
        DPanel.add(clientP12Field);
        DPanel.add(requestsStringField);

        JPanel ControlPanel = new VerticalPanel();
        ControlPanel.add(DPanel);
        ControlPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.gray), "引數"));
        mainPanel.add(ControlPanel);

        /**這是是輸出**/
        JPanel ContentPanel = new VerticalPanel();
        JPanel messageContentPanel = new JPanel(new BorderLayout());
        messageContentPanel.add(this.textArea, BorderLayout.NORTH);
        messageContentPanel.add(this.textPanel, BorderLayout.CENTER);
        ContentPanel.add(messageContentPanel);
        ContentPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.gray), "Content"));
        mainPanel.add(ContentPanel);

    }

    /**
     * 該方法建立一個新的Sampler,然後將介面中的資料設定到這個新的Sampler例項中。
     * **/
    @Override
    public TestElement createTestElement() {
        HttpsSampler sampler = new HttpsSampler();
        this.setupSamplerProperties(sampler);
        return sampler;
    }

    /**
     * 該方法會在reset新介面的時候呼叫,這裡可以填入介面控制元件中需要顯示的一些預設的值。
     * **/
    @Override
    public void clearGui(){
        super.clearGui();
        this.sslVersionField.setText("TLSv1.2");
        this.cipherField.setText("ECDHE-AES256-SHA384");
        this.twoWayField.setText("y");
        this.caCertField.setText("xxx/xxx/ca.cert");
        this.clientCertField.setText("xxx/xxx/client.cert");
        this.clientP12Field.setText("xxx/xxx/client.key");
        this.requestsStringField.setText("GET /1k.html HTTP1.0\r\n");

    }

    /**
     * 介面與Sampler之間的資料交換
     * 該方法用於把Sampler中的資料載入到介面中。
     * 在實現自己的邏輯之前,先呼叫一下父類的方法super.configure(el),這樣可以確保框架自動為你載入一些預設資料,比如Sampler的名字。
     * **/
    @Override
    public void configure(TestElement el){
        super.configure(el);
        HttpsSampler sampler = (HttpsSampler) el;
        this.sslVersionField.setText(sampler.getHttpsSslVersion());
        this.cipherField.setText(sampler.getHttpsCipher());
        this.twoWayField.setText(sampler.getHttpsTwoWay());
        this.caCertField.setText(sampler.getHttpsCa());
        this.clientCertField.setText(sampler.getHttpsClientCert());
        this.clientP12Field.setText(sampler.getHttpsClientP12());
        this.requestsStringField.setText(sampler.getHttpsRequest());
    }


    private void setupSamplerProperties(HttpsSampler sampler) {
        this.configureTestElement(sampler);
        sampler.setSslVersion(this.sslVersionField.getText());
        sampler.setCipher(this.cipherField.getText());
        sampler.setHttpsTwoWay(this.twoWayField.getText());
        sampler.setHttpsCa(this.caCertField.getText());
        sampler.setHttpsClientCert(this.clientCertField.getText());
        sampler.setHttpsClientP12(this.clientP12Field.getText());
        sampler.setHttpsRequest(this.requestsStringField.getText());
    }

    /**gui顯示sample的名稱**/
    @Override
    public String getStaticLabel() {
        return "Https Sampler";
    }

    @Override
    public String getLabelResource() {
        throw new IllegalStateException("This shouldn't be called");
    }

    /**
     * 這個方法用於把介面的資料移到Sampler中,剛好與上面的方法相反。
     * 在呼叫自己的實現方法之前,請先呼叫一下super.configureTestElement(e),這個會幫助移到一些預設的資料。
     * **/
    @Override
    public void modifyTestElement(TestElement testElement) {
        HttpsSampler sampler = (HttpsSampler) testElement;
        this.setupSamplerProperties(sampler);
    }

}

4.3 準備Java的取樣器

新建org.apache.jmeter.protocol.https.sampler 繼承 org.apache.jmeter.samplers.AbstractSampler 實現 org.apache.jmeter.testelement.TestStateListener介面

重寫函式

//該方法是JMeter實現對目標系統發起請求實際工作的地方
public SampleResult sample(Entry entry)

這個4個方法必須覆寫,否則無法識別

@Override
public void testStarted() {

}

@Override
public void testStarted(String s) {

}

@Override
public void testEnded() {
    this.testEnded("local");
}

@Override
public void testEnded(String s) {

}

全程式碼

package org.apache.jmeter.protocol.https.sampler;

import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestStateListener;

import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

public class HttpsSampler extends AbstractSampler implements TestStateListener {

    private static final long serialVersionUID = 1L;
    private static final String HTTPS_SSL_VERSION = "https.sslVersion";
    private static final String HTTPS_CIPHER = "https.cipher";
    private static final String HTTPS_TWO_WAY= "https.twoWay";
    private static final String HTTPS_CA = "https.caCert";
    private static final String HTTPS_CLIENT_CERT = "https.clientCert";
    private static final String HTTPS_CLIENT_P12 = "https.clientP12";
    private static final String HTTPS_REQUEST = "https.requestsString";
    private static final Logger log = LoggingManager.getLoggerForClass();

    // 建構函式要為pubilc
    public HttpsSampler(){
        setName("Https Sampler");
    }

    /**
     * 該方法是JMeter實現對目標系統發起請求實際工作的地方
     * **/
    @Override
    public SampleResult sample(Entry entry) {
        SampleResult result = new SampleResult();
        result.setSampleLabel(getName());
        result.sampleStart();
        try {
            /**
             * 取樣器執行code處
             * **/
            result.setRequestHeaders("請求頭---請求頭展示資料");
            result.setSamplerData("設定取樣器資料");
            if (!getHttpsSslVersion().startsWith("TLS")) {
                // stop stopwatch
                result.sampleEnd();
                result.setSuccessful(false);
                // 設定取樣器結果裡面響應資訊
                result.setResponseMessage("ssl不為TLS開頭,取樣失敗(示例)");
                result.setDataType(org.apache.jmeter.samplers.SampleResult.TEXT);
                result.setResponseCode("FAILED");
                // 設定響應資料的響應body
                result.setResponseData("響應資訊---響應體展示資料", null);
                // 設定響應資料的響應header
                result.setResponseHeaders("響應頭資訊---響應頭展示資料");
            }else {
                result.sampleEnd();
                result.setSuccessful(true);
                result.setResponseCodeOK();
            }

        } catch (Exception e) {
            result.sampleEnd(); // stop stopwatch
            result.setSuccessful(false);
            result.setResponseMessage("Exception: " + e);
            // get stack trace as a String to return as document data
            java.io.StringWriter stringWriter = new java.io.StringWriter();
            e.printStackTrace(new java.io.PrintWriter(stringWriter));
            result.setResponseData(stringWriter.toString(), null);
            result.setDataType(org.apache.jmeter.samplers.SampleResult.TEXT);
            result.setResponseCode("FAILED");
        }
        return result;
    }


    public void setSslVersion(String sslVersion){
        setProperty(HTTPS_SSL_VERSION, sslVersion);
    }

    public void setCipher(String cipher){
        setProperty(HTTPS_CIPHER, cipher);
    }

    public void setHttpsTwoWay(String twoWay){
        setProperty(HTTPS_TWO_WAY, twoWay);
    }

    public void setHttpsCa(String ca){
        setProperty(HTTPS_CA, ca);
    }

    public void setHttpsClientCert(String clientCert){
        setProperty(HTTPS_CLIENT_CERT, clientCert);
    }

    public void setHttpsClientP12(String clientP12){
        setProperty(HTTPS_CLIENT_P12, clientP12);
    }

    public void setHttpsRequest(String requestsString){
        setProperty(HTTPS_REQUEST, requestsString);
    }

    public String getHttpsSslVersion(){
        return getPropertyAsString(HTTPS_SSL_VERSION);
    }

    public String getHttpsCipher(){
        return getPropertyAsString(HTTPS_CIPHER);
    }

    public String getHttpsTwoWay(){
        return getPropertyAsString(HTTPS_TWO_WAY);
    }

    public String getHttpsCa(){
        return getPropertyAsString(HTTPS_CA);
    }

    public String getHttpsClientCert(){
        return getPropertyAsString(HTTPS_CLIENT_CERT);
    }

    public String getHttpsClientP12(){
        return getPropertyAsString(HTTPS_CLIENT_P12);
    }

    public String getHttpsRequest(){
        return getPropertyAsString(HTTPS_REQUEST);
    }

    @Override
    public void testStarted() {

    }

    @Override
    public void testStarted(String s) {

    }

    @Override
    public void testEnded() {
        this.testEnded("local");
    }

    @Override
    public void testEnded(String s) {

    }
}

5. 打包&部署

maven install後會在target/下會有打包好的jar包。

然後將jar包拷貝到lib/ext/下,啟動JMeter。選取新增的取樣器即可。

6. 參考文章

JMeter擴充套件外掛實現對自定義協議進行支援

相關文章