src/share/jaxws_classes/com/sun/xml/internal/ws/transport/http/HttpAdapter.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;
    28 import java.io.ByteArrayOutputStream;
    29 import java.io.IOException;
    30 import java.io.InputStream;
    31 import java.io.OutputStream;
    32 import java.io.OutputStreamWriter;
    33 import java.io.PrintWriter;
    34 import java.net.HttpURLConnection;
    35 import java.util.Collections;
    36 import java.util.HashMap;
    37 import java.util.List;
    38 import java.util.Map;
    39 import java.util.Map.Entry;
    40 import java.util.TreeMap;
    41 import java.util.logging.Level;
    42 import java.util.logging.Logger;
    44 import javax.xml.ws.Binding;
    45 import javax.xml.ws.WebServiceException;
    46 import javax.xml.ws.http.HTTPBinding;
    48 import com.oracle.webservices.internal.api.message.PropertySet;
    49 import com.sun.istack.internal.NotNull;
    50 import com.sun.istack.internal.Nullable;
    51 import com.sun.xml.internal.ws.api.Component;
    52 import com.sun.xml.internal.ws.api.EndpointAddress;
    53 import com.sun.xml.internal.ws.api.SOAPVersion;
    54 import com.sun.xml.internal.ws.api.addressing.AddressingVersion;
    55 import com.sun.xml.internal.ws.api.addressing.NonAnonymousResponseProcessor;
    56 import com.sun.xml.internal.ws.api.ha.HaInfo;
    57 import com.sun.xml.internal.ws.api.message.ExceptionHasMessage;
    58 import com.sun.xml.internal.ws.api.message.Message;
    59 import com.sun.xml.internal.ws.api.message.Packet;
    60 import com.sun.xml.internal.ws.api.pipe.Codec;
    61 import com.sun.xml.internal.ws.api.pipe.ContentType;
    62 import com.sun.xml.internal.ws.api.server.AbstractServerAsyncTransport;
    63 import com.sun.xml.internal.ws.api.server.Adapter;
    64 import com.sun.xml.internal.ws.api.server.BoundEndpoint;
    65 import com.sun.xml.internal.ws.api.server.DocumentAddressResolver;
    66 import com.sun.xml.internal.ws.api.server.Module;
    67 import com.sun.xml.internal.ws.api.server.PortAddressResolver;
    68 import com.sun.xml.internal.ws.api.server.SDDocument;
    69 import com.sun.xml.internal.ws.api.server.ServiceDefinition;
    70 import com.sun.xml.internal.ws.api.server.TransportBackChannel;
    71 import com.sun.xml.internal.ws.api.server.WSEndpoint;
    72 import com.sun.xml.internal.ws.api.server.WebServiceContextDelegate;
    73 import com.sun.xml.internal.ws.fault.SOAPFaultBuilder;
    74 import com.sun.xml.internal.ws.resources.WsservletMessages;
    75 import com.sun.xml.internal.ws.server.UnsupportedMediaException;
    76 import com.sun.xml.internal.ws.util.ByteArrayBuffer;
    77 import com.sun.xml.internal.ws.util.Pool;
    80 /**
    81  * {@link com.sun.xml.internal.ws.api.server.Adapter} that receives messages in HTTP.
    82  *
    83  * <p>
    84  * This object also assigns unique query string (such as "xsd=1") to
    85  * each {@link com.sun.xml.internal.ws.api.server.SDDocument} so that they can be served by HTTP GET requests.
    86  *
    87  * @author Kohsuke Kawaguchi
    88  * @author Jitendra Kotamraju
    89  */
    90 public class HttpAdapter extends Adapter<HttpAdapter.HttpToolkit> {
    92     private static final Logger LOGGER = Logger.getLogger(HttpAdapter.class.getName());
    94     /**
    95      * {@link com.sun.xml.internal.ws.api.server.SDDocument}s keyed by the query string like "?abc".
    96      * Used for serving documents via HTTP GET.
    97      *
    98      * Empty if the endpoint doesn't have {@link com.sun.xml.internal.ws.api.server.ServiceDefinition}.
    99      * Read-only.
   100      */
   101     protected Map<String,SDDocument> wsdls;
   103     /**
   104      * Reverse map of {@link #wsdls}. Read-only.
   105      */
   106     private Map<SDDocument,String> revWsdls;
   108     /**
   109      * A reference to the service definition from which the map of wsdls/revWsdls
   110      * was created. This allows us to establish if the service definition documents
   111      * have changed in the meantime.
   112      */
   113     private ServiceDefinition serviceDefinition = null;
   115     public final HttpAdapterList<? extends HttpAdapter> owner;
   117     /**
   118      * Servlet URL pattern with which this {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} is associated.
   119      */
   120     public final String urlPattern;
   122     protected boolean stickyCookie;
   124     protected boolean disableJreplicaCookie = false;
   126     /**
   127      * Creates a lone {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} that does not know of any other
   128      * {@link com.sun.xml.internal.ws.transport.http.HttpAdapter}s.
   129      *
   130      * This is convenient for creating an {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} for an environment
   131      * where they don't know each other (such as JavaSE deployment.)
   132      *
   133      * @param endpoint web service endpoint
   134      * @return singe adapter to process HTTP messages
   135      */
   136     public static HttpAdapter createAlone(WSEndpoint endpoint) {
   137         return new DummyList().createAdapter("","",endpoint);
   138     }
   140     /**
   141      * @deprecated
   142      *      remove as soon as we can update the test util.
   143      * @param endpoint web service endpoint
   144      * @param owner list of related adapters
   145      */
   146     protected HttpAdapter(WSEndpoint endpoint,
   147                           HttpAdapterList<? extends HttpAdapter> owner) {
   148         this(endpoint,owner,null);
   149     }
   151     protected HttpAdapter(WSEndpoint endpoint,
   152                           HttpAdapterList<? extends HttpAdapter> owner,
   153                           String urlPattern) {
   154         super(endpoint);
   155         this.owner = owner;
   156         this.urlPattern = urlPattern;
   158         initWSDLMap(endpoint.getServiceDefinition());
   159     }
   161     /**
   162      * Return the last known service definition of the endpoint.
   163      *
   164      * @return The service definition of the endpoint
   165      */
   166     public ServiceDefinition getServiceDefinition() {
   167         return this.serviceDefinition;
   168     }
   170     /**
   171      * Fill in WSDL map.
   172      *
   173      * @param sdef service definition
   174      */
   175     public final void initWSDLMap(ServiceDefinition sdef) {
   176         this.serviceDefinition = sdef;
   177         if(sdef==null) {
   178             wsdls = Collections.emptyMap();
   179             revWsdls = Collections.emptyMap();
   180         } else {
   181             wsdls = new HashMap<String, SDDocument>();  // wsdl=1 --> Doc
   182             // Sort WSDL, Schema documents based on SystemId so that the same
   183             // document gets wsdl=x mapping
   184             Map<String, SDDocument> systemIds = new TreeMap<String, SDDocument>();
   185             for (SDDocument sdd : sdef) {
   186                 if (sdd == sdef.getPrimary()) { // No sorting for Primary WSDL
   187                     wsdls.put("wsdl", sdd);
   188                     wsdls.put("WSDL", sdd);
   189                 } else {
   190                     systemIds.put(sdd.getURL().toString(), sdd);
   191                 }
   192             }
   194             int wsdlnum = 1;
   195             int xsdnum = 1;
   196             for (Entry<String, SDDocument> e : systemIds.entrySet()) {
   197                 SDDocument sdd = e.getValue();
   198                 if (sdd.isWSDL()) {
   199                     wsdls.put("wsdl="+(wsdlnum++),sdd);
   200                 }
   201                 if (sdd.isSchema()) {
   202                     wsdls.put("xsd="+(xsdnum++),sdd);
   203                 }
   204             }
   206             revWsdls = new HashMap<SDDocument,String>();    // Doc --> wsdl=1
   207             for (Entry<String,SDDocument> e : wsdls.entrySet()) {
   208                 if (!e.getKey().equals("WSDL")) {           // map Doc --> wsdl, not WSDL
   209                     revWsdls.put(e.getValue(),e.getKey());
   210                 }
   211             }
   212         }
   213     }
   215     /**
   216      * Returns the "/abc/def/ghi" portion if
   217      * the URL pattern is "/abc/def/ghi/*".
   218      */
   219     public String getValidPath() {
   220         if (urlPattern.endsWith("/*")) {
   221             return urlPattern.substring(0, urlPattern.length() - 2);
   222         } else {
   223             return urlPattern;
   224         }
   225     }
   227     @Override
   228     protected HttpToolkit createToolkit() {
   229         return new HttpToolkit();
   230     }
   232     /**
   233      * Receives the incoming HTTP connection and dispatches
   234      * it to JAX-WS. This method returns when JAX-WS completes
   235      * processing the request and the whole reply is written
   236      * to {@link WSHTTPConnection}.
   237      *
   238      * <p>
   239      * This method is invoked by the lower-level HTTP stack,
   240      * and "connection" here is an HTTP connection.
   241      *
   242      * <p>
   243      * To populate a request {@link com.sun.xml.internal.ws.api.message.Packet} with more info,
   244      * define {@link com.oracle.webservices.internal.api.message.PropertySet.Property properties} on
   245      * {@link WSHTTPConnection}.
   246      *
   247      * @param connection to receive/send HTTP messages for web service endpoints
   248      * @throws java.io.IOException when I/O errors happen
   249      */
   250     public void handle(@NotNull WSHTTPConnection connection) throws IOException {
   251         if (handleGet(connection)) {
   252             return;
   253         }
   255         // Make sure the Toolkit is recycled by the same pool instance from which it was taken
   256         final Pool<HttpToolkit> currentPool = getPool();
   257         // normal request handling
   258         final HttpToolkit tk = currentPool.take();
   259         try {
   260             tk.handle(connection);
   261         } finally {
   262             currentPool.recycle(tk);
   263         }
   264     }
   266     public boolean handleGet(@NotNull WSHTTPConnection connection) throws IOException {
   267         if (connection.getRequestMethod().equals("GET")) {
   268             // metadata query. let the interceptor run
   269             for (Component c : endpoint.getComponents()) {
   270                 HttpMetadataPublisher spi = c.getSPI(HttpMetadataPublisher.class);
   271                 if (spi != null && spi.handleMetadataRequest(this, connection)) {
   272                     return true;
   273                 } // handled
   274             }
   276             if (isMetadataQuery(connection.getQueryString())) {
   277                 // Sends published WSDL and schema documents as the default action.
   278                 publishWSDL(connection);
   279                 return true;
   280             }
   282             Binding binding = getEndpoint().getBinding();
   283             if (!(binding instanceof HTTPBinding)) {
   284                 // Writes HTML page with all the endpoint descriptions
   285                 writeWebServicesHtmlPage(connection);
   286                 return true;
   287             }
   288         } else if (connection.getRequestMethod().equals("HEAD")) {
   289             connection.getInput().close();
   290             Binding binding = getEndpoint().getBinding();
   291             if (isMetadataQuery(connection.getQueryString())) {
   292                 SDDocument doc = wsdls.get(connection.getQueryString());
   293                 connection.setStatus(doc != null
   294                         ? HttpURLConnection.HTTP_OK
   295                         : HttpURLConnection.HTTP_NOT_FOUND);
   296                 connection.getOutput().close();
   297                 connection.close();
   298                 return true;
   299             } else if (!(binding instanceof HTTPBinding)) {
   300                 connection.setStatus(HttpURLConnection.HTTP_NOT_FOUND);
   301                 connection.getOutput().close();
   302                 connection.close();
   303                 return true;
   304             }
   305             // Let the endpoint handle for HTTPBinding
   306         }
   308         return false;
   310     }
   311     /*
   312      *
   313      * @param con
   314      * @param codec
   315      * @return
   316      * @throws IOException
   317      *         ExceptionHasMessage exception that contains particular fault message
   318      *         UnsupportedMediaException to indicate to send 415 error code
   319      */
   320     private Packet decodePacket(@NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException {
   321         String ct = con.getRequestHeader("Content-Type");
   322         InputStream in = con.getInput();
   323         Packet packet = new Packet();
   324         packet.soapAction = fixQuotesAroundSoapAction(con.getRequestHeader("SOAPAction"));
   325         packet.wasTransportSecure = con.isSecure();
   326         packet.acceptableMimeTypes = con.getRequestHeader("Accept");
   327         packet.addSatellite(con);
   328         addSatellites(packet);
   329         packet.isAdapterDeliversNonAnonymousResponse = true;
   330         packet.component = this;
   331         packet.transportBackChannel = new Oneway(con);
   332         packet.webServiceContextDelegate = con.getWebServiceContextDelegate();
   333         packet.setState(Packet.State.ServerRequest);
   334         if (dump || LOGGER.isLoggable(Level.FINER)) {
   335             ByteArrayBuffer buf = new ByteArrayBuffer();
   336             buf.write(in);
   337             in.close();
   338             dump(buf, "HTTP request", con.getRequestHeaders());
   339             in = buf.newInputStream();
   340         }
   341         codec.decode(in, ct, packet);
   342         return packet;
   343     }
   345     protected void addSatellites(Packet packet) {
   346     }
   348     /**
   349      * Some stacks may send non WS-I BP 1.2 conforming SoapAction.
   350      * Make sure SOAPAction is quoted as {@link com.sun.xml.internal.ws.api.message.Packet#soapAction} expects quoted soapAction value.
   351      *
   352      * @param soapAction SoapAction HTTP Header
   353      * @return quoted SOAPAction value
   354      */
   355     static public String fixQuotesAroundSoapAction(String soapAction) {
   356         if(soapAction != null && (!soapAction.startsWith("\"") || !soapAction.endsWith("\"")) ) {
   357             if (LOGGER.isLoggable(Level.INFO)) {
   358                 LOGGER.log(Level.INFO, "Received WS-I BP non-conformant Unquoted SoapAction HTTP header: {0}", soapAction);
   359             }
   360             String fixedSoapAction = soapAction;
   361             if(!soapAction.startsWith("\"")) {
   362                 fixedSoapAction = "\"" + fixedSoapAction;
   363             }
   364             if(!soapAction.endsWith("\"")) {
   365                 fixedSoapAction = fixedSoapAction + "\"";
   366             }
   367             return fixedSoapAction;
   368         }
   369         return soapAction;
   370     }
   372     protected NonAnonymousResponseProcessor getNonAnonymousResponseProcessor() {
   373         return NonAnonymousResponseProcessor.getDefault();
   374     }
   376     /**
   377      * This method is added for the case of the sub-class wants to override the method to
   378      * print details. E.g. convert soapfault as HTML msg for 403 error connstatus.
   379      * @param os
   380      */
   381     protected void writeClientError(int connStatus, @NotNull OutputStream os, @NotNull Packet packet) throws IOException {
   382         //do nothing
   383     }
   385     private boolean isClientErrorStatus(int connStatus)
   386     {
   387         return (connStatus == HttpURLConnection.HTTP_FORBIDDEN); // add more for future.
   388     }
   390     private boolean isNonAnonymousUri(EndpointAddress addr){
   391         return (addr != null) && !addr.toString().equals(AddressingVersion.W3C.anonymousUri) &&
   392                         !addr.toString().equals(AddressingVersion.MEMBER.anonymousUri);
   393     }
   395     private void encodePacket(@NotNull Packet packet, @NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException {
   396         if (isNonAnonymousUri(packet.endpointAddress) && packet.getMessage() != null) {
   397            try {
   398                 // Message is targeted to non-anonymous response endpoint.
   399                 // After call to non-anonymous processor, typically, packet.getMessage() will be null
   400                 // however, processors could use this pattern to modify the response sent on the back-channel,
   401                 // e.g. send custom HTTP headers with the HTTP 202
   402                     packet = getNonAnonymousResponseProcessor().process(packet);
   403             } catch (RuntimeException re) {
   404                 // if processing by NonAnonymousResponseProcessor fails, new SOAPFaultMessage is created to be sent
   405                 // to back-channel client
   406                 SOAPVersion soapVersion = packet.getBinding().getSOAPVersion();
   407                 Message faultMsg = SOAPFaultBuilder.createSOAPFaultMessage(soapVersion, null, re);
   408                 packet = packet.createServerResponse(faultMsg, packet.endpoint.getPort(), null, packet.endpoint.getBinding());
   409             }
   410         }
   412         if (con.isClosed()) {
   413             return;                 // Connection is already closed
   414         }
   415         Message responseMessage = packet.getMessage();
   416         addStickyCookie(con);
   417         addReplicaCookie(con, packet);
   418         if (responseMessage == null) {
   419             if (!con.isClosed()) {
   420                 // set the response code if not already set
   421                 // for example, 415 may have been set earlier for Unsupported Content-Type
   422                 if (con.getStatus() == 0) {
   423                     con.setStatus(WSHTTPConnection.ONEWAY);
   424                 }
   425                 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
   426                 if (dump || LOGGER.isLoggable(Level.FINER)) {
   427                     ByteArrayBuffer buf = new ByteArrayBuffer();
   428                     codec.encode(packet, buf);
   429                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
   430                     buf.writeTo(os);
   431                 } else {
   432                     codec.encode(packet, os);
   433                 }
   434                 // close the response channel now
   435                 try {
   436                     os.close(); // no payload
   437                 } catch (IOException e) {
   438                     throw new WebServiceException(e);
   439                 }
   440             }
   441         } else {
   442             if (con.getStatus() == 0) {
   443                 // if the appliation didn't set the status code,
   444                 // set the default one.
   445                 con.setStatus(responseMessage.isFault()
   446                         ? HttpURLConnection.HTTP_INTERNAL_ERROR
   447                         : HttpURLConnection.HTTP_OK);
   448             }
   450             if (isClientErrorStatus(con.getStatus())) {
   451                 OutputStream os = con.getOutput();
   452                 if (dump || LOGGER.isLoggable(Level.FINER)) {
   453                     ByteArrayBuffer buf = new ByteArrayBuffer();
   454                     writeClientError(con.getStatus(), buf, packet);
   455                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
   456                     buf.writeTo(os);
   457                 } else {
   458                     writeClientError(con.getStatus(), os, packet);
   459                 }
   460                 os.close();
   461                   return;
   462             }
   464             ContentType contentType = codec.getStaticContentType(packet);
   465             if (contentType != null) {
   466                 con.setContentTypeResponseHeader(contentType.getContentType());
   467                 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
   468                 if (dump || LOGGER.isLoggable(Level.FINER)) {
   469                     ByteArrayBuffer buf = new ByteArrayBuffer();
   470                     codec.encode(packet, buf);
   471                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
   472                     buf.writeTo(os);
   473                 } else {
   474                     codec.encode(packet, os);
   475                 }
   476                 os.close();
   477             } else {
   479                 ByteArrayBuffer buf = new ByteArrayBuffer();
   480                 contentType = codec.encode(packet, buf);
   481                 con.setContentTypeResponseHeader(contentType.getContentType());
   482                 if (dump || LOGGER.isLoggable(Level.FINER)) {
   483                     dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
   484                 }
   485                 OutputStream os = con.getOutput();
   486                 buf.writeTo(os);
   487                 os.close();
   488             }
   489         }
   490     }
   492     /*
   493      * GlassFish Load-balancer plugin always add a header proxy-jroute on
   494      * request being send from load-balancer plugin to server
   495      *
   496      * JROUTE cookie need to be stamped in two cases
   497      * 1 : At the time of session creation. In this case, request will not have
   498      * any JROUTE cookie.
   499      * 2 : At the time of fail-over. In this case, value of proxy-jroute
   500      * header(will point to current instance) and JROUTE cookie(will point to
   501      * previous failed instance) will be different. This logic can be used
   502      * to determine fail-over scenario.
   503      */
   504     private void addStickyCookie(WSHTTPConnection con) {
   505         if (stickyCookie) {
   506             String proxyJroute = con.getRequestHeader("proxy-jroute");
   507             if (proxyJroute == null) {
   508                 // Load-balancer plugin is not front-ending this instance
   509                 return;
   510             }
   512             String jrouteId = con.getCookie("JROUTE");
   513             if (jrouteId == null || !jrouteId.equals(proxyJroute)) {
   514                 // Initial request or failover
   515                 con.setCookie("JROUTE", proxyJroute);
   516             }
   517         }
   518     }
   520     private void addReplicaCookie(WSHTTPConnection con, Packet packet) {
   521         if (stickyCookie) {
   522             HaInfo haInfo = null;
   523             if (packet.supports(Packet.HA_INFO)) {
   524                 haInfo = (HaInfo)packet.get(Packet.HA_INFO);
   525             }
   526             if (haInfo != null) {
   527                 con.setCookie("METRO_KEY", haInfo.getKey());
   528                 if (!disableJreplicaCookie) {
   529                     con.setCookie("JREPLICA", haInfo.getReplicaInstance());
   530                 }
   531             }
   532         }
   533     }
   535     public void invokeAsync(final WSHTTPConnection con) throws IOException {
   536         invokeAsync(con, NO_OP_COMPLETION_CALLBACK);
   537     }
   539     public void invokeAsync(final WSHTTPConnection con, final CompletionCallback callback) throws IOException {
   541             if (handleGet(con)) {
   542                 callback.onCompletion();
   543                 return;
   544             }
   545             final Pool<HttpToolkit> currentPool = getPool();
   546             final HttpToolkit tk = currentPool.take();
   547             final Packet request;
   549             try {
   551                 request = decodePacket(con, tk.codec);
   552             } catch (ExceptionHasMessage e) {
   553                 LOGGER.log(Level.SEVERE, e.getMessage(), e);
   554                 Packet response = new Packet();
   555                 response.setMessage(e.getFaultMessage());
   556                 encodePacket(response, con, tk.codec);
   557                 currentPool.recycle(tk);
   558                 con.close();
   559                 callback.onCompletion();
   560                 return;
   561             } catch (UnsupportedMediaException e) {
   562                 LOGGER.log(Level.SEVERE, e.getMessage(), e);
   563                 Packet response = new Packet();
   564                 con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);
   565                 encodePacket(response, con, tk.codec);
   566                 currentPool.recycle(tk);
   567                 con.close();
   568                 callback.onCompletion();
   569                 return;
   570             }
   572             endpoint.process(request, new WSEndpoint.CompletionCallback() {
   573                 @Override
   574                 public void onCompletion(@NotNull Packet response) {
   575                     try {
   576                         try {
   577                             encodePacket(response, con, tk.codec);
   578                         } catch (IOException ioe) {
   579                             LOGGER.log(Level.SEVERE, ioe.getMessage(), ioe);
   580                         }
   581                         currentPool.recycle(tk);
   582                     } finally {
   583                         con.close();
   584                         callback.onCompletion();
   586                     }
   587                 }
   588             },null);
   590     }
   592     public static  final CompletionCallback NO_OP_COMPLETION_CALLBACK = new CompletionCallback() {
   594         @Override
   595         public void onCompletion() {
   596             //NO-OP
   597         }
   598     };
   600     public interface CompletionCallback{
   601         void onCompletion();
   602     }
   604     final class AsyncTransport extends AbstractServerAsyncTransport<WSHTTPConnection> {
   606         public AsyncTransport() {
   607             super(endpoint);
   608         }
   610         public void handleAsync(WSHTTPConnection con) throws IOException {
   611             super.handle(con);
   612         }
   614         @Override
   615         protected void encodePacket(WSHTTPConnection con, @NotNull Packet packet, @NotNull Codec codec) throws IOException {
   616             HttpAdapter.this.encodePacket(packet, con, codec);
   617         }
   619         protected @Override @Nullable String getAcceptableMimeTypes(WSHTTPConnection con) {
   620             return null;
   621         }
   623         protected @Override @Nullable TransportBackChannel getTransportBackChannel(WSHTTPConnection con) {
   624             return new Oneway(con);
   625         }
   627         protected @Override @NotNull
   628         PropertySet getPropertySet(WSHTTPConnection con) {
   629             return con;
   630         }
   632         protected @Override @NotNull WebServiceContextDelegate getWebServiceContextDelegate(WSHTTPConnection con) {
   633             return con.getWebServiceContextDelegate();
   634         }
   635     }
   637     static final class Oneway implements TransportBackChannel {
   638         WSHTTPConnection con;
   639         boolean closed;
   641         Oneway(WSHTTPConnection con) {
   642             this.con = con;
   643         }
   644         @Override
   645         public void close() {
   646             if (!closed) {
   647                 closed = true;
   648                 // close the response channel now
   649                 if (con.getStatus() == 0) {
   650                     // if the appliation didn't set the status code,
   651                     // set the default one.
   652                     con.setStatus(WSHTTPConnection.ONEWAY);
   653                 }
   655                 OutputStream output = null;
   656                 try {
   657                     output = con.getOutput();
   658                 } catch (IOException e) {
   659                     // no-op
   660                 }
   662                 if (dump || LOGGER.isLoggable(Level.FINER)) {
   663                     try {
   664                         ByteArrayBuffer buf = new ByteArrayBuffer();
   665                         dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
   666                     } catch (Exception e) {
   667                         throw new WebServiceException(e.toString(), e);
   668                     }
   669                 }
   671                 if (output != null) {
   672                         try {
   673                                 output.close(); // no payload
   674                         } catch (IOException e) {
   675                                 throw new WebServiceException(e);
   676                         }
   677                 }
   678                 con.close();
   679             }
   680         }
   681     }
   683     final class HttpToolkit extends Adapter.Toolkit {
   684         public void handle(WSHTTPConnection con) throws IOException {
   685             try {
   686                 boolean invoke = false;
   687                 Packet packet;
   688                 try {
   689                     packet = decodePacket(con, codec);
   690                     invoke = true;
   691                 } catch(Exception e) {
   692                     packet = new Packet();
   693                     if (e instanceof ExceptionHasMessage) {
   694                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
   695                         packet.setMessage(((ExceptionHasMessage)e).getFaultMessage());
   696                     } else if (e instanceof UnsupportedMediaException) {
   697                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
   698                         con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);
   699                     } else {
   700                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
   701                         con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
   702                     }
   703                 }
   704                 if (invoke) {
   705                     try {
   706                         packet = head.process(packet, con.getWebServiceContextDelegate(),
   707                                 packet.transportBackChannel);
   708                     } catch(Throwable e) {
   709                         LOGGER.log(Level.SEVERE, e.getMessage(), e);
   710                         if (!con.isClosed()) {
   711                             writeInternalServerError(con);
   712                         }
   713                         return;
   714                     }
   715                 }
   716                 encodePacket(packet, con, codec);
   717             } finally {
   718                 if (!con.isClosed()) {
   719                     if (LOGGER.isLoggable(Level.FINE)) {
   720                         LOGGER.log(Level.FINE, "Closing HTTP Connection with status: {0}", con.getStatus());
   721                     }
   722                     con.close();
   723                 }
   724             }
   725         }
   726     }
   728     /**
   729      * Returns true if the given query string is for metadata request.
   730      *
   731      * @param query
   732      *      String like "xsd=1" or "perhaps=some&amp;unrelated=query".
   733      *      Can be null.
   734      * @return true for metadata requests
   735      *         false for web service requests
   736      */
   737     private boolean isMetadataQuery(String query) {
   738         // we intentionally return true even if documents don't exist,
   739         // so that they get 404.
   740         return query != null && (query.equals("WSDL") || query.startsWith("wsdl") || query.startsWith("xsd="));
   741     }
   743     /**
   744      * Sends out the WSDL (and other referenced documents)
   745      * in response to the GET requests to URLs like "?wsdl" or "?xsd=2".
   746      *
   747      * @param con
   748      *      The connection to which the data will be sent.
   749      *
   750      * @throws java.io.IOException when I/O errors happen
   751      */
   752     public void publishWSDL(@NotNull WSHTTPConnection con) throws IOException {
   753         con.getInput().close();
   755         SDDocument doc = wsdls.get(con.getQueryString());
   756         if (doc == null) {
   757             writeNotFoundErrorPage(con,"Invalid Request");
   758             return;
   759         }
   761         con.setStatus(HttpURLConnection.HTTP_OK);
   762         con.setContentTypeResponseHeader("text/xml;charset=utf-8");
   764         OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
   766         PortAddressResolver portAddressResolver = getPortAddressResolver(con.getBaseAddress());
   767         DocumentAddressResolver resolver = getDocumentAddressResolver(portAddressResolver);
   769         doc.writeTo(portAddressResolver, resolver, os);
   770         os.close();
   771     }
   773     public PortAddressResolver getPortAddressResolver(String baseAddress) {
   774         return owner.createPortAddressResolver(baseAddress, endpoint.getImplementationClass());
   775     }
   777     public DocumentAddressResolver getDocumentAddressResolver(
   778                         PortAddressResolver portAddressResolver) {
   779         final String address = portAddressResolver.getAddressFor(endpoint.getServiceName(), endpoint.getPortName().getLocalPart());
   780         assert address != null;
   781         return new DocumentAddressResolver() {
   782             @Override
   783             public String getRelativeAddressFor(@NotNull SDDocument current, @NotNull SDDocument referenced) {
   784                 // the map on endpoint should account for all SDDocument
   785                 assert revWsdls.containsKey(referenced);
   786                 return address+'?'+ revWsdls.get(referenced);
   787             }
   788         };
   789     }
   791     /**
   792      * HTTP/1.0 connections require Content-Length. So just buffer to find out
   793      * the length.
   794      */
   795     private final static class Http10OutputStream extends ByteArrayBuffer {
   796         private final WSHTTPConnection con;
   798         Http10OutputStream(WSHTTPConnection con) {
   799             this.con = con;
   800         }
   802         @Override
   803         public void close() throws IOException {
   804             super.close();
   805             con.setContentLengthResponseHeader(size());
   806             OutputStream os = con.getOutput();
   807             writeTo(os);
   808             os.close();
   809         }
   810     }
   812     private void writeNotFoundErrorPage(WSHTTPConnection con, String message) throws IOException {
   813         con.setStatus(HttpURLConnection.HTTP_NOT_FOUND);
   814         con.setContentTypeResponseHeader("text/html; charset=utf-8");
   816         PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8"));
   817         out.println("<html>");
   818         out.println("<head><title>");
   819         out.println(WsservletMessages.SERVLET_HTML_TITLE());
   820         out.println("</title></head>");
   821         out.println("<body>");
   822         out.println(WsservletMessages.SERVLET_HTML_NOT_FOUND(message));
   823         out.println("</body>");
   824         out.println("</html>");
   825         out.close();
   826     }
   828     private void writeInternalServerError(WSHTTPConnection con) throws IOException {
   829         con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
   830         con.getOutput().close();        // Sets the status code
   831     }
   833     private static final class DummyList extends HttpAdapterList<HttpAdapter> {
   834         @Override
   835         protected HttpAdapter createHttpAdapter(String name, String urlPattern, WSEndpoint<?> endpoint) {
   836             return new HttpAdapter(endpoint,this,urlPattern);
   837         }
   838     }
   840     private static void dump(ByteArrayBuffer buf, String caption, Map<String, List<String>> headers) throws IOException {
   841         ByteArrayOutputStream baos = new ByteArrayOutputStream();
   842         PrintWriter pw = new PrintWriter(baos, true);
   843         pw.println("---["+caption +"]---");
   844         if (headers != null) {
   845             for (Entry<String, List<String>> header : headers.entrySet()) {
   846                 if (header.getValue().isEmpty()) {
   847                     // I don't think this is legal, but let's just dump it,
   848                     // as the point of the dump is to uncover problems.
   849                     pw.println(header.getValue());
   850                 } else {
   851                     for (String value : header.getValue()) {
   852                         pw.println(header.getKey() + ": " + value);
   853                     }
   854                 }
   855             }
   856         }
   857         if (buf.size() > dump_threshold) {
   858             byte[] b = buf.getRawData();
   859             baos.write(b, 0, dump_threshold);
   860             pw.println();
   861             pw.println(WsservletMessages.MESSAGE_TOO_LONG(HttpAdapter.class.getName() + ".dumpTreshold"));
   862         } else {
   863             buf.writeTo(baos);
   864         }
   865         pw.println("--------------------");
   867         String msg = baos.toString();
   868         if (dump) {
   869           System.out.println(msg);
   870         }
   871         if (LOGGER.isLoggable(Level.FINER)) {
   872           LOGGER.log(Level.FINER, msg);
   873         }
   874     }
   876     /*
   877      * Generates the listing of all services.
   878      */
   879     private void writeWebServicesHtmlPage(WSHTTPConnection con) throws IOException {
   880         if (!publishStatusPage) {
   881             return;
   882         }
   884         // TODO: resurrect the ability to localize according to the current request.
   886         con.getInput().close();
   888         // standard browsable page
   889         con.setStatus(WSHTTPConnection.OK);
   890         con.setContentTypeResponseHeader("text/html; charset=utf-8");
   892         PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8"));
   893         out.println("<html>");
   894         out.println("<head><title>");
   895         // out.println("Web Services");
   896         out.println(WsservletMessages.SERVLET_HTML_TITLE());
   897         out.println("</title></head>");
   898         out.println("<body>");
   899         // out.println("<h1>Web Services</h1>");
   900         out.println(WsservletMessages.SERVLET_HTML_TITLE_2());
   902         // what endpoints do we have in this system?
   903         Module module = getEndpoint().getContainer().getSPI(Module.class);
   904         List<BoundEndpoint> endpoints = Collections.emptyList();
   905         if(module!=null) {
   906             endpoints = module.getBoundEndpoints();
   907         }
   909         if (endpoints.isEmpty()) {
   910             // out.println("<p>No JAX-WS context information available.</p>");
   911             out.println(WsservletMessages.SERVLET_HTML_NO_INFO_AVAILABLE());
   912         } else {
   913             out.println("<table width='100%' border='1'>");
   914             out.println("<tr>");
   915             out.println("<td>");
   916             // out.println("Endpoint");
   917             out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_PORT_NAME());
   918             out.println("</td>");
   920             out.println("<td>");
   921             // out.println("Information");
   922             out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_INFORMATION());
   923             out.println("</td>");
   924             out.println("</tr>");
   926             for (BoundEndpoint a : endpoints) {
   927                 String endpointAddress = a.getAddress(con.getBaseAddress()).toString();
   928                 out.println("<tr>");
   930                 out.println("<td>");
   931                 out.println(WsservletMessages.SERVLET_HTML_ENDPOINT_TABLE(
   932                     a.getEndpoint().getServiceName(),
   933                     a.getEndpoint().getPortName()
   934                 ));
   935                 out.println("</td>");
   937                 out.println("<td>");
   938                 out.println(WsservletMessages.SERVLET_HTML_INFORMATION_TABLE(
   939                     endpointAddress,
   940                     a.getEndpoint().getImplementationClass().getName()
   941                 ));
   942                 out.println("</td>");
   944                 out.println("</tr>");
   945             }
   946             out.println("</table>");
   947         }
   948         out.println("</body>");
   949         out.println("</html>");
   950         out.close();
   951     }
   953     /**
   954      * Dumps what goes across HTTP transport.
   955      */
   956     public static volatile boolean dump = false;
   958     public static volatile int dump_threshold = 4096;
   960     public static volatile boolean publishStatusPage = true;
   962     public static synchronized void setPublishStatus(boolean publish) {
   963         publishStatusPage = publish;
   964     }
   966     static {
   967         try {
   968             dump = Boolean.getBoolean(HttpAdapter.class.getName() + ".dump");
   969         } catch (SecurityException se) {
   970             if (LOGGER.isLoggable(Level.CONFIG)) {
   971                 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
   972                         new Object[] {HttpAdapter.class.getName() + ".dump"});
   973             }
   974         }
   975         try {
   976             dump_threshold = Integer.getInteger(HttpAdapter.class.getName() + ".dumpTreshold", 4096);
   977         } catch (SecurityException se) {
   978             if (LOGGER.isLoggable(Level.CONFIG)) {
   979                 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
   980                         new Object[] {HttpAdapter.class.getName() + ".dumpTreshold"});
   981             }
   982         }
   983         try {
   984             setPublishStatus(Boolean.getBoolean(HttpAdapter.class.getName() + ".publishStatusPage"));
   985         } catch (SecurityException se) {
   986             if (LOGGER.isLoggable(Level.CONFIG)) {
   987                 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
   988                         new Object[] {HttpAdapter.class.getName() + ".publishStatusPage"});
   989             }
   990         }
   991     }
   993     public static void setDump(boolean dumpMessages) {
   994         HttpAdapter.dump = dumpMessages;
   995     }
   996 }

mercurial