aoqi@0: /* aoqi@0: * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.xml.internal.ws.transport.http; aoqi@0: aoqi@0: import java.io.ByteArrayOutputStream; aoqi@0: import java.io.IOException; aoqi@0: import java.io.InputStream; aoqi@0: import java.io.OutputStream; aoqi@0: import java.io.OutputStreamWriter; aoqi@0: import java.io.PrintWriter; aoqi@0: import java.net.HttpURLConnection; aoqi@0: import java.util.Collections; aoqi@0: import java.util.HashMap; aoqi@0: import java.util.List; aoqi@0: import java.util.Map; aoqi@0: import java.util.Map.Entry; aoqi@0: import java.util.TreeMap; aoqi@0: import java.util.logging.Level; aoqi@0: import java.util.logging.Logger; aoqi@0: aoqi@0: import javax.xml.ws.Binding; aoqi@0: import javax.xml.ws.WebServiceException; aoqi@0: import javax.xml.ws.http.HTTPBinding; aoqi@0: aoqi@0: import com.oracle.webservices.internal.api.message.PropertySet; aoqi@0: import com.sun.istack.internal.NotNull; aoqi@0: import com.sun.istack.internal.Nullable; aoqi@0: import com.sun.xml.internal.ws.api.Component; aoqi@0: import com.sun.xml.internal.ws.api.EndpointAddress; aoqi@0: import com.sun.xml.internal.ws.api.SOAPVersion; aoqi@0: import com.sun.xml.internal.ws.api.addressing.AddressingVersion; aoqi@0: import com.sun.xml.internal.ws.api.addressing.NonAnonymousResponseProcessor; aoqi@0: import com.sun.xml.internal.ws.api.ha.HaInfo; aoqi@0: import com.sun.xml.internal.ws.api.message.ExceptionHasMessage; aoqi@0: import com.sun.xml.internal.ws.api.message.Message; aoqi@0: import com.sun.xml.internal.ws.api.message.Packet; aoqi@0: import com.sun.xml.internal.ws.api.pipe.Codec; aoqi@0: import com.sun.xml.internal.ws.api.pipe.ContentType; aoqi@0: import com.sun.xml.internal.ws.api.server.AbstractServerAsyncTransport; aoqi@0: import com.sun.xml.internal.ws.api.server.Adapter; aoqi@0: import com.sun.xml.internal.ws.api.server.BoundEndpoint; aoqi@0: import com.sun.xml.internal.ws.api.server.DocumentAddressResolver; aoqi@0: import com.sun.xml.internal.ws.api.server.Module; aoqi@0: import com.sun.xml.internal.ws.api.server.PortAddressResolver; aoqi@0: import com.sun.xml.internal.ws.api.server.SDDocument; aoqi@0: import com.sun.xml.internal.ws.api.server.ServiceDefinition; aoqi@0: import com.sun.xml.internal.ws.api.server.TransportBackChannel; aoqi@0: import com.sun.xml.internal.ws.api.server.WSEndpoint; aoqi@0: import com.sun.xml.internal.ws.api.server.WebServiceContextDelegate; aoqi@0: import com.sun.xml.internal.ws.fault.SOAPFaultBuilder; aoqi@0: import com.sun.xml.internal.ws.resources.WsservletMessages; aoqi@0: import com.sun.xml.internal.ws.server.UnsupportedMediaException; aoqi@0: import com.sun.xml.internal.ws.util.ByteArrayBuffer; aoqi@0: import com.sun.xml.internal.ws.util.Pool; aoqi@0: aoqi@0: aoqi@0: /** aoqi@0: * {@link com.sun.xml.internal.ws.api.server.Adapter} that receives messages in HTTP. aoqi@0: * aoqi@0: *

aoqi@0: * This object also assigns unique query string (such as "xsd=1") to aoqi@0: * each {@link com.sun.xml.internal.ws.api.server.SDDocument} so that they can be served by HTTP GET requests. aoqi@0: * aoqi@0: * @author Kohsuke Kawaguchi aoqi@0: * @author Jitendra Kotamraju aoqi@0: */ aoqi@0: public class HttpAdapter extends Adapter { aoqi@0: aoqi@0: private static final Logger LOGGER = Logger.getLogger(HttpAdapter.class.getName()); aoqi@0: aoqi@0: /** aoqi@0: * {@link com.sun.xml.internal.ws.api.server.SDDocument}s keyed by the query string like "?abc". aoqi@0: * Used for serving documents via HTTP GET. aoqi@0: * aoqi@0: * Empty if the endpoint doesn't have {@link com.sun.xml.internal.ws.api.server.ServiceDefinition}. aoqi@0: * Read-only. aoqi@0: */ aoqi@0: protected Map wsdls; aoqi@0: aoqi@0: /** aoqi@0: * Reverse map of {@link #wsdls}. Read-only. aoqi@0: */ aoqi@0: private Map revWsdls; aoqi@0: aoqi@0: /** aoqi@0: * A reference to the service definition from which the map of wsdls/revWsdls aoqi@0: * was created. This allows us to establish if the service definition documents aoqi@0: * have changed in the meantime. aoqi@0: */ aoqi@0: private ServiceDefinition serviceDefinition = null; aoqi@0: aoqi@0: public final HttpAdapterList owner; aoqi@0: aoqi@0: /** aoqi@0: * Servlet URL pattern with which this {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} is associated. aoqi@0: */ aoqi@0: public final String urlPattern; aoqi@0: aoqi@0: protected boolean stickyCookie; aoqi@0: aoqi@0: protected boolean disableJreplicaCookie = false; aoqi@0: aoqi@0: /** aoqi@0: * Creates a lone {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} that does not know of any other aoqi@0: * {@link com.sun.xml.internal.ws.transport.http.HttpAdapter}s. aoqi@0: * aoqi@0: * This is convenient for creating an {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} for an environment aoqi@0: * where they don't know each other (such as JavaSE deployment.) aoqi@0: * aoqi@0: * @param endpoint web service endpoint aoqi@0: * @return singe adapter to process HTTP messages aoqi@0: */ aoqi@0: public static HttpAdapter createAlone(WSEndpoint endpoint) { aoqi@0: return new DummyList().createAdapter("","",endpoint); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * @deprecated aoqi@0: * remove as soon as we can update the test util. aoqi@0: * @param endpoint web service endpoint aoqi@0: * @param owner list of related adapters aoqi@0: */ aoqi@0: protected HttpAdapter(WSEndpoint endpoint, aoqi@0: HttpAdapterList owner) { aoqi@0: this(endpoint,owner,null); aoqi@0: } aoqi@0: aoqi@0: protected HttpAdapter(WSEndpoint endpoint, aoqi@0: HttpAdapterList owner, aoqi@0: String urlPattern) { aoqi@0: super(endpoint); aoqi@0: this.owner = owner; aoqi@0: this.urlPattern = urlPattern; aoqi@0: aoqi@0: initWSDLMap(endpoint.getServiceDefinition()); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Return the last known service definition of the endpoint. aoqi@0: * aoqi@0: * @return The service definition of the endpoint aoqi@0: */ aoqi@0: public ServiceDefinition getServiceDefinition() { aoqi@0: return this.serviceDefinition; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Fill in WSDL map. aoqi@0: * aoqi@0: * @param sdef service definition aoqi@0: */ aoqi@0: public final void initWSDLMap(ServiceDefinition sdef) { aoqi@0: this.serviceDefinition = sdef; aoqi@0: if(sdef==null) { aoqi@0: wsdls = Collections.emptyMap(); aoqi@0: revWsdls = Collections.emptyMap(); aoqi@0: } else { aoqi@0: wsdls = new HashMap(); // wsdl=1 --> Doc aoqi@0: // Sort WSDL, Schema documents based on SystemId so that the same aoqi@0: // document gets wsdl=x mapping aoqi@0: Map systemIds = new TreeMap(); aoqi@0: for (SDDocument sdd : sdef) { aoqi@0: if (sdd == sdef.getPrimary()) { // No sorting for Primary WSDL aoqi@0: wsdls.put("wsdl", sdd); aoqi@0: wsdls.put("WSDL", sdd); aoqi@0: } else { aoqi@0: systemIds.put(sdd.getURL().toString(), sdd); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: int wsdlnum = 1; aoqi@0: int xsdnum = 1; aoqi@0: for (Entry e : systemIds.entrySet()) { aoqi@0: SDDocument sdd = e.getValue(); aoqi@0: if (sdd.isWSDL()) { aoqi@0: wsdls.put("wsdl="+(wsdlnum++),sdd); aoqi@0: } aoqi@0: if (sdd.isSchema()) { aoqi@0: wsdls.put("xsd="+(xsdnum++),sdd); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: revWsdls = new HashMap(); // Doc --> wsdl=1 aoqi@0: for (Entry e : wsdls.entrySet()) { aoqi@0: if (!e.getKey().equals("WSDL")) { // map Doc --> wsdl, not WSDL aoqi@0: revWsdls.put(e.getValue(),e.getKey()); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns the "/abc/def/ghi" portion if aoqi@0: * the URL pattern is "/abc/def/ghi/*". aoqi@0: */ aoqi@0: public String getValidPath() { aoqi@0: if (urlPattern.endsWith("/*")) { aoqi@0: return urlPattern.substring(0, urlPattern.length() - 2); aoqi@0: } else { aoqi@0: return urlPattern; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: protected HttpToolkit createToolkit() { aoqi@0: return new HttpToolkit(); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Receives the incoming HTTP connection and dispatches aoqi@0: * it to JAX-WS. This method returns when JAX-WS completes aoqi@0: * processing the request and the whole reply is written aoqi@0: * to {@link WSHTTPConnection}. aoqi@0: * aoqi@0: *

aoqi@0: * This method is invoked by the lower-level HTTP stack, aoqi@0: * and "connection" here is an HTTP connection. aoqi@0: * aoqi@0: *

aoqi@0: * To populate a request {@link com.sun.xml.internal.ws.api.message.Packet} with more info, aoqi@0: * define {@link com.oracle.webservices.internal.api.message.PropertySet.Property properties} on aoqi@0: * {@link WSHTTPConnection}. aoqi@0: * aoqi@0: * @param connection to receive/send HTTP messages for web service endpoints aoqi@0: * @throws java.io.IOException when I/O errors happen aoqi@0: */ aoqi@0: public void handle(@NotNull WSHTTPConnection connection) throws IOException { aoqi@0: if (handleGet(connection)) { aoqi@0: return; aoqi@0: } aoqi@0: aoqi@0: // Make sure the Toolkit is recycled by the same pool instance from which it was taken aoqi@0: final Pool currentPool = getPool(); aoqi@0: // normal request handling aoqi@0: final HttpToolkit tk = currentPool.take(); aoqi@0: try { aoqi@0: tk.handle(connection); aoqi@0: } finally { aoqi@0: currentPool.recycle(tk); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public boolean handleGet(@NotNull WSHTTPConnection connection) throws IOException { aoqi@0: if (connection.getRequestMethod().equals("GET")) { aoqi@0: // metadata query. let the interceptor run aoqi@0: for (Component c : endpoint.getComponents()) { aoqi@0: HttpMetadataPublisher spi = c.getSPI(HttpMetadataPublisher.class); aoqi@0: if (spi != null && spi.handleMetadataRequest(this, connection)) { aoqi@0: return true; aoqi@0: } // handled aoqi@0: } aoqi@0: aoqi@0: if (isMetadataQuery(connection.getQueryString())) { aoqi@0: // Sends published WSDL and schema documents as the default action. aoqi@0: publishWSDL(connection); aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: Binding binding = getEndpoint().getBinding(); aoqi@0: if (!(binding instanceof HTTPBinding)) { aoqi@0: // Writes HTML page with all the endpoint descriptions aoqi@0: writeWebServicesHtmlPage(connection); aoqi@0: return true; aoqi@0: } aoqi@0: } else if (connection.getRequestMethod().equals("HEAD")) { aoqi@0: connection.getInput().close(); aoqi@0: Binding binding = getEndpoint().getBinding(); aoqi@0: if (isMetadataQuery(connection.getQueryString())) { aoqi@0: SDDocument doc = wsdls.get(connection.getQueryString()); aoqi@0: connection.setStatus(doc != null aoqi@0: ? HttpURLConnection.HTTP_OK aoqi@0: : HttpURLConnection.HTTP_NOT_FOUND); aoqi@0: connection.getOutput().close(); aoqi@0: connection.close(); aoqi@0: return true; aoqi@0: } else if (!(binding instanceof HTTPBinding)) { aoqi@0: connection.setStatus(HttpURLConnection.HTTP_NOT_FOUND); aoqi@0: connection.getOutput().close(); aoqi@0: connection.close(); aoqi@0: return true; aoqi@0: } aoqi@0: // Let the endpoint handle for HTTPBinding aoqi@0: } aoqi@0: aoqi@0: return false; aoqi@0: aoqi@0: } aoqi@0: /* aoqi@0: * aoqi@0: * @param con aoqi@0: * @param codec aoqi@0: * @return aoqi@0: * @throws IOException aoqi@0: * ExceptionHasMessage exception that contains particular fault message aoqi@0: * UnsupportedMediaException to indicate to send 415 error code aoqi@0: */ aoqi@0: private Packet decodePacket(@NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException { aoqi@0: String ct = con.getRequestHeader("Content-Type"); aoqi@0: InputStream in = con.getInput(); aoqi@0: Packet packet = new Packet(); aoqi@0: packet.soapAction = fixQuotesAroundSoapAction(con.getRequestHeader("SOAPAction")); aoqi@0: packet.wasTransportSecure = con.isSecure(); aoqi@0: packet.acceptableMimeTypes = con.getRequestHeader("Accept"); aoqi@0: packet.addSatellite(con); aoqi@0: addSatellites(packet); aoqi@0: packet.isAdapterDeliversNonAnonymousResponse = true; aoqi@0: packet.component = this; aoqi@0: packet.transportBackChannel = new Oneway(con); aoqi@0: packet.webServiceContextDelegate = con.getWebServiceContextDelegate(); aoqi@0: packet.setState(Packet.State.ServerRequest); aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: buf.write(in); aoqi@0: in.close(); aoqi@0: dump(buf, "HTTP request", con.getRequestHeaders()); aoqi@0: in = buf.newInputStream(); aoqi@0: } aoqi@0: codec.decode(in, ct, packet); aoqi@0: return packet; aoqi@0: } aoqi@0: aoqi@0: protected void addSatellites(Packet packet) { aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Some stacks may send non WS-I BP 1.2 conforming SoapAction. aoqi@0: * Make sure SOAPAction is quoted as {@link com.sun.xml.internal.ws.api.message.Packet#soapAction} expects quoted soapAction value. aoqi@0: * aoqi@0: * @param soapAction SoapAction HTTP Header aoqi@0: * @return quoted SOAPAction value aoqi@0: */ aoqi@0: static public String fixQuotesAroundSoapAction(String soapAction) { aoqi@0: if(soapAction != null && (!soapAction.startsWith("\"") || !soapAction.endsWith("\"")) ) { aoqi@0: if (LOGGER.isLoggable(Level.INFO)) { aoqi@0: LOGGER.log(Level.INFO, "Received WS-I BP non-conformant Unquoted SoapAction HTTP header: {0}", soapAction); aoqi@0: } aoqi@0: String fixedSoapAction = soapAction; aoqi@0: if(!soapAction.startsWith("\"")) { aoqi@0: fixedSoapAction = "\"" + fixedSoapAction; aoqi@0: } aoqi@0: if(!soapAction.endsWith("\"")) { aoqi@0: fixedSoapAction = fixedSoapAction + "\""; aoqi@0: } aoqi@0: return fixedSoapAction; aoqi@0: } aoqi@0: return soapAction; aoqi@0: } aoqi@0: aoqi@0: protected NonAnonymousResponseProcessor getNonAnonymousResponseProcessor() { aoqi@0: return NonAnonymousResponseProcessor.getDefault(); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * This method is added for the case of the sub-class wants to override the method to aoqi@0: * print details. E.g. convert soapfault as HTML msg for 403 error connstatus. aoqi@0: * @param os aoqi@0: */ aoqi@0: protected void writeClientError(int connStatus, @NotNull OutputStream os, @NotNull Packet packet) throws IOException { aoqi@0: //do nothing aoqi@0: } aoqi@0: aoqi@0: private boolean isClientErrorStatus(int connStatus) aoqi@0: { aoqi@0: return (connStatus == HttpURLConnection.HTTP_FORBIDDEN); // add more for future. aoqi@0: } aoqi@0: aoqi@0: private boolean isNonAnonymousUri(EndpointAddress addr){ aoqi@0: return (addr != null) && !addr.toString().equals(AddressingVersion.W3C.anonymousUri) && aoqi@0: !addr.toString().equals(AddressingVersion.MEMBER.anonymousUri); aoqi@0: } aoqi@0: aoqi@0: private void encodePacket(@NotNull Packet packet, @NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException { aoqi@0: if (isNonAnonymousUri(packet.endpointAddress) && packet.getMessage() != null) { aoqi@0: try { aoqi@0: // Message is targeted to non-anonymous response endpoint. aoqi@0: // After call to non-anonymous processor, typically, packet.getMessage() will be null aoqi@0: // however, processors could use this pattern to modify the response sent on the back-channel, aoqi@0: // e.g. send custom HTTP headers with the HTTP 202 aoqi@0: packet = getNonAnonymousResponseProcessor().process(packet); aoqi@0: } catch (RuntimeException re) { aoqi@0: // if processing by NonAnonymousResponseProcessor fails, new SOAPFaultMessage is created to be sent aoqi@0: // to back-channel client aoqi@0: SOAPVersion soapVersion = packet.getBinding().getSOAPVersion(); aoqi@0: Message faultMsg = SOAPFaultBuilder.createSOAPFaultMessage(soapVersion, null, re); aoqi@0: packet = packet.createServerResponse(faultMsg, packet.endpoint.getPort(), null, packet.endpoint.getBinding()); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: if (con.isClosed()) { aoqi@0: return; // Connection is already closed aoqi@0: } aoqi@0: Message responseMessage = packet.getMessage(); aoqi@0: addStickyCookie(con); aoqi@0: addReplicaCookie(con, packet); aoqi@0: if (responseMessage == null) { aoqi@0: if (!con.isClosed()) { aoqi@0: // set the response code if not already set aoqi@0: // for example, 415 may have been set earlier for Unsupported Content-Type aoqi@0: if (con.getStatus() == 0) { aoqi@0: con.setStatus(WSHTTPConnection.ONEWAY); aoqi@0: } aoqi@0: OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con); aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: codec.encode(packet, buf); aoqi@0: dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders()); aoqi@0: buf.writeTo(os); aoqi@0: } else { aoqi@0: codec.encode(packet, os); aoqi@0: } aoqi@0: // close the response channel now aoqi@0: try { aoqi@0: os.close(); // no payload aoqi@0: } catch (IOException e) { aoqi@0: throw new WebServiceException(e); aoqi@0: } aoqi@0: } aoqi@0: } else { aoqi@0: if (con.getStatus() == 0) { aoqi@0: // if the appliation didn't set the status code, aoqi@0: // set the default one. aoqi@0: con.setStatus(responseMessage.isFault() aoqi@0: ? HttpURLConnection.HTTP_INTERNAL_ERROR aoqi@0: : HttpURLConnection.HTTP_OK); aoqi@0: } aoqi@0: aoqi@0: if (isClientErrorStatus(con.getStatus())) { aoqi@0: OutputStream os = con.getOutput(); aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: writeClientError(con.getStatus(), buf, packet); aoqi@0: dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders()); aoqi@0: buf.writeTo(os); aoqi@0: } else { aoqi@0: writeClientError(con.getStatus(), os, packet); aoqi@0: } aoqi@0: os.close(); aoqi@0: return; aoqi@0: } aoqi@0: aoqi@0: ContentType contentType = codec.getStaticContentType(packet); aoqi@0: if (contentType != null) { aoqi@0: con.setContentTypeResponseHeader(contentType.getContentType()); aoqi@0: OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con); aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: codec.encode(packet, buf); aoqi@0: dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders()); aoqi@0: buf.writeTo(os); aoqi@0: } else { aoqi@0: codec.encode(packet, os); aoqi@0: } aoqi@0: os.close(); aoqi@0: } else { aoqi@0: aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: contentType = codec.encode(packet, buf); aoqi@0: con.setContentTypeResponseHeader(contentType.getContentType()); aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders()); aoqi@0: } aoqi@0: OutputStream os = con.getOutput(); aoqi@0: buf.writeTo(os); aoqi@0: os.close(); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /* aoqi@0: * GlassFish Load-balancer plugin always add a header proxy-jroute on aoqi@0: * request being send from load-balancer plugin to server aoqi@0: * aoqi@0: * JROUTE cookie need to be stamped in two cases aoqi@0: * 1 : At the time of session creation. In this case, request will not have aoqi@0: * any JROUTE cookie. aoqi@0: * 2 : At the time of fail-over. In this case, value of proxy-jroute aoqi@0: * header(will point to current instance) and JROUTE cookie(will point to aoqi@0: * previous failed instance) will be different. This logic can be used aoqi@0: * to determine fail-over scenario. aoqi@0: */ aoqi@0: private void addStickyCookie(WSHTTPConnection con) { aoqi@0: if (stickyCookie) { aoqi@0: String proxyJroute = con.getRequestHeader("proxy-jroute"); aoqi@0: if (proxyJroute == null) { aoqi@0: // Load-balancer plugin is not front-ending this instance aoqi@0: return; aoqi@0: } aoqi@0: aoqi@0: String jrouteId = con.getCookie("JROUTE"); aoqi@0: if (jrouteId == null || !jrouteId.equals(proxyJroute)) { aoqi@0: // Initial request or failover aoqi@0: con.setCookie("JROUTE", proxyJroute); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private void addReplicaCookie(WSHTTPConnection con, Packet packet) { aoqi@0: if (stickyCookie) { aoqi@0: HaInfo haInfo = null; aoqi@0: if (packet.supports(Packet.HA_INFO)) { aoqi@0: haInfo = (HaInfo)packet.get(Packet.HA_INFO); aoqi@0: } aoqi@0: if (haInfo != null) { aoqi@0: con.setCookie("METRO_KEY", haInfo.getKey()); aoqi@0: if (!disableJreplicaCookie) { aoqi@0: con.setCookie("JREPLICA", haInfo.getReplicaInstance()); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public void invokeAsync(final WSHTTPConnection con) throws IOException { aoqi@0: invokeAsync(con, NO_OP_COMPLETION_CALLBACK); aoqi@0: } aoqi@0: aoqi@0: public void invokeAsync(final WSHTTPConnection con, final CompletionCallback callback) throws IOException { aoqi@0: aoqi@0: if (handleGet(con)) { aoqi@0: callback.onCompletion(); aoqi@0: return; aoqi@0: } aoqi@0: final Pool currentPool = getPool(); aoqi@0: final HttpToolkit tk = currentPool.take(); aoqi@0: final Packet request; aoqi@0: aoqi@0: try { aoqi@0: aoqi@0: request = decodePacket(con, tk.codec); aoqi@0: } catch (ExceptionHasMessage e) { aoqi@0: LOGGER.log(Level.SEVERE, e.getMessage(), e); aoqi@0: Packet response = new Packet(); aoqi@0: response.setMessage(e.getFaultMessage()); aoqi@0: encodePacket(response, con, tk.codec); aoqi@0: currentPool.recycle(tk); aoqi@0: con.close(); aoqi@0: callback.onCompletion(); aoqi@0: return; aoqi@0: } catch (UnsupportedMediaException e) { aoqi@0: LOGGER.log(Level.SEVERE, e.getMessage(), e); aoqi@0: Packet response = new Packet(); aoqi@0: con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA); aoqi@0: encodePacket(response, con, tk.codec); aoqi@0: currentPool.recycle(tk); aoqi@0: con.close(); aoqi@0: callback.onCompletion(); aoqi@0: return; aoqi@0: } aoqi@0: aoqi@0: endpoint.process(request, new WSEndpoint.CompletionCallback() { aoqi@0: @Override aoqi@0: public void onCompletion(@NotNull Packet response) { aoqi@0: try { aoqi@0: try { aoqi@0: encodePacket(response, con, tk.codec); aoqi@0: } catch (IOException ioe) { aoqi@0: LOGGER.log(Level.SEVERE, ioe.getMessage(), ioe); aoqi@0: } aoqi@0: currentPool.recycle(tk); aoqi@0: } finally { aoqi@0: con.close(); aoqi@0: callback.onCompletion(); aoqi@0: aoqi@0: } aoqi@0: } aoqi@0: },null); aoqi@0: aoqi@0: } aoqi@0: aoqi@0: public static final CompletionCallback NO_OP_COMPLETION_CALLBACK = new CompletionCallback() { aoqi@0: aoqi@0: @Override aoqi@0: public void onCompletion() { aoqi@0: //NO-OP aoqi@0: } aoqi@0: }; aoqi@0: aoqi@0: public interface CompletionCallback{ aoqi@0: void onCompletion(); aoqi@0: } aoqi@0: aoqi@0: final class AsyncTransport extends AbstractServerAsyncTransport { aoqi@0: aoqi@0: public AsyncTransport() { aoqi@0: super(endpoint); aoqi@0: } aoqi@0: aoqi@0: public void handleAsync(WSHTTPConnection con) throws IOException { aoqi@0: super.handle(con); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: protected void encodePacket(WSHTTPConnection con, @NotNull Packet packet, @NotNull Codec codec) throws IOException { aoqi@0: HttpAdapter.this.encodePacket(packet, con, codec); aoqi@0: } aoqi@0: aoqi@0: protected @Override @Nullable String getAcceptableMimeTypes(WSHTTPConnection con) { aoqi@0: return null; aoqi@0: } aoqi@0: aoqi@0: protected @Override @Nullable TransportBackChannel getTransportBackChannel(WSHTTPConnection con) { aoqi@0: return new Oneway(con); aoqi@0: } aoqi@0: aoqi@0: protected @Override @NotNull aoqi@0: PropertySet getPropertySet(WSHTTPConnection con) { aoqi@0: return con; aoqi@0: } aoqi@0: aoqi@0: protected @Override @NotNull WebServiceContextDelegate getWebServiceContextDelegate(WSHTTPConnection con) { aoqi@0: return con.getWebServiceContextDelegate(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: static final class Oneway implements TransportBackChannel { aoqi@0: WSHTTPConnection con; aoqi@0: boolean closed; aoqi@0: aoqi@0: Oneway(WSHTTPConnection con) { aoqi@0: this.con = con; aoqi@0: } aoqi@0: @Override aoqi@0: public void close() { aoqi@0: if (!closed) { aoqi@0: closed = true; aoqi@0: // close the response channel now aoqi@0: if (con.getStatus() == 0) { aoqi@0: // if the appliation didn't set the status code, aoqi@0: // set the default one. aoqi@0: con.setStatus(WSHTTPConnection.ONEWAY); aoqi@0: } aoqi@0: aoqi@0: OutputStream output = null; aoqi@0: try { aoqi@0: output = con.getOutput(); aoqi@0: } catch (IOException e) { aoqi@0: // no-op aoqi@0: } aoqi@0: aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: try { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders()); aoqi@0: } catch (Exception e) { aoqi@0: throw new WebServiceException(e.toString(), e); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: if (output != null) { aoqi@0: try { aoqi@0: output.close(); // no payload aoqi@0: } catch (IOException e) { aoqi@0: throw new WebServiceException(e); aoqi@0: } aoqi@0: } aoqi@0: con.close(); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: final class HttpToolkit extends Adapter.Toolkit { aoqi@0: public void handle(WSHTTPConnection con) throws IOException { aoqi@0: try { aoqi@0: boolean invoke = false; aoqi@0: Packet packet; aoqi@0: try { aoqi@0: packet = decodePacket(con, codec); aoqi@0: invoke = true; aoqi@0: } catch(Exception e) { aoqi@0: packet = new Packet(); aoqi@0: if (e instanceof ExceptionHasMessage) { aoqi@0: LOGGER.log(Level.SEVERE, e.getMessage(), e); aoqi@0: packet.setMessage(((ExceptionHasMessage)e).getFaultMessage()); aoqi@0: } else if (e instanceof UnsupportedMediaException) { aoqi@0: LOGGER.log(Level.SEVERE, e.getMessage(), e); aoqi@0: con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA); aoqi@0: } else { aoqi@0: LOGGER.log(Level.SEVERE, e.getMessage(), e); aoqi@0: con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR); aoqi@0: } aoqi@0: } aoqi@0: if (invoke) { aoqi@0: try { aoqi@0: packet = head.process(packet, con.getWebServiceContextDelegate(), aoqi@0: packet.transportBackChannel); aoqi@0: } catch(Throwable e) { aoqi@0: LOGGER.log(Level.SEVERE, e.getMessage(), e); aoqi@0: if (!con.isClosed()) { aoqi@0: writeInternalServerError(con); aoqi@0: } aoqi@0: return; aoqi@0: } aoqi@0: } aoqi@0: encodePacket(packet, con, codec); aoqi@0: } finally { aoqi@0: if (!con.isClosed()) { aoqi@0: if (LOGGER.isLoggable(Level.FINE)) { aoqi@0: LOGGER.log(Level.FINE, "Closing HTTP Connection with status: {0}", con.getStatus()); aoqi@0: } aoqi@0: con.close(); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns true if the given query string is for metadata request. aoqi@0: * aoqi@0: * @param query aoqi@0: * String like "xsd=1" or "perhaps=some&unrelated=query". aoqi@0: * Can be null. aoqi@0: * @return true for metadata requests aoqi@0: * false for web service requests aoqi@0: */ aoqi@0: private boolean isMetadataQuery(String query) { aoqi@0: // we intentionally return true even if documents don't exist, aoqi@0: // so that they get 404. aoqi@0: return query != null && (query.equals("WSDL") || query.startsWith("wsdl") || query.startsWith("xsd=")); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Sends out the WSDL (and other referenced documents) aoqi@0: * in response to the GET requests to URLs like "?wsdl" or "?xsd=2". aoqi@0: * aoqi@0: * @param con aoqi@0: * The connection to which the data will be sent. aoqi@0: * aoqi@0: * @throws java.io.IOException when I/O errors happen aoqi@0: */ aoqi@0: public void publishWSDL(@NotNull WSHTTPConnection con) throws IOException { aoqi@0: con.getInput().close(); aoqi@0: aoqi@0: SDDocument doc = wsdls.get(con.getQueryString()); aoqi@0: if (doc == null) { aoqi@0: writeNotFoundErrorPage(con,"Invalid Request"); aoqi@0: return; aoqi@0: } aoqi@0: aoqi@0: con.setStatus(HttpURLConnection.HTTP_OK); aoqi@0: con.setContentTypeResponseHeader("text/xml;charset=utf-8"); aoqi@0: aoqi@0: OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con); aoqi@0: aoqi@0: PortAddressResolver portAddressResolver = getPortAddressResolver(con.getBaseAddress()); aoqi@0: DocumentAddressResolver resolver = getDocumentAddressResolver(portAddressResolver); aoqi@0: aoqi@0: doc.writeTo(portAddressResolver, resolver, os); aoqi@0: os.close(); aoqi@0: } aoqi@0: aoqi@0: public PortAddressResolver getPortAddressResolver(String baseAddress) { aoqi@0: return owner.createPortAddressResolver(baseAddress, endpoint.getImplementationClass()); aoqi@0: } aoqi@0: aoqi@0: public DocumentAddressResolver getDocumentAddressResolver( aoqi@0: PortAddressResolver portAddressResolver) { aoqi@0: final String address = portAddressResolver.getAddressFor(endpoint.getServiceName(), endpoint.getPortName().getLocalPart()); aoqi@0: assert address != null; aoqi@0: return new DocumentAddressResolver() { aoqi@0: @Override aoqi@0: public String getRelativeAddressFor(@NotNull SDDocument current, @NotNull SDDocument referenced) { aoqi@0: // the map on endpoint should account for all SDDocument aoqi@0: assert revWsdls.containsKey(referenced); aoqi@0: return address+'?'+ revWsdls.get(referenced); aoqi@0: } aoqi@0: }; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * HTTP/1.0 connections require Content-Length. So just buffer to find out aoqi@0: * the length. aoqi@0: */ aoqi@0: private final static class Http10OutputStream extends ByteArrayBuffer { aoqi@0: private final WSHTTPConnection con; aoqi@0: aoqi@0: Http10OutputStream(WSHTTPConnection con) { aoqi@0: this.con = con; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public void close() throws IOException { aoqi@0: super.close(); aoqi@0: con.setContentLengthResponseHeader(size()); aoqi@0: OutputStream os = con.getOutput(); aoqi@0: writeTo(os); aoqi@0: os.close(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private void writeNotFoundErrorPage(WSHTTPConnection con, String message) throws IOException { aoqi@0: con.setStatus(HttpURLConnection.HTTP_NOT_FOUND); aoqi@0: con.setContentTypeResponseHeader("text/html; charset=utf-8"); aoqi@0: aoqi@0: PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8")); aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_TITLE()); aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_NOT_FOUND(message)); aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: out.close(); aoqi@0: } aoqi@0: aoqi@0: private void writeInternalServerError(WSHTTPConnection con) throws IOException { aoqi@0: con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR); aoqi@0: con.getOutput().close(); // Sets the status code aoqi@0: } aoqi@0: aoqi@0: private static final class DummyList extends HttpAdapterList { aoqi@0: @Override aoqi@0: protected HttpAdapter createHttpAdapter(String name, String urlPattern, WSEndpoint endpoint) { aoqi@0: return new HttpAdapter(endpoint,this,urlPattern); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private static void dump(ByteArrayBuffer buf, String caption, Map> headers) throws IOException { aoqi@0: ByteArrayOutputStream baos = new ByteArrayOutputStream(); aoqi@0: PrintWriter pw = new PrintWriter(baos, true); aoqi@0: pw.println("---["+caption +"]---"); aoqi@0: if (headers != null) { aoqi@0: for (Entry> header : headers.entrySet()) { aoqi@0: if (header.getValue().isEmpty()) { aoqi@0: // I don't think this is legal, but let's just dump it, aoqi@0: // as the point of the dump is to uncover problems. aoqi@0: pw.println(header.getValue()); aoqi@0: } else { aoqi@0: for (String value : header.getValue()) { aoqi@0: pw.println(header.getKey() + ": " + value); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: if (buf.size() > dump_threshold) { aoqi@0: byte[] b = buf.getRawData(); aoqi@0: baos.write(b, 0, dump_threshold); aoqi@0: pw.println(); aoqi@0: pw.println(WsservletMessages.MESSAGE_TOO_LONG(HttpAdapter.class.getName() + ".dumpTreshold")); aoqi@0: } else { aoqi@0: buf.writeTo(baos); aoqi@0: } aoqi@0: pw.println("--------------------"); aoqi@0: aoqi@0: String msg = baos.toString(); aoqi@0: if (dump) { aoqi@0: System.out.println(msg); aoqi@0: } aoqi@0: if (LOGGER.isLoggable(Level.FINER)) { aoqi@0: LOGGER.log(Level.FINER, msg); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /* aoqi@0: * Generates the listing of all services. aoqi@0: */ aoqi@0: private void writeWebServicesHtmlPage(WSHTTPConnection con) throws IOException { aoqi@0: if (!publishStatusPage) { aoqi@0: return; aoqi@0: } aoqi@0: aoqi@0: // TODO: resurrect the ability to localize according to the current request. aoqi@0: aoqi@0: con.getInput().close(); aoqi@0: aoqi@0: // standard browsable page aoqi@0: con.setStatus(WSHTTPConnection.OK); aoqi@0: con.setContentTypeResponseHeader("text/html; charset=utf-8"); aoqi@0: aoqi@0: PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8")); aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: // out.println("Web Services"); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_TITLE()); aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: // out.println("

Web Services

"); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_TITLE_2()); aoqi@0: aoqi@0: // what endpoints do we have in this system? aoqi@0: Module module = getEndpoint().getContainer().getSPI(Module.class); aoqi@0: List endpoints = Collections.emptyList(); aoqi@0: if(module!=null) { aoqi@0: endpoints = module.getBoundEndpoints(); aoqi@0: } aoqi@0: aoqi@0: if (endpoints.isEmpty()) { aoqi@0: // out.println("

No JAX-WS context information available.

"); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_NO_INFO_AVAILABLE()); aoqi@0: } else { aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: aoqi@0: for (BoundEndpoint a : endpoints) { aoqi@0: String endpointAddress = a.getAddress(con.getBaseAddress()).toString(); aoqi@0: out.println(""); aoqi@0: aoqi@0: out.println(""); aoqi@0: aoqi@0: out.println(""); aoqi@0: aoqi@0: out.println(""); aoqi@0: } aoqi@0: out.println("
"); aoqi@0: // out.println("Endpoint"); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_PORT_NAME()); aoqi@0: out.println(""); aoqi@0: // out.println("Information"); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_INFORMATION()); aoqi@0: out.println("
"); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_ENDPOINT_TABLE( aoqi@0: a.getEndpoint().getServiceName(), aoqi@0: a.getEndpoint().getPortName() aoqi@0: )); aoqi@0: out.println(""); aoqi@0: out.println(WsservletMessages.SERVLET_HTML_INFORMATION_TABLE( aoqi@0: endpointAddress, aoqi@0: a.getEndpoint().getImplementationClass().getName() aoqi@0: )); aoqi@0: out.println("
"); aoqi@0: } aoqi@0: out.println(""); aoqi@0: out.println(""); aoqi@0: out.close(); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Dumps what goes across HTTP transport. aoqi@0: */ aoqi@0: public static volatile boolean dump = false; aoqi@0: aoqi@0: public static volatile int dump_threshold = 4096; aoqi@0: aoqi@0: public static volatile boolean publishStatusPage = true; aoqi@0: aoqi@0: public static synchronized void setPublishStatus(boolean publish) { aoqi@0: publishStatusPage = publish; aoqi@0: } aoqi@0: aoqi@0: static { aoqi@0: try { aoqi@0: dump = Boolean.getBoolean(HttpAdapter.class.getName() + ".dump"); aoqi@0: } catch (SecurityException se) { aoqi@0: if (LOGGER.isLoggable(Level.CONFIG)) { aoqi@0: LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.", aoqi@0: new Object[] {HttpAdapter.class.getName() + ".dump"}); aoqi@0: } aoqi@0: } aoqi@0: try { aoqi@0: dump_threshold = Integer.getInteger(HttpAdapter.class.getName() + ".dumpTreshold", 4096); aoqi@0: } catch (SecurityException se) { aoqi@0: if (LOGGER.isLoggable(Level.CONFIG)) { aoqi@0: LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.", aoqi@0: new Object[] {HttpAdapter.class.getName() + ".dumpTreshold"}); aoqi@0: } aoqi@0: } aoqi@0: try { aoqi@0: setPublishStatus(Boolean.getBoolean(HttpAdapter.class.getName() + ".publishStatusPage")); aoqi@0: } catch (SecurityException se) { aoqi@0: if (LOGGER.isLoggable(Level.CONFIG)) { aoqi@0: LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.", aoqi@0: new Object[] {HttpAdapter.class.getName() + ".publishStatusPage"}); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public static void setDump(boolean dumpMessages) { aoqi@0: HttpAdapter.dump = dumpMessages; aoqi@0: } aoqi@0: }