001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.invocation.http.interfaces;
023:
024: import java.io.InputStream;
025: import java.io.ObjectInputStream;
026: import java.io.ObjectStreamException;
027: import java.io.OutputStream;
028: import java.io.ObjectOutputStream;
029: import java.lang.reflect.InvocationTargetException;
030: import java.lang.reflect.Method;
031: import java.net.Authenticator;
032: import java.net.HttpURLConnection;
033: import java.net.MalformedURLException;
034: import java.net.URL;
035: import java.security.PrivilegedAction;
036: import java.security.AccessController;
037: import java.util.zip.GZIPInputStream;
038:
039: import javax.net.ssl.HttpsURLConnection;
040: import javax.net.ssl.SSLSocketFactory;
041:
042: import org.jboss.invocation.Invocation;
043: import org.jboss.invocation.InvocationException;
044: import org.jboss.invocation.MarshalledValue;
045: import org.jboss.logging.Logger;
046: import org.jboss.security.SecurityAssociationAuthenticator;
047: import org.jboss.net.ssl.SSLSocketFactoryBuilder;
048:
049: /** Common client utility methods
050: *
051: * @author Scott.Stark@jboss.org
052: * @version $Revision: 57209 $
053: */
054: public class Util {
055: /** A property to override the default https url host verification */
056: public static final String IGNORE_HTTPS_HOST = "org.jboss.security.ignoreHttpsHost";
057: /** A property to install the https connection ssl socket factory */
058: public static final String SSL_FACTORY_BUILDER = "org.jboss.security.httpInvoker.sslSocketFactoryBuilder";
059: /**
060: * A serialized MarshalledInvocation
061: */
062: private static String REQUEST_CONTENT_TYPE = "application/x-java-serialized-object; class=org.jboss.invocation.MarshalledInvocation";
063: private static Logger log = Logger.getLogger(Util.class);
064: /** A custom SSLSocketFactory builder to use for https connections */
065: private static SSLSocketFactoryBuilder sslSocketFactoryBuilder;
066:
067: static class SetAuthenticator implements PrivilegedAction {
068: public Object run() {
069: Authenticator
070: .setDefault(new SecurityAssociationAuthenticator());
071: return null;
072: }
073:
074: }
075:
076: static class ReadSSLBuilder implements PrivilegedAction {
077: public Object run() {
078: String value = System.getProperty(SSL_FACTORY_BUILDER);
079: return value;
080: }
081: }
082:
083: static {
084: // Install the java.net.Authenticator to use
085: try {
086: SetAuthenticator action = new SetAuthenticator();
087: AccessController.doPrivileged(action);
088: } catch (Exception e) {
089: log
090: .warn(
091: "Failed to install SecurityAssociationAuthenticator",
092: e);
093: }
094: ClassLoader loader = Thread.currentThread()
095: .getContextClassLoader();
096:
097: String factoryFactoryFQCN = null;
098: try {
099: ReadSSLBuilder action = new ReadSSLBuilder();
100: factoryFactoryFQCN = (String) AccessController
101: .doPrivileged(action);
102: } catch (Exception e) {
103: log.warn("Failed to read " + SSL_FACTORY_BUILDER, e);
104: }
105:
106: if (factoryFactoryFQCN != null) {
107: try {
108: Class clazz = loader.loadClass(factoryFactoryFQCN);
109: sslSocketFactoryBuilder = (SSLSocketFactoryBuilder) clazz
110: .newInstance();
111: } catch (Exception e) {
112: log
113: .warn(
114: "Could not instantiate SSLSocketFactoryFactory",
115: e);
116: }
117: }
118: }
119:
120: /** Install the SecurityAssociationAuthenticator as the default
121: * java.net.Authenticator
122: */
123: public static void init() {
124: try {
125: SetAuthenticator action = new SetAuthenticator();
126: AccessController.doPrivileged(action);
127: } catch (Exception e) {
128: log
129: .warn(
130: "Failed to install SecurityAssociationAuthenticator",
131: e);
132: }
133: }
134:
135: /** Post the Invocation as a serialized MarshalledInvocation object. This is
136: using the URL class for now but this should be improved to a cluster aware
137: layer with full usage of HTTP 1.1 features, pooling, etc.
138: */
139: public static Object invoke(URL externalURL, Invocation mi)
140: throws Exception {
141: if (log.isTraceEnabled())
142: log.trace("invoke, externalURL=" + externalURL);
143: /* Post the MarshalledInvocation data. This is using the URL class
144: for now but this should be improved to a cluster aware layer with
145: full usage of HTTP 1.1 features, pooling, etc.
146: */
147: HttpURLConnection conn = (HttpURLConnection) externalURL
148: .openConnection();
149: configureHttpsHostVerifier(conn);
150: conn.setDoInput(true);
151: conn.setDoOutput(true);
152: conn.setRequestProperty("ContentType", REQUEST_CONTENT_TYPE);
153: conn.setRequestMethod("POST");
154: // @todo this should be configurable
155: conn.setRequestProperty("Accept-Encoding",
156: "x-gzip,x-deflate,gzip,deflate");
157: OutputStream os = conn.getOutputStream();
158: ObjectOutputStream oos = new ObjectOutputStream(os);
159: try {
160: oos.writeObject(mi);
161: oos.flush();
162: } catch (ObjectStreamException e) {
163: // This generally represents a programming/deployment error,
164: // not a communication problem
165: throw new InvocationException(e);
166: }
167:
168: // Get the response MarshalledValue object
169: InputStream is = conn.getInputStream();
170: // Check the headers for gzip Content-Encoding
171: String encoding = conn.getHeaderField("Content-Encoding");
172: if (encoding != null && encoding.indexOf("gzip") >= 0)
173: is = new GZIPInputStream(is);
174: ObjectInputStream ois = new ObjectInputStream(is);
175: MarshalledValue mv = (MarshalledValue) ois.readObject();
176: // A hack for jsse connection pooling (see patch ).
177: ois.read();
178: ois.close();
179: oos.close();
180:
181: // If the encoded value is an exception throw it
182: Object value = mv.get();
183: if (value instanceof Exception) {
184: throw (Exception) value;
185: }
186:
187: return value;
188: }
189:
190: /** Given an Https URL connection check the org.jboss.security.ignoreHttpsHost
191: * system property and if true, install the AnyhostVerifier as the
192: * com.sun.net.ssl.HostnameVerifier or javax.net.ssl.HostnameVerifier
193: * depending on the version of JSSE seen. If HttpURLConnection is not a
194: * HttpsURLConnection then nothing is done.
195: *
196: * @param conn a HttpsURLConnection
197: */
198: public static void configureHttpsHostVerifier(HttpURLConnection conn) {
199: if (conn instanceof HttpsURLConnection) {
200: // See if the org.jboss.security.ignoreHttpsHost property is set
201: if (Boolean.getBoolean(IGNORE_HTTPS_HOST) == true) {
202: AnyhostVerifier.setHostnameVerifier(conn);
203: }
204: }
205: }
206:
207: /** Override the SSLSocketFactory used by the HttpsURLConnection. This method
208: * will invoke setSSLSocketFactory on any HttpsURLConnection if there was
209: * a SSLSocketFactoryBuilder implementation specified via the
210: * org.jboss.security.httpInvoker.sslSocketFactoryBuilder system property.
211: *
212: * @param conn possibly a HttpsURLConnection
213: * @throws InvocationTargetException thrown on failure to invoke setSSLSocketFactory
214: */
215: public static void configureSSLSocketFactory(HttpURLConnection conn)
216: throws InvocationTargetException {
217: Class connClass = conn.getClass();
218: if (conn instanceof HttpsURLConnection
219: && sslSocketFactoryBuilder != null) {
220: try {
221: SSLSocketFactory socketFactory = sslSocketFactoryBuilder
222: .getSocketFactory();
223: Class[] sig = { SSLSocketFactory.class };
224: Method method = connClass.getMethod(
225: "setSSLSocketFactory", sig);
226: Object[] args = { socketFactory };
227: method.invoke(conn, args);
228: log.trace("Socket factory set on connection");
229: } catch (Exception e) {
230: throw new InvocationTargetException(e);
231: }
232: }
233: }
234:
235: /**
236: * First try to use the externalURLValue as a URL string and if this
237: * fails to produce a valid URL treat the externalURLValue as a system
238: * property name from which to obtain the URL string. This allows the
239: * proxy url to not be set until the proxy is unmarshalled in the client
240: * vm, and is necessary when the server is sitting behind a firewall or
241: * proxy and does not know what its public http interface is named.
242: */
243: public static URL resolveURL(String urlValue)
244: throws MalformedURLException {
245: if (urlValue == null)
246: return null;
247:
248: URL externalURL = null;
249: try {
250: externalURL = new URL(urlValue);
251: } catch (MalformedURLException e) {
252: // See if externalURL refers to a property
253: String urlProperty = System.getProperty(urlValue);
254: if (urlProperty == null)
255: throw e;
256: externalURL = new URL(urlProperty);
257: }
258: return externalURL;
259: }
260: }
|