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