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

Thu, 31 Aug 2017 15:18:52 +0800

author
aoqi
date
Thu, 31 Aug 2017 15:18:52 +0800
changeset 637
9c07ef4934dd
parent 408
b0610cd08440
parent 0
373ffda63c9a
permissions
-rw-r--r--

merge

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

mercurial