1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/share/jaxws_classes/com/sun/xml/internal/ws/transport/http/client/HttpTransportPipe.java Wed Apr 27 01:27:09 2016 +0800 1.3 @@ -0,0 +1,450 @@ 1.4 +/* 1.5 + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. Oracle designates this 1.11 + * particular file as subject to the "Classpath" exception as provided 1.12 + * by Oracle in the LICENSE file that accompanied this code. 1.13 + * 1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.17 + * version 2 for more details (a copy is included in the LICENSE file that 1.18 + * accompanied this code). 1.19 + * 1.20 + * You should have received a copy of the GNU General Public License version 1.21 + * 2 along with this work; if not, write to the Free Software Foundation, 1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.23 + * 1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.25 + * or visit www.oracle.com if you need additional information or have any 1.26 + * questions. 1.27 + */ 1.28 + 1.29 +package com.sun.xml.internal.ws.transport.http.client; 1.30 + 1.31 +import com.sun.istack.internal.NotNull; 1.32 +import com.sun.xml.internal.ws.api.SOAPVersion; 1.33 +import com.sun.xml.internal.ws.api.WSBinding; 1.34 +import com.sun.xml.internal.ws.api.ha.StickyFeature; 1.35 +import com.sun.xml.internal.ws.api.message.Packet; 1.36 +import com.sun.xml.internal.ws.api.pipe.*; 1.37 +import com.sun.xml.internal.ws.api.pipe.helper.AbstractTubeImpl; 1.38 +import com.sun.xml.internal.ws.client.ClientTransportException; 1.39 +import com.sun.xml.internal.ws.developer.HttpConfigFeature; 1.40 +import com.sun.xml.internal.ws.resources.ClientMessages; 1.41 +import com.sun.xml.internal.ws.resources.WsservletMessages; 1.42 +import com.sun.xml.internal.ws.transport.Headers; 1.43 +import com.sun.xml.internal.ws.transport.http.HttpAdapter; 1.44 +import com.sun.xml.internal.ws.util.ByteArrayBuffer; 1.45 +import com.sun.xml.internal.ws.util.RuntimeVersion; 1.46 +import com.sun.xml.internal.ws.util.StreamUtils; 1.47 + 1.48 +import javax.xml.bind.DatatypeConverter; 1.49 +import javax.xml.ws.BindingProvider; 1.50 +import javax.xml.ws.WebServiceException; 1.51 +import javax.xml.ws.WebServiceFeature; 1.52 +import javax.xml.ws.handler.MessageContext; 1.53 +import javax.xml.ws.soap.SOAPBinding; 1.54 +import java.io.*; 1.55 +import java.net.CookieHandler; 1.56 +import java.net.HttpURLConnection; 1.57 +import java.util.*; 1.58 +import java.util.Map.Entry; 1.59 +import java.util.logging.Level; 1.60 +import java.util.logging.Logger; 1.61 + 1.62 +/** 1.63 + * {@link Tube} that sends a request to a remote HTTP server. 1.64 + * 1.65 + * TODO: need to create separate HTTP transport pipes for binding. SOAP1.1, SOAP1.2, 1.66 + * TODO: XML/HTTP differ in handling status codes. 1.67 + * 1.68 + * @author Jitendra Kotamraju 1.69 + */ 1.70 +public class HttpTransportPipe extends AbstractTubeImpl { 1.71 + 1.72 + private static final List<String> USER_AGENT = Collections.singletonList(RuntimeVersion.VERSION.toString()); 1.73 + private static final Logger LOGGER = Logger.getLogger(HttpTransportPipe.class.getName()); 1.74 + 1.75 + /** 1.76 + * Dumps what goes across HTTP transport. 1.77 + */ 1.78 + public static boolean dump; 1.79 + 1.80 + private final Codec codec; 1.81 + private final WSBinding binding; 1.82 + private final CookieHandler cookieJar; // shared object among the tubes 1.83 + private final boolean sticky; 1.84 + 1.85 + static { 1.86 + boolean b; 1.87 + try { 1.88 + b = Boolean.getBoolean(HttpTransportPipe.class.getName()+".dump"); 1.89 + } catch( Throwable t ) { 1.90 + b = false; 1.91 + } 1.92 + dump = b; 1.93 + } 1.94 + 1.95 + public HttpTransportPipe(Codec codec, WSBinding binding) { 1.96 + this.codec = codec; 1.97 + this.binding = binding; 1.98 + this.sticky = isSticky(binding); 1.99 + HttpConfigFeature configFeature = binding.getFeature(HttpConfigFeature.class); 1.100 + if (configFeature == null) { 1.101 + configFeature = new HttpConfigFeature(); 1.102 + } 1.103 + this.cookieJar = configFeature.getCookieHandler(); 1.104 + } 1.105 + 1.106 + private static boolean isSticky(WSBinding binding) { 1.107 + boolean tSticky = false; 1.108 + WebServiceFeature[] features = binding.getFeatures().toArray(); 1.109 + for(WebServiceFeature f : features) { 1.110 + if (f instanceof StickyFeature) { 1.111 + tSticky = true; 1.112 + break; 1.113 + } 1.114 + } 1.115 + return tSticky; 1.116 + } 1.117 + 1.118 + /* 1.119 + * Copy constructor for {@link Tube#copy(TubeCloner)}. 1.120 + */ 1.121 + private HttpTransportPipe(HttpTransportPipe that, TubeCloner cloner) { 1.122 + this(that.codec.copy(), that.binding); 1.123 + cloner.add(that,this); 1.124 + } 1.125 + 1.126 + @Override 1.127 + public NextAction processException(@NotNull Throwable t) { 1.128 + return doThrow(t); 1.129 + } 1.130 + 1.131 + @Override 1.132 + public NextAction processRequest(@NotNull Packet request) { 1.133 + return doReturnWith(process(request)); 1.134 + } 1.135 + 1.136 + @Override 1.137 + public NextAction processResponse(@NotNull Packet response) { 1.138 + return doReturnWith(response); 1.139 + } 1.140 + 1.141 + protected HttpClientTransport getTransport(Packet request, Map<String, List<String>> reqHeaders) { 1.142 + return new HttpClientTransport(request, reqHeaders); 1.143 + } 1.144 + 1.145 + @Override 1.146 + public Packet process(Packet request) { 1.147 + HttpClientTransport con; 1.148 + try { 1.149 + // get transport headers from message 1.150 + Map<String, List<String>> reqHeaders = new Headers(); 1.151 + @SuppressWarnings("unchecked") 1.152 + Map<String, List<String>> userHeaders = (Map<String, List<String>>) request.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS); 1.153 + boolean addUserAgent = true; 1.154 + if (userHeaders != null) { 1.155 + // userHeaders may not be modifiable like SingletonMap, just copy them 1.156 + reqHeaders.putAll(userHeaders); 1.157 + // application wants to use its own User-Agent header 1.158 + if (userHeaders.get("User-Agent") != null) { 1.159 + addUserAgent = false; 1.160 + } 1.161 + } 1.162 + if (addUserAgent) { 1.163 + reqHeaders.put("User-Agent", USER_AGENT); 1.164 + } 1.165 + 1.166 + addBasicAuth(request, reqHeaders); 1.167 + addCookies(request, reqHeaders); 1.168 + 1.169 + con = getTransport(request, reqHeaders); 1.170 + request.addSatellite(new HttpResponseProperties(con)); 1.171 + 1.172 + ContentType ct = codec.getStaticContentType(request); 1.173 + if (ct == null) { 1.174 + ByteArrayBuffer buf = new ByteArrayBuffer(); 1.175 + 1.176 + ct = codec.encode(request, buf); 1.177 + // data size is available, set it as Content-Length 1.178 + reqHeaders.put("Content-Length", Collections.singletonList(Integer.toString(buf.size()))); 1.179 + reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType())); 1.180 + if (ct.getAcceptHeader() != null) { 1.181 + reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader())); 1.182 + } 1.183 + if (binding instanceof SOAPBinding) { 1.184 + writeSOAPAction(reqHeaders, ct.getSOAPActionHeader()); 1.185 + } 1.186 + 1.187 + if (dump || LOGGER.isLoggable(Level.FINER)) { 1.188 + dump(buf, "HTTP request", reqHeaders); 1.189 + } 1.190 + 1.191 + buf.writeTo(con.getOutput()); 1.192 + } else { 1.193 + // Set static Content-Type 1.194 + reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType())); 1.195 + if (ct.getAcceptHeader() != null) { 1.196 + reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader())); 1.197 + } 1.198 + if (binding instanceof SOAPBinding) { 1.199 + writeSOAPAction(reqHeaders, ct.getSOAPActionHeader()); 1.200 + } 1.201 + 1.202 + if(dump || LOGGER.isLoggable(Level.FINER)) { 1.203 + ByteArrayBuffer buf = new ByteArrayBuffer(); 1.204 + codec.encode(request, buf); 1.205 + dump(buf, "HTTP request - "+request.endpointAddress, reqHeaders); 1.206 + OutputStream out = con.getOutput(); 1.207 + if (out != null) { 1.208 + buf.writeTo(out); 1.209 + } 1.210 + } else { 1.211 + OutputStream os = con.getOutput(); 1.212 + if (os != null) { 1.213 + codec.encode(request, os); 1.214 + } 1.215 + } 1.216 + } 1.217 + 1.218 + con.closeOutput(); 1.219 + 1.220 + return createResponsePacket(request, con); 1.221 + } catch(WebServiceException wex) { 1.222 + throw wex; 1.223 + } catch(Exception ex) { 1.224 + throw new WebServiceException(ex); 1.225 + } 1.226 + } 1.227 + 1.228 + private Packet createResponsePacket(Packet request, HttpClientTransport con) throws IOException { 1.229 + con.readResponseCodeAndMessage(); // throws IOE 1.230 + recordCookies(request, con); 1.231 + 1.232 + InputStream responseStream = con.getInput(); 1.233 + if (dump || LOGGER.isLoggable(Level.FINER)) { 1.234 + ByteArrayBuffer buf = new ByteArrayBuffer(); 1.235 + if (responseStream != null) { 1.236 + buf.write(responseStream); 1.237 + responseStream.close(); 1.238 + } 1.239 + dump(buf,"HTTP response - "+request.endpointAddress+" - "+con.statusCode, con.getHeaders()); 1.240 + responseStream = buf.newInputStream(); 1.241 + } 1.242 + 1.243 + // Check if stream contains any data 1.244 + int cl = con.contentLength; 1.245 + InputStream tempIn = null; 1.246 + if (cl == -1) { // No Content-Length header 1.247 + tempIn = StreamUtils.hasSomeData(responseStream); 1.248 + if (tempIn != null) { 1.249 + responseStream = tempIn; 1.250 + } 1.251 + } 1.252 + if (cl == 0 || (cl == -1 && tempIn == null)) { 1.253 + if(responseStream != null) { 1.254 + responseStream.close(); // No data, so close the stream 1.255 + responseStream = null; 1.256 + } 1.257 + 1.258 + } 1.259 + 1.260 + // Allows only certain http status codes for a binding. For all 1.261 + // other status codes, throws exception 1.262 + checkStatusCode(responseStream, con); // throws ClientTransportException 1.263 + 1.264 + Packet reply = request.createClientResponse(null); 1.265 + reply.wasTransportSecure = con.isSecure(); 1.266 + if (responseStream != null) { 1.267 + String contentType = con.getContentType(); 1.268 + if (contentType != null && contentType.contains("text/html") && binding instanceof SOAPBinding) { 1.269 + throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(con.statusCode, con.statusMessage)); 1.270 + } 1.271 + codec.decode(responseStream, contentType, reply); 1.272 + } 1.273 + return reply; 1.274 + } 1.275 + 1.276 + /* 1.277 + * Allows the following HTTP status codes. 1.278 + * SOAP 1.1/HTTP - 200, 202, 500 1.279 + * SOAP 1.2/HTTP - 200, 202, 400, 500 1.280 + * XML/HTTP - all 1.281 + * 1.282 + * For all other status codes, it throws an exception 1.283 + */ 1.284 + private void checkStatusCode(InputStream in, HttpClientTransport con) throws IOException { 1.285 + int statusCode = con.statusCode; 1.286 + String statusMessage = con.statusMessage; 1.287 + // SOAP1.1 and SOAP1.2 differ here 1.288 + if (binding instanceof SOAPBinding) { 1.289 + if (binding.getSOAPVersion() == SOAPVersion.SOAP_12) { 1.290 + //In SOAP 1.2, Fault messages can be sent with 4xx and 5xx error codes 1.291 + if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || isErrorCode(statusCode)) { 1.292 + // acceptable status codes for SOAP 1.2 1.293 + if (isErrorCode(statusCode) && in == null) { 1.294 + // No envelope for the error, so throw an exception with http error details 1.295 + throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage)); 1.296 + } 1.297 + return; 1.298 + } 1.299 + } else { 1.300 + // SOAP 1.1 1.301 + if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR) { 1.302 + // acceptable status codes for SOAP 1.1 1.303 + if (statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR && in == null) { 1.304 + // No envelope for the error, so throw an exception with http error details 1.305 + throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage)); 1.306 + } 1.307 + return; 1.308 + } 1.309 + } 1.310 + if (in != null) { 1.311 + in.close(); 1.312 + } 1.313 + throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage)); 1.314 + } 1.315 + // Every status code is OK for XML/HTTP 1.316 + } 1.317 + 1.318 + private boolean isErrorCode(int code) { 1.319 + //if(code/100 == 5/*Server-side error*/ || code/100 == 4 /*client error*/ ) { 1.320 + return code == 500 || code == 400; 1.321 + } 1.322 + 1.323 + private void addCookies(Packet context, Map<String, List<String>> reqHeaders) throws IOException { 1.324 + Boolean shouldMaintainSessionProperty = 1.325 + (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY); 1.326 + if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) { 1.327 + return; // explicitly turned off 1.328 + } 1.329 + if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) { 1.330 + Map<String, List<String>> rememberedCookies = cookieJar.get(context.endpointAddress.getURI(), reqHeaders); 1.331 + processCookieHeaders(reqHeaders, rememberedCookies, "Cookie"); 1.332 + processCookieHeaders(reqHeaders, rememberedCookies, "Cookie2"); 1.333 + } 1.334 + } 1.335 + 1.336 + private void processCookieHeaders(Map<String, List<String>> requestHeaders, Map<String, List<String>> rememberedCookies, String cookieHeader) { 1.337 + List<String> jarCookies = rememberedCookies.get(cookieHeader); 1.338 + if (jarCookies != null && !jarCookies.isEmpty()) { 1.339 + List<String> resultCookies = mergeUserCookies(jarCookies, requestHeaders.get(cookieHeader)); 1.340 + requestHeaders.put(cookieHeader, resultCookies); 1.341 + } 1.342 + } 1.343 + 1.344 + private List<String> mergeUserCookies(List<String> rememberedCookies, List<String> userCookies) { 1.345 + 1.346 + // nothing to merge 1.347 + if (userCookies == null || userCookies.isEmpty()) { 1.348 + return rememberedCookies; 1.349 + } 1.350 + 1.351 + Map<String, String> map = new HashMap<String, String>(); 1.352 + cookieListToMap(rememberedCookies, map); 1.353 + cookieListToMap(userCookies, map); 1.354 + 1.355 + return new ArrayList<String>(map.values()); 1.356 + } 1.357 + 1.358 + private void cookieListToMap(List<String> cookieList, Map<String, String> targetMap) { 1.359 + for(String cookie : cookieList) { 1.360 + int index = cookie.indexOf("="); 1.361 + String cookieName = cookie.substring(0, index); 1.362 + targetMap.put(cookieName, cookie); 1.363 + } 1.364 + } 1.365 + 1.366 + private void recordCookies(Packet context, HttpClientTransport con) throws IOException { 1.367 + Boolean shouldMaintainSessionProperty = 1.368 + (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY); 1.369 + if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) { 1.370 + return; // explicitly turned off 1.371 + } 1.372 + if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) { 1.373 + cookieJar.put(context.endpointAddress.getURI(), con.getHeaders()); 1.374 + } 1.375 + } 1.376 + 1.377 + private void addBasicAuth(Packet context, Map<String, List<String>> reqHeaders) { 1.378 + String user = (String) context.invocationProperties.get(BindingProvider.USERNAME_PROPERTY); 1.379 + if (user != null) { 1.380 + String pw = (String) context.invocationProperties.get(BindingProvider.PASSWORD_PROPERTY); 1.381 + if (pw != null) { 1.382 + StringBuilder buf = new StringBuilder(user); 1.383 + buf.append(":"); 1.384 + buf.append(pw); 1.385 + String creds = DatatypeConverter.printBase64Binary(buf.toString().getBytes()); 1.386 + reqHeaders.put("Authorization", Collections.singletonList("Basic "+creds)); 1.387 + } 1.388 + } 1.389 + } 1.390 + 1.391 + /* 1.392 + * write SOAPAction header if the soapAction parameter is non-null or BindingProvider properties set. 1.393 + * BindingProvider properties take precedence. 1.394 + */ 1.395 + private void writeSOAPAction(Map<String, List<String>> reqHeaders, String soapAction) { 1.396 + //dont write SOAPAction HTTP header for SOAP 1.2 messages. 1.397 + if(SOAPVersion.SOAP_12.equals(binding.getSOAPVersion())) { 1.398 + return; 1.399 + } 1.400 + if (soapAction != null) { 1.401 + reqHeaders.put("SOAPAction", Collections.singletonList(soapAction)); 1.402 + } else { 1.403 + reqHeaders.put("SOAPAction", Collections.singletonList("\"\"")); 1.404 + } 1.405 + } 1.406 + 1.407 + @Override 1.408 + public void preDestroy() { 1.409 + // nothing to do. Intentionally left empty. 1.410 + } 1.411 + 1.412 + @Override 1.413 + public HttpTransportPipe copy(TubeCloner cloner) { 1.414 + return new HttpTransportPipe(this,cloner); 1.415 + } 1.416 + 1.417 + 1.418 + private void dump(ByteArrayBuffer buf, String caption, Map<String, List<String>> headers) throws IOException { 1.419 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1.420 + PrintWriter pw = new PrintWriter(baos, true); 1.421 + pw.println("---["+caption +"]---"); 1.422 + for (Entry<String,List<String>> header : headers.entrySet()) { 1.423 + if(header.getValue().isEmpty()) { 1.424 + // I don't think this is legal, but let's just dump it, 1.425 + // as the point of the dump is to uncover problems. 1.426 + pw.println(header.getValue()); 1.427 + } else { 1.428 + for (String value : header.getValue()) { 1.429 + pw.println(header.getKey()+": "+value); 1.430 + } 1.431 + } 1.432 + } 1.433 + 1.434 + if (buf.size() > HttpAdapter.dump_threshold) { 1.435 + byte[] b = buf.getRawData(); 1.436 + baos.write(b, 0, HttpAdapter.dump_threshold); 1.437 + pw.println(); 1.438 + pw.println(WsservletMessages.MESSAGE_TOO_LONG(HttpAdapter.class.getName() + ".dumpTreshold")); 1.439 + } else { 1.440 + buf.writeTo(baos); 1.441 + } 1.442 + pw.println("--------------------"); 1.443 + 1.444 + String msg = baos.toString(); 1.445 + if (dump) { 1.446 + System.out.println(msg); 1.447 + } 1.448 + if (LOGGER.isLoggable(Level.FINER)) { 1.449 + LOGGER.log(Level.FINER, msg); 1.450 + } 1.451 + } 1.452 + 1.453 +}