0001: /**
0002: * Licensed to the Apache Software Foundation (ASF) under one
0003: * or more contributor license agreements. See the NOTICE file
0004: * distributed with this work for additional information
0005: * regarding copyright ownership. The ASF licenses this file
0006: * to you under the Apache License, Version 2.0 (the
0007: * "License"); you may not use this file except in compliance
0008: * with the License. You may obtain a copy of the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing,
0013: * software distributed under the License is distributed on an
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0015: * KIND, either express or implied. See the License for the
0016: * specific language governing permissions and limitations
0017: * under the License.
0018: */package org.apache.cxf.transport.http;
0019:
0020: import java.io.IOException;
0021: import java.io.InputStream;
0022: import java.io.OutputStream;
0023: import java.io.PushbackInputStream;
0024: import java.net.HttpURLConnection;
0025: import java.net.InetSocketAddress;
0026: import java.net.MalformedURLException;
0027: import java.net.Proxy;
0028: import java.net.URL;
0029: import java.net.URLConnection;
0030: import java.util.Arrays;
0031: import java.util.HashMap;
0032: import java.util.HashSet;
0033: import java.util.List;
0034: import java.util.Map;
0035: import java.util.Set;
0036: import java.util.logging.Level;
0037: import java.util.logging.Logger;
0038:
0039: import javax.annotation.Resource;
0040: import javax.xml.namespace.QName;
0041:
0042: import org.apache.cxf.Bus;
0043: import org.apache.cxf.common.logging.LogUtils;
0044: import org.apache.cxf.common.util.Base64Utility;
0045: import org.apache.cxf.configuration.Configurable;
0046: import org.apache.cxf.configuration.jsse.TLSClientParameters;
0047: import org.apache.cxf.configuration.security.AuthorizationPolicy;
0048: import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy;
0049: import org.apache.cxf.helpers.CastUtils;
0050: import org.apache.cxf.helpers.HttpHeaderHelper;
0051: import org.apache.cxf.helpers.IOUtils;
0052: import org.apache.cxf.io.AbstractWrappedOutputStream;
0053: import org.apache.cxf.io.CacheAndWriteOutputStream;
0054: import org.apache.cxf.message.Exchange;
0055: import org.apache.cxf.message.ExchangeImpl;
0056: import org.apache.cxf.message.Message;
0057: import org.apache.cxf.message.MessageImpl;
0058: import org.apache.cxf.service.model.EndpointInfo;
0059: import org.apache.cxf.transport.AbstractConduit;
0060: import org.apache.cxf.transport.Destination;
0061: import org.apache.cxf.transport.DestinationFactory;
0062: import org.apache.cxf.transport.DestinationFactoryManager;
0063: import org.apache.cxf.transport.MessageObserver;
0064: import org.apache.cxf.transport.http.policy.PolicyUtils;
0065: import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
0066: import org.apache.cxf.ws.addressing.EndpointReferenceType;
0067: import org.apache.cxf.ws.policy.Assertor;
0068: import org.apache.cxf.ws.policy.PolicyEngine;
0069: import org.apache.cxf.wsdl.EndpointReferenceUtils;
0070: import org.apache.geronimo.mail.util.StringBufferOutputStream;
0071:
0072: import static org.apache.cxf.message.Message.DECOUPLED_CHANNEL_MESSAGE;
0073:
0074: /*
0075: * HTTP Conduit implementation.
0076: * <p>
0077: * This implementation is a based on the java.net.URLConnection interface and
0078: * dependent upon installed implementations of that URLConnection,
0079: * HttpURLConnection, and HttpsURLConnection. Currently, this implemenation
0080: * has been known to work with the Sun JDK 1.5 default implementations. The
0081: * HttpsURLConnection is part of Sun's implemenation of the JSSE.
0082: * Presently, the source code for the Sun JSSE implemenation is unavaiable
0083: * and therefore we may only lay a guess of whether its HttsURLConnection
0084: * implemenation correctly works as far as security is concerned.
0085: * <p>
0086: * The Trust Decision. If a MessageTrustDecider is configured/set for the
0087: * Conduit, it is called upon the first flush of the headers in the
0088: * WrappedOutputStream. This reason for this approach is two fold.
0089: * Theoretically, in order to get connection information out of the
0090: * URLConnection, it must be "connected". We assume that its implementation will
0091: * only follow through up to the point at which it will be ready to send
0092: * one byte of data down to the endpoint, but through proxies, and the
0093: * commpletion of a TLS handshake in the case of HttpsURLConnection.
0094: * However, if we force the connect() call right away, the default
0095: * implementations will not allow any calls to add/setRequestProperty,
0096: * throwing an exception that the URLConnection is already connected.
0097: * <p>
0098: * We need to keep the semantic that later CXF interceptors may add to the
0099: * PROTOCOL_HEADERS in the Message. This architectual decision forces us to
0100: * delay the connection until after that point, then pulling the trust decision.
0101: * <p>
0102: * The security caveat is that we don't really know when the connection is
0103: * really established. The call to "connect" is stated to force the
0104: * "connection," but it is a no-op if the connection was already established.
0105: * It is entirely possible that an implementation of an URLConnection may
0106: * indeed connect at will and start sending the headers down the connection
0107: * during calls to add/setRequestProperty!
0108: * <p>
0109: * We know that the JDK 1.5 sun.com.net.www.HttpURLConnection does not send
0110: * this information before the "connect" call, because we can look at the
0111: * source code. However, we can only assume, not verify, that the JSSE 1.5
0112: * HttpsURLConnection does the same, in that it is probable that the
0113: * HttpsURLConnection shares the HttpURLConnection implemenation.
0114: * <p>
0115: * Due to these implementations following redirects without trust checks, we
0116: * force the URLConnection implementations not to follow redirects. If
0117: * client side policy dictates that we follow redirects, trust decisions are
0118: * placed before each retransmit. On a redirect, any authorization information
0119: * dynamically acquired by a BasicAuth UserPass supplier is removed before
0120: * being retransmitted, as it may no longer be applicable to the new url to
0121: * which the connection is redirected.
0122: */
0123:
0124: /**
0125: * This Conduit handles the "http" and "https" transport protocols. An
0126: * instance is governed by policies either explicity set or by
0127: * configuration.
0128: */
0129: public class HTTPConduit extends AbstractConduit implements
0130: Configurable, Assertor {
0131:
0132: /**
0133: * This constant is the Message(Map) key for the HttpURLConnection that
0134: * is used to get the response.
0135: */
0136: public static final String KEY_HTTP_CONNECTION = "http.connection";
0137:
0138: /**
0139: * This constant is the Message(Map) key for a list of visited URLs that
0140: * is used in redirect loop protection.
0141: */
0142: private static final String KEY_VISITED_URLS = "VisitedURLs";
0143:
0144: /**
0145: * This constant is the Message(Map) key for a list of URLs that
0146: * is used in authorization loop protection.
0147: */
0148: private static final String KEY_AUTH_URLS = "AuthURLs";
0149:
0150: /**
0151: * The Logger for this class.
0152: */
0153: private static final Logger LOG = LogUtils
0154: .getL7dLogger(HTTPConduit.class);
0155:
0156: /**
0157: * This constant holds the suffix ".http-conduit" that is appended to the
0158: * Endpoint Qname to give the configuration name of this conduit.
0159: */
0160: private static final String SC_HTTP_CONDUIT_SUFFIX = ".http-conduit";
0161:
0162: /**
0163: * Buffer to use to suck unread bytes off of input streams
0164: */
0165: private static final byte BUFFER[] = new byte[1024];
0166:
0167: /**
0168: * This field holds the connection factory, which primarily is used to
0169: * factor out SSL specific code from this implementation.
0170: * <p>
0171: * This field is "protected" to facilitate some contrived UnitTesting so
0172: * that an extended class may alter its value with an EasyMock URLConnection
0173: * Factory.
0174: */
0175: protected HttpURLConnectionFactory connectionFactory;
0176:
0177: /**
0178: * This field holds a reference to the CXF bus associated this conduit.
0179: */
0180: private final Bus bus;
0181:
0182: /**
0183: * This field is used for two reasons. First it provides the base name for
0184: * the conduit for Spring configuration. The other is to hold default
0185: * address information, should it not be supplied in the Message Map, by the
0186: * Message.ENDPOINT_ADDRESS property.
0187: */
0188: private final EndpointInfo endpointInfo;
0189:
0190: /**
0191: * This field holds the "default" address for this particular conduit, which
0192: * is set at construction.
0193: */
0194: private final String defaultEndpointAddress;
0195:
0196: /**
0197: * This field holds the "default" URL for this particular conduit, which
0198: * is created on demand.
0199: */
0200: private URL defaultEndpointURL;
0201:
0202: private Destination decoupledDestination;
0203: private MessageObserver decoupledObserver;
0204: private int decoupledDestinationRefCount;
0205:
0206: // Configuratble/settable values
0207:
0208: /**
0209: * This field holds the QoS configuration settings for this conduit.
0210: * This field is injected via spring configuration based on the conduit
0211: * name.
0212: */
0213: private HTTPClientPolicy clientSidePolicy;
0214:
0215: /**
0216: * This field holds the password authorization configuration.
0217: * This field is injected via spring configuration based on the conduit
0218: * name.
0219: */
0220: private AuthorizationPolicy authorizationPolicy;
0221:
0222: /**
0223: * This field holds the password authorization configuration for the
0224: * configured proxy. This field is injected via spring configuration based
0225: * on the conduit name.
0226: */
0227: private ProxyAuthorizationPolicy proxyAuthorizationPolicy;
0228:
0229: /**
0230: * This field holds the configuration TLS configuration which
0231: * is programmatically configured.
0232: */
0233: private TLSClientParameters tlsClientParameters;
0234:
0235: /**
0236: * This field contains the MessageTrustDecider.
0237: */
0238: private MessageTrustDecider trustDecider;
0239:
0240: /**
0241: * This field contains the HttpBasicAuthSupplier.
0242: */
0243: private HttpBasicAuthSupplier basicAuthSupplier;
0244:
0245: /**
0246: * This boolean signfies that that finalizeConfig is called, which is
0247: * after the HTTPTransportFactory configures this object via spring.
0248: * At this point, any change by a "setter" is dynamic, and any change
0249: * should be handled as such.
0250: */
0251: private boolean configFinalized;
0252:
0253: /**
0254: * Variables for holding session state if sessions are supposed to be maintained
0255: */
0256: private String sessionId;
0257: private boolean maintainSession;
0258:
0259: /**
0260: * Constructor
0261: *
0262: * @param b the associated Bus
0263: * @param ei the endpoint info of the initiator
0264: * @throws IOException
0265: */
0266: public HTTPConduit(Bus b, EndpointInfo ei) throws IOException {
0267: this (b, ei, null);
0268: }
0269:
0270: /**
0271: * Constructor
0272: *
0273: * @param b the associated Bus.
0274: * @param endpoint the endpoint info of the initiator.
0275: * @param t the endpoint reference of the target.
0276: * @throws IOException
0277: */
0278: public HTTPConduit(Bus b, EndpointInfo ei, EndpointReferenceType t)
0279: throws IOException {
0280: super (getTargetReference(ei, t, b));
0281:
0282: bus = b;
0283: endpointInfo = ei;
0284:
0285: defaultEndpointAddress = t == null ? ei.getAddress() : t
0286: .getAddress().getValue();
0287:
0288: initializeConfig();
0289: }
0290:
0291: /**
0292: * This method returns the registered Logger for this conduit.
0293: */
0294: protected Logger getLogger() {
0295: return LOG;
0296: }
0297:
0298: /**
0299: * This method returns the name of the conduit, which is based on the
0300: * endpoint name plus the SC_HTTP_CONDUIT_SUFFIX.
0301: * @return
0302: */
0303: public final String getConduitName() {
0304: return endpointInfo.getName() + SC_HTTP_CONDUIT_SUFFIX;
0305: }
0306:
0307: /**
0308: * This method is called from the constructor which initializes
0309: * the configuration. The TransportFactory will call configureBean
0310: * on this object after construction.
0311: */
0312: private void initializeConfig() {
0313:
0314: // wsdl extensors are superseded by policies which in
0315: // turn are superseded by injection
0316:
0317: PolicyEngine pe = bus.getExtension(PolicyEngine.class);
0318: if (null != pe && pe.isEnabled()
0319: && endpointInfo.getService() != null) {
0320: clientSidePolicy = PolicyUtils.getClient(pe, endpointInfo,
0321: this );
0322: }
0323:
0324: }
0325:
0326: /**
0327: * This call gets called by the HTTPTransportFactory after it
0328: * causes an injection of the Spring configuration properties
0329: * of this Conduit.
0330: */
0331: void finalizeConfig() {
0332: // See if not set by configuration, if there are defaults
0333: // in order from the Endpoint, Service, or Bus.
0334:
0335: if (this .clientSidePolicy == null) {
0336: clientSidePolicy = endpointInfo.getTraversedExtensor(
0337: new HTTPClientPolicy(), HTTPClientPolicy.class);
0338: }
0339: if (this .authorizationPolicy == null) {
0340: authorizationPolicy = endpointInfo.getTraversedExtensor(
0341: new AuthorizationPolicy(),
0342: AuthorizationPolicy.class);
0343:
0344: }
0345: if (this .proxyAuthorizationPolicy == null) {
0346: proxyAuthorizationPolicy = endpointInfo
0347: .getTraversedExtensor(
0348: new ProxyAuthorizationPolicy(),
0349: ProxyAuthorizationPolicy.class);
0350:
0351: }
0352: if (this .tlsClientParameters == null) {
0353: tlsClientParameters = endpointInfo.getTraversedExtensor(
0354: null, TLSClientParameters.class);
0355: }
0356: if (this .trustDecider == null) {
0357: trustDecider = endpointInfo.getTraversedExtensor(null,
0358: MessageTrustDecider.class);
0359: }
0360: if (this .basicAuthSupplier == null) {
0361: basicAuthSupplier = endpointInfo.getTraversedExtensor(null,
0362: HttpBasicAuthSupplier.class);
0363: }
0364: if (trustDecider == null) {
0365: if (LOG.isLoggable(Level.FINE)) {
0366: LOG.log(Level.FINE,
0367: "No Trust Decider configured for Conduit '"
0368: + getConduitName() + "'");
0369: }
0370: } else {
0371: if (LOG.isLoggable(Level.FINE)) {
0372: LOG.log(Level.FINE, "Message Trust Decider of class '"
0373: + trustDecider.getClass().getName()
0374: + "' with logical name of '"
0375: + trustDecider.getLogicalName()
0376: + "' has been configured for Conduit '"
0377: + getConduitName() + "'");
0378: }
0379: }
0380: if (basicAuthSupplier == null) {
0381: if (LOG.isLoggable(Level.FINE)) {
0382: LOG.log(Level.FINE,
0383: "No Basic Auth Supplier configured for Conduit '"
0384: + getConduitName() + "'");
0385: }
0386: } else {
0387: if (LOG.isLoggable(Level.FINE)) {
0388: LOG.log(Level.FINE, "HttpBasicAuthSupplier of class '"
0389: + basicAuthSupplier.getClass().getName()
0390: + "' with logical name of '"
0391: + basicAuthSupplier.getLogicalName()
0392: + "' has been configured for Conduit '"
0393: + getConduitName() + "'");
0394: }
0395: }
0396: if (this .tlsClientParameters != null) {
0397: if (LOG.isLoggable(Level.FINE)) {
0398: LOG.log(Level.FINE, "Conduit '" + getConduitName()
0399: + "' has been configured for TLS "
0400: + "keyManagers "
0401: + tlsClientParameters.getKeyManagers()
0402: + "trustManagers "
0403: + tlsClientParameters.getTrustManagers()
0404: + "secureRandom "
0405: + tlsClientParameters.getSecureRandom());
0406: }
0407: } else {
0408: if (LOG.isLoggable(Level.FINE)) {
0409: LOG.log(Level.FINE, "Conduit '" + getConduitName()
0410: + "' has been configured for plain http.");
0411: }
0412: }
0413:
0414: // Get the correct URLConnection factory based on the
0415: // configuration.
0416: retrieveConnectionFactory();
0417:
0418: // We have finalized the configuration. Any configurable entity
0419: // set now, must make changes dynamically.
0420: configFinalized = true;
0421: }
0422:
0423: /**
0424: * This method sets the connectionFactory field for this object. It is called
0425: * after an SSL Client Policy is set or an HttpsHostnameVerifier
0426: * because we need to reinitialize the connection factory.
0427: * <p>
0428: * This method is "protected" so that this class may be extended and override
0429: * this method to put an EasyMock URL Connection factory for some contrived
0430: * UnitTest that will of course break, should the calls to the URL Connection
0431: * Factory get altered.
0432: */
0433: protected void retrieveConnectionFactory() {
0434: connectionFactory = AbstractHTTPTransportFactory
0435: .getConnectionFactory(this );
0436: }
0437:
0438: /**
0439: * Prepare to send an outbound HTTP message over this http conduit to a
0440: * particular endpoint.
0441: * <P>
0442: * If the Message.PATH_INFO property is set it gets appended
0443: * to the Conduit's endpoint URL. If the Message.QUERY_STRING
0444: * property is set, it gets appended to the resultant URL following
0445: * a "?".
0446: * <P>
0447: * If the Message.HTTP_REQUEST_METHOD property is NOT set, the
0448: * Http request method defaults to "POST".
0449: * <P>
0450: * If the Message.PROTOCOL_HEADERS is not set on the message, it is
0451: * initialized to an empty map.
0452: * <P>
0453: * This call creates the OutputStream for the content of the message.
0454: * It also assigns the created Http(s)URLConnection to the Message
0455: * Map.
0456: *
0457: * @param message The message to be sent.
0458: */
0459: public void prepare(Message message) throws IOException {
0460: Map<String, List<String>> headers = getSetProtocolHeaders(message);
0461:
0462: // This call can possibly change the conduit endpoint address and
0463: // protocol from the default set in EndpointInfo that is associated
0464: // with the Conduit.
0465: URL currentURL = setupURL(message);
0466:
0467: HttpBasicAuthSupplier.UserPass userPass = null;
0468:
0469: // The need to cache the request is off by default
0470: boolean needToCacheRequest = false;
0471:
0472: HttpURLConnection connection = connectionFactory
0473: .createConnection(getProxy(clientSidePolicy),
0474: currentURL);
0475: connection.setDoOutput(true);
0476:
0477: //TODO using Message context to decided HTTP send properties
0478:
0479: connection.setConnectTimeout((int) clientSidePolicy
0480: .getConnectionTimeout());
0481: connection.setReadTimeout((int) clientSidePolicy
0482: .getReceiveTimeout());
0483: connection.setUseCaches(false);
0484: // We implement redirects in this conduit. We do not
0485: // rely on the underlying URLConnection implementation
0486: // because of trust issues.
0487: connection.setInstanceFollowRedirects(false);
0488:
0489: // If the HTTP_REQUEST_METHOD is not set, the default is "POST".
0490: String httpRequestMethod = (String) message
0491: .get(Message.HTTP_REQUEST_METHOD);
0492:
0493: if (null != httpRequestMethod) {
0494: connection.setRequestMethod(httpRequestMethod);
0495: } else {
0496: connection.setRequestMethod("POST");
0497: }
0498:
0499: boolean isChunking = false;
0500: // We must cache the request if we have basic auth supplier
0501: // without preemptive basic auth.
0502: if (basicAuthSupplier != null) {
0503: userPass = basicAuthSupplier.getPreemptiveUserPass(
0504: getConduitName(), currentURL, message);
0505: needToCacheRequest = userPass == null;
0506: LOG.log(Level.INFO,
0507: "Basic Auth Supplier, but no Premeptive User Pass."
0508: + " We must cache request.");
0509: }
0510: if (getClient().isAutoRedirect()) {
0511: // If the AutoRedirect property is set then we cannot
0512: // use chunked streaming mode. We ignore the "AllowChunking"
0513: // property if AutoRedirect is turned on.
0514:
0515: needToCacheRequest = true;
0516: LOG.log(Level.INFO, "AutoRedirect is turned on.");
0517: } else {
0518: if (!connection.getRequestMethod().equals("GET")
0519: && getClient().isAllowChunking()
0520: && !needToCacheRequest) {
0521: //TODO: The chunking mode be configured or at least some
0522: // documented client constant.
0523: //use -1 and allow the URL connection to pick a default value
0524: connection.setChunkedStreamingMode(-1);
0525: isChunking = true;
0526: }
0527: }
0528:
0529: //Do we need to maintain a session?
0530: maintainSession = Boolean.TRUE.equals((Boolean) message
0531: .get(Message.MAINTAIN_SESSION));
0532:
0533: //If we have a sessionId and we are maintaining sessions, then use it
0534: if (maintainSession && sessionId != null) {
0535: connection.setRequestProperty(HttpHeaderHelper.COOKIE,
0536: "JSESSIONID=" + sessionId);
0537: }
0538:
0539: // The trust decision is relagated to after the "flushing" of the
0540: // request headers.
0541:
0542: // We place the connection on the message to pick it up
0543: // in the WrappedOutputStream.
0544:
0545: message.put(KEY_HTTP_CONNECTION, connection);
0546:
0547: // Set the headers on the message according to configured
0548: // client side policy.
0549:
0550: setHeadersByPolicy(message, currentURL, headers);
0551:
0552: message.setContent(OutputStream.class, new WrappedOutputStream(
0553: message, connection, needToCacheRequest, isChunking));
0554:
0555: // We are now "ready" to "send" the message.
0556: }
0557:
0558: public void close(Message msg) throws IOException {
0559: InputStream in = msg.getContent(InputStream.class);
0560: try {
0561: if (in != null) {
0562: int count = 0;
0563: while (in.read(BUFFER) != -1 && count < 25) {
0564: //don't do anything, we just need to pull off the unread data (like
0565: //closing tags that we didn't need to read
0566:
0567: //however, limit it so we don't read off gigabytes of data we won't use.
0568: ++count;
0569: }
0570: }
0571: } finally {
0572: super .close(msg);
0573: }
0574: }
0575:
0576: /**
0577: * This call must take place before anything is written to the
0578: * URLConnection. The URLConnection.connect() will be called in order
0579: * to get the connection information.
0580: *
0581: * This method is invoked just after setURLRequestHeaders() from the
0582: * WrappedOutputStream before it writes data to the URLConnection.
0583: *
0584: * If trust cannot be established the Trust Decider implemenation
0585: * throws an IOException.
0586: *
0587: * @param message The message being sent.
0588: * @throws IOException This exception is thrown if trust cannot be
0589: * established by the configured MessageTrustDecider.
0590: * @see MessageTrustDecider
0591: */
0592: private void makeTrustDecision(Message message) throws IOException {
0593:
0594: HttpURLConnection connection = (HttpURLConnection) message
0595: .get(KEY_HTTP_CONNECTION);
0596:
0597: if (trustDecider != null) {
0598: try {
0599: // We must connect or we will not get the credentials.
0600: // The call is (said to be) ingored internally if
0601: // already connected.
0602: connection.connect();
0603: trustDecider
0604: .establishTrust(getConduitName(),
0605: connectionFactory
0606: .getConnectionInfo(connection),
0607: message);
0608: if (LOG.isLoggable(Level.FINE)) {
0609: LOG.log(Level.FINE, "Trust Decider "
0610: + trustDecider.getLogicalName()
0611: + " considers Conduit " + getConduitName()
0612: + " trusted.");
0613: }
0614: } catch (UntrustedURLConnectionIOException untrustedEx) {
0615: // This cast covers HttpsURLConnection as well.
0616: ((HttpURLConnection) connection).disconnect();
0617: if (LOG.isLoggable(Level.INFO)) {
0618: LOG.log(Level.INFO, "Trust Decider "
0619: + trustDecider.getLogicalName()
0620: + " considers Conduit " + getConduitName()
0621: + " untrusted.", untrustedEx);
0622: }
0623: throw untrustedEx;
0624: }
0625: } else {
0626: // This case, when there is no trust decider, a trust
0627: // decision should be a matter of policy.
0628: if (LOG.isLoggable(Level.FINE)) {
0629: LOG
0630: .log(
0631: Level.FINE,
0632: "No Trust Decider for Conduit '"
0633: + getConduitName()
0634: + "'. An afirmative Trust Decision is assumed.");
0635: }
0636: }
0637: }
0638:
0639: /**
0640: * This function sets up a URL based on ENDPOINT_ADDRESS, PATH_INFO,
0641: * and QUERY_STRING properties in the Message. The QUERY_STRING gets
0642: * added with a "?" after the PATH_INFO. If the ENDPOINT_ADDRESS is not
0643: * set on the Message, the endpoint address is taken from the
0644: * "defaultEndpointURL".
0645: * <p>
0646: * The PATH_INFO is only added to the endpoint address string should
0647: * the PATH_INFO not equal the end of the endpoint address string.
0648: *
0649: * @param message The message holds the addressing information.
0650: *
0651: * @return The full URL specifying the HTTP request to the endpoint.
0652: *
0653: * @throws MalformedURLException
0654: */
0655: private URL setupURL(Message message) throws MalformedURLException {
0656: String value = (String) message.get(Message.ENDPOINT_ADDRESS);
0657: String pathInfo = (String) message.get(Message.PATH_INFO);
0658: String queryString = (String) message.get(Message.QUERY_STRING);
0659:
0660: String result = value != null ? value : getURL().toString();
0661:
0662: // REVISIT: is this really correct?
0663: if (null != pathInfo && !result.endsWith(pathInfo)) {
0664: result = result + pathInfo;
0665: }
0666: if (queryString != null) {
0667: result = result + "?" + queryString;
0668: }
0669: return new URL(result);
0670: }
0671:
0672: /**
0673: * Retreive the back-channel Destination.
0674: *
0675: * @return the backchannel Destination (or null if the backchannel is
0676: * built-in)
0677: */
0678: public synchronized Destination getBackChannel() {
0679: if (decoupledDestination == null
0680: && getClient().getDecoupledEndpoint() != null) {
0681: setUpDecoupledDestination();
0682: }
0683: return decoupledDestination;
0684: }
0685:
0686: /**
0687: * Close the conduit
0688: */
0689: public void close() {
0690: if (defaultEndpointURL != null) {
0691: try {
0692: URLConnection connect = defaultEndpointURL
0693: .openConnection();
0694: if (connect instanceof HttpURLConnection) {
0695: ((HttpURLConnection) connect).disconnect();
0696: }
0697: } catch (IOException ex) {
0698: //ignore
0699: }
0700: //defaultEndpointURL = null;
0701: }
0702:
0703: // in decoupled case, close response Destination if reference count
0704: // hits zero
0705: //
0706: if (decoupledDestination != null) {
0707: releaseDecoupledDestination();
0708: }
0709: }
0710:
0711: /**
0712: * @return the default target address
0713: */
0714: protected String getAddress() throws MalformedURLException {
0715: return defaultEndpointAddress;
0716: }
0717:
0718: /**
0719: * @return the default target URL
0720: */
0721: protected synchronized URL getURL() throws MalformedURLException {
0722: return getURL(true);
0723: }
0724:
0725: /**
0726: * @param createOnDemand create URL on-demand if null
0727: * @return the default target URL
0728: */
0729: protected synchronized URL getURL(boolean createOnDemand)
0730: throws MalformedURLException {
0731: if (defaultEndpointURL == null && createOnDemand) {
0732: defaultEndpointURL = new URL(defaultEndpointAddress);
0733: }
0734: return defaultEndpointURL;
0735: }
0736:
0737: /**
0738: * While extracting the Message.PROTOCOL_HEADERS property from the Message,
0739: * this call ensures that the Message.PROTOCOL_HEADERS property is
0740: * set on the Message. If it is not set, an empty map is placed there, and
0741: * then returned.
0742: *
0743: * @param message The outbound message
0744: * @return The PROTOCOL_HEADERS map
0745: */
0746: private Map<String, List<String>> getSetProtocolHeaders(
0747: Message message) {
0748: Map<String, List<String>> headers = CastUtils
0749: .cast((Map<?, ?>) message.get(Message.PROTOCOL_HEADERS));
0750: if (null == headers) {
0751: headers = new HashMap<String, List<String>>();
0752: message.put(Message.PROTOCOL_HEADERS, headers);
0753: }
0754: return headers;
0755: }
0756:
0757: /* PMD for non-use!
0758: private void printHeaders(URLConnection connection) {
0759: int i = 0;
0760: String k = connection.getHeaderFieldKey(i);
0761: String h = connection.getHeaderField(i);
0762: while (h != null) {
0763: System.out.println(k + ": " + h);
0764: k = connection.getHeaderFieldKey(++i);
0765: h = connection.getHeaderField(i);
0766: }
0767: }
0768: */
0769:
0770: /**
0771: * This procedure sets the URLConnection request properties
0772: * from the PROTOCOL_HEADERS in the message.
0773: */
0774: private void transferProtocolHeadersToURLConnection(
0775: Message message, URLConnection connection) {
0776: Map<String, List<String>> headers = getSetProtocolHeaders(message);
0777: for (String header : headers.keySet()) {
0778: List<String> headerList = headers.get(header);
0779: for (String value : headerList) {
0780: connection.addRequestProperty(header, value);
0781: }
0782: }
0783: }
0784:
0785: /**
0786: * This procedure logs the PROTOCOL_HEADERS from the
0787: * Message at the specified logging level.
0788: *
0789: * @param level The Logging Level.
0790: * @param message The Message.
0791: */
0792: private void logProtocolHeaders(Level level, Message message) {
0793: Map<String, List<String>> headers = getSetProtocolHeaders(message);
0794: for (String header : headers.keySet()) {
0795: List<String> headerList = headers.get(header);
0796: for (String value : headerList) {
0797: LOG.log(level, header + ": " + value);
0798: }
0799: }
0800: }
0801:
0802: /**
0803: * Put the headers from Message.PROTOCOL_HEADERS headers into the URL
0804: * connection.
0805: * Note, this does not mean they immediately get written to the output
0806: * stream or the wire. They just just get set on the HTTP request.
0807: *
0808: * @param message The outbound message.
0809: * @throws IOException
0810: */
0811: private void setURLRequestHeaders(Message message)
0812: throws IOException {
0813: HttpURLConnection connection = (HttpURLConnection) message
0814: .get(KEY_HTTP_CONNECTION);
0815:
0816: String ct = (String) message.get(Message.CONTENT_TYPE);
0817: String enc = (String) message.get(Message.ENCODING);
0818:
0819: if (null != ct) {
0820: if (enc != null && ct.indexOf("charset=") == -1) {
0821: ct = ct + "; charset=" + enc;
0822: }
0823: connection.setRequestProperty(
0824: HttpHeaderHelper.CONTENT_TYPE, ct);
0825: } else if (enc != null) {
0826: connection.setRequestProperty(
0827: HttpHeaderHelper.CONTENT_TYPE, "text/xml; charset="
0828: + enc);
0829: } else {
0830: connection.setRequestProperty(
0831: HttpHeaderHelper.CONTENT_TYPE, "text/xml");
0832: }
0833:
0834: if (LOG.isLoggable(Level.FINE)) {
0835: LOG.fine("Sending " + connection.getRequestMethod()
0836: + " Message with Headers to " + connection.getURL()
0837: + " Conduit :" + getConduitName());
0838: logProtocolHeaders(Level.FINE, message);
0839: }
0840:
0841: transferProtocolHeadersToURLConnection(message, connection);
0842:
0843: }
0844:
0845: /**
0846: * Set up the decoupled Destination if necessary.
0847: */
0848: private void setUpDecoupledDestination() {
0849: EndpointReferenceType reference = EndpointReferenceUtils
0850: .getEndpointReference(getClient()
0851: .getDecoupledEndpoint());
0852: if (reference != null) {
0853: String decoupledAddress = reference.getAddress().getValue();
0854: LOG
0855: .info("creating decoupled endpoint: "
0856: + decoupledAddress);
0857: try {
0858: decoupledDestination = getDestination(decoupledAddress);
0859: duplicateDecoupledDestination();
0860: } catch (Exception e) {
0861: // REVISIT move message to localizable Messages.properties
0862: LOG.log(Level.WARNING,
0863: "decoupled endpoint creation failed: ", e);
0864: }
0865: }
0866: }
0867:
0868: /**
0869: * @param address the address
0870: * @return a Destination for the address
0871: */
0872: private Destination getDestination(String address)
0873: throws IOException {
0874: Destination destination = null;
0875: DestinationFactoryManager factoryManager = bus
0876: .getExtension(DestinationFactoryManager.class);
0877: DestinationFactory factory = factoryManager
0878: .getDestinationFactoryForUri(address);
0879: if (factory != null) {
0880: EndpointInfo ei = new EndpointInfo();
0881: ei.setAddress(address);
0882: destination = factory.getDestination(ei);
0883: decoupledObserver = new InterposedMessageObserver();
0884: destination.setMessageObserver(decoupledObserver);
0885: }
0886: return destination;
0887: }
0888:
0889: /**
0890: * @return the decoupled observer
0891: */
0892: protected MessageObserver getDecoupledObserver() {
0893: return decoupledObserver;
0894: }
0895:
0896: private synchronized void duplicateDecoupledDestination() {
0897: decoupledDestinationRefCount++;
0898: }
0899:
0900: private synchronized void releaseDecoupledDestination() {
0901: if (--decoupledDestinationRefCount == 0) {
0902: LOG.log(Level.INFO, "shutting down decoupled destination");
0903: decoupledDestination.shutdown();
0904: }
0905: }
0906:
0907: /**
0908: * This predicate returns true iff the exchange indicates
0909: * a oneway MEP.
0910: *
0911: * @param exchange The exchange in question
0912: */
0913: private boolean isOneway(Exchange exchange) {
0914: return exchange != null && exchange.isOneWay();
0915: }
0916:
0917: /**
0918: * @return true if expecting a decoupled response
0919: */
0920: private boolean isDecoupled() {
0921: return decoupledDestination != null;
0922: }
0923:
0924: /**
0925: * Get an input stream containing the partial response if one is present.
0926: *
0927: * @param connection the connection in question
0928: * @param responseCode the response code
0929: * @return an input stream if a partial response is pending on the connection
0930: */
0931: protected static InputStream getPartialResponse(
0932: HttpURLConnection connection, int responseCode)
0933: throws IOException {
0934: InputStream in = null;
0935: if (responseCode == HttpURLConnection.HTTP_ACCEPTED
0936: || responseCode == HttpURLConnection.HTTP_OK) {
0937: if (connection.getContentLength() > 0) {
0938: in = connection.getInputStream();
0939: } else if (hasChunkedResponse(connection)
0940: || hasEofTerminatedResponse(connection)) {
0941: // ensure chunked or EOF-terminated response is non-empty
0942: in = getNonEmptyContent(connection);
0943: }
0944: }
0945: return in;
0946: }
0947:
0948: /**
0949: * @param connection the given HttpURLConnection
0950: * @return true iff the connection has a chunked response pending
0951: */
0952: private static boolean hasChunkedResponse(
0953: HttpURLConnection connection) {
0954: return HttpHeaderHelper.CHUNKED.equalsIgnoreCase(connection
0955: .getHeaderField(HttpHeaderHelper.TRANSFER_ENCODING));
0956: }
0957:
0958: /**
0959: * @param connection the given HttpURLConnection
0960: * @return true iff the connection has a chunked response pending
0961: */
0962: private static boolean hasEofTerminatedResponse(
0963: HttpURLConnection connection) {
0964: return HttpHeaderHelper.CLOSE.equalsIgnoreCase(connection
0965: .getHeaderField(HttpHeaderHelper.CONNECTION));
0966: }
0967:
0968: /**
0969: * @param connection the given HttpURLConnection
0970: * @return an input stream containing the response content if non-empty
0971: */
0972: private static InputStream getNonEmptyContent(
0973: HttpURLConnection connection) {
0974: InputStream in = null;
0975: try {
0976: PushbackInputStream pin = new PushbackInputStream(
0977: connection.getInputStream());
0978: int c = pin.read();
0979: if (c != -1) {
0980: pin.unread((byte) c);
0981: in = pin;
0982: }
0983: } catch (IOException ioe) {
0984: // ignore
0985: }
0986: return in;
0987: }
0988:
0989: /**
0990: * This method returns the Proxy server should it be set on the
0991: * Client Side Policy.
0992: *
0993: * @return The proxy server or null, if not set.
0994: */
0995: private Proxy getProxy(HTTPClientPolicy policy) {
0996: Proxy proxy = null;
0997: if (policy != null && policy.isSetProxyServer()) {
0998: proxy = new Proxy(Proxy.Type.valueOf(policy
0999: .getProxyServerType().toString()),
1000: new InetSocketAddress(policy.getProxyServer(),
1001: policy.getProxyServerPort()));
1002: }
1003: return proxy;
1004: }
1005:
1006: /**
1007: * This call places HTTP Header strings into the headers that are relevant
1008: * to the Authorization policies that are set on this conduit by
1009: * configuration.
1010: * <p>
1011: * An AuthorizationPolicy may also be set on the message. If so, those
1012: * policies are merged. A user name or password set on the messsage
1013: * overrides settings in the AuthorizationPolicy is retrieved from the
1014: * configuration.
1015: * <p>
1016: * The precedence is as follows:
1017: * 1. AuthorizationPolicy that is set on the Message, if exists.
1018: * 2. Preemptive UserPass from BasicAuthSupplier, if exists.
1019: * 3. AuthorizationPolicy set/configured for conduit.
1020: *
1021: * REVISIT: Since the AuthorizationPolicy is set on the message by class, then
1022: * how does one override the ProxyAuthorizationPolicy which is the same
1023: * type?
1024: *
1025: * @param message
1026: * @param headers
1027: */
1028: private void setHeadersByAuthorizationPolicy(Message message,
1029: URL url, Map<String, List<String>> headers) {
1030: AuthorizationPolicy authPolicy = getAuthorization();
1031:
1032: HttpBasicAuthSupplier.UserPass userpass = null;
1033: if (basicAuthSupplier != null) {
1034: userpass = basicAuthSupplier.getPreemptiveUserPass(
1035: getConduitName(), url, message);
1036: }
1037:
1038: AuthorizationPolicy newPolicy = message
1039: .get(AuthorizationPolicy.class);
1040: String userName = null;
1041: String passwd = null;
1042: if (null != newPolicy) {
1043: userName = newPolicy.getUserName();
1044: passwd = newPolicy.getPassword();
1045: }
1046: if (userName == null && userpass != null) {
1047: userName = userpass.getUserid();
1048: passwd = userpass.getPassword();
1049: }
1050: if (userName == null && authPolicy != null
1051: && authPolicy.isSetUserName()) {
1052: userName = authPolicy.getUserName();
1053: }
1054: if (userName != null) {
1055: if (passwd == null && authPolicy != null
1056: && authPolicy.isSetPassword()) {
1057: passwd = authPolicy.getPassword();
1058: }
1059: setBasicAuthHeader(userName, passwd, headers);
1060: } else if (authPolicy != null
1061: && authPolicy.isSetAuthorizationType()
1062: && authPolicy.isSetAuthorization()) {
1063: String type = authPolicy.getAuthorizationType();
1064: type += " ";
1065: type += authPolicy.getAuthorization();
1066: headers.put("Authorization", Arrays
1067: .asList(new String[] { type }));
1068: }
1069: AuthorizationPolicy proxyAuthPolicy = getProxyAuthorization();
1070: if (proxyAuthPolicy != null && proxyAuthPolicy.isSetUserName()) {
1071: userName = proxyAuthPolicy.getUserName();
1072: if (userName != null) {
1073: passwd = "";
1074: if (proxyAuthPolicy.isSetPassword()) {
1075: passwd = proxyAuthPolicy.getPassword();
1076: }
1077: setProxyBasicAuthHeader(userName, passwd, headers);
1078: } else if (proxyAuthPolicy.isSetAuthorizationType()
1079: && proxyAuthPolicy.isSetAuthorization()) {
1080: String type = proxyAuthPolicy.getAuthorizationType();
1081: type += " ";
1082: type += proxyAuthPolicy.getAuthorization();
1083: headers.put("Proxy-Authorization", Arrays
1084: .asList(new String[] { type }));
1085: }
1086: }
1087: }
1088:
1089: /**
1090: * This call places HTTP Header strings into the headers that are relevant
1091: * to the ClientPolicy that is set on this conduit by configuration.
1092: *
1093: * REVISIT: A cookie is set statically from configuration?
1094: */
1095: private void setHeadersByClientPolicy(Message message,
1096: Map<String, List<String>> headers) {
1097: HTTPClientPolicy policy = getClient(message);
1098: if (policy == null) {
1099: return;
1100: }
1101: if (policy.isSetCacheControl()) {
1102: headers.put("Cache-Control", Arrays
1103: .asList(new String[] { policy.getCacheControl()
1104: .value() }));
1105: }
1106: if (policy.isSetHost()) {
1107: headers.put("Host", Arrays.asList(new String[] { policy
1108: .getHost() }));
1109: }
1110: if (policy.isSetConnection()) {
1111: headers.put("Connection", Arrays
1112: .asList(new String[] { policy.getConnection()
1113: .value() }));
1114: }
1115: if (policy.isSetAccept()) {
1116: headers.put("Accept", Arrays.asList(new String[] { policy
1117: .getAccept() }));
1118: } else {
1119: headers.put("Accept", Arrays.asList(new String[] { "*" }));
1120: }
1121: if (policy.isSetAcceptEncoding()) {
1122: headers.put("Accept-Encoding",
1123: Arrays.asList(new String[] { policy
1124: .getAcceptEncoding() }));
1125: }
1126: if (policy.isSetAcceptLanguage()) {
1127: headers.put("Accept-Language",
1128: Arrays.asList(new String[] { policy
1129: .getAcceptLanguage() }));
1130: }
1131: if (policy.isSetContentType()) {
1132: headers.put(HttpHeaderHelper.CONTENT_TYPE, Arrays
1133: .asList(new String[] { policy.getContentType() }));
1134: }
1135: if (policy.isSetCookie()) {
1136: headers.put("Cookie", Arrays.asList(new String[] { policy
1137: .getCookie() }));
1138: }
1139: if (policy.isSetBrowserType()) {
1140: headers.put("BrowserType", Arrays
1141: .asList(new String[] { policy.getBrowserType() }));
1142: }
1143: if (policy.isSetReferer()) {
1144: headers.put("Referer", Arrays.asList(new String[] { policy
1145: .getReferer() }));
1146: }
1147: }
1148:
1149: /**
1150: * This call places HTTP Header strings into the headers that are relevant
1151: * to the polices that are set on this conduit by configuration for the
1152: * ClientPolicy and AuthorizationPolicy.
1153: *
1154: *
1155: * @param message The outgoing message.
1156: * @param url The URL the message is going to.
1157: * @param headers The headers in the outgoing message.
1158: */
1159: private void setHeadersByPolicy(Message message, URL url,
1160: Map<String, List<String>> headers) {
1161: setHeadersByAuthorizationPolicy(message, url, headers);
1162: setHeadersByClientPolicy(message, headers);
1163: }
1164:
1165: /**
1166: * This is part of the Configurable interface which retrieves the
1167: * configuration from spring injection.
1168: */
1169: // REVISIT:What happens when the endpoint/bean name is null?
1170: public String getBeanName() {
1171: if (endpointInfo.getName() != null) {
1172: return endpointInfo.getName().toString() + ".http-conduit";
1173: }
1174: return null;
1175: }
1176:
1177: /**
1178: * This method gets the Authorization Policy that was configured or
1179: * explicitly set for this HTTPConduit.
1180: */
1181: public AuthorizationPolicy getAuthorization() {
1182: return authorizationPolicy;
1183: }
1184:
1185: /**
1186: * This method is used to set the Authorization Policy for this conduit.
1187: * Using this method will override any Authorization Policy set in
1188: * configuration.
1189: */
1190: @Resource
1191: public void setAuthorization(AuthorizationPolicy authorization) {
1192: this .authorizationPolicy = authorization;
1193: }
1194:
1195: public HTTPClientPolicy getClient(Message message) {
1196: return PolicyUtils.getClient(message, clientSidePolicy);
1197: }
1198:
1199: /**
1200: * This method retrieves the Client Side Policy set/configured for this
1201: * HTTPConduit.
1202: */
1203: public HTTPClientPolicy getClient() {
1204: return clientSidePolicy;
1205: }
1206:
1207: /**
1208: * This method sets the Client Side Policy for this HTTPConduit. Using this
1209: * method will override any HTTPClientPolicy set in configuration.
1210: */
1211: @Resource
1212: public void setClient(HTTPClientPolicy client) {
1213: this .clientSidePolicy = client;
1214: }
1215:
1216: /**
1217: * This method retrieves the Proxy Authorization Policy for a proxy that is
1218: * set/configured for this HTTPConduit.
1219: */
1220: public ProxyAuthorizationPolicy getProxyAuthorization() {
1221: return proxyAuthorizationPolicy;
1222: }
1223:
1224: /**
1225: * This method sets the Proxy Authorization Policy for a specified proxy.
1226: * Using this method overrides any Authorization Policy for the proxy
1227: * that is set in the configuration.
1228: */
1229: @Resource
1230: public void setProxyAuthorization(
1231: ProxyAuthorizationPolicy proxyAuthorization) {
1232: this .proxyAuthorizationPolicy = proxyAuthorization;
1233: }
1234:
1235: /**
1236: * This method returns the TLS Client Parameters that is set/configured
1237: * for this HTTPConduit.
1238: */
1239: public TLSClientParameters getTlsClientParameters() {
1240: return tlsClientParameters;
1241: }
1242:
1243: /**
1244: * This method sets the TLS Client Parameters for this HTTPConduit.
1245: * Using this method overrides any TLS Client Parameters that is configured
1246: * for this HTTPConduit.
1247: */
1248: @Resource
1249: public void setTlsClientParameters(TLSClientParameters params) {
1250: this .tlsClientParameters = params;
1251: if (this .tlsClientParameters != null) {
1252: if (LOG.isLoggable(Level.FINE)) {
1253: LOG.log(Level.FINE, "Conduit '" + getConduitName()
1254: + "' has been (re) configured for TLS "
1255: + "keyManagers "
1256: + tlsClientParameters.getKeyManagers()
1257: + "trustManagers "
1258: + tlsClientParameters.getTrustManagers()
1259: + "secureRandom "
1260: + tlsClientParameters.getSecureRandom());
1261: }
1262: } else {
1263: if (LOG.isLoggable(Level.FINE)) {
1264: LOG.log(Level.FINE, "Conduit '" + getConduitName()
1265: + "' has been (re)configured for plain http.");
1266: }
1267: }
1268: // If this is called after the HTTPTransportFactory called
1269: // finalizeConfig, we need to update the connection factory.
1270: if (configFinalized) {
1271: retrieveConnectionFactory();
1272: }
1273: }
1274:
1275: /**
1276: * This method gets the Trust Decider that was set/configured for this
1277: * HTTPConduit.
1278: * @return The Message Trust Decider or null.
1279: */
1280: public MessageTrustDecider getTrustDecider() {
1281: return this .trustDecider;
1282: }
1283:
1284: /**
1285: * This method sets the Trust Decider for this HTTP Conduit.
1286: * Using this method overrides any trust decider configured for this
1287: * HTTPConduit.
1288: */
1289: @Resource
1290: public void setTrustDecider(MessageTrustDecider decider) {
1291: this .trustDecider = decider;
1292: }
1293:
1294: /**
1295: * This method gets the Basic Auth Supplier that was set/configured for this
1296: * HTTPConduit.
1297: * @return The Basic Auth Supplier or null.
1298: */
1299: public HttpBasicAuthSupplier getBasicAuthSupplier() {
1300: return this .basicAuthSupplier;
1301: }
1302:
1303: /**
1304: * This method sets the Trust Decider for this HTTP Conduit.
1305: * Using this method overrides any trust decider configured for this
1306: * HTTPConduit.
1307: */
1308: @Resource
1309: public void setBasicAuthSupplier(HttpBasicAuthSupplier supplier) {
1310: this .basicAuthSupplier = supplier;
1311: }
1312:
1313: /**
1314: * This function processes any retransmits at the direction of redirections
1315: * or "unauthorized" responses.
1316: * <p>
1317: * If the request was not retransmitted, it returns the given connection.
1318: * If the request was retransmitted, it returns the new connection on
1319: * which the request was sent.
1320: *
1321: * @param connection The active URL connection.
1322: * @param message The outgoing message.
1323: * @param cachedStream The cached request.
1324: * @return
1325: * @throws IOException
1326: */
1327: private HttpURLConnection processRetransmit(
1328: HttpURLConnection connection, Message message,
1329: CacheAndWriteOutputStream cachedStream) throws IOException {
1330:
1331: int responseCode = connection.getResponseCode();
1332:
1333: // Process Redirects first.
1334: switch (responseCode) {
1335: case HttpURLConnection.HTTP_MOVED_PERM:
1336: case HttpURLConnection.HTTP_MOVED_TEMP:
1337: connection = redirectRetransmit(connection, message,
1338: cachedStream);
1339: break;
1340: case HttpURLConnection.HTTP_UNAUTHORIZED:
1341: connection = authorizationRetransmit(connection, message,
1342: cachedStream);
1343: break;
1344: default:
1345: break;
1346: }
1347: return connection;
1348: }
1349:
1350: /**
1351: * This method performs a redirection retransmit in response to
1352: * a 302 or 305 response code.
1353: *
1354: * @param connection The active URL connection
1355: * @param message The outbound message.
1356: * @param cachedStream The cached request.
1357: * @return This method returns the new HttpURLConnection if
1358: * redirected. If it cannot be redirected for some reason
1359: * the same connection is returned.
1360: *
1361: * @throws IOException
1362: */
1363: private HttpURLConnection redirectRetransmit(
1364: HttpURLConnection connection, Message message,
1365: CacheAndWriteOutputStream cachedStream) throws IOException {
1366:
1367: // If we are not redirecting by policy, then we don't.
1368: if (!getClient().isAutoRedirect()) {
1369: return connection;
1370: }
1371:
1372: // We keep track of the redirections for redirect loop protection.
1373: Set<String> visitedURLs = getSetVisitedURLs(message);
1374:
1375: String lastURL = connection.getURL().toString();
1376: visitedURLs.add(lastURL);
1377:
1378: String newURL = extractLocation(connection.getHeaderFields());
1379: if (newURL != null) {
1380: // See if we are being redirected in a loop as best we can,
1381: // using string equality on URL.
1382: if (visitedURLs.contains(newURL)) {
1383: // We are in a redirect loop; -- bail
1384: if (LOG.isLoggable(Level.INFO)) {
1385: LOG.log(Level.INFO,
1386: "Redirect loop detected on Conduit \""
1387: + getConduitName() + "\" on '"
1388: + newURL + "'");
1389: }
1390: return connection;
1391: }
1392: // We are going to redirect.
1393: // Remove any Server Authentication Information for the previous
1394: // URL.
1395: Map<String, List<String>> headers = getSetProtocolHeaders(message);
1396: headers.remove("Authorization");
1397: headers.remove("Proxy-Authorization");
1398:
1399: URL url = new URL(newURL);
1400:
1401: // If user configured this Conduit with preemptive authorization
1402: // it is meant to make it to the end. (Too bad that information
1403: // went to every URL along the way, but that's what the user
1404: // wants!
1405: // TODO: Make this issue a security release note.
1406: setHeadersByAuthorizationPolicy(message, url, headers);
1407:
1408: connection = retransmit(connection, url, message,
1409: cachedStream);
1410: }
1411: return connection;
1412: }
1413:
1414: /**
1415: * This function gets the Set of URLs on the message that is used to
1416: * keep track of the URLs that were used in getting authorization
1417: * information.
1418: *
1419: * @param message The message where the Set of URLs is stored.
1420: * @return The modifiable set of URLs that were visited.
1421: */
1422: private Set<String> getSetAuthoriationURLs(Message message) {
1423: @SuppressWarnings("unchecked")
1424: Set<String> authURLs = (Set<String>) message.get(KEY_AUTH_URLS);
1425: if (authURLs == null) {
1426: authURLs = new HashSet<String>();
1427: message.put(KEY_AUTH_URLS, authURLs);
1428: }
1429: return authURLs;
1430: }
1431:
1432: /**
1433: * This function get the set of URLs on the message that is used to keep
1434: * track of the URLs that were visited in redirects.
1435: *
1436: * If it is not set on the message, an new empty set is stored.
1437: * @param message The message where the Set is stored.
1438: * @return The modifiable set of URLs that were visited.
1439: */
1440: private Set<String> getSetVisitedURLs(Message message) {
1441: @SuppressWarnings("unchecked")
1442: Set<String> visitedURLs = (Set<String>) message
1443: .get(KEY_VISITED_URLS);
1444: if (visitedURLs == null) {
1445: visitedURLs = new HashSet<String>();
1446: message.put(KEY_VISITED_URLS, visitedURLs);
1447: }
1448: return visitedURLs;
1449: }
1450:
1451: /**
1452: * This method performs a retransmit for authorization information.
1453: *
1454: * @param connection The currently active connection.
1455: * @param message The outbound message.
1456: * @param cachedStream The cached request.
1457: * @return A new connection if retransmitted. If not retransmitted
1458: * then this method returns the same connection.
1459: * @throws IOException
1460: */
1461: private HttpURLConnection authorizationRetransmit(
1462: HttpURLConnection connection, Message message,
1463: CacheAndWriteOutputStream cachedStream) throws IOException {
1464:
1465: // If we don't have a dynamic supply of user pass, then
1466: // we don't retransmit. We just die with a Http 401 response.
1467: if (basicAuthSupplier == null) {
1468: return connection;
1469: }
1470:
1471: URL currentURL = connection.getURL();
1472:
1473: String realm = extractAuthorizationRealm(connection
1474: .getHeaderFields());
1475:
1476: Set<String> authURLs = getSetAuthoriationURLs(message);
1477:
1478: // If we have been here (URL & Realm) before for this particular message
1479: // retransmit, it means we have already supplied information
1480: // which must have been wrong, or we wouldn't be here again.
1481: // Otherwise, the server may be 401 looping us around the realms.
1482: if (authURLs.contains(currentURL.toString() + realm)) {
1483:
1484: if (LOG.isLoggable(Level.INFO)) {
1485: LOG.log(Level.INFO,
1486: "Authorization loop detected on Conduit \""
1487: + getConduitName() + "\" on URL \""
1488: + "\" with realm \"" + realm + "\"");
1489: }
1490:
1491: return connection;
1492: }
1493:
1494: HttpBasicAuthSupplier.UserPass up = basicAuthSupplier
1495: .getUserPassForRealm(getConduitName(), currentURL,
1496: message, realm);
1497:
1498: // No user pass combination. We give up.
1499: if (up == null) {
1500: return connection;
1501: }
1502:
1503: // Register that we have been here before we go.
1504: authURLs.add(currentURL.toString() + realm);
1505:
1506: Map<String, List<String>> headers = getSetProtocolHeaders(message);
1507:
1508: setBasicAuthHeader(up.getUserid(), up.getPassword(), headers);
1509:
1510: return retransmit(connection, currentURL, message, cachedStream);
1511: }
1512:
1513: /**
1514: * This method retransmits the request.
1515: *
1516: * @param connection The currently active connection.
1517: * @param newURL The newURL to connection to.
1518: * @param message The outbound message.
1519: * @param stream The cached request.
1520: * @return This function returns a new connection if
1521: * retransmitted, otherwise it returns the given
1522: * connection.
1523: *
1524: * @throws IOException
1525: */
1526: private HttpURLConnection retransmit(HttpURLConnection connection,
1527: URL newURL, Message message,
1528: CacheAndWriteOutputStream stream) throws IOException {
1529:
1530: // Disconnect the old, and in with the new.
1531: connection.disconnect();
1532:
1533: connection = connectionFactory.createConnection(
1534: getProxy(clientSidePolicy), newURL);
1535:
1536: connection.setDoOutput(true);
1537: // TODO: using Message context to deceided HTTP send properties
1538: connection.setConnectTimeout((int) getClient()
1539: .getConnectionTimeout());
1540: connection
1541: .setReadTimeout((int) getClient().getReceiveTimeout());
1542: connection.setUseCaches(false);
1543: connection.setInstanceFollowRedirects(false);
1544:
1545: // If the HTTP_REQUEST_METHOD is not set, the default is "POST".
1546: String httpRequestMethod = (String) message
1547: .get(Message.HTTP_REQUEST_METHOD);
1548:
1549: if (null != httpRequestMethod) {
1550: connection.setRequestMethod(httpRequestMethod);
1551: } else {
1552: connection.setRequestMethod("POST");
1553: }
1554: message.put(KEY_HTTP_CONNECTION, connection);
1555:
1556: // Need to set the headers before the trust decision
1557: // because they are set before the connect().
1558: setURLRequestHeaders(message);
1559:
1560: //
1561: // This point is where the trust decision is made because the
1562: // Sun implementation of URLConnection will not let us
1563: // set/addRequestProperty after a connect() call, and
1564: // makeTrustDecision needs to make a connect() call to
1565: // make sure the proper information is available.
1566: //
1567: makeTrustDecision(message);
1568:
1569: // If this is a GET method we must not touch the output
1570: // stream as this automagically turns the request into a POST.
1571: if (connection.getRequestMethod().equals("GET")) {
1572: return connection;
1573: }
1574:
1575: // Trust is okay, write the cached request.
1576: OutputStream out = connection.getOutputStream();
1577:
1578: CacheAndWriteOutputStream.copyStream(stream.getInputStream(),
1579: out, 2048);
1580: out.close();
1581:
1582: if (LOG.isLoggable(Level.FINE)) {
1583: StringBuffer sbuf = new StringBuffer();
1584: StringBufferOutputStream sout = new StringBufferOutputStream(
1585: sbuf);
1586: CacheAndWriteOutputStream.copyStream(stream
1587: .getInputStream(), sout, 2048);
1588: sout.close();
1589:
1590: LOG.fine("Conduit \"" + getConduitName()
1591: + "\" Retransmit message to: "
1592: + connection.getURL() + ": " + sbuf);
1593: }
1594: return connection;
1595: }
1596:
1597: /**
1598: * This function extracts the authorization realm from the
1599: * "WWW-Authenticate" Http response header.
1600: *
1601: * @param headers The Http Response Headers
1602: * @return The realm, or null if it is non-existent.
1603: */
1604: private String extractAuthorizationRealm(
1605: Map<String, List<String>> headers) {
1606: List<String> auth = headers.get("WWW-Authenticate");
1607: if (auth != null) {
1608: for (String a : auth) {
1609: if (a.startsWith("Basic realm=")) {
1610: return a.substring(a.indexOf("=") + 1);
1611: }
1612: }
1613: }
1614: return null;
1615: }
1616:
1617: /**
1618: * This method extracts the value of the "Location" Http
1619: * Response header.
1620: *
1621: * @param headers The Http response headers.
1622: * @return The value of the "Location" header, null if non-existent.
1623: */
1624: private String extractLocation(Map<String, List<String>> headers) {
1625: List<String> locs = headers.get("Location");
1626: if (locs != null && locs.size() > 0) {
1627: return locs.get(0);
1628: }
1629: return null;
1630: }
1631:
1632: /**
1633: * This procedure sets the "Authorization" header with the
1634: * BasicAuth token, which is Base64 encoded.
1635: *
1636: * @param userid The user's id, which cannot be null.
1637: * @param password The password, it may be null.
1638: *
1639: * @param headers The headers map that gets the "Authorization" header set.
1640: */
1641: private void setBasicAuthHeader(String userid, String password,
1642: Map<String, List<String>> headers) {
1643: String userpass = userid;
1644:
1645: userpass += ":";
1646: if (password != null) {
1647: userpass += password;
1648: }
1649: String token = Base64Utility.encode(userpass.getBytes());
1650: headers.put("Authorization", Arrays
1651: .asList(new String[] { "Basic " + token }));
1652: }
1653:
1654: /**
1655: * This procedure sets the "ProxyAuthorization" header with the
1656: * BasicAuth token, which is Base64 encoded.
1657: *
1658: * @param userid The user's id, which cannot be null.
1659: * @param password The password, it may be null.
1660: *
1661: * @param headers The headers map that gets the "Proxy-Authorization"
1662: * header set.
1663: */
1664: private void setProxyBasicAuthHeader(String userid,
1665: String password, Map<String, List<String>> headers) {
1666: String userpass = userid;
1667:
1668: userpass += ":";
1669: if (password != null) {
1670: userpass += password;
1671: }
1672: String token = Base64Utility.encode(userpass.getBytes());
1673: headers.put("Proxy-Authorization", Arrays
1674: .asList(new String[] { "Basic " + token }));
1675: }
1676:
1677: /**
1678: * Wrapper output stream responsible for flushing headers and handling
1679: * the incoming HTTP-level response (not necessarily the MEP response).
1680: */
1681: private class WrappedOutputStream extends
1682: AbstractWrappedOutputStream {
1683: /**
1684: * This field contains the currently active connection.
1685: */
1686: private HttpURLConnection connection;
1687:
1688: /**
1689: * This boolean is true if the request must be cached.
1690: */
1691: private boolean cachingForRetransmision;
1692:
1693: /**
1694: * If we are going to be chunking, we won't flush till close which causes
1695: * new chunks, small network packets, etc..
1696: */
1697: private final boolean chunking;
1698:
1699: /**
1700: * This field contains the output stream with which we cache
1701: * the request. It maybe null if we are not caching.
1702: */
1703: private CacheAndWriteOutputStream cachedStream;
1704:
1705: private Message outMessage;
1706:
1707: WrappedOutputStream(Message m, HttpURLConnection c,
1708: boolean possibleRetransmit, boolean isChunking) {
1709: super ();
1710: this .outMessage = m;
1711: connection = c;
1712: cachingForRetransmision = possibleRetransmit;
1713: chunking = isChunking;
1714: }
1715:
1716: /**
1717: * Perform any actions required on stream flush (freeze headers,
1718: * reset output stream ... etc.)
1719: */
1720: @Override
1721: protected void onFirstWrite() throws IOException {
1722: handleHeadersTrustCaching();
1723: }
1724:
1725: protected void handleHeadersTrustCaching() throws IOException {
1726: // Need to set the headers before the trust decision
1727: // because they are set before the connect().
1728: setURLRequestHeaders(outMessage);
1729:
1730: //
1731: // This point is where the trust decision is made because the
1732: // Sun implementation of URLConnection will not let us
1733: // set/addRequestProperty after a connect() call, and
1734: // makeTrustDecision needs to make a connect() call to
1735: // make sure the proper information is available.
1736: //
1737: makeTrustDecision(outMessage);
1738:
1739: // Trust is okay, set up for writing the request.
1740:
1741: // If this is a GET method we must not touch the output
1742: // stream as this automagically turns the reqest into a POST.
1743: if (connection.getRequestMethod().equals("GET")) {
1744: return;
1745: }
1746:
1747: // If we need to cache for retransmission, store data in a
1748: // CacheAndWriteOutputStream. Otherwise write directly to the output stream.
1749: if (cachingForRetransmision) {
1750: cachedStream = new CacheAndWriteOutputStream(connection
1751: .getOutputStream());
1752: wrappedStream = cachedStream;
1753: } else {
1754: wrappedStream = connection.getOutputStream();
1755: }
1756: }
1757:
1758: public void flush() throws IOException {
1759: if (!chunking) {
1760: super .flush();
1761: }
1762: }
1763:
1764: /**
1765: * Perform any actions required on stream closure (handle response etc.)
1766: */
1767: public void close() throws IOException {
1768: if (!written) {
1769: handleHeadersTrustCaching();
1770: }
1771: super .flush();
1772: super .close();
1773: handleResponse();
1774: }
1775:
1776: /**
1777: * This procedure handles all retransmits, if any.
1778: *
1779: * @throws IOException
1780: */
1781: private void handleRetransmits() throws IOException {
1782: // If we have a cachedStream, we are caching the request.
1783: if (cachedStream != null) {
1784:
1785: if (LOG.isLoggable(Level.FINE)) {
1786: StringBuffer sbuf = new StringBuffer();
1787: StringBufferOutputStream sout = new StringBufferOutputStream(
1788: sbuf);
1789: IOUtils.copy(cachedStream.getInputStream(), sout,
1790: 2048);
1791: sout.close();
1792:
1793: LOG.fine("Conduit \"" + getConduitName()
1794: + "\" Transmit cached message to: "
1795: + connection.getURL() + ": " + sbuf);
1796: }
1797:
1798: HttpURLConnection oldcon = connection;
1799:
1800: HTTPClientPolicy policy = getClient();
1801:
1802: // Default MaxRetransmits is -1 which means unlimited.
1803: int maxRetransmits = (policy == null) ? -1 : policy
1804: .getMaxRetransmits();
1805:
1806: // MaxRetransmits of zero means zero.
1807: if (maxRetransmits == 0) {
1808: return;
1809: }
1810:
1811: int nretransmits = 0;
1812:
1813: connection = processRetransmit(connection, outMessage,
1814: cachedStream);
1815:
1816: while (connection != oldcon) {
1817: nretransmits++;
1818: oldcon = connection;
1819:
1820: // A negative max means unlimited.
1821: if (maxRetransmits < 0
1822: || nretransmits < maxRetransmits) {
1823: connection = processRetransmit(connection,
1824: outMessage, cachedStream);
1825: }
1826: }
1827: }
1828: }
1829:
1830: /**
1831: * This procedure is called on the close of the output stream so
1832: * we are ready to handle the response from the connection.
1833: * We may retransmit until we finally get a response.
1834: *
1835: * @throws IOException
1836: */
1837: private void handleResponse() throws IOException {
1838:
1839: // Process retransmits until we fall out.
1840: handleRetransmits();
1841:
1842: int responseCode = connection.getResponseCode();
1843:
1844: if (LOG.isLoggable(Level.FINE)) {
1845: LOG.fine("Response Code: " + responseCode
1846: + " Conduit: " + getConduitName());
1847: LOG.fine("Content length: "
1848: + connection.getContentLength());
1849: Map<String, List<String>> headerFields = connection
1850: .getHeaderFields();
1851: if (null != headerFields) {
1852: StringBuffer buf = new StringBuffer();
1853: buf.append("Header fields: ");
1854: buf.append(System.getProperty("line.separator"));
1855: for (String h : headerFields.keySet()) {
1856: buf.append(" ");
1857: buf.append(h);
1858: buf.append(": ");
1859: buf.append(headerFields.get(h));
1860: buf
1861: .append(System
1862: .getProperty("line.separator"));
1863: }
1864: LOG.fine(buf.toString());
1865: }
1866: }
1867:
1868: if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
1869: throw new IOException(connection.getResponseMessage());
1870: }
1871:
1872: Exchange exchange = outMessage.getExchange();
1873:
1874: InputStream in = null;
1875: if (isOneway(exchange) || isDecoupled()) {
1876: in = getPartialResponse(connection, responseCode);
1877: if (in == null) {
1878: // oneway operation or decoupled MEP without
1879: // partial response
1880: connection.getInputStream().close();
1881: return;
1882: }
1883: }
1884:
1885: Message inMessage = new MessageImpl();
1886: inMessage.setExchange(exchange);
1887:
1888: Map<String, List<String>> headers = new HashMap<String, List<String>>();
1889: for (String key : connection.getHeaderFields().keySet()) {
1890: headers.put(HttpHeaderHelper.getHeaderKey(key),
1891: connection.getHeaderFields().get(key));
1892: }
1893: inMessage.put(Message.PROTOCOL_HEADERS, headers);
1894: inMessage.put(Message.RESPONSE_CODE, responseCode);
1895: inMessage.put(Message.CONTENT_TYPE, connection
1896: .getContentType());
1897: inMessage.put(Message.ENCODING, connection
1898: .getContentEncoding());
1899:
1900: if (maintainSession) {
1901: String cookieStr = connection
1902: .getHeaderField("Set-Cookie");
1903: if (cookieStr != null) {
1904: String cookies[] = cookieStr.split(";");
1905: for (int i = 0; i < cookies.length; i++) {
1906: String nameValue[] = cookies[i].split("=");
1907: if (nameValue[0].equals("JSESSIONID")
1908: || nameValue[0].equals("jsessionid")) {
1909: sessionId = nameValue[1];
1910: }
1911: }
1912: }
1913: }
1914:
1915: in = in == null ? connection.getErrorStream() == null ? connection
1916: .getInputStream()
1917: : connection.getErrorStream()
1918: : in;
1919:
1920: if (in == null) {
1921: LOG.log(Level.WARNING, "Input Stream is null!");
1922: }
1923: inMessage.setContent(InputStream.class, in);
1924:
1925: incomingObserver.onMessage(inMessage);
1926: }
1927: }
1928:
1929: /**
1930: * Used to set appropriate message properties, exchange etc.
1931: * as required for an incoming decoupled response (as opposed
1932: * what's normally set by the Destination for an incoming
1933: * request).
1934: */
1935: protected class InterposedMessageObserver implements
1936: MessageObserver {
1937: /**
1938: * Called for an incoming message.
1939: *
1940: * @param inMessage
1941: */
1942: public void onMessage(Message inMessage) {
1943: // disposable exchange, swapped with real Exchange on correlation
1944: inMessage.setExchange(new ExchangeImpl());
1945: inMessage.put(DECOUPLED_CHANNEL_MESSAGE, Boolean.TRUE);
1946: // REVISIT: how to get response headers?
1947: //inMessage.put(Message.PROTOCOL_HEADERS, req.getXXX());
1948: getSetProtocolHeaders(inMessage);
1949: inMessage.put(Message.RESPONSE_CODE,
1950: HttpURLConnection.HTTP_OK);
1951:
1952: // remove server-specific properties
1953: inMessage.remove(AbstractHTTPDestination.HTTP_REQUEST);
1954: inMessage.remove(AbstractHTTPDestination.HTTP_RESPONSE);
1955: inMessage.remove(Message.ASYNC_POST_RESPONSE_DISPATCH);
1956:
1957: incomingObserver.onMessage(inMessage);
1958: }
1959: }
1960:
1961: public void assertMessage(Message message) {
1962: PolicyUtils.assertClientPolicy(message, clientSidePolicy);
1963: }
1964:
1965: public boolean canAssert(QName type) {
1966: return PolicyUtils.HTTPCLIENTPOLICY_ASSERTION_QNAME
1967: .equals(type);
1968: }
1969:
1970: }
|