| /* |
| * Portions Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Sun designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Sun in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| */ |
| |
| package com.sun.xml.internal.ws.transport.http.client; |
| |
| import static com.sun.xml.internal.ws.client.BindingProviderProperties.*; |
| import com.sun.xml.internal.ws.client.ClientTransportException; |
| import com.sun.xml.internal.ws.client.BindingProviderProperties; |
| import com.sun.xml.internal.ws.transport.WSConnectionImpl; |
| import com.sun.xml.internal.ws.util.ByteArrayBuffer; |
| |
| import javax.net.ssl.HostnameVerifier; |
| import javax.net.ssl.HttpsURLConnection; |
| import javax.net.ssl.SSLSession; |
| import javax.xml.ws.BindingProvider; |
| import javax.xml.ws.WebServiceException; |
| import javax.xml.ws.http.HTTPBinding; |
| import javax.xml.ws.handler.MessageContext; |
| import javax.xml.soap.MessageFactory; |
| import javax.xml.soap.MimeHeader; |
| import javax.xml.soap.MimeHeaders; |
| import javax.xml.soap.SOAPConstants; |
| import static javax.xml.ws.BindingProvider.ENDPOINT_ADDRESS_PROPERTY; |
| import static javax.xml.ws.BindingProvider.SESSION_MAINTAIN_PROPERTY; |
| import javax.xml.ws.soap.SOAPBinding; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author WS Development Team |
| */ |
| public class HttpClientTransport extends WSConnectionImpl { |
| |
| private static String LAST_ENDPOINT = ""; |
| private static boolean redirect = true; |
| private static final int START_REDIRECT_COUNT = 3; |
| private static int redirectCount = START_REDIRECT_COUNT; |
| int statusCode; |
| private Map<String, List<String>> respHeaders = null; |
| |
| public HttpClientTransport() { |
| this(null, new HashMap<String, Object>()); |
| } |
| |
| public HttpClientTransport(OutputStream logStream, Map<String, Object> context) { |
| this.context = context; |
| _logStream = logStream; |
| |
| String bindingId = (String) context.get(BINDING_ID_PROPERTY); |
| try { |
| if (bindingId == null) |
| bindingId = SOAPBinding.SOAP11HTTP_BINDING; |
| |
| if (bindingId.equals(SOAPBinding.SOAP12HTTP_BINDING)) |
| _messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); |
| else |
| _messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); |
| |
| endpoint = (String) context.get(ENDPOINT_ADDRESS_PROPERTY); |
| } catch (Exception e) { |
| throw new ClientTransportException("http.client.cannotCreateMessageFactory"); |
| } |
| } |
| |
| /** |
| * Prepare the stream for HTTP request |
| */ |
| @Override |
| public OutputStream getOutput() { |
| try { |
| httpConnection = createHttpConnection(endpoint, context); |
| cookieJar = sendCookieAsNeeded(); |
| |
| // how to incorporate redirect processing: message dispatcher does not seem to tbe right place |
| String requestMethod = httpConnection.getRequestMethod(); |
| boolean skipOut = ("GET".equalsIgnoreCase(requestMethod) || |
| "HEAD".equalsIgnoreCase(requestMethod) || |
| "DELETE".equalsIgnoreCase(requestMethod)); |
| if (!skipOut) |
| outputStream = httpConnection.getOutputStream(); |
| //if use getOutputStream method set as "POST" |
| //but for "Get" request no need to get outputStream |
| connectForResponse(); |
| |
| } catch (Exception ex) { |
| throw new ClientTransportException("http.client.failed", ex); |
| } |
| |
| return outputStream; |
| } |
| |
| /** |
| * Get the response from HTTP connection and prepare the input stream for response |
| */ |
| @Override |
| public InputStream getInput() { |
| // response processing |
| |
| InputStream in; |
| try { |
| in = readResponse(); |
| } catch (IOException e) { |
| if (statusCode == HttpURLConnection.HTTP_NO_CONTENT |
| || (isFailure |
| && statusCode != HttpURLConnection.HTTP_INTERNAL_ERROR)) { |
| try { |
| throw new ClientTransportException("http.status.code", |
| statusCode, httpConnection.getResponseMessage()); |
| } catch (IOException ex) { |
| throw new ClientTransportException("http.status.code", |
| statusCode, ex); |
| } |
| } |
| throw new ClientTransportException("http.client.failed", |
| e.getMessage()); |
| } |
| httpConnection = null; |
| |
| return in; |
| } |
| |
| @Override |
| public OutputStream getDebug() { |
| return _logStream; |
| } |
| |
| @Override |
| public Map<String, List<String>> getHeaders() { |
| if (respHeaders != null) { |
| return respHeaders; |
| } |
| try { |
| isFailure = checkResponseCode(); |
| |
| respHeaders = collectResponseMimeHeaders(); |
| |
| saveCookieAsNeeded(cookieJar); |
| setHeaders(respHeaders); |
| |
| return respHeaders; |
| } catch (IOException e) { |
| if (statusCode == HttpURLConnection.HTTP_NO_CONTENT |
| || (isFailure |
| && statusCode != HttpURLConnection.HTTP_INTERNAL_ERROR)) { |
| try { |
| throw new ClientTransportException("http.status.code", |
| new Object[]{ |
| statusCode, |
| httpConnection.getResponseMessage()}); |
| } catch (IOException ex) { |
| throw new ClientTransportException("http.status.code", |
| new Object[]{ |
| statusCode, |
| ex}); |
| } |
| } |
| throw new ClientTransportException("http.client.failed", |
| e.getMessage()); |
| } |
| |
| } |
| |
| // public void invoke(String endpoint, SOAPMessageContext context) |
| // throws ClientTransportException { |
| |
| // try { |
| // int statusCode = httpConnection.getResponseCode(); |
| // |
| // //http URL redirection does not redirect http requests |
| // //to an https endpoint probably due to a bug in the jdk |
| // //or by intent - to workaround this if an error code |
| // //of HTTP_MOVED_TEMP or HTTP_MOVED_PERM is received then |
| // //the jaxws client will reinvoke the original request |
| // //to the new endpoint - kw bug 4890118 |
| // if (checkForRedirect(statusCode)) { |
| // redirectRequest(httpConnection, context); |
| // return; |
| // } |
| // } |
| |
| protected InputStream readResponse() |
| throws IOException { |
| InputStream contentIn = |
| (isFailure |
| ? httpConnection.getErrorStream() |
| : httpConnection.getInputStream()); |
| |
| ByteArrayBuffer bab = new ByteArrayBuffer(); |
| if (contentIn != null) { // is this really possible? |
| bab.write(contentIn); |
| bab.close(); |
| } |
| |
| int length = |
| httpConnection.getContentLength() == -1 |
| ? bab.size() |
| : httpConnection.getContentLength(); |
| |
| return bab.newInputStream(0, length); |
| } |
| |
| protected Map<String, List<String>> collectResponseMimeHeaders() { |
| /* |
| MimeHeaders mimeHeaders = new MimeHeaders(); |
| for (int i = 1; ; ++i) { |
| String key = httpConnection.getHeaderFieldKey(i); |
| if (key == null) { |
| break; |
| } |
| String value = httpConnection.getHeaderField(i); |
| try { |
| mimeHeaders.addHeader(key, value); |
| } catch (IllegalArgumentException e) { |
| // ignore headers that are illegal in MIME |
| } |
| } |
| |
| Map<String, List<String>> headers = new HashMap<String, List<String>>(); |
| for (Iterator iter = mimeHeaders.getAllHeaders(); iter.hasNext();) { |
| MimeHeader header = (MimeHeader)iter.next(); |
| List<String> h = new ArrayList<String>(); |
| h.add(header.getValue()); |
| headers.put (header.getName (), h); |
| } |
| return headers; |
| */ |
| return httpConnection.getHeaderFields(); |
| } |
| |
| protected void connectForResponse() |
| throws IOException { |
| |
| httpConnection.connect(); |
| } |
| |
| /* |
| * Will throw an exception instead of returning 'false' if there is no |
| * return message to be processed (i.e., in the case of an UNAUTHORIZED |
| * response from the servlet or 404 not found) |
| */ |
| protected boolean checkResponseCode() |
| throws IOException { |
| boolean isFailure = false; |
| try { |
| |
| statusCode = httpConnection.getResponseCode(); |
| setStatus(statusCode); |
| |
| if ((httpConnection.getResponseCode() |
| == HttpURLConnection.HTTP_INTERNAL_ERROR)) { |
| isFailure = true; |
| //added HTTP_ACCEPT for 1-way operations |
| } else if ( |
| httpConnection.getResponseCode() |
| == HttpURLConnection.HTTP_UNAUTHORIZED) { |
| |
| // no soap message returned, so skip reading message and throw exception |
| throw new ClientTransportException("http.client.unauthorized", |
| httpConnection.getResponseMessage()); |
| } else if ( |
| httpConnection.getResponseCode() |
| == HttpURLConnection.HTTP_NOT_FOUND) { |
| |
| // no message returned, so skip reading message and throw exception |
| throw new ClientTransportException("http.not.found", |
| httpConnection.getResponseMessage()); |
| } else if ( |
| (statusCode == HttpURLConnection.HTTP_MOVED_TEMP) || |
| (statusCode == HttpURLConnection.HTTP_MOVED_PERM)) { |
| isFailure = true; |
| |
| if (!redirect || (redirectCount <= 0)) { |
| throw new ClientTransportException("http.status.code", |
| new Object[]{ |
| statusCode, |
| getStatusMessage(httpConnection)}); |
| } |
| } else if ( |
| statusCode < 200 || (statusCode >= 303 && statusCode < 500)) { |
| throw new ClientTransportException("http.status.code", |
| new Object[]{ |
| statusCode, |
| getStatusMessage(httpConnection)}); |
| } else if (statusCode >= 500) { |
| isFailure = true; |
| } |
| } catch (IOException e) { |
| throw new WebServiceException(e); |
| // on JDK1.3.1_01, we end up here, but then getResponseCode() succeeds! |
| // if (httpConnection.getResponseCode() |
| // == HttpURLConnection.HTTP_INTERNAL_ERROR) { |
| // isFailure = true; |
| // } else { |
| // throw e; |
| // } |
| } |
| |
| return isFailure; |
| } |
| |
| protected String getStatusMessage(HttpURLConnection httpConnection) |
| throws IOException { |
| int statusCode = httpConnection.getResponseCode(); |
| String message = httpConnection.getResponseMessage(); |
| if (statusCode == HttpURLConnection.HTTP_CREATED |
| || (statusCode >= HttpURLConnection.HTTP_MULT_CHOICE |
| && statusCode != HttpURLConnection.HTTP_NOT_MODIFIED |
| && statusCode < HttpURLConnection.HTTP_BAD_REQUEST)) { |
| String location = httpConnection.getHeaderField("Location"); |
| if (location != null) |
| message += " - Location: " + location; |
| } |
| return message; |
| } |
| |
| protected CookieJar sendCookieAsNeeded() { |
| Boolean shouldMaintainSessionProperty = |
| (Boolean) context.get(SESSION_MAINTAIN_PROPERTY); |
| if (shouldMaintainSessionProperty == null) { |
| return null; |
| } |
| if (shouldMaintainSessionProperty.booleanValue()) { |
| CookieJar cookieJar = (CookieJar) context.get(HTTP_COOKIE_JAR); |
| if (cookieJar == null) { |
| cookieJar = new CookieJar(); |
| |
| // need to store in binding's context so it is not lost |
| BindingProvider bp = |
| (BindingProvider) context.get(JAXWS_CLIENT_HANDLE_PROPERTY); |
| bp.getRequestContext().put(HTTP_COOKIE_JAR, cookieJar); |
| } |
| cookieJar.applyRelevantCookies(httpConnection); |
| return cookieJar; |
| } else { |
| return null; |
| } |
| } |
| |
| protected void saveCookieAsNeeded(CookieJar cookieJar) { |
| if (cookieJar != null) { |
| cookieJar.recordAnyCookies(httpConnection); |
| } |
| } |
| |
| protected HttpURLConnection createHttpConnection(String endpoint, |
| Map<String, Object> context) |
| throws IOException { |
| |
| boolean verification = false; |
| // does the client want client hostname verification by the service |
| String verificationProperty = |
| (String) context.get(HOSTNAME_VERIFICATION_PROPERTY); |
| if (verificationProperty != null) { |
| if (verificationProperty.equalsIgnoreCase("true")) |
| verification = true; |
| } |
| |
| // does the client want request redirection to occur |
| String redirectProperty = |
| (String) context.get(REDIRECT_REQUEST_PROPERTY); |
| if (redirectProperty != null) { |
| if (redirectProperty.equalsIgnoreCase("false")) |
| redirect = false; |
| } |
| |
| checkEndpoints(endpoint); |
| |
| HttpURLConnection httpConnection = createConnection(endpoint); |
| |
| if (!verification) { |
| // for https hostname verification - turn off by default |
| if (httpConnection instanceof HttpsURLConnection) { |
| ((HttpsURLConnection) httpConnection).setHostnameVerifier(new HttpClientVerifier()); |
| } |
| } |
| |
| // allow interaction with the web page - user may have to supply |
| // username, password id web page is accessed from web browser |
| httpConnection.setAllowUserInteraction(true); |
| // enable input, output streams |
| httpConnection.setDoOutput(true); |
| httpConnection.setDoInput(true); |
| // the soap message is always sent as a Http POST |
| // HTTP Get is disallowed by BP 1.0 |
| // needed for XML/HTTPBinding and SOAP12Binding |
| // for xml/http binding other methods are allowed. |
| // for Soap 1.2 "GET" is allowed. |
| String method = "POST"; |
| String requestMethod = (String) context.get(MessageContext.HTTP_REQUEST_METHOD); |
| if (context.get(BindingProviderProperties.BINDING_ID_PROPERTY).equals(HTTPBinding.HTTP_BINDING)){ |
| method = (requestMethod != null)?requestMethod:method; |
| } else if |
| (context.get(BindingProviderProperties.BINDING_ID_PROPERTY).equals(SOAPBinding.SOAP12HTTP_BINDING) && |
| "GET".equalsIgnoreCase(requestMethod)) { |
| method = (requestMethod != null)?requestMethod:method; |
| } |
| ((HttpURLConnection)httpConnection).setRequestMethod(method); |
| |
| Integer reqTimeout = (Integer)context.get(BindingProviderProperties.REQUEST_TIMEOUT); |
| if (reqTimeout != null) { |
| httpConnection.setReadTimeout(reqTimeout); |
| } |
| |
| // set the properties on HttpURLConnection |
| for (Map.Entry entry : super.getHeaders().entrySet()) { |
| httpConnection.addRequestProperty((String) entry.getKey(), ((List<String>) entry.getValue()).get(0)); |
| } |
| |
| return httpConnection; |
| } |
| |
| private java.net.HttpURLConnection createConnection(String endpoint) |
| throws IOException { |
| return (HttpURLConnection) new URL(endpoint).openConnection(); |
| } |
| |
| // private void redirectRequest(HttpURLConnection httpConnection, SOAPMessageContext context) { |
| // String redirectEndpoint = httpConnection.getHeaderField("Location"); |
| // if (redirectEndpoint != null) { |
| // httpConnection.disconnect(); |
| // invoke(redirectEndpoint, context); |
| // } else |
| // System.out.println("redirection Failed"); |
| // } |
| |
| private boolean checkForRedirect(int statusCode) { |
| return (((statusCode == 301) || (statusCode == 302)) && redirect && (redirectCount-- > 0)); |
| } |
| |
| private void checkEndpoints(String currentEndpoint) { |
| if (!LAST_ENDPOINT.equalsIgnoreCase(currentEndpoint)) { |
| redirectCount = START_REDIRECT_COUNT; |
| LAST_ENDPOINT = currentEndpoint; |
| } |
| } |
| |
| // overide default SSL HttpClientVerifier to always return true |
| // effectively overiding Hostname client verification when using SSL |
| static class HttpClientVerifier implements HostnameVerifier { |
| public boolean verify(String s, SSLSession sslSession) { |
| return true; |
| } |
| } |
| |
| private MessageFactory _messageFactory; |
| HttpURLConnection httpConnection = null; |
| String endpoint = null; |
| Map<String, Object> context = null; |
| CookieJar cookieJar = null; |
| boolean isFailure = false; |
| OutputStream _logStream = null; |
| } |