src/share/jaxws_classes/com/sun/xml/internal/ws/transport/http/HttpAdapter.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 384
8f2986ff0235
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

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

mercurial