ohair@286: /* alanb@368: * Copyright (c) 1997, 2012, 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.xml.internal.ws.api.EndpointAddress; ohair@286: import com.sun.xml.internal.ws.api.message.Packet; ohair@286: import com.sun.xml.internal.ws.client.BindingProviderProperties; ohair@286: import static com.sun.xml.internal.ws.client.BindingProviderProperties.*; ohair@286: import com.sun.xml.internal.ws.client.ClientTransportException; ohair@286: import com.sun.xml.internal.ws.resources.ClientMessages; ohair@286: import com.sun.xml.internal.ws.transport.Headers; ohair@286: import com.sun.xml.internal.ws.developer.JAXWSProperties; ohair@286: import com.sun.istack.internal.Nullable; ohair@286: import com.sun.istack.internal.NotNull; ohair@286: ohair@286: import javax.net.ssl.SSLSocketFactory; ohair@286: import javax.net.ssl.HttpsURLConnection; ohair@286: import javax.net.ssl.HostnameVerifier; ohair@286: import javax.net.ssl.SSLSession; ohair@286: import javax.xml.bind.JAXBContext; ohair@286: import javax.xml.bind.JAXBException; ohair@286: import javax.xml.ws.WebServiceException; ohair@286: import javax.xml.ws.handler.MessageContext; ohair@286: import java.io.FilterInputStream; ohair@286: import java.io.FilterOutputStream; ohair@286: import java.io.IOException; ohair@286: import java.io.InputStream; ohair@286: import java.io.OutputStream; ohair@286: import java.net.HttpURLConnection; ohair@286: import java.util.List; ohair@286: import java.util.Map; ohair@286: import java.util.zip.GZIPOutputStream; ohair@286: import java.util.zip.GZIPInputStream; ohair@286: ohair@286: /** ohair@286: * ohair@286: * @author WS Development Team ohair@286: */ ohair@286: public class HttpClientTransport { ohair@286: ohair@286: private static final byte[] THROW_AWAY_BUFFER = new byte[8192]; ohair@286: ohair@286: // Need to use JAXB first to register DatatypeConverter ohair@286: static { ohair@286: try { ohair@286: JAXBContext.newInstance().createUnmarshaller(); ohair@286: } catch(JAXBException je) { ohair@286: // Nothing much can be done. Intentionally left empty ohair@286: } ohair@286: } ohair@286: ohair@286: /*package*/ int statusCode; ohair@286: /*package*/ String statusMessage; ohair@286: /*package*/ int contentLength; ohair@286: private final Map> reqHeaders; ohair@286: private Map> respHeaders = null; ohair@286: ohair@286: private OutputStream outputStream; ohair@286: private boolean https; ohair@286: private HttpURLConnection httpConnection = null; ohair@286: private final EndpointAddress endpoint; ohair@286: private final Packet context; ohair@286: private final Integer chunkSize; ohair@286: ohair@286: ohair@286: public HttpClientTransport(@NotNull Packet packet, @NotNull Map> reqHeaders) { ohair@286: endpoint = packet.endpointAddress; ohair@286: context = packet; ohair@286: this.reqHeaders = reqHeaders; ohair@286: chunkSize = (Integer)context.invocationProperties.get(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE); ohair@286: } ohair@286: ohair@286: /* ohair@286: * Prepare the stream for HTTP request ohair@286: */ ohair@286: OutputStream getOutput() { ohair@286: try { ohair@286: createHttpConnection(); ohair@286: // for "GET" request no need to get outputStream ohair@286: if (requiresOutputStream()) { ohair@286: outputStream = httpConnection.getOutputStream(); ohair@286: if (chunkSize != null) { ohair@286: outputStream = new WSChunkedOuputStream(outputStream, chunkSize); ohair@286: } ohair@286: List contentEncoding = reqHeaders.get("Content-Encoding"); ohair@286: // TODO need to find out correct encoding based on q value - RFC 2616 ohair@286: if (contentEncoding != null && contentEncoding.get(0).contains("gzip")) { ohair@286: outputStream = new GZIPOutputStream(outputStream); ohair@286: } ohair@286: } ohair@286: httpConnection.connect(); ohair@286: } catch (Exception ex) { ohair@286: throw new ClientTransportException( ohair@286: ClientMessages.localizableHTTP_CLIENT_FAILED(ex),ex); ohair@286: } ohair@286: ohair@286: return outputStream; ohair@286: } ohair@286: ohair@286: void closeOutput() throws IOException { ohair@286: if (outputStream != null) { ohair@286: outputStream.close(); ohair@286: outputStream = null; ohair@286: } ohair@286: } ohair@286: ohair@286: /* ohair@286: * Get the response from HTTP connection and prepare the input stream for response ohair@286: */ ohair@286: @Nullable InputStream getInput() { ohair@286: // response processing ohair@286: ohair@286: InputStream in; ohair@286: try { ohair@286: in = readResponse(); ohair@286: if (in != null) { ohair@286: String contentEncoding = httpConnection.getContentEncoding(); ohair@286: if (contentEncoding != null && contentEncoding.contains("gzip")) { ohair@286: in = new GZIPInputStream(in); ohair@286: } ohair@286: } ohair@286: } catch (IOException e) { ohair@286: throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage), e); ohair@286: } ohair@286: return in; ohair@286: } ohair@286: ohair@286: public Map> getHeaders() { ohair@286: if (respHeaders != null) { ohair@286: return respHeaders; ohair@286: } ohair@286: respHeaders = new Headers(); ohair@286: respHeaders.putAll(httpConnection.getHeaderFields()); ohair@286: return respHeaders; ohair@286: } ohair@286: ohair@286: protected @Nullable InputStream readResponse() { ohair@286: InputStream is; ohair@286: try { ohair@286: is = httpConnection.getInputStream(); ohair@286: } catch(IOException ioe) { ohair@286: is = httpConnection.getErrorStream(); ohair@286: } ohair@286: if (is == null) { ohair@286: return is; ohair@286: } ohair@286: // Since StreamMessage doesn't read , there ohair@286: // are some bytes left in the InputStream. This confuses JDK and may ohair@286: // not reuse underlying sockets. Hopefully JDK fixes it in its code ! ohair@286: final InputStream temp = is; ohair@286: return new FilterInputStream(temp) { ohair@286: // Workaround for "SJSXP XMLStreamReader.next() closes stream". ohair@286: // So it doesn't read from the closed stream ohair@286: boolean closed; ohair@286: @Override ohair@286: public void close() throws IOException { ohair@286: if (!closed) { ohair@286: closed = true; ohair@286: while(temp.read(THROW_AWAY_BUFFER) != -1); ohair@286: super.close(); ohair@286: } ohair@286: } ohair@286: }; ohair@286: } ohair@286: ohair@286: protected void readResponseCodeAndMessage() { ohair@286: try { ohair@286: statusCode = httpConnection.getResponseCode(); ohair@286: statusMessage = httpConnection.getResponseMessage(); ohair@286: contentLength = httpConnection.getContentLength(); ohair@286: } catch(IOException ioe) { ohair@286: throw new WebServiceException(ioe); ohair@286: } ohair@286: } ohair@286: ohair@286: protected HttpURLConnection openConnection(Packet packet) { ohair@286: // default do nothing ohair@286: return null; ohair@286: } ohair@286: ohair@286: protected boolean checkHTTPS(HttpURLConnection connection) { ohair@286: if (connection instanceof HttpsURLConnection) { ohair@286: ohair@286: // TODO The above property needs to be removed in future version as the semantics of this property are not preoperly defined. ohair@286: // One should use JAXWSProperties.HOSTNAME_VERIFIER to control the behavior ohair@286: ohair@286: // does the client want client hostname verification by the service ohair@286: String verificationProperty = ohair@286: (String) context.invocationProperties.get(HOSTNAME_VERIFICATION_PROPERTY); ohair@286: if (verificationProperty != null) { ohair@286: if (verificationProperty.equalsIgnoreCase("true")) { ohair@286: ((HttpsURLConnection) connection).setHostnameVerifier(new HttpClientVerifier()); ohair@286: } ohair@286: } ohair@286: ohair@286: // Set application's HostNameVerifier for this connection ohair@286: HostnameVerifier verifier = ohair@286: (HostnameVerifier) context.invocationProperties.get(JAXWSProperties.HOSTNAME_VERIFIER); ohair@286: if (verifier != null) { ohair@286: ((HttpsURLConnection) connection).setHostnameVerifier(verifier); ohair@286: } ohair@286: ohair@286: // Set application's SocketFactory for this connection ohair@286: SSLSocketFactory sslSocketFactory = ohair@286: (SSLSocketFactory) context.invocationProperties.get(JAXWSProperties.SSL_SOCKET_FACTORY); ohair@286: if (sslSocketFactory != null) { ohair@286: ((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory); ohair@286: } ohair@286: ohair@286: return true; ohair@286: } ohair@286: return false; ohair@286: } ohair@286: ohair@286: private void createHttpConnection() throws IOException { ohair@286: httpConnection = openConnection(context); ohair@286: ohair@286: if (httpConnection == null) ohair@286: httpConnection = (HttpURLConnection) endpoint.openConnection(); ohair@286: ohair@286: String scheme = endpoint.getURI().getScheme(); ohair@286: if (scheme.equals("https")) { ohair@286: https = true; ohair@286: } ohair@286: if (checkHTTPS(httpConnection)) ohair@286: https = true; ohair@286: ohair@286: // allow interaction with the web page - user may have to supply ohair@286: // username, password id web page is accessed from web browser ohair@286: httpConnection.setAllowUserInteraction(true); ohair@286: ohair@286: // enable input, output streams ohair@286: httpConnection.setDoOutput(true); ohair@286: httpConnection.setDoInput(true); ohair@286: ohair@286: String requestMethod = (String) context.invocationProperties.get(MessageContext.HTTP_REQUEST_METHOD); ohair@286: String method = (requestMethod != null) ? requestMethod : "POST"; ohair@286: httpConnection.setRequestMethod(method); ohair@286: ohair@286: //this code or something similiar needs t be moved elsewhere for error checking ohair@286: /*if (context.invocationProperties.get(BindingProviderProperties.BINDING_ID_PROPERTY).equals(HTTPBinding.HTTP_BINDING)){ ohair@286: method = (requestMethod != null)?requestMethod:method; ohair@286: } else if ohair@286: (context.invocationProperties.get(BindingProviderProperties.BINDING_ID_PROPERTY).equals(SOAPBinding.SOAP12HTTP_BINDING) && ohair@286: "GET".equalsIgnoreCase(requestMethod)) { ohair@286: } ohair@286: */ ohair@286: ohair@286: Integer reqTimeout = (Integer)context.invocationProperties.get(BindingProviderProperties.REQUEST_TIMEOUT); ohair@286: if (reqTimeout != null) { ohair@286: httpConnection.setReadTimeout(reqTimeout); ohair@286: } ohair@286: ohair@286: Integer connectTimeout = (Integer)context.invocationProperties.get(JAXWSProperties.CONNECT_TIMEOUT); ohair@286: if (connectTimeout != null) { ohair@286: httpConnection.setConnectTimeout(connectTimeout); ohair@286: } ohair@286: ohair@286: Integer chunkSize = (Integer)context.invocationProperties.get(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE); ohair@286: if (chunkSize != null) { ohair@286: httpConnection.setChunkedStreamingMode(chunkSize); ohair@286: } ohair@286: ohair@286: // set the properties on HttpURLConnection ohair@286: for (Map.Entry> entry : reqHeaders.entrySet()) { ohair@286: if ("Content-Length".equals(entry.getKey())) continue; ohair@286: for(String value : entry.getValue()) { ohair@286: httpConnection.addRequestProperty(entry.getKey(), value); ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: boolean isSecure() { ohair@286: return https; ohair@286: } ohair@286: ohair@286: protected void setStatusCode(int statusCode) { ohair@286: this.statusCode = statusCode; ohair@286: } ohair@286: ohair@286: private boolean requiresOutputStream() { ohair@286: return !(httpConnection.getRequestMethod().equalsIgnoreCase("GET") || ohair@286: httpConnection.getRequestMethod().equalsIgnoreCase("HEAD") || ohair@286: httpConnection.getRequestMethod().equalsIgnoreCase("DELETE")); ohair@286: } ohair@286: ohair@286: @Nullable String getContentType() { ohair@286: return httpConnection.getContentType(); ohair@286: } ohair@286: ohair@286: public int getContentLength() { ohair@286: return httpConnection.getContentLength(); ohair@286: } ohair@286: ohair@286: // overide default SSL HttpClientVerifier to always return true ohair@286: // effectively overiding Hostname client verification when using SSL ohair@286: private static class HttpClientVerifier implements HostnameVerifier { ohair@286: public boolean verify(String s, SSLSession sslSession) { ohair@286: return true; ohair@286: } ohair@286: } ohair@286: ohair@286: private static class LocalhostHttpClientVerifier implements HostnameVerifier { ohair@286: public boolean verify(String s, SSLSession sslSession) { ohair@286: return "localhost".equalsIgnoreCase(s) || "127.0.0.1".equals(s); ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * HttpURLConnection.getOuputStream() returns sun.net.www.http.ChunkedOuputStream in chunked ohair@286: * streaming mode. If you call ChunkedOuputStream.write(byte[20MB], int, int), then the whole data ohair@286: * is kept in memory. This wraps the ChunkedOuputStream so that it writes only small ohair@286: * chunks. ohair@286: */ ohair@286: private static final class WSChunkedOuputStream extends FilterOutputStream { ohair@286: final int chunkSize; ohair@286: ohair@286: WSChunkedOuputStream(OutputStream actual, int chunkSize) { ohair@286: super(actual); ohair@286: this.chunkSize = chunkSize; ohair@286: } ohair@286: ohair@286: @Override ohair@286: public void write(byte b[], int off, int len) throws IOException { ohair@286: while(len > 0) { ohair@286: int sent = (len > chunkSize) ? chunkSize : len; ohair@286: out.write(b, off, sent); // don't use super.write() as it writes byte-by-byte ohair@286: len -= sent; ohair@286: off += sent; ohair@286: } ohair@286: } ohair@286: ohair@286: } ohair@286: ohair@286: }