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

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

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

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

Web Services

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

No JAX-WS context information available.

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