src/share/jaxws_classes/com/sun/xml/internal/ws/transport/http/client/HttpTransportPipe.java

Tue, 09 Apr 2013 14:51:13 +0100

author
alanb
date
Tue, 09 Apr 2013 14:51:13 +0100
changeset 368
0989ad8c0860
parent 286
f50545b5e2f1
child 408
b0610cd08440
permissions
-rw-r--r--

8010393: Update JAX-WS RI to 2.2.9-b12941
Reviewed-by: alanb, erikj
Contributed-by: miroslav.kos@oracle.com, martin.grebac@oracle.com

ohair@286 1 /*
alanb@368 2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
ohair@286 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
ohair@286 4 *
ohair@286 5 * This code is free software; you can redistribute it and/or modify it
ohair@286 6 * under the terms of the GNU General Public License version 2 only, as
ohair@286 7 * published by the Free Software Foundation. Oracle designates this
ohair@286 8 * particular file as subject to the "Classpath" exception as provided
ohair@286 9 * by Oracle in the LICENSE file that accompanied this code.
ohair@286 10 *
ohair@286 11 * This code is distributed in the hope that it will be useful, but WITHOUT
ohair@286 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
ohair@286 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
ohair@286 14 * version 2 for more details (a copy is included in the LICENSE file that
ohair@286 15 * accompanied this code).
ohair@286 16 *
ohair@286 17 * You should have received a copy of the GNU General Public License version
ohair@286 18 * 2 along with this work; if not, write to the Free Software Foundation,
ohair@286 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
ohair@286 20 *
ohair@286 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
ohair@286 22 * or visit www.oracle.com if you need additional information or have any
ohair@286 23 * questions.
ohair@286 24 */
ohair@286 25
ohair@286 26 package com.sun.xml.internal.ws.transport.http.client;
ohair@286 27
ohair@286 28 import com.sun.istack.internal.NotNull;
ohair@286 29 import com.sun.xml.internal.ws.api.SOAPVersion;
ohair@286 30 import com.sun.xml.internal.ws.api.WSBinding;
ohair@286 31 import com.sun.xml.internal.ws.api.ha.StickyFeature;
ohair@286 32 import com.sun.xml.internal.ws.api.message.Packet;
ohair@286 33 import com.sun.xml.internal.ws.api.pipe.*;
ohair@286 34 import com.sun.xml.internal.ws.api.pipe.helper.AbstractTubeImpl;
alanb@368 35 import com.sun.xml.internal.ws.client.ClientTransportException;
ohair@286 36 import com.sun.xml.internal.ws.developer.HttpConfigFeature;
alanb@368 37 import com.sun.xml.internal.ws.resources.ClientMessages;
ohair@286 38 import com.sun.xml.internal.ws.transport.Headers;
ohair@286 39 import com.sun.xml.internal.ws.util.ByteArrayBuffer;
ohair@286 40 import com.sun.xml.internal.ws.util.RuntimeVersion;
ohair@286 41 import com.sun.xml.internal.ws.util.StreamUtils;
ohair@286 42
ohair@286 43 import javax.xml.bind.DatatypeConverter;
ohair@286 44 import javax.xml.ws.BindingProvider;
ohair@286 45 import javax.xml.ws.WebServiceException;
ohair@286 46 import javax.xml.ws.WebServiceFeature;
alanb@368 47 import javax.xml.ws.handler.MessageContext;
ohair@286 48 import javax.xml.ws.soap.SOAPBinding;
alanb@368 49 import java.io.*;
ohair@286 50 import java.net.CookieHandler;
alanb@368 51 import java.net.HttpURLConnection;
alanb@368 52 import java.util.*;
alanb@368 53 import java.util.Map.Entry;
alanb@368 54 import java.util.logging.Level;
ohair@286 55 import java.util.logging.Logger;
ohair@286 56
ohair@286 57 /**
ohair@286 58 * {@link Tube} that sends a request to a remote HTTP server.
ohair@286 59 *
ohair@286 60 * TODO: need to create separate HTTP transport pipes for binding. SOAP1.1, SOAP1.2,
ohair@286 61 * TODO: XML/HTTP differ in handling status codes.
ohair@286 62 *
ohair@286 63 * @author Jitendra Kotamraju
ohair@286 64 */
ohair@286 65 public class HttpTransportPipe extends AbstractTubeImpl {
alanb@368 66
alanb@368 67 private static final List<String> USER_AGENT = Collections.singletonList(RuntimeVersion.VERSION.toString());
alanb@368 68 private static final Logger LOGGER = Logger.getLogger(HttpTransportPipe.class.getName());
alanb@368 69
alanb@368 70 /**
alanb@368 71 * Dumps what goes across HTTP transport.
alanb@368 72 */
alanb@368 73 public static boolean dump;
ohair@286 74
ohair@286 75 private final Codec codec;
ohair@286 76 private final WSBinding binding;
ohair@286 77 private final CookieHandler cookieJar; // shared object among the tubes
ohair@286 78 private final boolean sticky;
ohair@286 79
ohair@286 80 static {
alanb@368 81 boolean b;
ohair@286 82 try {
alanb@368 83 b = Boolean.getBoolean(HttpTransportPipe.class.getName()+".dump");
alanb@368 84 } catch( Throwable t ) {
alanb@368 85 b = false;
ohair@286 86 }
alanb@368 87 dump = b;
ohair@286 88 }
ohair@286 89
ohair@286 90 public HttpTransportPipe(Codec codec, WSBinding binding) {
ohair@286 91 this.codec = codec;
ohair@286 92 this.binding = binding;
ohair@286 93 this.sticky = isSticky(binding);
ohair@286 94 HttpConfigFeature configFeature = binding.getFeature(HttpConfigFeature.class);
ohair@286 95 if (configFeature == null) {
ohair@286 96 configFeature = new HttpConfigFeature();
ohair@286 97 }
ohair@286 98 this.cookieJar = configFeature.getCookieHandler();
ohair@286 99 }
ohair@286 100
ohair@286 101 private static boolean isSticky(WSBinding binding) {
ohair@286 102 boolean tSticky = false;
ohair@286 103 WebServiceFeature[] features = binding.getFeatures().toArray();
ohair@286 104 for(WebServiceFeature f : features) {
ohair@286 105 if (f instanceof StickyFeature) {
ohair@286 106 tSticky = true;
ohair@286 107 break;
ohair@286 108 }
ohair@286 109 }
ohair@286 110 return tSticky;
ohair@286 111 }
ohair@286 112
ohair@286 113 /*
ohair@286 114 * Copy constructor for {@link Tube#copy(TubeCloner)}.
ohair@286 115 */
ohair@286 116 private HttpTransportPipe(HttpTransportPipe that, TubeCloner cloner) {
ohair@286 117 this(that.codec.copy(), that.binding);
ohair@286 118 cloner.add(that,this);
ohair@286 119 }
ohair@286 120
alanb@368 121 @Override
ohair@286 122 public NextAction processException(@NotNull Throwable t) {
ohair@286 123 return doThrow(t);
ohair@286 124 }
ohair@286 125
alanb@368 126 @Override
ohair@286 127 public NextAction processRequest(@NotNull Packet request) {
ohair@286 128 return doReturnWith(process(request));
ohair@286 129 }
ohair@286 130
alanb@368 131 @Override
ohair@286 132 public NextAction processResponse(@NotNull Packet response) {
ohair@286 133 return doReturnWith(response);
ohair@286 134 }
ohair@286 135
ohair@286 136 protected HttpClientTransport getTransport(Packet request, Map<String, List<String>> reqHeaders) {
ohair@286 137 return new HttpClientTransport(request, reqHeaders);
ohair@286 138 }
ohair@286 139
ohair@286 140 @Override
ohair@286 141 public Packet process(Packet request) {
ohair@286 142 HttpClientTransport con;
ohair@286 143 try {
ohair@286 144 // get transport headers from message
ohair@286 145 Map<String, List<String>> reqHeaders = new Headers();
ohair@286 146 @SuppressWarnings("unchecked")
ohair@286 147 Map<String, List<String>> userHeaders = (Map<String, List<String>>) request.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS);
ohair@286 148 boolean addUserAgent = true;
ohair@286 149 if (userHeaders != null) {
ohair@286 150 // userHeaders may not be modifiable like SingletonMap, just copy them
ohair@286 151 reqHeaders.putAll(userHeaders);
ohair@286 152 // application wants to use its own User-Agent header
ohair@286 153 if (userHeaders.get("User-Agent") != null) {
ohair@286 154 addUserAgent = false;
ohair@286 155 }
ohair@286 156 }
ohair@286 157 if (addUserAgent) {
ohair@286 158 reqHeaders.put("User-Agent", USER_AGENT);
ohair@286 159 }
ohair@286 160
ohair@286 161 addBasicAuth(request, reqHeaders);
ohair@286 162 addCookies(request, reqHeaders);
ohair@286 163
ohair@286 164 con = getTransport(request, reqHeaders);
ohair@286 165 request.addSatellite(new HttpResponseProperties(con));
ohair@286 166
ohair@286 167 ContentType ct = codec.getStaticContentType(request);
ohair@286 168 if (ct == null) {
ohair@286 169 ByteArrayBuffer buf = new ByteArrayBuffer();
ohair@286 170
ohair@286 171 ct = codec.encode(request, buf);
ohair@286 172 // data size is available, set it as Content-Length
ohair@286 173 reqHeaders.put("Content-Length", Collections.singletonList(Integer.toString(buf.size())));
ohair@286 174 reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType()));
ohair@286 175 if (ct.getAcceptHeader() != null) {
ohair@286 176 reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader()));
ohair@286 177 }
ohair@286 178 if (binding instanceof SOAPBinding) {
ohair@286 179 writeSOAPAction(reqHeaders, ct.getSOAPActionHeader());
ohair@286 180 }
ohair@286 181
alanb@368 182 if (dump || LOGGER.isLoggable(Level.FINER)) {
ohair@286 183 dump(buf, "HTTP request", reqHeaders);
alanb@368 184 }
ohair@286 185
ohair@286 186 buf.writeTo(con.getOutput());
ohair@286 187 } else {
ohair@286 188 // Set static Content-Type
ohair@286 189 reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType()));
ohair@286 190 if (ct.getAcceptHeader() != null) {
ohair@286 191 reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader()));
ohair@286 192 }
ohair@286 193 if (binding instanceof SOAPBinding) {
ohair@286 194 writeSOAPAction(reqHeaders, ct.getSOAPActionHeader());
ohair@286 195 }
ohair@286 196
ohair@286 197 if(dump || LOGGER.isLoggable(Level.FINER)) {
ohair@286 198 ByteArrayBuffer buf = new ByteArrayBuffer();
ohair@286 199 codec.encode(request, buf);
ohair@286 200 dump(buf, "HTTP request - "+request.endpointAddress, reqHeaders);
ohair@286 201 OutputStream out = con.getOutput();
ohair@286 202 if (out != null) {
ohair@286 203 buf.writeTo(out);
ohair@286 204 }
ohair@286 205 } else {
ohair@286 206 OutputStream os = con.getOutput();
ohair@286 207 if (os != null) {
ohair@286 208 codec.encode(request, os);
ohair@286 209 }
ohair@286 210 }
ohair@286 211 }
ohair@286 212
ohair@286 213 con.closeOutput();
ohair@286 214
ohair@286 215 return createResponsePacket(request, con);
ohair@286 216 } catch(WebServiceException wex) {
ohair@286 217 throw wex;
ohair@286 218 } catch(Exception ex) {
ohair@286 219 throw new WebServiceException(ex);
ohair@286 220 }
ohair@286 221 }
ohair@286 222
ohair@286 223 private Packet createResponsePacket(Packet request, HttpClientTransport con) throws IOException {
ohair@286 224 con.readResponseCodeAndMessage(); // throws IOE
ohair@286 225 recordCookies(request, con);
ohair@286 226
ohair@286 227 InputStream responseStream = con.getInput();
ohair@286 228 if (dump || LOGGER.isLoggable(Level.FINER)) {
ohair@286 229 ByteArrayBuffer buf = new ByteArrayBuffer();
ohair@286 230 if (responseStream != null) {
ohair@286 231 buf.write(responseStream);
ohair@286 232 responseStream.close();
ohair@286 233 }
ohair@286 234 dump(buf,"HTTP response - "+request.endpointAddress+" - "+con.statusCode, con.getHeaders());
ohair@286 235 responseStream = buf.newInputStream();
ohair@286 236 }
ohair@286 237
ohair@286 238 // Check if stream contains any data
ohair@286 239 int cl = con.contentLength;
ohair@286 240 InputStream tempIn = null;
ohair@286 241 if (cl == -1) { // No Content-Length header
ohair@286 242 tempIn = StreamUtils.hasSomeData(responseStream);
ohair@286 243 if (tempIn != null) {
ohair@286 244 responseStream = tempIn;
ohair@286 245 }
ohair@286 246 }
ohair@286 247 if (cl == 0 || (cl == -1 && tempIn == null)) {
ohair@286 248 if(responseStream != null) {
ohair@286 249 responseStream.close(); // No data, so close the stream
ohair@286 250 responseStream = null;
ohair@286 251 }
ohair@286 252
ohair@286 253 }
ohair@286 254
ohair@286 255 // Allows only certain http status codes for a binding. For all
ohair@286 256 // other status codes, throws exception
ohair@286 257 checkStatusCode(responseStream, con); // throws ClientTransportException
ohair@286 258
ohair@286 259 Packet reply = request.createClientResponse(null);
ohair@286 260 reply.wasTransportSecure = con.isSecure();
ohair@286 261 if (responseStream != null) {
ohair@286 262 String contentType = con.getContentType();
ohair@286 263 if (contentType != null && contentType.contains("text/html") && binding instanceof SOAPBinding) {
ohair@286 264 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(con.statusCode, con.statusMessage));
ohair@286 265 }
ohair@286 266 codec.decode(responseStream, contentType, reply);
ohair@286 267 }
ohair@286 268 return reply;
ohair@286 269 }
ohair@286 270
ohair@286 271 /*
ohair@286 272 * Allows the following HTTP status codes.
ohair@286 273 * SOAP 1.1/HTTP - 200, 202, 500
ohair@286 274 * SOAP 1.2/HTTP - 200, 202, 400, 500
ohair@286 275 * XML/HTTP - all
ohair@286 276 *
ohair@286 277 * For all other status codes, it throws an exception
ohair@286 278 */
ohair@286 279 private void checkStatusCode(InputStream in, HttpClientTransport con) throws IOException {
ohair@286 280 int statusCode = con.statusCode;
ohair@286 281 String statusMessage = con.statusMessage;
ohair@286 282 // SOAP1.1 and SOAP1.2 differ here
ohair@286 283 if (binding instanceof SOAPBinding) {
ohair@286 284 if (binding.getSOAPVersion() == SOAPVersion.SOAP_12) {
ohair@286 285 //In SOAP 1.2, Fault messages can be sent with 4xx and 5xx error codes
ohair@286 286 if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || isErrorCode(statusCode)) {
ohair@286 287 // acceptable status codes for SOAP 1.2
ohair@286 288 if (isErrorCode(statusCode) && in == null) {
ohair@286 289 // No envelope for the error, so throw an exception with http error details
ohair@286 290 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage));
ohair@286 291 }
ohair@286 292 return;
ohair@286 293 }
ohair@286 294 } else {
ohair@286 295 // SOAP 1.1
ohair@286 296 if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
ohair@286 297 // acceptable status codes for SOAP 1.1
ohair@286 298 if (statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR && in == null) {
ohair@286 299 // No envelope for the error, so throw an exception with http error details
ohair@286 300 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage));
ohair@286 301 }
ohair@286 302 return;
ohair@286 303 }
ohair@286 304 }
ohair@286 305 if (in != null) {
ohair@286 306 in.close();
ohair@286 307 }
ohair@286 308 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage));
ohair@286 309 }
ohair@286 310 // Every status code is OK for XML/HTTP
ohair@286 311 }
ohair@286 312
ohair@286 313 private boolean isErrorCode(int code) {
ohair@286 314 //if(code/100 == 5/*Server-side error*/ || code/100 == 4 /*client error*/ ) {
ohair@286 315 return code == 500 || code == 400;
ohair@286 316 }
ohair@286 317
ohair@286 318 private void addCookies(Packet context, Map<String, List<String>> reqHeaders) throws IOException {
ohair@286 319 Boolean shouldMaintainSessionProperty =
alanb@368 320 (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY);
ohair@286 321 if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) {
ohair@286 322 return; // explicitly turned off
ohair@286 323 }
ohair@286 324 if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) {
alanb@368 325 Map<String, List<String>> rememberedCookies = cookieJar.get(context.endpointAddress.getURI(), reqHeaders);
alanb@368 326 processCookieHeaders(reqHeaders, rememberedCookies, "Cookie");
alanb@368 327 processCookieHeaders(reqHeaders, rememberedCookies, "Cookie2");
alanb@368 328 }
alanb@368 329 }
alanb@368 330
alanb@368 331 private void processCookieHeaders(Map<String, List<String>> requestHeaders, Map<String, List<String>> rememberedCookies, String cookieHeader) {
alanb@368 332 List<String> jarCookies = rememberedCookies.get(cookieHeader);
alanb@368 333 if (jarCookies != null && !jarCookies.isEmpty()) {
alanb@368 334 List<String> resultCookies = mergeUserCookies(jarCookies, requestHeaders.get(cookieHeader));
alanb@368 335 requestHeaders.put(cookieHeader, resultCookies);
alanb@368 336 }
alanb@368 337 }
alanb@368 338
alanb@368 339 private List<String> mergeUserCookies(List<String> rememberedCookies, List<String> userCookies) {
alanb@368 340
alanb@368 341 // nothing to merge
alanb@368 342 if (userCookies == null || userCookies.isEmpty()) {
alanb@368 343 return rememberedCookies;
alanb@368 344 }
alanb@368 345
alanb@368 346 Map<String, String> map = new HashMap<String, String>();
alanb@368 347 cookieListToMap(rememberedCookies, map);
alanb@368 348 cookieListToMap(userCookies, map);
alanb@368 349
alanb@368 350 return new ArrayList<String>(map.values());
alanb@368 351 }
alanb@368 352
alanb@368 353 private void cookieListToMap(List<String> cookieList, Map<String, String> targetMap) {
alanb@368 354 for(String cookie : cookieList) {
alanb@368 355 int index = cookie.indexOf("=");
alanb@368 356 String cookieName = cookie.substring(0, index);
alanb@368 357 targetMap.put(cookieName, cookie);
ohair@286 358 }
ohair@286 359 }
ohair@286 360
ohair@286 361 private void recordCookies(Packet context, HttpClientTransport con) throws IOException {
ohair@286 362 Boolean shouldMaintainSessionProperty =
alanb@368 363 (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY);
ohair@286 364 if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) {
ohair@286 365 return; // explicitly turned off
ohair@286 366 }
ohair@286 367 if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) {
ohair@286 368 cookieJar.put(context.endpointAddress.getURI(), con.getHeaders());
ohair@286 369 }
ohair@286 370 }
ohair@286 371
ohair@286 372 private void addBasicAuth(Packet context, Map<String, List<String>> reqHeaders) {
ohair@286 373 String user = (String) context.invocationProperties.get(BindingProvider.USERNAME_PROPERTY);
ohair@286 374 if (user != null) {
ohair@286 375 String pw = (String) context.invocationProperties.get(BindingProvider.PASSWORD_PROPERTY);
ohair@286 376 if (pw != null) {
alanb@368 377 StringBuilder buf = new StringBuilder(user);
ohair@286 378 buf.append(":");
ohair@286 379 buf.append(pw);
ohair@286 380 String creds = DatatypeConverter.printBase64Binary(buf.toString().getBytes());
ohair@286 381 reqHeaders.put("Authorization", Collections.singletonList("Basic "+creds));
ohair@286 382 }
ohair@286 383 }
ohair@286 384 }
ohair@286 385
ohair@286 386 /*
ohair@286 387 * write SOAPAction header if the soapAction parameter is non-null or BindingProvider properties set.
ohair@286 388 * BindingProvider properties take precedence.
ohair@286 389 */
ohair@286 390 private void writeSOAPAction(Map<String, List<String>> reqHeaders, String soapAction) {
ohair@286 391 //dont write SOAPAction HTTP header for SOAP 1.2 messages.
alanb@368 392 if(SOAPVersion.SOAP_12.equals(binding.getSOAPVersion())) {
ohair@286 393 return;
alanb@368 394 }
alanb@368 395 if (soapAction != null) {
ohair@286 396 reqHeaders.put("SOAPAction", Collections.singletonList(soapAction));
alanb@368 397 } else {
ohair@286 398 reqHeaders.put("SOAPAction", Collections.singletonList("\"\""));
alanb@368 399 }
ohair@286 400 }
ohair@286 401
alanb@368 402 @Override
ohair@286 403 public void preDestroy() {
ohair@286 404 // nothing to do. Intentionally left empty.
ohair@286 405 }
ohair@286 406
alanb@368 407 @Override
ohair@286 408 public HttpTransportPipe copy(TubeCloner cloner) {
ohair@286 409 return new HttpTransportPipe(this,cloner);
ohair@286 410 }
ohair@286 411
ohair@286 412
ohair@286 413 private void dump(ByteArrayBuffer buf, String caption, Map<String, List<String>> headers) throws IOException {
ohair@286 414 ByteArrayOutputStream baos = new ByteArrayOutputStream();
ohair@286 415 PrintWriter pw = new PrintWriter(baos, true);
ohair@286 416 pw.println("---["+caption +"]---");
ohair@286 417 for (Entry<String,List<String>> header : headers.entrySet()) {
ohair@286 418 if(header.getValue().isEmpty()) {
ohair@286 419 // I don't think this is legal, but let's just dump it,
ohair@286 420 // as the point of the dump is to uncover problems.
ohair@286 421 pw.println(header.getValue());
ohair@286 422 } else {
ohair@286 423 for (String value : header.getValue()) {
ohair@286 424 pw.println(header.getKey()+": "+value);
ohair@286 425 }
ohair@286 426 }
ohair@286 427 }
ohair@286 428
ohair@286 429 buf.writeTo(baos);
ohair@286 430 pw.println("--------------------");
ohair@286 431
ohair@286 432 String msg = baos.toString();
ohair@286 433 if (dump) {
alanb@368 434 System.out.println(msg);
ohair@286 435 }
ohair@286 436 if (LOGGER.isLoggable(Level.FINER)) {
alanb@368 437 LOGGER.log(Level.FINER, msg);
ohair@286 438 }
ohair@286 439 }
ohair@286 440
ohair@286 441 }

mercurial