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.client; aoqi@0: aoqi@0: import com.sun.istack.internal.NotNull; aoqi@0: import com.sun.xml.internal.ws.api.SOAPVersion; aoqi@0: import com.sun.xml.internal.ws.api.WSBinding; aoqi@0: import com.sun.xml.internal.ws.api.ha.StickyFeature; aoqi@0: import com.sun.xml.internal.ws.api.message.Packet; aoqi@0: import com.sun.xml.internal.ws.api.pipe.*; aoqi@0: import com.sun.xml.internal.ws.api.pipe.helper.AbstractTubeImpl; aoqi@0: import com.sun.xml.internal.ws.client.ClientTransportException; aoqi@0: import com.sun.xml.internal.ws.developer.HttpConfigFeature; aoqi@0: import com.sun.xml.internal.ws.resources.ClientMessages; aoqi@0: import com.sun.xml.internal.ws.resources.WsservletMessages; aoqi@0: import com.sun.xml.internal.ws.transport.Headers; aoqi@0: import com.sun.xml.internal.ws.transport.http.HttpAdapter; aoqi@0: import com.sun.xml.internal.ws.util.ByteArrayBuffer; aoqi@0: import com.sun.xml.internal.ws.util.RuntimeVersion; aoqi@0: import com.sun.xml.internal.ws.util.StreamUtils; aoqi@0: aoqi@0: import javax.xml.bind.DatatypeConverter; aoqi@0: import javax.xml.ws.BindingProvider; aoqi@0: import javax.xml.ws.WebServiceException; aoqi@0: import javax.xml.ws.WebServiceFeature; aoqi@0: import javax.xml.ws.handler.MessageContext; aoqi@0: import javax.xml.ws.soap.SOAPBinding; aoqi@0: import java.io.*; aoqi@0: import java.net.CookieHandler; aoqi@0: import java.net.HttpURLConnection; aoqi@0: import java.util.*; aoqi@0: import java.util.Map.Entry; aoqi@0: import java.util.logging.Level; aoqi@0: import java.util.logging.Logger; aoqi@0: aoqi@0: /** aoqi@0: * {@link Tube} that sends a request to a remote HTTP server. aoqi@0: * aoqi@0: * TODO: need to create separate HTTP transport pipes for binding. SOAP1.1, SOAP1.2, aoqi@0: * TODO: XML/HTTP differ in handling status codes. aoqi@0: * aoqi@0: * @author Jitendra Kotamraju aoqi@0: */ aoqi@0: public class HttpTransportPipe extends AbstractTubeImpl { aoqi@0: aoqi@0: private static final List USER_AGENT = Collections.singletonList(RuntimeVersion.VERSION.toString()); aoqi@0: private static final Logger LOGGER = Logger.getLogger(HttpTransportPipe.class.getName()); aoqi@0: aoqi@0: /** aoqi@0: * Dumps what goes across HTTP transport. aoqi@0: */ aoqi@0: public static boolean dump; aoqi@0: aoqi@0: private final Codec codec; aoqi@0: private final WSBinding binding; aoqi@0: private final CookieHandler cookieJar; // shared object among the tubes aoqi@0: private final boolean sticky; aoqi@0: aoqi@0: static { aoqi@0: boolean b; aoqi@0: try { aoqi@0: b = Boolean.getBoolean(HttpTransportPipe.class.getName()+".dump"); aoqi@0: } catch( Throwable t ) { aoqi@0: b = false; aoqi@0: } aoqi@0: dump = b; aoqi@0: } aoqi@0: aoqi@0: public HttpTransportPipe(Codec codec, WSBinding binding) { aoqi@0: this.codec = codec; aoqi@0: this.binding = binding; aoqi@0: this.sticky = isSticky(binding); aoqi@0: HttpConfigFeature configFeature = binding.getFeature(HttpConfigFeature.class); aoqi@0: if (configFeature == null) { aoqi@0: configFeature = new HttpConfigFeature(); aoqi@0: } aoqi@0: this.cookieJar = configFeature.getCookieHandler(); aoqi@0: } aoqi@0: aoqi@0: private static boolean isSticky(WSBinding binding) { aoqi@0: boolean tSticky = false; aoqi@0: WebServiceFeature[] features = binding.getFeatures().toArray(); aoqi@0: for(WebServiceFeature f : features) { aoqi@0: if (f instanceof StickyFeature) { aoqi@0: tSticky = true; aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: return tSticky; aoqi@0: } aoqi@0: aoqi@0: /* aoqi@0: * Copy constructor for {@link Tube#copy(TubeCloner)}. aoqi@0: */ aoqi@0: private HttpTransportPipe(HttpTransportPipe that, TubeCloner cloner) { aoqi@0: this(that.codec.copy(), that.binding); aoqi@0: cloner.add(that,this); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public NextAction processException(@NotNull Throwable t) { aoqi@0: return doThrow(t); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public NextAction processRequest(@NotNull Packet request) { aoqi@0: return doReturnWith(process(request)); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public NextAction processResponse(@NotNull Packet response) { aoqi@0: return doReturnWith(response); aoqi@0: } aoqi@0: aoqi@0: protected HttpClientTransport getTransport(Packet request, Map> reqHeaders) { aoqi@0: return new HttpClientTransport(request, reqHeaders); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public Packet process(Packet request) { aoqi@0: HttpClientTransport con; aoqi@0: try { aoqi@0: // get transport headers from message aoqi@0: Map> reqHeaders = new Headers(); aoqi@0: @SuppressWarnings("unchecked") aoqi@0: Map> userHeaders = (Map>) request.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS); aoqi@0: boolean addUserAgent = true; aoqi@0: if (userHeaders != null) { aoqi@0: // userHeaders may not be modifiable like SingletonMap, just copy them aoqi@0: reqHeaders.putAll(userHeaders); aoqi@0: // application wants to use its own User-Agent header aoqi@0: if (userHeaders.get("User-Agent") != null) { aoqi@0: addUserAgent = false; aoqi@0: } aoqi@0: } aoqi@0: if (addUserAgent) { aoqi@0: reqHeaders.put("User-Agent", USER_AGENT); aoqi@0: } aoqi@0: aoqi@0: addBasicAuth(request, reqHeaders); aoqi@0: addCookies(request, reqHeaders); aoqi@0: aoqi@0: con = getTransport(request, reqHeaders); aoqi@0: request.addSatellite(new HttpResponseProperties(con)); aoqi@0: aoqi@0: ContentType ct = codec.getStaticContentType(request); aoqi@0: if (ct == null) { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: aoqi@0: ct = codec.encode(request, buf); aoqi@0: // data size is available, set it as Content-Length aoqi@0: reqHeaders.put("Content-Length", Collections.singletonList(Integer.toString(buf.size()))); aoqi@0: reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType())); aoqi@0: if (ct.getAcceptHeader() != null) { aoqi@0: reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader())); aoqi@0: } aoqi@0: if (binding instanceof SOAPBinding) { aoqi@0: writeSOAPAction(reqHeaders, ct.getSOAPActionHeader()); aoqi@0: } aoqi@0: aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: dump(buf, "HTTP request", reqHeaders); aoqi@0: } aoqi@0: aoqi@0: buf.writeTo(con.getOutput()); aoqi@0: } else { aoqi@0: // Set static Content-Type aoqi@0: reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType())); aoqi@0: if (ct.getAcceptHeader() != null) { aoqi@0: reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader())); aoqi@0: } aoqi@0: if (binding instanceof SOAPBinding) { aoqi@0: writeSOAPAction(reqHeaders, ct.getSOAPActionHeader()); aoqi@0: } aoqi@0: aoqi@0: if(dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: codec.encode(request, buf); aoqi@0: dump(buf, "HTTP request - "+request.endpointAddress, reqHeaders); aoqi@0: OutputStream out = con.getOutput(); aoqi@0: if (out != null) { aoqi@0: buf.writeTo(out); aoqi@0: } aoqi@0: } else { aoqi@0: OutputStream os = con.getOutput(); aoqi@0: if (os != null) { aoqi@0: codec.encode(request, os); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: con.closeOutput(); aoqi@0: aoqi@0: return createResponsePacket(request, con); aoqi@0: } catch(WebServiceException wex) { aoqi@0: throw wex; aoqi@0: } catch(Exception ex) { aoqi@0: throw new WebServiceException(ex); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private Packet createResponsePacket(Packet request, HttpClientTransport con) throws IOException { aoqi@0: con.readResponseCodeAndMessage(); // throws IOE aoqi@0: recordCookies(request, con); aoqi@0: aoqi@0: InputStream responseStream = con.getInput(); aoqi@0: if (dump || LOGGER.isLoggable(Level.FINER)) { aoqi@0: ByteArrayBuffer buf = new ByteArrayBuffer(); aoqi@0: if (responseStream != null) { aoqi@0: buf.write(responseStream); aoqi@0: responseStream.close(); aoqi@0: } aoqi@0: dump(buf,"HTTP response - "+request.endpointAddress+" - "+con.statusCode, con.getHeaders()); aoqi@0: responseStream = buf.newInputStream(); aoqi@0: } aoqi@0: aoqi@0: // Check if stream contains any data aoqi@0: int cl = con.contentLength; aoqi@0: InputStream tempIn = null; aoqi@0: if (cl == -1) { // No Content-Length header aoqi@0: tempIn = StreamUtils.hasSomeData(responseStream); aoqi@0: if (tempIn != null) { aoqi@0: responseStream = tempIn; aoqi@0: } aoqi@0: } aoqi@0: if (cl == 0 || (cl == -1 && tempIn == null)) { aoqi@0: if(responseStream != null) { aoqi@0: responseStream.close(); // No data, so close the stream aoqi@0: responseStream = null; aoqi@0: } aoqi@0: aoqi@0: } aoqi@0: aoqi@0: // Allows only certain http status codes for a binding. For all aoqi@0: // other status codes, throws exception aoqi@0: checkStatusCode(responseStream, con); // throws ClientTransportException aoqi@0: aoqi@0: Packet reply = request.createClientResponse(null); aoqi@0: reply.wasTransportSecure = con.isSecure(); aoqi@0: if (responseStream != null) { aoqi@0: String contentType = con.getContentType(); aoqi@0: if (contentType != null && contentType.contains("text/html") && binding instanceof SOAPBinding) { aoqi@0: throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(con.statusCode, con.statusMessage)); aoqi@0: } aoqi@0: codec.decode(responseStream, contentType, reply); aoqi@0: } aoqi@0: return reply; aoqi@0: } aoqi@0: aoqi@0: /* aoqi@0: * Allows the following HTTP status codes. aoqi@0: * SOAP 1.1/HTTP - 200, 202, 500 aoqi@0: * SOAP 1.2/HTTP - 200, 202, 400, 500 aoqi@0: * XML/HTTP - all aoqi@0: * aoqi@0: * For all other status codes, it throws an exception aoqi@0: */ aoqi@0: private void checkStatusCode(InputStream in, HttpClientTransport con) throws IOException { aoqi@0: int statusCode = con.statusCode; aoqi@0: String statusMessage = con.statusMessage; aoqi@0: // SOAP1.1 and SOAP1.2 differ here aoqi@0: if (binding instanceof SOAPBinding) { aoqi@0: if (binding.getSOAPVersion() == SOAPVersion.SOAP_12) { aoqi@0: //In SOAP 1.2, Fault messages can be sent with 4xx and 5xx error codes aoqi@0: if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || isErrorCode(statusCode)) { aoqi@0: // acceptable status codes for SOAP 1.2 aoqi@0: if (isErrorCode(statusCode) && in == null) { aoqi@0: // No envelope for the error, so throw an exception with http error details aoqi@0: throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage)); aoqi@0: } aoqi@0: return; aoqi@0: } aoqi@0: } else { aoqi@0: // SOAP 1.1 aoqi@0: if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR) { aoqi@0: // acceptable status codes for SOAP 1.1 aoqi@0: if (statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR && in == null) { aoqi@0: // No envelope for the error, so throw an exception with http error details aoqi@0: throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage)); aoqi@0: } aoqi@0: return; aoqi@0: } aoqi@0: } aoqi@0: if (in != null) { aoqi@0: in.close(); aoqi@0: } aoqi@0: throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage)); aoqi@0: } aoqi@0: // Every status code is OK for XML/HTTP aoqi@0: } aoqi@0: aoqi@0: private boolean isErrorCode(int code) { aoqi@0: //if(code/100 == 5/*Server-side error*/ || code/100 == 4 /*client error*/ ) { aoqi@0: return code == 500 || code == 400; aoqi@0: } aoqi@0: aoqi@0: private void addCookies(Packet context, Map> reqHeaders) throws IOException { aoqi@0: Boolean shouldMaintainSessionProperty = aoqi@0: (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY); aoqi@0: if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) { aoqi@0: return; // explicitly turned off aoqi@0: } aoqi@0: if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) { aoqi@0: Map> rememberedCookies = cookieJar.get(context.endpointAddress.getURI(), reqHeaders); aoqi@0: processCookieHeaders(reqHeaders, rememberedCookies, "Cookie"); aoqi@0: processCookieHeaders(reqHeaders, rememberedCookies, "Cookie2"); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private void processCookieHeaders(Map> requestHeaders, Map> rememberedCookies, String cookieHeader) { aoqi@0: List jarCookies = rememberedCookies.get(cookieHeader); aoqi@0: if (jarCookies != null && !jarCookies.isEmpty()) { aoqi@0: List resultCookies = mergeUserCookies(jarCookies, requestHeaders.get(cookieHeader)); aoqi@0: requestHeaders.put(cookieHeader, resultCookies); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private List mergeUserCookies(List rememberedCookies, List userCookies) { aoqi@0: aoqi@0: // nothing to merge aoqi@0: if (userCookies == null || userCookies.isEmpty()) { aoqi@0: return rememberedCookies; aoqi@0: } aoqi@0: aoqi@0: Map map = new HashMap(); aoqi@0: cookieListToMap(rememberedCookies, map); aoqi@0: cookieListToMap(userCookies, map); aoqi@0: aoqi@0: return new ArrayList(map.values()); aoqi@0: } aoqi@0: aoqi@0: private void cookieListToMap(List cookieList, Map targetMap) { aoqi@0: for(String cookie : cookieList) { aoqi@0: int index = cookie.indexOf("="); aoqi@0: String cookieName = cookie.substring(0, index); aoqi@0: targetMap.put(cookieName, cookie); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private void recordCookies(Packet context, HttpClientTransport con) throws IOException { aoqi@0: Boolean shouldMaintainSessionProperty = aoqi@0: (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY); aoqi@0: if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) { aoqi@0: return; // explicitly turned off aoqi@0: } aoqi@0: if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) { aoqi@0: cookieJar.put(context.endpointAddress.getURI(), con.getHeaders()); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private void addBasicAuth(Packet context, Map> reqHeaders) { aoqi@0: String user = (String) context.invocationProperties.get(BindingProvider.USERNAME_PROPERTY); aoqi@0: if (user != null) { aoqi@0: String pw = (String) context.invocationProperties.get(BindingProvider.PASSWORD_PROPERTY); aoqi@0: if (pw != null) { aoqi@0: StringBuilder buf = new StringBuilder(user); aoqi@0: buf.append(":"); aoqi@0: buf.append(pw); aoqi@0: String creds = DatatypeConverter.printBase64Binary(buf.toString().getBytes()); aoqi@0: reqHeaders.put("Authorization", Collections.singletonList("Basic "+creds)); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /* aoqi@0: * write SOAPAction header if the soapAction parameter is non-null or BindingProvider properties set. aoqi@0: * BindingProvider properties take precedence. aoqi@0: */ aoqi@0: private void writeSOAPAction(Map> reqHeaders, String soapAction) { aoqi@0: //dont write SOAPAction HTTP header for SOAP 1.2 messages. aoqi@0: if(SOAPVersion.SOAP_12.equals(binding.getSOAPVersion())) { aoqi@0: return; aoqi@0: } aoqi@0: if (soapAction != null) { aoqi@0: reqHeaders.put("SOAPAction", Collections.singletonList(soapAction)); aoqi@0: } else { aoqi@0: reqHeaders.put("SOAPAction", Collections.singletonList("\"\"")); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public void preDestroy() { aoqi@0: // nothing to do. Intentionally left empty. aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public HttpTransportPipe copy(TubeCloner cloner) { aoqi@0: return new HttpTransportPipe(this,cloner); aoqi@0: } aoqi@0: aoqi@0: aoqi@0: private 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: 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() > HttpAdapter.dump_threshold) { aoqi@0: byte[] b = buf.getRawData(); aoqi@0: baos.write(b, 0, HttpAdapter.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: }