You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jmeter-dev@jakarta.apache.org by se...@apache.org on 2007/06/02 15:48:57 UTC
svn commit: r543739 - in /jakarta/jmeter/branches/rel-2-2:
bin/user.properties
src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java
src/core/org/apache/jmeter/util/JsseSSLManager.java xdocs/changes.xml
xdocs/usermanual/component_reference.xml
Author: sebb
Date: Sat Jun 2 06:48:56 2007
New Revision: 543739
URL: http://svn.apache.org/viewvc?view=rev&rev=543739
Log:
Bug 42506 - JMeter threads all use the same SSL session
Modified:
jakarta/jmeter/branches/rel-2-2/bin/user.properties
jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java
jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java
jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml
jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml
Modified: jakarta/jmeter/branches/rel-2-2/bin/user.properties
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/bin/user.properties?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/bin/user.properties (original)
+++ jakarta/jmeter/branches/rel-2-2/bin/user.properties Sat Jun 2 06:48:56 2007
@@ -16,4 +16,8 @@
## limitations under the License.
#
#search_paths=../addons/addons.jar
-#log_level.jorphan.reflect=DEBUG
\ No newline at end of file
+#log_level.jorphan.reflect=DEBUG
+# Warning: enabling the next debug line causes javax.net.ssl.SSLException: Received fatal alert: unexpected_message
+# for certain sites when used with the default HTTP Sampler
+#log_level.jmeter.util.HttpSSLProtocolSocketFactory=DEBUG
+#log_level.jmeter.util.JsseSSLManager=DEBUG
\ No newline at end of file
Modified: jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java (original)
+++ jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/HttpSSLProtocolSocketFactory.java Sat Jun 2 06:48:56 2007
@@ -23,8 +23,10 @@
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
@@ -46,37 +48,53 @@
private static final Logger log = LoggingManager.getLoggerForClass();
- private SSLSocketFactory sslfac;
+ private JsseSSLManager sslManager;
private HttpSSLProtocolSocketFactory(){
}
- public HttpSSLProtocolSocketFactory(SSLContext context) {
+ public HttpSSLProtocolSocketFactory(JsseSSLManager sslManager) {
super();
- sslfac=context.getSocketFactory();
+ this.sslManager = sslManager;
}
- private static final String protocolList = JMeterUtils.getPropDefault("https.socket.protocols", "");
+ private static final String protocolList =
+ JMeterUtils.getPropDefault("https.socket.protocols", ""); // $NON-NLS-1$ $NON-NLS-2$
static {
if (protocolList.length()>0){
log.info("Using protocol list: "+protocolList);
}
}
- private static final String[] protocols = protocolList.split(" ");
- private void setSocket(Socket sock){
- if (protocolList.length() <= 0) return;
- if (sock instanceof SSLSocket){
- try {
- ((SSLSocket) sock).setEnabledProtocols(protocols);
- } catch (IllegalArgumentException e) {
- log.warn("Could not set protocol list: "+protocolList+".");
- log.warn("Valid protocols are: "+join(((SSLSocket) sock).getSupportedProtocols()));
- }
- } else {
- throw new IllegalArgumentException("Expecting only SSL socket; found "+sock.getClass().getName());
+ private static final String[] protocols = protocolList.split(" "); // $NON-NLS-1$
+
+ private void setSocket(Socket socket){
+ if (!(socket instanceof SSLSocket)) {
+ throw new IllegalArgumentException("Expected SSLSocket");
}
+ SSLSocket sock = (SSLSocket) socket;
+ if (log.isDebugEnabled()) {
+ SSLSession sslSession = sock.getSession();
+ byte[] bytes = sslSession.getId();
+
+ StringBuffer buffer = new StringBuffer("SSL session id: ");
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i] & 0xff;
+ buffer.append(Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)));
+ buffer.append(Character.toUpperCase(Character.forDigit(b & 0xF, 16)));
+ }
+ buffer.append(" for ").append(Thread.currentThread().getName());
+ log.debug(buffer.toString());
+ }
+ if (protocolList.length() > 0) {
+ try {
+ sock.setEnabledProtocols(protocols);
+ } catch (IllegalArgumentException e) {
+ log.warn("Could not set protocol list: " + protocolList + ".");
+ log.warn("Valid protocols are: " + join(sock.getSupportedProtocols()));
+ }
+ }
}
private String join(String[] strings) {
@@ -87,6 +105,15 @@
}
return sb.toString();
}
+
+ private SSLSocketFactory getSSLSocketFactory() throws IOException {
+ try {
+ SSLContext sslContext = this.sslManager.getContext();
+ return sslContext.getSocketFactory();
+ } catch (GeneralSecurityException ex) {
+ throw new IOException(ex.getMessage());
+ }
+ }
/**
* Attempts to get a new socket connection to the given host within the given time limit.
@@ -114,6 +141,8 @@
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
+
+ SSLSocketFactory sslfac = getSSLSocketFactory();
Socket socket;
if (timeout == 0) {
socket = sslfac.createSocket(host, port, localAddress, localPort);
@@ -133,6 +162,7 @@
*/
public Socket createSocket(String host, int port)
throws IOException, UnknownHostException {
+ SSLSocketFactory sslfac = getSSLSocketFactory();
Socket sock = sslfac.createSocket(
host,
port
@@ -150,6 +180,7 @@
int port,
boolean autoClose)
throws IOException, UnknownHostException {
+ SSLSocketFactory sslfac = getSSLSocketFactory();
Socket sock = sslfac.createSocket(
socket,
host,
@@ -170,6 +201,7 @@
int clientPort)
throws IOException, UnknownHostException {
+ SSLSocketFactory sslfac = getSSLSocketFactory();
Socket sock = sslfac.createSocket(
host,
port,
@@ -181,22 +213,34 @@
}
public Socket createSocket(InetAddress host, int port) throws IOException {
+ SSLSocketFactory sslfac = getSSLSocketFactory();
Socket sock=sslfac.createSocket(host,port);
setSocket(sock);
return sock;
}
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ SSLSocketFactory sslfac = getSSLSocketFactory();
Socket sock=sslfac.createSocket(address, port, localAddress, localPort);
setSocket(sock);
return sock;
}
public String[] getDefaultCipherSuites() {
- return sslfac.getDefaultCipherSuites();
+ try {
+ SSLSocketFactory sslfac = getSSLSocketFactory();
+ return sslfac.getDefaultCipherSuites();
+ } catch (IOException ex) {
+ return new String[] {};
+ }
}
public String[] getSupportedCipherSuites() {
- return sslfac.getSupportedCipherSuites();
+ try {
+ SSLSocketFactory sslfac = getSSLSocketFactory();
+ return sslfac.getSupportedCipherSuites();
+ } catch (IOException ex) {
+ return new String[] {};
+ }
}
}
Modified: jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java (original)
+++ jakarta/jmeter/branches/rel-2-2/src/core/org/apache/jmeter/util/JsseSSLManager.java Sat Jun 2 06:48:56 2007
@@ -20,18 +20,13 @@
import java.net.HttpURLConnection;
import java.net.Socket;
+import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
-import org.apache.commons.httpclient.protocol.Protocol;
-import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
-import org.apache.jmeter.util.keystore.JmeterKeyStore;
-import org.apache.jorphan.logging.LoggingManager;
-import org.apache.log.Logger;
-
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
@@ -43,6 +38,12 @@
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
+import org.apache.commons.httpclient.protocol.Protocol;
+import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
+import org.apache.jmeter.util.keystore.JmeterKeyStore;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
/**
* The SSLManager handles the KeyStore information for JMeter. Basically, it
* handles all the logic for loading and initializing all the JSSE parameters
@@ -53,8 +54,6 @@
*
* TODO: does not actually prompt
*
- * @author <a href="bloritsch@apache.org">Berin Loritsch</a> Created March 21,
- * 2002
*/
public class JsseSSLManager extends SSLManager {
private static final Logger log = LoggingManager.getLoggerForClass();
@@ -65,8 +64,13 @@
private static final String DEFAULT_SSL_PROTOCOL =
JMeterUtils.getPropDefault("https.default.protocol","TLS"); // $NON-NLS-1$ // $NON-NLS-2$
+ // Allow reversion to original shared session context
+ private static final boolean SHARED_SESSION_CONTEXT =
+ JMeterUtils.getPropDefault("https.sessioncontext.shared",false); // $NON-NLS-1$
+
static {
log.info("Using default SSL protocol: "+DEFAULT_SSL_PROTOCOL);
+ log.info("SSL session context: "+(SHARED_SESSION_CONTEXT ? "shared" : "per-thread"));
}
/**
@@ -74,13 +78,11 @@
*/
private SecureRandom rand;
- /**
- * Cache the Context so we can retrieve it from other places
- */
- private SSLContext context = null;
-
private Provider pro = null;
+ private SSLContext defaultContext; // If we are using a single session
+ private ThreadLocal threadlocal; // Otherwise
+
/**
* Create the SSLContext, and wrap all the X509KeyManagers with
* our X509KeyManager so that we can choose our alias.
@@ -91,11 +93,37 @@
public JsseSSLManager(Provider provider) {
log.debug("ssl Provider = " + provider);
setProvider(provider);
- if (null == this.rand) {
+ if (null == this.rand) { // Surely this is always null in the constructor?
this.rand = new SecureRandom();
}
-
- this.getContext();
+ try {
+ if (SHARED_SESSION_CONTEXT) {
+ log.debug("Creating shared context");
+ this.defaultContext = createContext();
+ } else {
+ this.threadlocal = new ThreadLocal();
+ }
+
+ HttpSSLProtocolSocketFactory sockFactory = new HttpSSLProtocolSocketFactory(this);
+
+ HttpsURLConnection.setDefaultSSLSocketFactory(sockFactory);
+ HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ });
+ /*
+ * Also set up HttpClient defaults
+ */
+ Protocol protocol = new Protocol(
+ JsseSSLManager.HTTPS,
+ (ProtocolSocketFactory) sockFactory,
+ 443);
+ Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol);
+ log.debug("SSL stuff all set");
+ } catch (GeneralSecurityException ex) {
+ log.error("Could not set up SSLContext", ex);
+ }
log.debug("JsseSSLManager installed");
}
@@ -133,94 +161,101 @@
}
/**
- * Returns the SSLContext we are using. It is useful for obtaining the
- * SSLSocketFactory so that your created sockets are authenticated.
+ * Returns the SSLContext we are using.
+ * This is either a context per thread,
+ * or, for backwards compatibility, a single shared context.
*
* @return The Context value
*/
- private SSLContext getContext() {
- if (null == this.context) {
- try {
- if (pro != null) {
- this.context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$
- } else {
- this.context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$
- }
- log.debug("SSL context = " + context);
- } catch (Exception ee) {
- log.error("Could not create SSLContext", ee);
- }
- try {
- KeyManagerFactory managerFactory =
- KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
- JmeterKeyStore keys = this.getKeyStore();
- managerFactory.init(null, this.defaultpw.toCharArray());
- KeyManager[] managers = managerFactory.getKeyManagers();
- log.debug(keys.getClass().toString());
-
- // Now wrap the default managers with our key manager
- for (int i = 0; i < managers.length; i++) {
- if (managers[i] instanceof X509KeyManager) {
- X509KeyManager manager = (X509KeyManager) managers[i];
- managers[i] = new WrappedX509KeyManager(manager, keys);
- }
- }
-
- // Get the default trust managers
- TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- tmfactory.init(this.getTrustStore());
-
- // Wrap the defaults in our custom trust manager
- TrustManager[] trustmanagers = tmfactory.getTrustManagers();
- for (int i = 0; i < trustmanagers.length; i++) {
- if (trustmanagers[i] instanceof X509TrustManager) {
- trustmanagers[i] = new CustomX509TrustManager(
- (X509TrustManager)trustmanagers[i]);
- }
- }
- context.init(managers, trustmanagers, this.rand);
-
- /*
- * The following will need to be removed if the SSL properties are to be
- * applied on a per-connection basis
- */
- HttpsURLConnection.setDefaultSSLSocketFactory(new HttpSSLProtocolSocketFactory(context));
- HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
- public boolean verify(String hostname, SSLSession session) {
- return true;
- }
- });
- /*
- * Also set up HttpClient defaults
- */
- Protocol protocol = new Protocol(
- JsseSSLManager.HTTPS,
- (ProtocolSocketFactory) new HttpSSLProtocolSocketFactory(context),
- 443
- );
- Protocol.registerProtocol(JsseSSLManager.HTTPS, protocol);
- log.debug("SSL stuff all set");
- } catch (Exception e) {
- log.error("Could not set up SSLContext", e);
- }
-
- if (log.isDebugEnabled()){
- String[] dCiphers = this.context.getSocketFactory().getDefaultCipherSuites();
- String[] sCiphers = this.context.getSocketFactory().getSupportedCipherSuites();
- int len = (dCiphers.length > sCiphers.length) ? dCiphers.length : sCiphers.length;
- for (int i = 0; i < len; i++) {
- if (i < dCiphers.length) {
- log.debug("Default Cipher: " + dCiphers[i]);
- }
- if (i < sCiphers.length) {
- log.debug("Supported Cipher: " + sCiphers[i]);
- }
- }
+ public SSLContext getContext() throws GeneralSecurityException {
+ if (SHARED_SESSION_CONTEXT) {
+ if (log.isDebugEnabled()){
+ log.debug("Using shared SSL context for: "+Thread.currentThread().getName());
+ }
+ return this.defaultContext;
+ }
+
+ SSLContext sslContext = (SSLContext) this.threadlocal.get();
+ if (sslContext == null) {
+ if (log.isDebugEnabled()){
+ log.debug("Creating threadLocal SSL context for: "+Thread.currentThread().getName());
+ }
+ sslContext = createContext();
+ this.threadlocal.set(sslContext);
+ }
+ if (log.isDebugEnabled()){
+ log.debug("Using threadLocal SSL context for: "+Thread.currentThread().getName());
+ }
+ return sslContext;
+ }
+
+ /**
+ * Resets the SSLContext if using per-thread contexts.
+ *
+ */
+ public void resetContext() {
+ if (!SHARED_SESSION_CONTEXT) {
+ log.debug("Clearing session context for current thread");
+ this.threadlocal.set(null);
+ }
+ }
+ /*
+ *
+ * Creates new SSL context
+ * @return SSL context
+ * @throws GeneralSecurityException
+ */
+ private SSLContext createContext() throws GeneralSecurityException {
+ SSLContext context;
+ if (pro != null) {
+ context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL, pro); // $NON-NLS-1$
+ } else {
+ context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); // $NON-NLS-1$
+ }
+ KeyManagerFactory managerFactory =
+ KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ JmeterKeyStore keys = this.getKeyStore();
+ managerFactory.init(null, this.defaultpw.toCharArray());
+ KeyManager[] managers = managerFactory.getKeyManagers();
+ log.debug(keys.getClass().toString());
+
+ // Now wrap the default managers with our key manager
+ for (int i = 0; i < managers.length; i++) {
+ if (managers[i] instanceof X509KeyManager) {
+ X509KeyManager manager = (X509KeyManager) managers[i];
+ managers[i] = new WrappedX509KeyManager(manager, keys);
}
- }
- return this.context;
- }
+ }
+
+ // Get the default trust managers
+ TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmfactory.init(this.getTrustStore());
+
+ // Wrap the defaults in our custom trust manager
+ TrustManager[] trustmanagers = tmfactory.getTrustManagers();
+ for (int i = 0; i < trustmanagers.length; i++) {
+ if (trustmanagers[i] instanceof X509TrustManager) {
+ trustmanagers[i] = new CustomX509TrustManager(
+ (X509TrustManager)trustmanagers[i]);
+ }
+ }
+ context.init(managers, trustmanagers, this.rand);
+ if (log.isDebugEnabled()){
+ String[] dCiphers = context.getSocketFactory().getDefaultCipherSuites();
+ String[] sCiphers = context.getSocketFactory().getSupportedCipherSuites();
+ int len = (dCiphers.length > sCiphers.length) ? dCiphers.length : sCiphers.length;
+ for (int i = 0; i < len; i++) {
+ if (i < dCiphers.length) {
+ log.debug("Default Cipher: " + dCiphers[i]);
+ }
+ if (i < sCiphers.length) {
+ log.debug("Supported Cipher: " + sCiphers[i]);
+ }
+ }
+ }
+ return context;
+ }
/**
* This is the X509KeyManager we have defined for the sole purpose of
Modified: jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml (original)
+++ jakarta/jmeter/branches/rel-2-2/xdocs/changes.xml Sat Jun 2 06:48:56 2007
@@ -43,6 +43,7 @@
<li>LDAP Ext sampler optionally parses result sets and supports secure mode</li>
<li>FTP Sampler supports Ascii/Binary mode and upload</li>
<li>Transaction Controller now generates Sample with subresults</li>
+<li>HTTPS session contexts are now per-thread, rather than shared. This gives better emulation of multiple users</li>
</ul>
<p>
The main bug fixes are:
@@ -93,6 +94,13 @@
<p>
Control-Z no longer used for Remote Start All; replaced by Control+Shift+R
</p>
+<p>
+By default, SSL session contexts are now created per-thread, rather than being shared.
+The original behaviour can be enabled by setting the JMeter property:
+<pre>
+https.sessioncontext.shared=true
+</pre>
+</p>
<h4>Incompatible changes (development):</h4>
<p>
Calulator and SamplingStatCalculator classes no longer provide any formatting of their data.
@@ -149,6 +157,7 @@
<li>Use ISO date-time format for Tree View Listener (previously the year was not shown)</li>
<li>Improve loading of CSV files: if possible, use header to determine format; guess timestamp format if not milliseconds</li>
<li>Bug 41913 - TransactionController now creates samples as sub-samples of the transaction</li>
+<li>Bug 42506 - JMeter threads all use the same SSL session</li>
</ul>
<h4>Non-functional improvements:</h4>
Modified: jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml
URL: http://svn.apache.org/viewvc/jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml?view=diff&rev=543739&r1=543738&r2=543739
==============================================================================
--- jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml (original)
+++ jakarta/jmeter/branches/rel-2-2/xdocs/usermanual/component_reference.xml Sat Jun 2 06:48:56 2007
@@ -122,6 +122,15 @@
and the appropriate parameters from the form definition.
If the page uses HTTP, you can use the JMeter Proxy to capture the login sequence.
</p>
+ <p>
+ In versions of JMeter up to 2.2, only a single SSL context was used for all threads and samplers.
+ This did not generate the proper load for multiple users.
+ A separate SSL context is now used for each thread.
+ To revert to the original behaviour, set the JMeter property:
+<pre>
+https.sessioncontext.shared=true
+</pre>
+ </p>
<p>If the request uses cookies, then you will also need an
<complink name="HTTP Cookie Manager"/>. You can
add either of these elements to the Thread Group or the HTTP Request. If you have
---------------------------------------------------------------------
To unsubscribe, e-mail: jmeter-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: jmeter-dev-help@jakarta.apache.org