Apache JMeter自带了许多的采样器供我们使用,而且能够满足大部分的测试需求。但是在实际使用过程中,难免需要针对项目自身的特点和需求对Apache JMeter进行扩展。虽然直接继承AbstractJavaSamplerClient即可编写Java采样器,但是其相对应的GUI界面不是很友好,或者我们想编写一个类似HTTP Request那样的sampler,该如何做呢?比如我们需要编写一个TLS的采样器,该采样器允许用户在发送TLS请求的时候制定TLS的版本,以及客户端证书等信息。请参考如下代码及注释:

package kg.apc.jmeter.samplers;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.Socket;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

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;

// FIXME: actually keep-alive does not work!
/**
 *
 * @author undera
 */
public class TLSRawSampler extends AbstractSampler implements Serializable, Cloneable, Interruptible{
    private static final Logger log = LoggingManager.getLoggerForClass();
    
    //following static final String are property keys in JMeter
    private static final String HOST_NAME = "TLS_hostName";
    private static final String PORT = "TLS_port";
    private static final String TLS_VERSON = "TLS_tlsversion";
    private static final String CIPHER_LIST = "TLS_cipherlist";
    private static final String DATA = "TLS_data";
    private static final String CLIENT_CERT = "TLS_client_cert";
    private static final String CLIENT_CERT_PASSWORD = "TLS_client_cert_password";
 
    public TLSRawSampler() {
    }
    
    /**
     * Process sample here
     */
    @Override
    public SampleResult sample(Entry entry) {
       SampleResult sr = new SampleResult();
       sr.sampleStart();
       sr.setSuccessful(true);
       sr.setSampleLabel(getName());
       sr.setResponseCode("200");
       
       //do the sample here
       try{
    	   Socket socket = getTLSSocket();
    	   socket.getOutputStream().write(getData().getBytes());
    	   StringBuilder headerSB = new StringBuilder();
    	   StringBuilder contentSB = new StringBuilder();
    	   BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    	   String line = null;
    	   boolean header = true;
    	   while((line = reader.readLine()) != null){
    		   if(line.length() == 0){
    			   header = false;
    			   continue;
    		   }
    		   if(header){
	    		   headerSB.append(line);
	    		   headerSB.append("\n");
    		   }
    		   else{
    			   contentSB.append(line);
    			   contentSB.append("\n");
    		   }
    	   }
    	   sr.setResponseHeaders(headerSB.toString());
    	   sr.setResponseData(contentSB.toString(), "");
    	   socket.close();
       }catch(Exception e){
    	   sr.setSuccessful(false);
    	   sr.setResponseCode("500");
    	   sr.setResponseMessage(e.getMessage());
       }
       
       sr.sampleEnd();
       return sr;
    }

	@Override
	public boolean interrupt() {
		// TODO Auto-generated method stub
		return false;
	}
	
	//following get and set methods are used for get and set properties in JMeter
    
    public String getHostName(){
    	return getPropertyAsString(TLSRawSampler.HOST_NAME);
    }
    
    public void setHostName(String hostName){
    	setProperty(TLSRawSampler.HOST_NAME, hostName);
    }
    
    public String getPort(){
    	return getPropertyAsString(TLSRawSampler.PORT);
    }
    
    public void setPort(String port){
    	setProperty(TLSRawSampler.PORT, port);
    }
    
    public String getTLSVersion(){
    	return getPropertyAsString(TLSRawSampler.TLS_VERSON);
    }
    
    public void setTLSVersion(String version){
    	setProperty(TLSRawSampler.TLS_VERSON, version);
    }
    
    public String getClientCert(){
    	return getPropertyAsString(TLSRawSampler.CLIENT_CERT);
    }
    
    public void setClientCert(String clientCert){
    	setProperty(TLSRawSampler.CLIENT_CERT, clientCert);
    }
    
    public String getCipherList(){
    	return getPropertyAsString(TLSRawSampler.CIPHER_LIST);
    }
    
    public void setCipherList(String list){
    	setProperty(TLSRawSampler.CIPHER_LIST, list);
    }
    
    public String getData(){
    	return getPropertyAsString(TLSRawSampler.DATA);
    }
    
    public void setData(String data){
    	setProperty(TLSRawSampler.DATA, data);
    }
    
    public String getClientCertPassword(){
    	return getPropertyAsString(TLSRawSampler.CLIENT_CERT_PASSWORD);
    }
    
    public void setClientCertPassword(String password){
    	setProperty(TLSRawSampler.CLIENT_CERT_PASSWORD, password);
    }
    
    private Socket getTLSSocket() throws Exception{
		TrustManager[] allTrusted = {new X509TrustManager(){

			@Override
			public void checkClientTrusted(X509Certificate[] chain,
					String authType) throws CertificateException {
				// TODO Auto-generated method stub
				
			}

			@Override
			public void checkServerTrusted(X509Certificate[] chain,
					String authType) throws CertificateException {
				// TODO Auto-generated method stub
				
			}

			@Override
			public X509Certificate[] getAcceptedIssuers() {
				// TODO Auto-generated method stub
				return null;
			}
			
		}};
		
		SSLContext ctx = SSLContext.getInstance(getTLSVersion(), "SunJSSE");//use 1.2
		
		//get client cert
		String clientCert = getClientCert();
		if(clientCert != null && clientCert.length() > 0){
			String clientCertPassword = getClientCertPassword();
			KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
			KeyStore keyStore = KeyStore.getInstance("PKCS12");

			InputStream keyInput = new FileInputStream(clientCert);
			keyStore.load(keyInput, clientCertPassword.toCharArray());
			keyInput.close();

			keyManagerFactory.init(keyStore, clientCertPassword.toCharArray());
			
			ctx.init(keyManagerFactory.getKeyManagers(), allTrusted, null);
		}
		else{
			ctx.init(null, allTrusted, null);
		}
		SSLParameters parameters = ctx.getDefaultSSLParameters();
		String cipherList = getCipherList();
		if(cipherList != null && cipherList.length() > 0){
			parameters.setCipherSuites(cipherList.trim().split(","));
		}
		for(String cipher : parameters.getCipherSuites()){
			log.info(cipher);
		}
		
		SSLSocket socket = (SSLSocket)ctx.getSocketFactory().createSocket(getHostName(), Integer.valueOf(getPort()));
		socket.setTcpNoDelay(true);
		socket.setSSLParameters(parameters);
		
		
		return socket;
	}
}