/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.invocation.http.interfaces;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.PrivilegedAction;
import java.security.AccessController;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.InvocationException;
import org.jboss.invocation.MarshalledValue;
import org.jboss.logging.Logger;
import org.jboss.security.SecurityAssociationAuthenticator;
import org.jboss.net.ssl.SSLSocketFactoryBuilder;
/** Common client utility methods
*
* @author Scott.Stark@jboss.org
* @version $Revision: 81030 $
*/
public class Util
{
/** A property to override the default https url host verification */
public static final String IGNORE_HTTPS_HOST = "org.jboss.security.ignoreHttpsHost";
/** A property to install the https connection ssl socket factory */
public static final String SSL_FACTORY_BUILDER = "org.jboss.security.httpInvoker.sslSocketFactoryBuilder";
/**
* A serialized MarshalledInvocation
*/
private static String REQUEST_CONTENT_TYPE =
"application/x-java-serialized-object; class=org.jboss.invocation.MarshalledInvocation";
private static Logger log = Logger.getLogger(Util.class);
/** A custom SSLSocketFactory builder to use for https connections */
private static SSLSocketFactoryBuilder sslSocketFactoryBuilder;
static class SetAuthenticator implements PrivilegedAction
{
public Object run()
{
Authenticator.setDefault(new SecurityAssociationAuthenticator());
return null;
}
}
static class ReadSSLBuilder implements PrivilegedAction
{
public Object run()
{
String value = System.getProperty(SSL_FACTORY_BUILDER);
return value;
}
}
static
{
// Install the java.net.Authenticator to use
try
{
SetAuthenticator action = new SetAuthenticator();
AccessController.doPrivileged(action);
}
catch(Exception e)
{
log.warn("Failed to install SecurityAssociationAuthenticator", e);
}
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String factoryFactoryFQCN = null;
try
{
ReadSSLBuilder action = new ReadSSLBuilder();
factoryFactoryFQCN = (String) AccessController.doPrivileged(action);
}
catch(Exception e)
{
log.warn("Failed to read "+SSL_FACTORY_BUILDER, e);
}
if (factoryFactoryFQCN != null)
{
try
{
Class clazz = loader.loadClass(factoryFactoryFQCN);
sslSocketFactoryBuilder = (SSLSocketFactoryBuilder) clazz.newInstance();
}
catch (Exception e)
{
log.warn("Could not instantiate SSLSocketFactoryFactory", e);
}
}
}
/** Install the SecurityAssociationAuthenticator as the default
* java.net.Authenticator
*/
public static void init()
{
try
{
SetAuthenticator action = new SetAuthenticator();
AccessController.doPrivileged(action);
}
catch(Exception e)
{
log.warn("Failed to install SecurityAssociationAuthenticator", e);
}
}
/** Post the Invocation as a serialized MarshalledInvocation object. This is
using the URL class for now but this should be improved to a cluster aware
layer with full usage of HTTP 1.1 features, pooling, etc.
*/
public static Object invoke(URL externalURL, Invocation mi)
throws Exception
{
if( log.isTraceEnabled() )
log.trace("invoke, externalURL="+externalURL);
/* Post the MarshalledInvocation data. This is using the URL class
for now but this should be improved to a cluster aware layer with
full usage of HTTP 1.1 features, pooling, etc.
*/
HttpURLConnection conn = (HttpURLConnection) externalURL.openConnection();
configureHttpsHostVerifier(conn);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestProperty("ContentType", REQUEST_CONTENT_TYPE);
conn.setRequestMethod("POST");
// @todo this should be configurable
conn.setRequestProperty("Accept-Encoding", "x-gzip,x-deflate,gzip,deflate");
OutputStream os = conn.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
try
{
oos.writeObject(mi);
oos.flush();
}
catch (ObjectStreamException e)
{
// This generally represents a programming/deployment error,
// not a communication problem
throw new InvocationException(e);
}
// Get the response MarshalledValue object
InputStream is = conn.getInputStream();
// Check the headers for gzip Content-Encoding
String encoding = conn.getHeaderField("Content-Encoding");
if( encoding != null && encoding.indexOf("gzip") >= 0 )
is = new GZIPInputStream(is);
ObjectInputStream ois = new ObjectInputStream(is);
MarshalledValue mv = (MarshalledValue) ois.readObject();
// A hack for jsse connection pooling (see patch ).
ois.read();
ois.close();
oos.close();
// If the encoded value is an exception throw it
Object value = mv.get();
if( value instanceof Exception )
{
throw (Exception) value;
}
return value;
}
/** Given an Https URL connection check the org.jboss.security.ignoreHttpsHost
* system property and if true, install the AnyhostVerifier as the
* com.sun.net.ssl.HostnameVerifier or javax.net.ssl.HostnameVerifier
* depending on the version of JSSE seen. If HttpURLConnection is not a
* HttpsURLConnection then nothing is done.
*
* @param conn a HttpsURLConnection
*/
public static void configureHttpsHostVerifier(HttpURLConnection conn)
{
if ( conn instanceof HttpsURLConnection )
{
// See if the org.jboss.security.ignoreHttpsHost property is set
if (Boolean.getBoolean(IGNORE_HTTPS_HOST) == true)
{
AnyhostVerifier.setHostnameVerifier(conn);
}
}
}
/** Override the SSLSocketFactory used by the HttpsURLConnection. This method
* will invoke setSSLSocketFactory on any HttpsURLConnection if there was
* a SSLSocketFactoryBuilder implementation specified via the
* org.jboss.security.httpInvoker.sslSocketFactoryBuilder system property.
*
* @param conn possibly a HttpsURLConnection
* @throws InvocationTargetException thrown on failure to invoke setSSLSocketFactory
*/
public static void configureSSLSocketFactory(HttpURLConnection conn)
throws InvocationTargetException
{
Class connClass = conn.getClass();
if ( conn instanceof HttpsURLConnection && sslSocketFactoryBuilder != null)
{
try
{
SSLSocketFactory socketFactory = sslSocketFactoryBuilder.getSocketFactory();
Class[] sig = {SSLSocketFactory.class};
Method method = connClass.getMethod("setSSLSocketFactory", sig);
Object[] args = {socketFactory};
method.invoke(conn, args);
log.trace("Socket factory set on connection");
}
catch(Exception e)
{
throw new InvocationTargetException(e);
}
}
}
/**
* First try to use the externalURLValue as a URL string and if this
* fails to produce a valid URL treat the externalURLValue as a system
* property name from which to obtain the URL string. This allows the
* proxy url to not be set until the proxy is unmarshalled in the client
* vm, and is necessary when the server is sitting behind a firewall or
* proxy and does not know what its public http interface is named.
*/
public static URL resolveURL(String urlValue) throws MalformedURLException
{
if( urlValue == null )
return null;
URL externalURL = null;
try
{
externalURL = new URL(urlValue);
}
catch(MalformedURLException e)
{
// See if externalURL refers to a property
String urlProperty = System.getProperty(urlValue);
if( urlProperty == null )
throw e;
externalURL = new URL(urlProperty);
}
return externalURL;
}
}