1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.cxf.transport.http;
20
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.io.PushbackInputStream;
26 import java.net.HttpRetryException;
27 import java.net.HttpURLConnection;
28 import java.net.InetSocketAddress;
29 import java.net.MalformedURLException;
30 import java.net.Proxy;
31 import java.net.URL;
32 import java.net.URLConnection;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.LinkedHashMap;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.concurrent.ConcurrentHashMap;
42 import java.util.logging.Level;
43 import java.util.logging.Logger;
44
45 import javax.xml.namespace.QName;
46
47 import org.apache.cxf.Bus;
48 import org.apache.cxf.common.logging.LogUtils;
49 import org.apache.cxf.common.util.Base64Utility;
50 import org.apache.cxf.common.util.StringUtils;
51 import org.apache.cxf.configuration.Configurable;
52 import org.apache.cxf.configuration.jsse.TLSClientParameters;
53 import org.apache.cxf.configuration.security.AuthorizationPolicy;
54 import org.apache.cxf.configuration.security.CertificateConstraintsType;
55 import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy;
56 import org.apache.cxf.helpers.CastUtils;
57 import org.apache.cxf.helpers.HttpHeaderHelper;
58 import org.apache.cxf.helpers.IOUtils;
59 import org.apache.cxf.helpers.LoadingByteArrayOutputStream;
60 import org.apache.cxf.io.AbstractThresholdOutputStream;
61 import org.apache.cxf.io.CacheAndWriteOutputStream;
62 import org.apache.cxf.io.CachedOutputStream;
63 import org.apache.cxf.message.Exchange;
64 import org.apache.cxf.message.ExchangeImpl;
65 import org.apache.cxf.message.Message;
66 import org.apache.cxf.message.MessageImpl;
67 import org.apache.cxf.message.MessageUtils;
68 import org.apache.cxf.phase.PhaseInterceptorChain;
69 import org.apache.cxf.service.model.EndpointInfo;
70 import org.apache.cxf.transport.AbstractConduit;
71 import org.apache.cxf.transport.Destination;
72 import org.apache.cxf.transport.DestinationFactory;
73 import org.apache.cxf.transport.DestinationFactoryManager;
74 import org.apache.cxf.transport.MessageObserver;
75 import org.apache.cxf.transport.http.policy.PolicyUtils;
76 import org.apache.cxf.transport.https.CertConstraints;
77 import org.apache.cxf.transport.https.CertConstraintsInterceptor;
78 import org.apache.cxf.transport.https.CertConstraintsJaxBUtils;
79 import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
80 import org.apache.cxf.version.Version;
81 import org.apache.cxf.workqueue.AutomaticWorkQueue;
82 import org.apache.cxf.workqueue.WorkQueueManager;
83 import org.apache.cxf.ws.addressing.EndpointReferenceType;
84 import org.apache.cxf.ws.policy.Assertor;
85 import org.apache.cxf.ws.policy.PolicyEngine;
86 import org.apache.cxf.wsdl.EndpointReferenceUtils;
87
88 import static org.apache.cxf.message.Message.DECOUPLED_CHANNEL_MESSAGE;
89
90
91 /*
92 * HTTP Conduit implementation.
93 * <p>
94 * This implementation is a based on the java.net.URLConnection interface and
95 * dependent upon installed implementations of that URLConnection,
96 * HttpURLConnection, and HttpsURLConnection. Currently, this implementation
97 * has been known to work with the Sun JDK 1.5 default implementations. The
98 * HttpsURLConnection is part of Sun's implementation of the JSSE.
99 * Presently, the source code for the Sun JSSE implementation is unavailable
100 * and therefore we may only lay a guess of whether its HttpsURLConnection
101 * implementation correctly works as far as security is concerned.
102 * <p>
103 * The Trust Decision. If a MessageTrustDecider is configured/set for the
104 * Conduit, it is called upon the first flush of the headers in the
105 * WrappedOutputStream. This reason for this approach is two-fold.
106 * Theoretically, in order to get connection information out of the
107 * URLConnection, it must be "connected". We assume that its implementation will
108 * only follow through up to the point at which it will be ready to send
109 * one byte of data down to the endpoint, but through proxies, and the
110 * commpletion of a TLS handshake in the case of HttpsURLConnection.
111 * However, if we force the connect() call right away, the default
112 * implementations will not allow any calls to add/setRequestProperty,
113 * throwing an exception that the URLConnection is already connected.
114 * <p>
115 * We need to keep the semantic that later CXF interceptors may add to the
116 * PROTOCOL_HEADERS in the Message. This architectual decision forces us to
117 * delay the connection until after that point, then pulling the trust decision.
118 * <p>
119 * The security caveat is that we don't really know when the connection is
120 * really established. The call to "connect" is stated to force the
121 * "connection," but it is a no-op if the connection was already established.
122 * It is entirely possible that an implementation of an URLConnection may
123 * indeed connect at will and start sending the headers down the connection
124 * during calls to add/setRequestProperty!
125 * <p>
126 * We know that the JDK 1.5 sun.com.net.www.HttpURLConnection does not send
127 * this information before the "connect" call, because we can look at the
128 * source code. However, we can only assume, not verify, that the JSSE 1.5
129 * HttpsURLConnection does the same, in that it is probable that the
130 * HttpsURLConnection shares the HttpURLConnection implementation.
131 * <p>
132 * Due to these implementations following redirects without trust checks, we
133 * force the URLConnection implementations not to follow redirects. If
134 * client side policy dictates that we follow redirects, trust decisions are
135 * placed before each retransmit. On a redirect, any authorization information
136 * dynamically acquired by a BasicAuth UserPass supplier is removed before
137 * being retransmitted, as it may no longer be applicable to the new url to
138 * which the connection is redirected.
139 */
140
141 /**
142 * This Conduit handles the "http" and "https" transport protocols. An
143 * instance is governed by policies either explicitly set or by
144 * configuration.
145 */
146 public class HTTPConduit
147 extends AbstractConduit
148 implements Configurable, Assertor {
149
150 /**
151 * This constant is the Message(Map) key for the HttpURLConnection that
152 * is used to get the response.
153 */
154 public static final String KEY_HTTP_CONNECTION = "http.connection";
155
156 /**
157 * This constant is the Message(Map) key for a list of visited URLs that
158 * is used in redirect loop protection.
159 */
160 private static final String KEY_VISITED_URLS = "VisitedURLs";
161
162 /**
163 * This constant is the Message(Map) key for a list of URLs that
164 * is used in authorization loop protection.
165 */
166 private static final String KEY_AUTH_URLS = "AuthURLs";
167
168 /**
169 * The Logger for this class.
170 */
171 private static final Logger LOG = LogUtils.getL7dLogger(HTTPConduit.class);
172
173 /**
174 * This constant holds the suffix ".http-conduit" that is appended to the
175 * Endpoint Qname to give the configuration name of this conduit.
176 */
177 private static final String SC_HTTP_CONDUIT_SUFFIX = ".http-conduit";
178
179 /**
180 * This field holds the connection factory, which primarily is used to
181 * factor out SSL specific code from this implementation.
182 * <p>
183 * This field is "protected" to facilitate some contrived UnitTesting so
184 * that an extended class may alter its value with an EasyMock URLConnection
185 * Factory.
186 */
187 protected HttpURLConnectionFactory connectionFactory;
188
189 /**
190 * This field holds a reference to the CXF bus associated this conduit.
191 */
192 private final Bus bus;
193
194 /**
195 * This field is used for two reasons. First it provides the base name for
196 * the conduit for Spring configuration. The other is to hold default
197 * address information, should it not be supplied in the Message Map, by the
198 * Message.ENDPOINT_ADDRESS property.
199 */
200 private final EndpointInfo endpointInfo;
201
202
203 /**
204 * This field holds the "default" URL for this particular conduit, which
205 * is created on demand.
206 */
207 private URL defaultEndpointURL;
208 private String defaultEndpointURLString;
209 private boolean fromEndpointReferenceType;
210
211 private Destination decoupledDestination;
212 private MessageObserver decoupledObserver;
213 private int decoupledDestinationRefCount;
214
215 // Configurable values
216
217 /**
218 * This field holds the QoS configuration settings for this conduit.
219 * This field is injected via spring configuration based on the conduit
220 * name.
221 */
222 private HTTPClientPolicy clientSidePolicy;
223
224 /**
225 * This field holds the password authorization configuration.
226 * This field is injected via spring configuration based on the conduit
227 * name.
228 */
229 private AuthorizationPolicy authorizationPolicy;
230
231 /**
232 * This field holds the password authorization configuration for the
233 * configured proxy. This field is injected via spring configuration based
234 * on the conduit name.
235 */
236 private ProxyAuthorizationPolicy proxyAuthorizationPolicy;
237
238 /**
239 * This field holds the configuration TLS configuration which
240 * is programmatically configured.
241 */
242 private TLSClientParameters tlsClientParameters;
243
244 /**
245 * This field contains the MessageTrustDecider.
246 */
247 private MessageTrustDecider trustDecider;
248
249 /**
250 * This field contains the HttpAuthSupplier.
251 */
252 private HttpAuthSupplier authSupplier;
253
254 /**
255 * This boolean signfies that that finalizeConfig is called, which is
256 * after the HTTPTransportFactory configures this object via spring.
257 * At this point, any change by a "setter" is dynamic, and any change
258 * should be handled as such.
259 */
260 private boolean configFinalized;
261
262 /**
263 * Variables for holding session state if sessions are supposed to be maintained
264 */
265 private Map<String, Cookie> sessionCookies = new ConcurrentHashMap<String, Cookie>();
266 private boolean maintainSession;
267
268 private CertConstraints certConstraints;
269
270 /**
271 * Constructor
272 *
273 * @param b the associated Bus
274 * @param ei the endpoint info of the initiator
275 * @throws IOException
276 */
277 public HTTPConduit(Bus b, EndpointInfo ei) throws IOException {
278 this(b,
279 ei,
280 null);
281 }
282
283 /**
284 * Constructor
285 *
286 * @param b the associated Bus.
287 * @param endpoint the endpoint info of the initiator.
288 * @param t the endpoint reference of the target.
289 * @throws IOException
290 */
291 public HTTPConduit(Bus b,
292 EndpointInfo ei,
293 EndpointReferenceType t) throws IOException {
294 super(getTargetReference(ei, t, b));
295
296 bus = b;
297 endpointInfo = ei;
298
299 if (t != null) {
300 fromEndpointReferenceType = true;
301 }
302
303 initializeConfig();
304 CXFAuthenticator.addAuthenticator();
305 }
306
307 /**
308 * This method returns the registered Logger for this conduit.
309 */
310 protected Logger getLogger() {
311 return LOG;
312 }
313
314 /**
315 * This method returns the name of the conduit, which is based on the
316 * endpoint name plus the SC_HTTP_CONDUIT_SUFFIX.
317 * @return
318 */
319 public final String getConduitName() {
320 return endpointInfo.getName() + SC_HTTP_CONDUIT_SUFFIX;
321 }
322
323 /**
324 * This method is called from the constructor which initializes
325 * the configuration. The TransportFactory will call configureBean
326 * on this object after construction.
327 */
328 private void initializeConfig() {
329
330 // wsdl extensors are superseded by policies which in
331 // turn are superseded by injection
332
333 PolicyEngine pe = bus.getExtension(PolicyEngine.class);
334 if (null != pe && pe.isEnabled() && endpointInfo.getService() != null) {
335 clientSidePolicy =
336 PolicyUtils.getClient(pe, endpointInfo, this);
337 }
338
339 }
340
341 /**
342 * This call gets called by the HTTPTransportFactory after it
343 * causes an injection of the Spring configuration properties
344 * of this Conduit.
345 */
346 protected void finalizeConfig() {
347 // See if not set by configuration, if there are defaults
348 // in order from the Endpoint, Service, or Bus.
349
350 if (this.clientSidePolicy == null) {
351 clientSidePolicy = endpointInfo.getTraversedExtensor(
352 new HTTPClientPolicy(), HTTPClientPolicy.class);
353 }
354 if (this.authorizationPolicy == null) {
355 authorizationPolicy = endpointInfo.getTraversedExtensor(
356 new AuthorizationPolicy(), AuthorizationPolicy.class);
357
358 }
359 if (this.proxyAuthorizationPolicy == null) {
360 proxyAuthorizationPolicy = endpointInfo.getTraversedExtensor(
361 new ProxyAuthorizationPolicy(), ProxyAuthorizationPolicy.class);
362
363 }
364 if (this.tlsClientParameters == null) {
365 tlsClientParameters = endpointInfo.getTraversedExtensor(
366 null, TLSClientParameters.class);
367 }
368 if (this.trustDecider == null) {
369 trustDecider = endpointInfo.getTraversedExtensor(
370 null, MessageTrustDecider.class);
371 }
372 if (this.authSupplier == null) {
373 authSupplier = endpointInfo.getTraversedExtensor(
374 null, HttpAuthSupplier.class);
375 }
376 if (trustDecider == null) {
377 if (LOG.isLoggable(Level.FINE)) {
378 LOG.log(Level.FINE,
379 "No Trust Decider configured for Conduit '"
380 + getConduitName() + "'");
381 }
382 } else {
383 if (LOG.isLoggable(Level.FINE)) {
384 LOG.log(Level.FINE, "Message Trust Decider of class '"
385 + trustDecider.getClass().getName()
386 + "' with logical name of '"
387 + trustDecider.getLogicalName()
388 + "' has been configured for Conduit '"
389 + getConduitName()
390 + "'");
391 }
392 }
393 if (authSupplier == null) {
394 if (LOG.isLoggable(Level.FINE)) {
395 LOG.log(Level.FINE,
396 "No Auth Supplier configured for Conduit '"
397 + getConduitName() + "'");
398 }
399 } else {
400 if (LOG.isLoggable(Level.FINE)) {
401 LOG.log(Level.FINE, "HttpAuthSupplier of class '"
402 + authSupplier.getClass().getName()
403 + "' with logical name of '"
404 + authSupplier.getLogicalName()
405 + "' has been configured for Conduit '"
406 + getConduitName()
407 + "'");
408 }
409 }
410 if (this.tlsClientParameters != null) {
411 if (LOG.isLoggable(Level.FINE)) {
412 LOG.log(Level.FINE, "Conduit '" + getConduitName()
413 + "' has been configured for TLS "
414 + "keyManagers " + Arrays.toString(tlsClientParameters.getKeyManagers())
415 + "trustManagers " + Arrays.toString(tlsClientParameters.getTrustManagers())
416 + "secureRandom " + tlsClientParameters.getSecureRandom()
417 + "Disable Common Name (CN) Check: " + tlsClientParameters.isDisableCNCheck());
418 }
419 } else {
420 if (LOG.isLoggable(Level.FINE)) {
421 LOG.log(Level.FINE, "Conduit '" + getConduitName()
422 + "' has been configured for plain http.");
423 }
424 }
425
426 // Get the correct URLConnection factory based on the
427 // configuration.
428 retrieveConnectionFactory(getAddress());
429
430 // We have finalized the configuration. Any configurable entity
431 // set now, must make changes dynamically.
432 configFinalized = true;
433 }
434
435 /**
436 * Allow access to the cookies that the conduit is maintaining
437 * @return the sessionCookies map
438 */
439 public Map<String, Cookie> getCookies() {
440 return sessionCookies;
441 }
442
443 /**
444 * This method sets the connectionFactory field for this object. It is called
445 * after an SSL Client Policy is set or an HttpsHostnameVerifier
446 * because we need to reinitialize the connection factory.
447 * <p>
448 * This method is "protected" so that this class may be extended and override
449 * this method to put an EasyMock URL Connection factory for some contrived
450 * UnitTest that will of course break, should the calls to the URL Connection
451 * Factory get altered.
452 */
453 protected synchronized void retrieveConnectionFactory() {
454 connectionFactory = AbstractHTTPTransportFactory.getConnectionFactory(this, getAddress());
455 }
456 protected synchronized void retrieveConnectionFactory(String url) {
457 connectionFactory = AbstractHTTPTransportFactory.getConnectionFactory(this, url);
458 }
459
460
461 protected synchronized HttpURLConnectionFactory getConnectionFactory(URL url) {
462 if (connectionFactory == null
463 || !url.getProtocol().equals(connectionFactory.getProtocol())) {
464 retrieveConnectionFactory(url.toString());
465 }
466
467 return connectionFactory;
468 }
469 /**
470 * Prepare to send an outbound HTTP message over this http conduit to a
471 * particular endpoint.
472 * <P>
473 * If the Message.PATH_INFO property is set it gets appended
474 * to the Conduit's endpoint URL. If the Message.QUERY_STRING
475 * property is set, it gets appended to the resultant URL following
476 * a "?".
477 * <P>
478 * If the Message.HTTP_REQUEST_METHOD property is NOT set, the
479 * Http request method defaults to "POST".
480 * <P>
481 * If the Message.PROTOCOL_HEADERS is not set on the message, it is
482 * initialized to an empty map.
483 * <P>
484 * This call creates the OutputStream for the content of the message.
485 * It also assigns the created Http(s)URLConnection to the Message
486 * Map.
487 *
488 * @param message The message to be sent.
489 */
490 public void prepare(Message message) throws IOException {
491 Map<String, List<String>> headers = getSetProtocolHeaders(message);
492
493 // This call can possibly change the conduit endpoint address and
494 // protocol from the default set in EndpointInfo that is associated
495 // with the Conduit.
496 URL currentURL = setupURL(message);
497
498 // The need to cache the request is off by default
499 boolean needToCacheRequest = false;
500
501 HTTPClientPolicy csPolicy = getClient(message);
502
503 HttpURLConnectionFactory f = getConnectionFactory(currentURL);
504 HttpURLConnection connection = f.createConnection(getProxy(csPolicy), currentURL);
505 connection.setDoOutput(true);
506
507 long timeout = csPolicy.getConnectionTimeout();
508 if (timeout > Integer.MAX_VALUE) {
509 timeout = Integer.MAX_VALUE;
510 }
511 connection.setConnectTimeout((int)timeout);
512 timeout = csPolicy.getReceiveTimeout();
513 if (timeout > Integer.MAX_VALUE) {
514 timeout = Integer.MAX_VALUE;
515 }
516 connection.setReadTimeout((int)timeout);
517 connection.setUseCaches(false);
518 // We implement redirects in this conduit. We do not
519 // rely on the underlying URLConnection implementation
520 // because of trust issues.
521 connection.setInstanceFollowRedirects(false);
522
523 // If the HTTP_REQUEST_METHOD is not set, the default is "POST".
524 String httpRequestMethod =
525 (String)message.get(Message.HTTP_REQUEST_METHOD);
526
527 if (null != httpRequestMethod) {
528 connection.setRequestMethod(httpRequestMethod);
529 } else {
530 connection.setRequestMethod("POST");
531 }
532
533 boolean isChunking = false;
534 int chunkThreshold = 0;
535 // We must cache the request if we have basic auth supplier
536 // without preemptive basic auth.
537 if (authSupplier != null) {
538 String auth = authSupplier.getPreemptiveAuthorization(
539 this, currentURL, message);
540 if (auth == null || authSupplier.requiresRequestCaching()) {
541 needToCacheRequest = true;
542 isChunking = false;
543 LOG.log(Level.FINE,
544 "Auth Supplier, but no Premeptive User Pass or Digest auth (nonce may be stale)"
545 + " We must cache request.");
546 }
547 message.put("AUTH_VALUE", auth);
548 }
549 if (csPolicy.isAutoRedirect()) {
550 needToCacheRequest = true;
551 LOG.log(Level.FINE, "AutoRedirect is turned on.");
552 }
553 if (csPolicy.getMaxRetransmits() > 0) {
554 needToCacheRequest = true;
555 LOG.log(Level.FINE, "MaxRetransmits is set > 0.");
556 }
557 // DELETE does not work and empty PUTs cause misleading exceptions
558 // if chunking is enabled
559 // TODO : ensure chunking can be enabled for non-empty PUTs - if requested
560 if (connection.getRequestMethod().equals("POST")
561 && csPolicy.isAllowChunking()) {
562 //TODO: The chunking mode be configured or at least some
563 // documented client constant.
564 //use -1 and allow the URL connection to pick a default value
565 isChunking = true;
566 chunkThreshold = csPolicy.getChunkingThreshold();
567 if (chunkThreshold <= 0) {
568 chunkThreshold = 0;
569 connection.setChunkedStreamingMode(-1);
570 }
571 }
572
573 //Do we need to maintain a session?
574 maintainSession = Boolean.TRUE.equals((Boolean)message.get(Message.MAINTAIN_SESSION));
575
576 //If we have any cookies and we are maintaining sessions, then use them
577 if (maintainSession && sessionCookies.size() > 0) {
578 List<String> cookies = null;
579 for (String s : headers.keySet()) {
580 if (HttpHeaderHelper.COOKIE.equalsIgnoreCase(s)) {
581 cookies = headers.remove(s);
582 break;
583 }
584 }
585 if (cookies == null) {
586 cookies = new ArrayList<String>();
587 } else {
588 cookies = new ArrayList<String>(cookies);
589 }
590 headers.put(HttpHeaderHelper.COOKIE, cookies);
591 for (Cookie c : sessionCookies.values()) {
592 cookies.add(c.requestCookieHeader());
593 }
594 }
595
596 // The trust decision is relegated to after the "flushing" of the
597 // request headers.
598
599 // We place the connection on the message to pick it up
600 // in the WrappedOutputStream.
601
602 message.put(KEY_HTTP_CONNECTION, connection);
603
604 if (certConstraints != null) {
605 message.put(CertConstraints.class.getName(), certConstraints);
606 message.getInterceptorChain().add(CertConstraintsInterceptor.INSTANCE);
607 }
608
609 // Set the headers on the message according to configured
610 // client side policy.
611 setHeadersByPolicy(message, currentURL, headers);
612
613
614 message.setContent(OutputStream.class,
615 new WrappedOutputStream(
616 message, connection,
617 needToCacheRequest,
618 isChunking,
619 chunkThreshold));
620 // We are now "ready" to "send" the message.
621 }
622
623 public void close(Message msg) throws IOException {
624 InputStream in = msg.getContent(InputStream.class);
625 try {
626 if (in != null) {
627 int count = 0;
628 byte buffer[] = new byte[1024];
629 while (in.read(buffer) != -1
630 && count < 25) {
631 //don't do anything, we just need to pull off the unread data (like
632 //closing tags that we didn't need to read
633
634 //however, limit it so we don't read off gigabytes of data we won't use.
635 ++count;
636 }
637 }
638 } finally {
639 super.close(msg);
640 }
641 }
642
643 /**
644 * This call must take place before anything is written to the
645 * URLConnection. The URLConnection.connect() will be called in order
646 * to get the connection information.
647 *
648 * This method is invoked just after setURLRequestHeaders() from the
649 * WrappedOutputStream before it writes data to the URLConnection.
650 *
651 * If trust cannot be established the Trust Decider implemenation
652 * throws an IOException.
653 *
654 * @param message The message being sent.
655 * @throws IOException This exception is thrown if trust cannot be
656 * established by the configured MessageTrustDecider.
657 * @see MessageTrustDecider
658 */
659 private void makeTrustDecision(Message message, HttpURLConnection connection)
660 throws IOException {
661
662 MessageTrustDecider decider2 = message.get(MessageTrustDecider.class);
663 if (trustDecider != null || decider2 != null) {
664 try {
665 // We must connect or we will not get the credentials.
666 // The call is (said to be) ingored internally if
667 // already connected.
668 connection.connect();
669 URLConnectionInfo info = getConnectionFactory(connection.getURL())
670 .getConnectionInfo(connection);
671 if (trustDecider != null) {
672 trustDecider.establishTrust(
673 getConduitName(),
674 info,
675 message);
676 if (LOG.isLoggable(Level.FINE)) {
677 LOG.log(Level.FINE, "Trust Decider "
678 + trustDecider.getLogicalName()
679 + " considers Conduit "
680 + getConduitName()
681 + " trusted.");
682 }
683 }
684 if (decider2 != null) {
685 decider2.establishTrust(getConduitName(),
686 info,
687 message);
688 if (LOG.isLoggable(Level.FINE)) {
689 LOG.log(Level.FINE, "Trust Decider "
690 + decider2.getLogicalName()
691 + " considers Conduit "
692 + getConduitName()
693 + " trusted.");
694 }
695 }
696 } catch (UntrustedURLConnectionIOException untrustedEx) {
697 // This cast covers HttpsURLConnection as well.
698 ((HttpURLConnection)connection).disconnect();
699 if (LOG.isLoggable(Level.FINE)) {
700 LOG.log(Level.FINE, "Trust Decider "
701 + trustDecider.getLogicalName()
702 + " considers Conduit "
703 + getConduitName()
704 + " untrusted.", untrustedEx);
705 }
706 throw untrustedEx;
707 }
708 } else {
709 // This case, when there is no trust decider, a trust
710 // decision should be a matter of policy.
711 if (LOG.isLoggable(Level.FINE)) {
712 LOG.log(Level.FINE, "No Trust Decider for Conduit '"
713 + getConduitName()
714 + "'. An afirmative Trust Decision is assumed.");
715 }
716 }
717 }
718
719 /**
720 * This function sets up a URL based on ENDPOINT_ADDRESS, PATH_INFO,
721 * and QUERY_STRING properties in the Message. The QUERY_STRING gets
722 * added with a "?" after the PATH_INFO. If the ENDPOINT_ADDRESS is not
723 * set on the Message, the endpoint address is taken from the
724 * "defaultEndpointURL".
725 * <p>
726 * The PATH_INFO is only added to the endpoint address string should
727 * the PATH_INFO not equal the end of the endpoint address string.
728 *
729 * @param message The message holds the addressing information.
730 *
731 * @return The full URL specifying the HTTP request to the endpoint.
732 *
733 * @throws MalformedURLException
734 */
735 private URL setupURL(Message message) throws MalformedURLException {
736 String result = (String)message.get(Message.ENDPOINT_ADDRESS);
737 String pathInfo = (String)message.get(Message.PATH_INFO);
738 String queryString = (String)message.get(Message.QUERY_STRING);
739 if (result == null) {
740 if (pathInfo == null && queryString == null) {
741 URL url = getURL();
742 message.put(Message.ENDPOINT_ADDRESS, defaultEndpointURLString);
743 return url;
744 }
745 result = getURL().toString();
746 message.put(Message.ENDPOINT_ADDRESS, result);
747 }
748
749 // REVISIT: is this really correct?
750 if (null != pathInfo && !result.endsWith(pathInfo)) {
751 result = result + pathInfo;
752 }
753 if (queryString != null) {
754 result = result + "?" + queryString;
755 }
756 return new URL(result);
757 }
758
759 /**
760 * Retreive the back-channel Destination.
761 *
762 * @return the backchannel Destination (or null if the backchannel is
763 * built-in)
764 */
765 public synchronized Destination getBackChannel() {
766 if (decoupledDestination == null
767 && getClient().getDecoupledEndpoint() != null) {
768 setUpDecoupledDestination();
769 }
770 return decoupledDestination;
771 }
772
773 /**
774 * Close the conduit
775 */
776 public void close() {
777 if (defaultEndpointURL != null) {
778 try {
779 URLConnection connect = defaultEndpointURL.openConnection();
780 if (connect instanceof HttpURLConnection) {
781 ((HttpURLConnection)connect).disconnect();
782 }
783 } catch (IOException ex) {
784 //ignore
785 }
786 //defaultEndpointURL = null;
787 }
788
789 // in decoupled case, close response Destination if reference count
790 // hits zero
791 //
792 if (decoupledDestination != null) {
793 releaseDecoupledDestination();
794
795 }
796 }
797
798 /**
799 * @return the default target address
800 */
801 protected String getAddress() {
802 if (defaultEndpointURL != null) {
803 return defaultEndpointURLString;
804 } else if (fromEndpointReferenceType) {
805 return getTarget().getAddress().getValue();
806 }
807 return endpointInfo.getAddress();
808 }
809
810 /**
811 * @return the default target URL
812 */
813 protected URL getURL() throws MalformedURLException {
814 if (defaultEndpointURL == null) {
815 return getURL(true);
816 }
817 return defaultEndpointURL;
818 }
819
820 /**
821 * @param createOnDemand create URL on-demand if null
822 * @return the default target URL
823 */
824 protected synchronized URL getURL(boolean createOnDemand)
825 throws MalformedURLException {
826 if (defaultEndpointURL == null && createOnDemand) {
827 if (fromEndpointReferenceType && getTarget().getAddress().getValue() != null) {
828 defaultEndpointURL = new URL(this.getTarget().getAddress().getValue());
829 defaultEndpointURLString = defaultEndpointURL.toExternalForm();
830 return defaultEndpointURL;
831 }
832 if (endpointInfo.getAddress() == null) {
833 throw new MalformedURLException("Invalid address. Endpoint address cannot be null.");
834 }
835 defaultEndpointURL = new URL(endpointInfo.getAddress());
836 defaultEndpointURLString = defaultEndpointURL.toExternalForm();
837 }
838 return defaultEndpointURL;
839 }
840
841 /**
842 * While extracting the Message.PROTOCOL_HEADERS property from the Message,
843 * this call ensures that the Message.PROTOCOL_HEADERS property is
844 * set on the Message. If it is not set, an empty map is placed there, and
845 * then returned.
846 *
847 * @param message The outbound message
848 * @return The PROTOCOL_HEADERS map
849 */
850 private Map<String, List<String>> getSetProtocolHeaders(Message message) {
851 Map<String, List<String>> headers =
852 CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
853 if (null == headers) {
854 headers = new LinkedHashMap<String, List<String>>();
855 } else if (headers instanceof HashMap) {
856 headers = new LinkedHashMap<String, List<String>>(headers);
857 }
858 message.put(Message.PROTOCOL_HEADERS, headers);
859 return headers;
860 }
861
862
863 /**
864 * This procedure sets the URLConnection request properties
865 * from the PROTOCOL_HEADERS in the message.
866 */
867 private void transferProtocolHeadersToURLConnection(
868 Message message,
869 URLConnection connection
870 ) {
871 Map<String, List<String>> headers = getSetProtocolHeaders(message);
872 for (String header : headers.keySet()) {
873 List<String> headerList = headers.get(header);
874 if (HttpHeaderHelper.CONTENT_TYPE.equalsIgnoreCase(header)) {
875 continue;
876 }
877 if (HttpHeaderHelper.COOKIE.equalsIgnoreCase(header)) {
878 for (String s : headerList) {
879 connection.addRequestProperty(HttpHeaderHelper.COOKIE, s);
880 }
881 } else {
882 StringBuilder b = new StringBuilder();
883 for (int i = 0; i < headerList.size(); i++) {
884 b.append(headerList.get(i));
885 if (i + 1 < headerList.size()) {
886 b.append(',');
887 }
888 }
889 connection.setRequestProperty(header, b.toString());
890 }
891 }
892 if (!connection.getRequestProperties().containsKey("User-Agent")) {
893 connection.addRequestProperty("User-Agent", Version.getCompleteVersionString());
894 }
895 }
896
897 /**
898 * This procedure logs the PROTOCOL_HEADERS from the
899 * Message at the specified logging level.
900 *
901 * @param level The Logging Level.
902 * @param headers The Message protocol headers.
903 */
904 private void logProtocolHeaders(
905 Level level,
906 Message message
907 ) {
908 Map<String, List<String>> headers = getSetProtocolHeaders(message);
909 for (String header : headers.keySet()) {
910 List<String> headerList = headers.get(header);
911 for (String value : headerList) {
912 LOG.log(level, header + ": " + value);
913 }
914 }
915 }
916
917 /**
918 * Put the headers from Message.PROTOCOL_HEADERS headers into the URL
919 * connection.
920 * Note, this does not mean they immediately get written to the output
921 * stream or the wire. They just just get set on the HTTP request.
922 *
923 * @param message The outbound message.
924 * @throws IOException
925 */
926 private void setURLRequestHeaders(Message message) throws IOException {
927 HttpURLConnection connection =
928 (HttpURLConnection)message.get(KEY_HTTP_CONNECTION);
929
930 String ct = (String)message.get(Message.CONTENT_TYPE);
931 String enc = (String)message.get(Message.ENCODING);
932
933 if (null != ct) {
934 if (enc != null
935 && ct.indexOf("charset=") == -1
936 && !ct.toLowerCase().contains("multipart/related")) {
937 ct = ct + "; charset=" + enc;
938 }
939 } else if (enc != null) {
940 ct = "text/xml; charset=" + enc;
941 } else {
942 ct = "text/xml";
943 }
944 connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, ct);
945
946 if (LOG.isLoggable(Level.FINE)) {
947 LOG.fine("Sending "
948 + connection.getRequestMethod()
949 + " Message with Headers to "
950 + connection.getURL()
951 + " Conduit :"
952 + getConduitName()
953 + "\nContent-Type: " + ct + "\n");
954 logProtocolHeaders(Level.FINE, message);
955 }
956
957 transferProtocolHeadersToURLConnection(message, connection);
958
959 }
960
961 /**
962 * Set up the decoupled Destination if necessary.
963 */
964 private void setUpDecoupledDestination() {
965 EndpointReferenceType reference =
966 EndpointReferenceUtils.getEndpointReference(
967 getClient().getDecoupledEndpoint());
968 if (reference != null) {
969 String decoupledAddress = reference.getAddress().getValue();
970 LOG.info("creating decoupled endpoint: " + decoupledAddress);
971 try {
972 decoupledDestination = getDestination(decoupledAddress);
973 duplicateDecoupledDestination();
974 } catch (Exception e) {
975 // REVISIT move message to localizable Messages.properties
976 LOG.log(Level.WARNING,
977 "decoupled endpoint creation failed: ", e);
978 }
979 }
980 }
981
982 /**
983 * @param address the address
984 * @return a Destination for the address
985 */
986 private Destination getDestination(String address) throws IOException {
987 Destination destination = null;
988 DestinationFactoryManager factoryManager =
989 bus.getExtension(DestinationFactoryManager.class);
990 DestinationFactory factory =
991 factoryManager.getDestinationFactoryForUri(address);
992 if (factory != null) {
993 EndpointInfo ei = new EndpointInfo();
994 ei.setAddress(address);
995 destination = factory.getDestination(ei);
996 decoupledObserver = new InterposedMessageObserver();
997 destination.setMessageObserver(decoupledObserver);
998 }
999 return destination;
1000 }
1001
1002 /**
1003 * @return the decoupled observer
1004 */
1005 protected MessageObserver getDecoupledObserver() {
1006 return decoupledObserver;
1007 }
1008
1009 private synchronized void duplicateDecoupledDestination() {
1010 decoupledDestinationRefCount++;
1011 }
1012
1013 private synchronized void releaseDecoupledDestination() {
1014 if (--decoupledDestinationRefCount == 0) {
1015 LOG.log(Level.FINE, "shutting down decoupled destination");
1016 decoupledDestination.shutdown();
1017
1018 //this way we can release the port of decoupled destination
1019 decoupledDestination.setMessageObserver(null);
1020 }
1021 }
1022
1023 /**
1024 * This predicate returns true iff the exchange indicates
1025 * a oneway MEP.
1026 *
1027 * @param exchange The exchange in question
1028 */
1029 private boolean isOneway(Exchange exchange) {
1030 return exchange != null && exchange.isOneWay();
1031 }
1032
1033 /**
1034 * @return true if expecting a decoupled response
1035 */
1036 private boolean isDecoupled() {
1037 return decoupledDestination != null;
1038 }
1039
1040 /**
1041 * Get an input stream containing the partial response if one is present.
1042 *
1043 * @param connection the connection in question
1044 * @param responseCode the response code
1045 * @return an input stream if a partial response is pending on the connection
1046 */
1047 protected static InputStream getPartialResponse(
1048 HttpURLConnection connection,
1049 int responseCode
1050 ) throws IOException {
1051 InputStream in = null;
1052 if (responseCode == HttpURLConnection.HTTP_ACCEPTED
1053 || responseCode == HttpURLConnection.HTTP_OK) {
1054 if (connection.getContentLength() > 0) {
1055 in = connection.getInputStream();
1056 } else if (hasChunkedResponse(connection)
1057 || hasEofTerminatedResponse(connection)) {
1058 // ensure chunked or EOF-terminated response is non-empty
1059 in = getNonEmptyContent(connection);
1060 }
1061 }
1062 return in;
1063 }
1064
1065 /**
1066 * @param connection the given HttpURLConnection
1067 * @return true iff the connection has a chunked response pending
1068 */
1069 private static boolean hasChunkedResponse(HttpURLConnection connection) {
1070 return HttpHeaderHelper.CHUNKED.equalsIgnoreCase(
1071 connection.getHeaderField(HttpHeaderHelper.TRANSFER_ENCODING));
1072 }
1073
1074 /**
1075 * @param connection the given HttpURLConnection
1076 * @return true iff the connection has a chunked response pending
1077 */
1078 private static boolean hasEofTerminatedResponse(
1079 HttpURLConnection connection
1080 ) {
1081 return HttpHeaderHelper.CLOSE.equalsIgnoreCase(
1082 connection.getHeaderField(HttpHeaderHelper.CONNECTION));
1083 }
1084
1085 /**
1086 * @param connection the given HttpURLConnection
1087 * @return an input stream containing the response content if non-empty
1088 */
1089 private static InputStream getNonEmptyContent(
1090 HttpURLConnection connection
1091 ) {
1092 InputStream in = null;
1093 try {
1094 PushbackInputStream pin =
1095 new PushbackInputStream(connection.getInputStream());
1096 int c = pin.read();
1097 if (c != -1) {
1098 pin.unread((byte)c);
1099 in = pin;
1100 }
1101 } catch (IOException ioe) {
1102 // ignore
1103 }
1104 return in;
1105 }
1106
1107 /**
1108 * This method returns the Proxy server should it be set on the
1109 * Client Side Policy.
1110 *
1111 * @return The proxy server or null, if not set.
1112 */
1113 private Proxy getProxy(HTTPClientPolicy policy) {
1114 Proxy proxy = null;
1115 if (policy != null
1116 && policy.isSetProxyServer()
1117 && !StringUtils.isEmpty(policy.getProxyServer())) {
1118 proxy = new Proxy(
1119 Proxy.Type.valueOf(policy.getProxyServerType().toString()),
1120 new InetSocketAddress(policy.getProxyServer(),
1121 policy.getProxyServerPort()));
1122 }
1123 return proxy;
1124 }
1125
1126 /**
1127 * This call places HTTP Header strings into the headers that are relevant
1128 * to the Authorization policies that are set on this conduit by
1129 * configuration.
1130 * <p>
1131 * An AuthorizationPolicy may also be set on the message. If so, those
1132 * policies are merged. A user name or password set on the messsage
1133 * overrides settings in the AuthorizationPolicy is retrieved from the
1134 * configuration.
1135 * <p>
1136 * The precedence is as follows:
1137 * 1. AuthorizationPolicy that is set on the Message, if exists.
1138 * 2. Authorization from AuthSupplier, if exists.
1139 * 3. AuthorizationPolicy set/configured for conduit.
1140 *
1141 * REVISIT: Since the AuthorizationPolicy is set on the message by class, then
1142 * how does one override the ProxyAuthorizationPolicy which is the same
1143 * type?
1144 *
1145 * @param message
1146 * @param headers
1147 */
1148 private void setHeadersByAuthorizationPolicy(
1149 Message message,
1150 URL url,
1151 Map<String, List<String>> headers
1152 ) {
1153 AuthorizationPolicy authPolicy = getAuthorization();
1154 AuthorizationPolicy newPolicy = message.get(AuthorizationPolicy.class);
1155
1156 String authString = null;
1157 if (authSupplier != null
1158 && (newPolicy == null
1159 || (!"Basic".equals(newPolicy.getAuthorizationType())
1160 && newPolicy.getAuthorization() == null))) {
1161 authString = (String)message.get("AUTH_VALUE");
1162 if (authString == null) {
1163 authString = authSupplier.getPreemptiveAuthorization(
1164 this, url, message);
1165 } else {
1166 message.remove("AUTH_VALUE");
1167 }
1168 if (authString != null) {
1169 headers.put("Authorization",
1170 createMutableList(authString));
1171 }
1172 return;
1173 }
1174 String userName = null;
1175 String passwd = null;
1176 if (null != newPolicy) {
1177 userName = newPolicy.getUserName();
1178 passwd = newPolicy.getPassword();
1179 }
1180
1181 if (userName == null
1182 && authPolicy != null && authPolicy.isSetUserName()) {
1183 userName = authPolicy.getUserName();
1184 }
1185 if (userName != null) {
1186 if (passwd == null
1187 && authPolicy != null && authPolicy.isSetPassword()) {
1188 passwd = authPolicy.getPassword();
1189 }
1190 setBasicAuthHeader(userName, passwd, headers);
1191 } else if (authPolicy != null
1192 && authPolicy.isSetAuthorizationType()
1193 && authPolicy.isSetAuthorization()) {
1194 String type = authPolicy.getAuthorizationType();
1195 type += " ";
1196 type += authPolicy.getAuthorization();
1197 headers.put("Authorization",
1198 createMutableList(type));
1199 }
1200 AuthorizationPolicy proxyAuthPolicy = getProxyAuthorization();
1201 if (proxyAuthPolicy != null && proxyAuthPolicy.isSetUserName()) {
1202 userName = proxyAuthPolicy.getUserName();
1203 if (userName != null) {
1204 passwd = "";
1205 if (proxyAuthPolicy.isSetPassword()) {
1206 passwd = proxyAuthPolicy.getPassword();
1207 }
1208 setProxyBasicAuthHeader(userName, passwd, headers);
1209 } else if (proxyAuthPolicy.isSetAuthorizationType()
1210 && proxyAuthPolicy.isSetAuthorization()) {
1211 String type = proxyAuthPolicy.getAuthorizationType();
1212 type += " ";
1213 type += proxyAuthPolicy.getAuthorization();
1214 headers.put("Proxy-Authorization",
1215 createMutableList(type));
1216 }
1217 }
1218 }
1219 private static List<String> createMutableList(String val) {
1220 return new ArrayList<String>(Arrays.asList(new String[] {val}));
1221 }
1222 /**
1223 * This call places HTTP Header strings into the headers that are relevant
1224 * to the ClientPolicy that is set on this conduit by configuration.
1225 *
1226 * REVISIT: A cookie is set statically from configuration?
1227 */
1228 private void setHeadersByClientPolicy(
1229 Message message,
1230 Map<String, List<String>> headers
1231 ) {
1232 HTTPClientPolicy policy = getClient(message);
1233 if (policy == null) {
1234 return;
1235 }
1236 if (policy.isSetCacheControl()) {
1237 headers.put("Cache-Control",
1238 createMutableList(policy.getCacheControl().value()));
1239 }
1240 if (policy.isSetHost()) {
1241 headers.put("Host",
1242 createMutableList(policy.getHost()));
1243 }
1244 if (policy.isSetConnection()) {
1245 headers.put("Connection",
1246 createMutableList(policy.getConnection().value()));
1247 }
1248 if (policy.isSetAccept()) {
1249 headers.put("Accept",
1250 createMutableList(policy.getAccept()));
1251 } else if (!headers.containsKey("Accept")) {
1252 headers.put("Accept", createMutableList("*/*"));
1253 }
1254 if (policy.isSetAcceptEncoding()) {
1255 headers.put("Accept-Encoding",
1256 createMutableList(policy.getAcceptEncoding()));
1257 }
1258 if (policy.isSetAcceptLanguage()) {
1259 headers.put("Accept-Language",
1260 createMutableList(policy.getAcceptLanguage()));
1261 }
1262 if (policy.isSetContentType()) {
1263 message.put(Message.CONTENT_TYPE, policy.getContentType());
1264 }
1265 if (policy.isSetCookie()) {
1266 headers.put("Cookie",
1267 createMutableList(policy.getCookie()));
1268 }
1269 if (policy.isSetBrowserType()) {
1270 headers.put("BrowserType",
1271 createMutableList(policy.getBrowserType()));
1272 }
1273 if (policy.isSetReferer()) {
1274 headers.put("Referer",
1275 createMutableList(policy.getReferer()));
1276 }
1277 }
1278
1279 /**
1280 * This call places HTTP Header strings into the headers that are relevant
1281 * to the polices that are set on this conduit by configuration for the
1282 * ClientPolicy and AuthorizationPolicy.
1283 *
1284 *
1285 * @param message The outgoing message.
1286 * @param url The URL the message is going to.
1287 * @param headers The headers in the outgoing message.
1288 */
1289 private void setHeadersByPolicy(
1290 Message message,
1291 URL url,
1292 Map<String, List<String>> headers
1293 ) {
1294 setHeadersByAuthorizationPolicy(message, url, headers);
1295 setHeadersByClientPolicy(message, headers);
1296 }
1297
1298 /**
1299 * This is part of the Configurable interface which retrieves the
1300 * configuration from spring injection.
1301 */
1302 // REVISIT:What happens when the endpoint/bean name is null?
1303 public String getBeanName() {
1304 if (endpointInfo.getName() != null) {
1305 return endpointInfo.getName().toString() + ".http-conduit";
1306 }
1307 return null;
1308 }
1309
1310 /**
1311 * This method gets the Authorization Policy that was configured or
1312 * explicitly set for this HTTPConduit.
1313 */
1314 public AuthorizationPolicy getAuthorization() {
1315 return authorizationPolicy;
1316 }
1317
1318 /**
1319 * This method is used to set the Authorization Policy for this conduit.
1320 * Using this method will override any Authorization Policy set in
1321 * configuration.
1322 */
1323 public void setAuthorization(AuthorizationPolicy authorization) {
1324 this.authorizationPolicy = authorization;
1325 }
1326
1327 public HTTPClientPolicy getClient(Message message) {
1328 return PolicyUtils.getClient(message, clientSidePolicy);
1329 }
1330
1331 /**
1332 * This method retrieves the Client Side Policy set/configured for this
1333 * HTTPConduit.
1334 */
1335 public HTTPClientPolicy getClient() {
1336 return clientSidePolicy;
1337 }
1338
1339 /**
1340 * This method sets the Client Side Policy for this HTTPConduit. Using this
1341 * method will override any HTTPClientPolicy set in configuration.
1342 */
1343 public void setClient(HTTPClientPolicy client) {
1344 this.clientSidePolicy = client;
1345 }
1346
1347 /**
1348 * This method retrieves the Proxy Authorization Policy for a proxy that is
1349 * set/configured for this HTTPConduit.
1350 */
1351 public ProxyAuthorizationPolicy getProxyAuthorization() {
1352 return proxyAuthorizationPolicy;
1353 }
1354
1355 /**
1356 * This method sets the Proxy Authorization Policy for a specified proxy.
1357 * Using this method overrides any Authorization Policy for the proxy
1358 * that is set in the configuration.
1359 */
1360 public void setProxyAuthorization(
1361 ProxyAuthorizationPolicy proxyAuthorization
1362 ) {
1363 this.proxyAuthorizationPolicy = proxyAuthorization;
1364 }
1365
1366 /**
1367 * This method returns the TLS Client Parameters that is set/configured
1368 * for this HTTPConduit.
1369 */
1370 public TLSClientParameters getTlsClientParameters() {
1371 return tlsClientParameters;
1372 }
1373
1374 /**
1375 * This method sets the TLS Client Parameters for this HTTPConduit.
1376 * Using this method overrides any TLS Client Parameters that is configured
1377 * for this HTTPConduit.
1378 */
1379 public void setTlsClientParameters(TLSClientParameters params) {
1380 this.tlsClientParameters = params;
1381 if (this.tlsClientParameters != null) {
1382 if (LOG.isLoggable(Level.FINE)) {
1383 LOG.log(Level.FINE, "Conduit '" + getConduitName()
1384 + "' has been (re) configured for TLS "
1385 + "keyManagers " + Arrays.toString(tlsClientParameters.getKeyManagers())
1386 + "trustManagers " + Arrays.toString(tlsClientParameters.getTrustManagers())
1387 + "secureRandom " + tlsClientParameters.getSecureRandom());
1388 }
1389 CertificateConstraintsType constraints = params.getCertConstraints();
1390 if (constraints != null) {
1391 certConstraints = CertConstraintsJaxBUtils.createCertConstraints(constraints);
1392 }
1393 } else {
1394 if (LOG.isLoggable(Level.FINE)) {
1395 LOG.log(Level.FINE, "Conduit '" + getConduitName()
1396 + "' has been (re)configured for plain http.");
1397 }
1398 }
1399 // If this is called after the HTTPTransportFactory called
1400 // finalizeConfig, we need to update the connection factory.
1401 if (configFinalized) {
1402 retrieveConnectionFactory(getAddress());
1403 }
1404 }
1405
1406 /**
1407 * This method gets the Trust Decider that was set/configured for this
1408 * HTTPConduit.
1409 * @return The Message Trust Decider or null.
1410 */
1411 public MessageTrustDecider getTrustDecider() {
1412 return this.trustDecider;
1413 }
1414
1415 /**
1416 * This method sets the Trust Decider for this HTTP Conduit.
1417 * Using this method overrides any trust decider configured for this
1418 * HTTPConduit.
1419 */
1420 public void setTrustDecider(MessageTrustDecider decider) {
1421 this.trustDecider = decider;
1422 }
1423
1424 /**
1425 * This method gets the Auth Supplier that was set/configured for this
1426 * HTTPConduit.
1427 * @return The Auth Supplier or null.
1428 */
1429 public HttpAuthSupplier getAuthSupplier() {
1430 return this.authSupplier;
1431 }
1432
1433 public void setAuthSupplier(HttpAuthSupplier supplier) {
1434 this.authSupplier = supplier;
1435 }
1436
1437 /**
1438 * This function processes any retransmits at the direction of redirections
1439 * or "unauthorized" responses.
1440 * <p>
1441 * If the request was not retransmitted, it returns the given connection.
1442 * If the request was retransmitted, it returns the new connection on
1443 * which the request was sent.
1444 *
1445 * @param connection The active URL connection.
1446 * @param message The outgoing message.
1447 * @param cachedStream The cached request.
1448 * @return
1449 * @throws IOException
1450 */
1451 private HttpURLConnection processRetransmit(
1452 HttpURLConnection connection,
1453 Message message,
1454 CacheAndWriteOutputStream cachedStream
1455 ) throws IOException {
1456
1457 int responseCode = connection.getResponseCode();
1458 if ((message != null) && (message.getExchange() != null)) {
1459 message.getExchange().put(Message.RESPONSE_CODE, responseCode);
1460 }
1461
1462 // Process Redirects first.
1463 switch(responseCode) {
1464 case HttpURLConnection.HTTP_MOVED_PERM:
1465 case HttpURLConnection.HTTP_MOVED_TEMP:
1466 connection =
1467 redirectRetransmit(connection, message, cachedStream);
1468 break;
1469 case HttpURLConnection.HTTP_UNAUTHORIZED:
1470 connection =
1471 authorizationRetransmit(connection, message, cachedStream);
1472 break;
1473 default:
1474 break;
1475 }
1476 return connection;
1477 }
1478
1479 /**
1480 * This method performs a redirection retransmit in response to
1481 * a 302 or 305 response code.
1482 *
1483 * @param connection The active URL connection
1484 * @param message The outbound message.
1485 * @param cachedStream The cached request.
1486 * @return This method returns the new HttpURLConnection if
1487 * redirected. If it cannot be redirected for some reason
1488 * the same connection is returned.
1489 *
1490 * @throws IOException
1491 */
1492 private HttpURLConnection redirectRetransmit(
1493 HttpURLConnection connection,
1494 Message message,
1495 CacheAndWriteOutputStream cachedStream
1496 ) throws IOException {
1497
1498 // If we are not redirecting by policy, then we don't.
1499 if (!getClient(message).isAutoRedirect()) {
1500 return connection;
1501 }
1502
1503 // We keep track of the redirections for redirect loop protection.
1504 Set<String> visitedURLs = getSetVisitedURLs(message);
1505
1506 String lastURL = connection.getURL().toString();
1507 visitedURLs.add(lastURL);
1508
1509 String newURL = extractLocation(connection.getHeaderFields());
1510 if (newURL != null) {
1511 // See if we are being redirected in a loop as best we can,
1512 // using string equality on URL.
1513 if (visitedURLs.contains(newURL)) {
1514 // We are in a redirect loop; -- bail
1515 if (LOG.isLoggable(Level.INFO)) {
1516 LOG.log(Level.INFO, "Redirect loop detected on Conduit \""
1517 + getConduitName()
1518 + "\" on '"
1519 + newURL
1520 + "'");
1521 }
1522 throw new IOException("Redirect loop detected on Conduit \""
1523 + getConduitName()
1524 + "\" on '"
1525 + newURL
1526 + "'");
1527 }
1528 // We are going to redirect.
1529 // Remove any Server Authentication Information for the previous
1530 // URL.
1531 Map<String, List<String>> headers =
1532 getSetProtocolHeaders(message);
1533 headers.remove("Authorization");
1534 headers.remove("Proxy-Authorization");
1535
1536 URL url = new URL(newURL);
1537
1538 // If user configured this Conduit with preemptive authorization
1539 // it is meant to make it to the end. (Too bad that information
1540 // went to every URL along the way, but that's what the user
1541 // wants!
1542 // TODO: Make this issue a security release note.
1543 setHeadersByAuthorizationPolicy(message, url, headers);
1544
1545 connection = retransmit(
1546 connection, url, message, cachedStream);
1547 }
1548 return connection;
1549 }
1550
1551 /**
1552 * This function gets the Set of URLs on the message that is used to
1553 * keep track of the URLs that were used in getting authorization
1554 * information.
1555 *
1556 * @param message The message where the Set of URLs is stored.
1557 * @return The modifiable set of URLs that were visited.
1558 */
1559 private Set<String> getSetAuthoriationURLs(Message message) {
1560 @SuppressWarnings("unchecked")
1561 Set<String> authURLs = (Set<String>) message.get(KEY_AUTH_URLS);
1562 if (authURLs == null) {
1563 authURLs = new HashSet<String>();
1564 message.put(KEY_AUTH_URLS, authURLs);
1565 }
1566 return authURLs;
1567 }
1568
1569 /**
1570 * This function get the set of URLs on the message that is used to keep
1571 * track of the URLs that were visited in redirects.
1572 *
1573 * If it is not set on the message, an new empty set is stored.
1574 * @param message The message where the Set is stored.
1575 * @return The modifiable set of URLs that were visited.
1576 */
1577 private Set<String> getSetVisitedURLs(Message message) {
1578 @SuppressWarnings("unchecked")
1579 Set<String> visitedURLs = (Set<String>) message.get(KEY_VISITED_URLS);
1580 if (visitedURLs == null) {
1581 visitedURLs = new HashSet<String>();
1582 message.put(KEY_VISITED_URLS, visitedURLs);
1583 }
1584 return visitedURLs;
1585 }
1586
1587 /**
1588 * This method performs a retransmit for authorization information.
1589 *
1590 * @param connection The currently active connection.
1591 * @param message The outbound message.
1592 * @param cachedStream The cached request.
1593 * @return A new connection if retransmitted. If not retransmitted
1594 * then this method returns the same connection.
1595 * @throws IOException
1596 */
1597 private HttpURLConnection authorizationRetransmit(
1598 HttpURLConnection connection,
1599 Message message,
1600 CacheAndWriteOutputStream cachedStream
1601 ) throws IOException {
1602
1603 // If we don't have a dynamic supply of user pass, then
1604 // we don't retransmit. We just die with a Http 401 response.
1605 if (authSupplier == null) {
1606 String auth = connection.getHeaderField("WWW-Authenticate");
1607 if (auth.startsWith("Digest ")) {
1608 authSupplier = new DigestAuthSupplier();
1609 } else {
1610 return connection;
1611 }
1612 }
1613
1614 URL currentURL = connection.getURL();
1615
1616 String realm = extractAuthorizationRealm(connection.getHeaderFields());
1617
1618 Set<String> authURLs = getSetAuthoriationURLs(message);
1619
1620 // If we have been here (URL & Realm) before for this particular message
1621 // retransmit, it means we have already supplied information
1622 // which must have been wrong, or we wouldn't be here again.
1623 // Otherwise, the server may be 401 looping us around the realms.
1624 if (authURLs.contains(currentURL.toString() + realm)) {
1625
1626 if (LOG.isLoggable(Level.INFO)) {
1627 LOG.log(Level.INFO, "Authorization loop detected on Conduit \""
1628 + getConduitName()
1629 + "\" on URL \""
1630 + "\" with realm \""
1631 + realm
1632 + "\"");
1633 }
1634
1635 throw new IOException("Authorization loop detected on Conduit \""
1636 + getConduitName()
1637 + "\" on URL \""
1638 + "\" with realm \""
1639 + realm
1640 + "\"");
1641 }
1642
1643 String up =
1644 authSupplier.getAuthorizationForRealm(
1645 this, currentURL, message, realm, connection.getHeaderField("WWW-Authenticate"));
1646
1647 // No user pass combination. We give up.
1648 if (up == null) {
1649 return connection;
1650 }
1651
1652 // Register that we have been here before we go.
1653 authURLs.add(currentURL.toString() + realm);
1654
1655 Map<String, List<String>> headers = getSetProtocolHeaders(message);
1656 headers.put("Authorization",
1657 createMutableList(up));
1658 return retransmit(
1659 connection, currentURL, message, cachedStream);
1660 }
1661
1662 /**
1663 * This method retransmits the request.
1664 *
1665 * @param connection The currently active connection.
1666 * @param newURL The newURL to connection to.
1667 * @param message The outbound message.
1668 * @param stream The cached request.
1669 * @return This function returns a new connection if
1670 * retransmitted, otherwise it returns the given
1671 * connection.
1672 *
1673 * @throws IOException
1674 */
1675 private HttpURLConnection retransmit(
1676 HttpURLConnection connection,
1677 URL newURL,
1678 Message message,
1679 CacheAndWriteOutputStream stream
1680 ) throws IOException {
1681
1682 // Disconnect the old, and in with the new.
1683 connection.disconnect();
1684
1685 HTTPClientPolicy cp = getClient(message);
1686 connection = getConnectionFactory(newURL).createConnection(getProxy(cp), newURL);
1687 connection.setDoOutput(true);
1688 // TODO: using Message context to deceided HTTP send properties
1689 connection.setConnectTimeout((int)cp.getConnectionTimeout());
1690 connection.setReadTimeout((int)cp.getReceiveTimeout());
1691 connection.setUseCaches(false);
1692 connection.setInstanceFollowRedirects(false);
1693
1694 // If the HTTP_REQUEST_METHOD is not set, the default is "POST".
1695 String httpRequestMethod =
1696 (String)message.get(Message.HTTP_REQUEST_METHOD);
1697
1698 if (null != httpRequestMethod) {
1699 connection.setRequestMethod(httpRequestMethod);
1700 } else {
1701 connection.setRequestMethod("POST");
1702 }
1703 message.put(KEY_HTTP_CONNECTION, connection);
1704
1705 connection.setFixedLengthStreamingMode(stream.size());
1706
1707 // Need to set the headers before the trust decision
1708 // because they are set before the connect().
1709 setURLRequestHeaders(message);
1710
1711 //
1712 // This point is where the trust decision is made because the
1713 // Sun implementation of URLConnection will not let us
1714 // set/addRequestProperty after a connect() call, and
1715 // makeTrustDecision needs to make a connect() call to
1716 // make sure the proper information is available.
1717 //
1718 makeTrustDecision(message, connection);
1719
1720 // If this is a GET method we must not touch the output
1721 // stream as this automagically turns the request into a POST.
1722 if (connection.getRequestMethod().equals("GET")) {
1723 return connection;
1724 }
1725
1726 // Trust is okay, write the cached request
1727 OutputStream out = connection.getOutputStream();
1728 stream.writeCacheTo(out);
1729
1730 if (LOG.isLoggable(Level.FINE)) {
1731 LOG.fine("Conduit \""
1732 + getConduitName()
1733 + "\" Retransmit message to: "
1734 + connection.getURL()
1735 + ": "
1736 + new String(stream.getBytes()));
1737 }
1738 return connection;
1739 }
1740
1741 /**
1742 * This function extracts the authorization realm from the
1743 * "WWW-Authenticate" Http response header.
1744 *
1745 * @param headers The Http Response Headers
1746 * @return The realm, or null if it is non-existent.
1747 */
1748 private String extractAuthorizationRealm(
1749 Map<String, List<String>> headers
1750 ) {
1751 List<String> auth = headers.get("WWW-Authenticate");
1752 if (auth != null) {
1753 for (String a : auth) {
1754 int idx = a.indexOf("realm=");
1755 if (idx != -1) {
1756 a = a.substring(idx + 6);
1757 if (a.charAt(0) == '"') {
1758 a = a.substring(1, a.indexOf('"', 1));
1759 } else if (a.contains(",")) {
1760 a = a.substring(0, a.indexOf(','));
1761 }
1762 return a;
1763 }
1764 }
1765 }
1766 return null;
1767 }
1768
1769 /**
1770 * This method extracts the value of the "Location" Http
1771 * Response header.
1772 *
1773 * @param headers The Http response headers.
1774 * @return The value of the "Location" header, null if non-existent.
1775 */
1776 private String extractLocation(
1777 Map<String, List<String>> headers
1778 ) {
1779
1780 for (Map.Entry<String, List<String>> head : headers.entrySet()) {
1781 if ("Location".equalsIgnoreCase(head.getKey())) {
1782 List<String> locs = head.getValue();
1783 if (locs != null && locs.size() > 0) {
1784 return locs.get(0);
1785 }
1786 }
1787 }
1788 return null;
1789 }
1790
1791 /**
1792 * This procedure sets the "Authorization" header with the
1793 * BasicAuth token, which is Base64 encoded.
1794 *
1795 * @param userid The user's id, which cannot be null.
1796 * @param password The password, it may be null.
1797 *
1798 * @param headers The headers map that gets the "Authorization" header set.
1799 */
1800 private void setBasicAuthHeader(
1801 String userid,
1802 String password,
1803 Map<String, List<String>> headers
1804 ) {
1805 String userpass = userid;
1806
1807 userpass += ":";
1808 if (password != null) {
1809 userpass += password;
1810 }
1811 String token = Base64Utility.encode(userpass.getBytes());
1812 headers.put("Authorization",
1813 createMutableList("Basic " + token));
1814 }
1815
1816 /**
1817 * This procedure sets the "ProxyAuthorization" header with the
1818 * BasicAuth token, which is Base64 encoded.
1819 *
1820 * @param userid The user's id, which cannot be null.
1821 * @param password The password, it may be null.
1822 *
1823 * @param headers The headers map that gets the "Proxy-Authorization"
1824 * header set.
1825 */
1826 private void setProxyBasicAuthHeader(
1827 String userid,
1828 String password,
1829 Map<String, List<String>> headers
1830 ) {
1831 String userpass = userid;
1832
1833 userpass += ":";
1834 if (password != null) {
1835 userpass += password;
1836 }
1837 String token = Base64Utility.encode(userpass.getBytes());
1838 headers.put("Proxy-Authorization",
1839 createMutableList("Basic " + token));
1840 }
1841
1842 /**
1843 * Wrapper output stream responsible for flushing headers and handling
1844 * the incoming HTTP-level response (not necessarily the MEP response).
1845 */
1846 protected class WrappedOutputStream extends AbstractThresholdOutputStream {
1847 /**
1848 * This field contains the currently active connection.
1849 */
1850 protected HttpURLConnection connection;
1851
1852 /**
1853 * This boolean is true if the request must be cached.
1854 */
1855 protected boolean cachingForRetransmission;
1856
1857 /**
1858 * If we are going to be chunking, we won't flush till close which causes
1859 * new chunks, small network packets, etc..
1860 */
1861 protected final boolean chunking;
1862
1863 /**
1864 * This field contains the output stream with which we cache
1865 * the request. It maybe null if we are not caching.
1866 */
1867 protected CacheAndWriteOutputStream cachedStream;
1868
1869 protected Message outMessage;
1870
1871 protected WrappedOutputStream(
1872 Message m,
1873 HttpURLConnection c,
1874 boolean possibleRetransmit,
1875 boolean isChunking,
1876 int chunkThreshold
1877 ) {
1878 super(chunkThreshold);
1879 this.outMessage = m;
1880 connection = c;
1881 cachingForRetransmission = possibleRetransmit;
1882 chunking = isChunking;
1883 }
1884
1885
1886 @Override
1887 public void thresholdNotReached() {
1888 if (chunking) {
1889 connection.setFixedLengthStreamingMode(buffer.size());
1890 }
1891 }
1892
1893 @Override
1894 public void thresholdReached() {
1895 if (chunking) {
1896 connection.setChunkedStreamingMode(-1);
1897 }
1898 }
1899
1900 /**
1901 * Perform any actions required on stream flush (freeze headers,
1902 * reset output stream ... etc.)
1903 */
1904 @Override
1905 protected void onFirstWrite() throws IOException {
1906 try {
1907 handleHeadersTrustCaching();
1908 } catch (IOException e) {
1909 if (e.getMessage() != null && e.getMessage().contains("HTTPS hostname wrong:")) {
1910 throw new IOException("The https URL hostname does not match the "
1911 + "Common Name (CN) on the server certificate. To disable this check "
1912 + "(NOT recommended for production) set the CXF client TLS configuration "
1913 + "property \"disableCNCheck\" to true.");
1914 } else {
1915 throw e;
1916 }
1917 }
1918 }
1919
1920 protected void handleHeadersTrustCaching() throws IOException {
1921 // Need to set the headers before the trust decision
1922 // because they are set before the connect().
1923 setURLRequestHeaders(outMessage);
1924
1925 //
1926 // This point is where the trust decision is made because the
1927 // Sun implementation of URLConnection will not let us
1928 // set/addRequestProperty after a connect() call, and
1929 // makeTrustDecision needs to make a connect() call to
1930 // make sure the proper information is available.
1931 //
1932 makeTrustDecision(outMessage, connection);
1933
1934 // Trust is okay, set up for writing the request.
1935
1936 // If this is a GET method we must not touch the output
1937 // stream as this automatically turns the request into a POST.
1938 // Nor it should be done in case of DELETE/HEAD/OPTIONS
1939 // - strangely, empty PUTs work ok
1940 if (!"POST".equals(connection.getRequestMethod())
1941 && !"PUT".equals(connection.getRequestMethod())) {
1942 return;
1943 }
1944 if (outMessage.get("org.apache.cxf.post.empty") != null) {
1945 return;
1946 }
1947
1948 // If we need to cache for retransmission, store data in a
1949 // CacheAndWriteOutputStream. Otherwise write directly to the output stream.
1950 if (cachingForRetransmission) {
1951 cachedStream =
1952 new CacheAndWriteOutputStream(connection.getOutputStream());
1953 wrappedStream = cachedStream;
1954 } else {
1955 wrappedStream = connection.getOutputStream();
1956 }
1957 }
1958
1959 public void flush() throws IOException {
1960 if (!chunking) {
1961 super.flush();
1962 }
1963 }
1964
1965 /**
1966 * Perform any actions required on stream closure (handle response etc.)
1967 */
1968 public void close() throws IOException {
1969 try {
1970 if (buffer != null && buffer.size() > 0) {
1971 thresholdNotReached();
1972 LoadingByteArrayOutputStream tmp = buffer;
1973 buffer = null;
1974 super.write(tmp.getRawBytes(), 0, tmp.size());
1975 }
1976 if (!written) {
1977 handleHeadersTrustCaching();
1978 }
1979 if (!cachingForRetransmission) {
1980 super.close();
1981 } else if (cachedStream != null) {
1982 super.flush();
1983 cachedStream.getOut().close();
1984 cachedStream.closeFlowthroughStream();
1985 }
1986
1987 try {
1988 handleResponse();
1989 } finally {
1990 if (cachingForRetransmission && cachedStream != null) {
1991 cachedStream.close();
1992 }
1993 }
1994 } catch (HttpRetryException e) {
1995 String msg = "HTTP response '" + e.responseCode() + ": "
1996 + connection.getResponseMessage() + "' invoking " + connection.getURL();
1997 switch (e.responseCode()) {
1998 case HttpURLConnection.HTTP_MOVED_PERM: // 301
1999 case HttpURLConnection.HTTP_MOVED_TEMP: // 302
2000 msg += " that returned location header '" + e.getLocation() + "'";
2001 break;
2002 case HttpURLConnection.HTTP_UNAUTHORIZED: // 401
2003 if (authorizationPolicy == null || authorizationPolicy.getUserName() == null) {
2004 msg += " with NO authorization username configured in conduit " + getConduitName();
2005 } else {
2006 msg += " with authorization username '" + authorizationPolicy.getUserName() + "'";
2007 }
2008 break;
2009 case HttpURLConnection.HTTP_PROXY_AUTH: // 407
2010 if (proxyAuthorizationPolicy == null || proxyAuthorizationPolicy.getUserName() == null) {
2011 msg += " with NO proxy authorization configured in conduit " + getConduitName();
2012 } else {
2013 msg += " with proxy authorization username '"
2014 + proxyAuthorizationPolicy.getUserName() + "'";
2015 }
2016 if (clientSidePolicy == null || clientSidePolicy.getProxyServer() == null) {
2017 if (connection.usingProxy()) {
2018 msg += " using a proxy even if NONE is configured in CXF conduit "
2019 + getConduitName()
2020 + " (maybe one is configured by java.net.ProxySelector)";
2021 } else {
2022 msg += " but NO proxy was used by the connection (none configured in cxf "
2023 + "conduit and none selected by java.net.ProxySelector)";
2024 }
2025 } else {
2026 msg += " using " + clientSidePolicy.getProxyServerType() + " proxy "
2027 + clientSidePolicy.getProxyServer() + ":"
2028 + clientSidePolicy.getProxyServerPort();
2029 }
2030 break;
2031 default:
2032 // No other type of HttpRetryException should be thrown
2033 break;
2034 }
2035 // pass cause with initCause() instead of constructor for jdk 1.5 compatibility
2036 throw (IOException) new IOException(msg).initCause(e);
2037 } catch (IOException e) {
2038 String url = connection.getURL().toString();
2039 String origMessage = e.getMessage();
2040 if (origMessage != null && origMessage.contains(url)) {
2041 throw e;
2042 }
2043 throw mapException(e.getClass().getSimpleName()
2044 + " invoking " + connection.getURL() + ": "
2045 + e.getMessage(), e,
2046 IOException.class);
2047 } catch (RuntimeException e) {
2048 throw mapException(e.getClass().getSimpleName()
2049 + " invoking " + connection.getURL() + ": "
2050 + e.getMessage(), e,
2051 RuntimeException.class);
2052 }
2053 }
2054 private <T extends Exception> T mapException(String msg,
2055 T ex, Class<T> cls) {
2056 T ex2 = ex;
2057 try {
2058 ex2 = cls.cast(ex.getClass().getConstructor(String.class).newInstance(msg));
2059 ex2.initCause(ex);
2060 } catch (Throwable e) {
2061 ex2 = ex;
2062 }
2063
2064
2065 return ex2;
2066 }
2067
2068 /**
2069 * This procedure handles all retransmits, if any.
2070 *
2071 * @throws IOException
2072 */
2073 protected void handleRetransmits() throws IOException {
2074 // If we have a cachedStream, we are caching the request.
2075 if (cachedStream != null) {
2076
2077 if (LOG.isLoggable(Level.FINE)) {
2078 LOG.fine("Conduit \""
2079 + getConduitName()
2080 + "\" Transmit cached message to: "
2081 + connection.getURL()
2082 + ": "
2083 + new String(cachedStream.getBytes()));
2084 }
2085
2086 HttpURLConnection oldcon = connection;
2087
2088 HTTPClientPolicy policy = getClient(outMessage);
2089
2090 // Default MaxRetransmits is -1 which means unlimited.
2091 int maxRetransmits = (policy == null)
2092 ? -1
2093 : policy.getMaxRetransmits();
2094
2095 // MaxRetransmits of zero means zero.
2096 if (maxRetransmits == 0) {
2097 return;
2098 }
2099
2100 int nretransmits = 0;
2101
2102 connection =
2103 processRetransmit(connection, outMessage, cachedStream);
2104
2105 while (connection != oldcon) {
2106 nretransmits++;
2107 oldcon = connection;
2108
2109 // A negative max means unlimited.
2110 if (maxRetransmits < 0 || nretransmits < maxRetransmits) {
2111 connection =
2112 processRetransmit(
2113 connection, outMessage, cachedStream);
2114 }
2115 }
2116 }
2117 }
2118
2119 /**
2120 * This procedure is called on the close of the output stream so
2121 * we are ready to handle the response from the connection.
2122 * We may retransmit until we finally get a response.
2123 *
2124 * @throws IOException
2125 */
2126 protected void handleResponse() throws IOException {
2127
2128 // Process retransmits until we fall out.
2129 handleRetransmits();
2130
2131 if (outMessage == null
2132 || outMessage.getExchange() == null
2133 || outMessage.getExchange().isSynchronous()) {
2134 handleResponseInternal();
2135 } else {
2136 Runnable runnable = new Runnable() {
2137 public void run() {
2138 try {
2139 handleResponseInternal();
2140 } catch (Exception e) {
2141 ((PhaseInterceptorChain)outMessage.getInterceptorChain()).unwind(outMessage);
2142 outMessage.setContent(Exception.class, e);
2143 outMessage.getInterceptorChain().getFaultObserver().onMessage(outMessage);
2144 }
2145 }
2146 };
2147 WorkQueueManager mgr = outMessage.getExchange().get(Bus.class)
2148 .getExtension(WorkQueueManager.class);
2149 AutomaticWorkQueue queue = mgr.getNamedWorkQueue("http-conduit");
2150 if (queue == null) {
2151 queue = mgr.getAutomaticWorkQueue();
2152 }
2153 queue.execute(runnable);
2154 }
2155 }
2156 protected void handleResponseInternal() throws IOException {
2157 Exchange exchange = outMessage.getExchange();
2158 int responseCode = connection.getResponseCode();
2159 if (outMessage != null && exchange != null) {
2160 exchange.put(Message.RESPONSE_CODE, responseCode);
2161 }
2162
2163 if (LOG.isLoggable(Level.FINE)) {
2164 LOG.fine("Response Code: "
2165 + responseCode
2166 + " Conduit: " + getConduitName());
2167 LOG.fine("Content length: " + connection.getContentLength());
2168 Map<String, List<String>> headerFields = connection.getHeaderFields();
2169 if (null != headerFields) {
2170 StringBuilder buf = new StringBuilder();
2171 buf.append("Header fields: ");
2172 buf.append(System.getProperty("line.separator"));
2173 for (String h : headerFields.keySet()) {
2174 buf.append(" ");
2175 buf.append(h);
2176 buf.append(": ");
2177 buf.append(headerFields.get(h));
2178 buf.append(System.getProperty("line.separator"));
2179 }
2180 LOG.fine(buf.toString());
2181 }
2182 }
2183
2184 if (responseCode == HttpURLConnection.HTTP_NOT_FOUND
2185 && !MessageUtils.isTrue(outMessage.getContextualProperty(
2186 "org.apache.cxf.http.no_io_exceptions"))) {
2187 throw new IOException("HTTP response '" + responseCode + ": "
2188 + connection.getResponseMessage() + "'");
2189 }
2190
2191
2192
2193 InputStream in = null;
2194 if (isOneway(exchange) || isDecoupled()) {
2195 in = getPartialResponse(connection, responseCode);
2196 if (in == null) {
2197 // oneway operation or decoupled MEP without
2198 // partial response
2199 connection.getInputStream().close();
2200 return;
2201 }
2202 } else {
2203 //not going to be resending or anything, clear out the stuff in the out message
2204 //to free memory
2205 outMessage.removeContent(OutputStream.class);
2206 if (cachingForRetransmission && cachedStream != null) {
2207 cachedStream.close();
2208 }
2209 cachedStream = null;
2210 }
2211
2212 Message inMessage = new MessageImpl();
2213 inMessage.setExchange(exchange);
2214 Map<String, List<String>> origHeaders = connection.getHeaderFields();
2215 Map<String, List<String>> headers =
2216 new HashMap<String, List<String>>();
2217 for (String key : connection.getHeaderFields().keySet()) {
2218 if (key != null) {
2219 headers.put(HttpHeaderHelper.getHeaderKey(key),
2220 origHeaders.get(key));
2221 }
2222 }
2223
2224 inMessage.put(Message.PROTOCOL_HEADERS, headers);
2225 inMessage.put(Message.RESPONSE_CODE, responseCode);
2226 String ct = connection.getContentType();
2227 inMessage.put(Message.CONTENT_TYPE, ct);
2228 String charset = HttpHeaderHelper.findCharset(ct);
2229 String normalizedEncoding = HttpHeaderHelper.mapCharset(charset);
2230 if (normalizedEncoding == null) {
2231 String m = new org.apache.cxf.common.i18n.Message("INVALID_ENCODING_MSG",
2232 LOG, charset).toString();
2233 LOG.log(Level.WARNING, m);
2234 throw new IOException(m);
2235 }
2236 inMessage.put(Message.ENCODING, normalizedEncoding);
2237
2238 if (maintainSession) {
2239 List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
2240 Cookie.handleSetCookie(sessionCookies, cookies);
2241 }
2242 if (responseCode != HttpURLConnection.HTTP_NOT_FOUND) {
2243 in = in == null
2244 ? connection.getErrorStream() == null
2245 ? connection.getInputStream()
2246 : connection.getErrorStream()
2247 : in;
2248 }
2249 // if (in == null) : it's perfectly ok for non-soap http services
2250 // have no response body : those interceptors which do need it will check anyway
2251 inMessage.setContent(InputStream.class, in);
2252
2253
2254 incomingObserver.onMessage(inMessage);
2255
2256 }
2257
2258 }
2259
2260 /**
2261 * Used to set appropriate message properties, exchange etc.
2262 * as required for an incoming decoupled response (as opposed
2263 * what's normally set by the Destination for an incoming
2264 * request).
2265 */
2266 protected class InterposedMessageObserver implements MessageObserver {
2267 /**
2268 * Called for an incoming message.
2269 *
2270 * @param inMessage
2271 */
2272 public void onMessage(Message inMessage) {
2273 // disposable exchange, swapped with real Exchange on correlation
2274 inMessage.setExchange(new ExchangeImpl());
2275 inMessage.getExchange().put(Bus.class, bus);
2276 inMessage.put(DECOUPLED_CHANNEL_MESSAGE, Boolean.TRUE);
2277 // REVISIT: how to get response headers?
2278 //inMessage.put(Message.PROTOCOL_HEADERS, req.getXXX());
2279 getSetProtocolHeaders(inMessage);
2280 inMessage.put(Message.RESPONSE_CODE, HttpURLConnection.HTTP_OK);
2281
2282 // remove server-specific properties
2283 inMessage.remove(AbstractHTTPDestination.HTTP_REQUEST);
2284 inMessage.remove(AbstractHTTPDestination.HTTP_RESPONSE);
2285 inMessage.remove(Message.ASYNC_POST_RESPONSE_DISPATCH);
2286
2287 //cache this inputstream since it's defer to use in case of async
2288 try {
2289 InputStream in = inMessage.getContent(InputStream.class);
2290 if (in != null) {
2291 CachedOutputStream cos = new CachedOutputStream();
2292 IOUtils.copy(in, cos);
2293 inMessage.setContent(InputStream.class, cos.getInputStream());
2294 }
2295 incomingObserver.onMessage(inMessage);
2296 } catch (IOException e) {
2297 e.printStackTrace();
2298 }
2299 }
2300 }
2301
2302 public void assertMessage(Message message) {
2303 PolicyUtils.assertClientPolicy(message, clientSidePolicy);
2304 }
2305
2306 public boolean canAssert(QName type) {
2307 return PolicyUtils.HTTPCLIENTPOLICY_ASSERTION_QNAME.equals(type);
2308 }
2309
2310 @Deprecated
2311 public void setBasicAuthSupplier(HttpBasicAuthSupplier basicAuthSupplier) {
2312 setAuthSupplier(basicAuthSupplier);
2313 }
2314
2315 }