| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.cxf.transport.http; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.HttpURLConnection; |
| import java.net.ProtocolException; |
| import java.net.Proxy; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.logging.Level; |
| |
| import javax.net.ssl.HttpsURLConnection; |
| |
| import org.apache.cxf.Bus; |
| import org.apache.cxf.common.util.ReflectionUtil; |
| import org.apache.cxf.common.util.SystemPropertyAction; |
| import org.apache.cxf.configuration.jsse.TLSClientParameters; |
| import org.apache.cxf.helpers.IOUtils; |
| import org.apache.cxf.io.CacheAndWriteOutputStream; |
| import org.apache.cxf.message.Message; |
| import org.apache.cxf.message.MessageUtils; |
| import org.apache.cxf.service.model.EndpointInfo; |
| import org.apache.cxf.transport.https.HttpsURLConnectionFactory; |
| import org.apache.cxf.transport.https.HttpsURLConnectionInfo; |
| import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; |
| import org.apache.cxf.ws.addressing.EndpointReferenceType; |
| |
| /** |
| * |
| */ |
| public class URLConnectionHTTPConduit extends HTTPConduit { |
| public static final String HTTPURL_CONNECTION_METHOD_REFLECTION = "use.httpurlconnection.method.reflection"; |
| public static final String SET_REASON_PHRASE_NOT_NULL = "set.reason.phrase.not.null"; |
| |
| private static final boolean DEFAULT_USE_REFLECTION; |
| private static final boolean SET_REASON_PHRASE; |
| static { |
| DEFAULT_USE_REFLECTION = |
| Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false")); |
| SET_REASON_PHRASE = |
| Boolean.valueOf(SystemPropertyAction.getProperty(SET_REASON_PHRASE_NOT_NULL, "false")); |
| } |
| |
| /** |
| * This field holds the connection factory, which primarily is used to |
| * factor out SSL specific code from this implementation. |
| * <p> |
| * This field is "protected" to facilitate some contrived UnitTesting so |
| * that an extended class may alter its value with an EasyMock URLConnection |
| * Factory. |
| */ |
| protected HttpsURLConnectionFactory connectionFactory; |
| |
| |
| public URLConnectionHTTPConduit(Bus b, EndpointInfo ei) throws IOException { |
| super(b, ei); |
| connectionFactory = new HttpsURLConnectionFactory(); |
| CXFAuthenticator.addAuthenticator(); |
| } |
| |
| public URLConnectionHTTPConduit(Bus b, EndpointInfo ei, EndpointReferenceType t) throws IOException { |
| super(b, ei, t); |
| connectionFactory = new HttpsURLConnectionFactory(); |
| CXFAuthenticator.addAuthenticator(); |
| } |
| |
| /** |
| * Close the conduit |
| */ |
| public void close() { |
| super.close(); |
| if (defaultAddress != null) { |
| try { |
| URLConnection connect = defaultAddress.getURL().openConnection(); |
| if (connect instanceof HttpURLConnection) { |
| ((HttpURLConnection)connect).disconnect(); |
| } |
| } catch (IOException ex) { |
| //ignore |
| } |
| //defaultEndpointURL = null; |
| } |
| } |
| |
| private HttpURLConnection createConnection(Message message, Address address, HTTPClientPolicy csPolicy) |
| throws IOException { |
| URL url = address.getURL(); |
| URI uri = address.getURI(); |
| Proxy proxy = proxyFactory.createProxy(csPolicy, uri); |
| message.put("http.scheme", uri.getScheme()); |
| // check tlsClientParameters from message header |
| TLSClientParameters clientParameters = message.get(TLSClientParameters.class); |
| if (clientParameters == null) { |
| clientParameters = tlsClientParameters; |
| } |
| return connectionFactory.createConnection(clientParameters, |
| proxy != null ? proxy : address.getDefaultProxy(), url); |
| } |
| protected void setupConnection(Message message, Address address, HTTPClientPolicy csPolicy) throws IOException { |
| HttpURLConnection connection = createConnection(message, address, csPolicy); |
| connection.setDoOutput(true); |
| |
| int ctimeout = determineConnectionTimeout(message, csPolicy); |
| connection.setConnectTimeout(ctimeout); |
| |
| int rtimeout = determineReceiveTimeout(message, csPolicy); |
| connection.setReadTimeout(rtimeout); |
| |
| connection.setUseCaches(false); |
| // We implement redirects in this conduit. We do not |
| // rely on the underlying URLConnection implementation |
| // because of trust issues. |
| connection.setInstanceFollowRedirects(false); |
| |
| // If the HTTP_REQUEST_METHOD is not set, the default is "POST". |
| String httpRequestMethod = |
| (String)message.get(Message.HTTP_REQUEST_METHOD); |
| if (httpRequestMethod == null) { |
| httpRequestMethod = "POST"; |
| message.put(Message.HTTP_REQUEST_METHOD, "POST"); |
| } |
| try { |
| connection.setRequestMethod(httpRequestMethod); |
| } catch (java.net.ProtocolException ex) { |
| boolean b = MessageUtils.getContextualBoolean(message, |
| HTTPURL_CONNECTION_METHOD_REFLECTION, |
| DEFAULT_USE_REFLECTION); |
| if (b) { |
| try { |
| java.lang.reflect.Field f = ReflectionUtil.getDeclaredField(HttpURLConnection.class, "method"); |
| if (connection instanceof HttpsURLConnection) { |
| try { |
| java.lang.reflect.Field f2 = ReflectionUtil.getDeclaredField(connection.getClass(), |
| "delegate"); |
| Object c = ReflectionUtil.setAccessible(f2).get(connection); |
| if (c instanceof HttpURLConnection) { |
| ReflectionUtil.setAccessible(f).set(c, httpRequestMethod); |
| } |
| |
| f2 = ReflectionUtil.getDeclaredField(c.getClass(), "httpsURLConnection"); |
| HttpsURLConnection c2 = (HttpsURLConnection)ReflectionUtil.setAccessible(f2) |
| .get(c); |
| |
| ReflectionUtil.setAccessible(f).set(c2, httpRequestMethod); |
| } catch (Throwable t) { |
| //ignore |
| logStackTrace(t); |
| } |
| } |
| ReflectionUtil.setAccessible(f).set(connection, httpRequestMethod); |
| message.put(HTTPURL_CONNECTION_METHOD_REFLECTION, true); |
| } catch (Throwable t) { |
| logStackTrace(t); |
| throw ex; |
| } |
| } else { |
| throw ex; |
| } |
| } |
| |
| // We place the connection on the message to pick it up |
| // in the WrappedOutputStream. |
| message.put(KEY_HTTP_CONNECTION, connection); |
| message.put(KEY_HTTP_CONNECTION_ADDRESS, address); |
| } |
| |
| |
| protected OutputStream createOutputStream(Message message, |
| boolean needToCacheRequest, |
| boolean isChunking, |
| int chunkThreshold) throws IOException { |
| HttpURLConnection connection = (HttpURLConnection)message.get(KEY_HTTP_CONNECTION); |
| |
| if (isChunking && chunkThreshold <= 0) { |
| chunkThreshold = 0; |
| connection.setChunkedStreamingMode(-1); |
| } |
| try { |
| return new URLConnectionWrappedOutputStream(message, connection, |
| needToCacheRequest, |
| isChunking, |
| chunkThreshold, |
| getConduitName()); |
| } catch (URISyntaxException e) { |
| throw new IOException(e); |
| } |
| } |
| |
| private static URI computeURI(Message message, HttpURLConnection connection) throws URISyntaxException { |
| Address address = (Address)message.get(KEY_HTTP_CONNECTION_ADDRESS); |
| return address != null ? address.getURI() : connection.getURL().toURI(); |
| } |
| |
| class URLConnectionWrappedOutputStream extends WrappedOutputStream { |
| HttpURLConnection connection; |
| URLConnectionWrappedOutputStream(Message message, HttpURLConnection connection, |
| boolean needToCacheRequest, boolean isChunking, |
| int chunkThreshold, String conduitName) throws URISyntaxException { |
| super(message, needToCacheRequest, isChunking, |
| chunkThreshold, conduitName, |
| computeURI(message, connection)); |
| this.connection = connection; |
| } |
| |
| // This construction makes extending the HTTPConduit more easier |
| protected URLConnectionWrappedOutputStream(URLConnectionWrappedOutputStream wos) { |
| super(wos); |
| this.connection = wos.connection; |
| } |
| private OutputStream connectAndGetOutputStream(Boolean b) throws IOException { |
| OutputStream cout = null; |
| |
| if (b != null && b) { |
| String method = connection.getRequestMethod(); |
| connection.connect(); |
| try { |
| java.lang.reflect.Field f = ReflectionUtil.getDeclaredField(HttpURLConnection.class, "method"); |
| ReflectionUtil.setAccessible(f).set(connection, "POST"); |
| cout = connection.getOutputStream(); |
| ReflectionUtil.setAccessible(f).set(connection, method); |
| } catch (Throwable t) { |
| logStackTrace(t); |
| } |
| |
| } else { |
| cout = connection.getOutputStream(); |
| } |
| return cout; |
| } |
| protected void setupWrappedStream() throws IOException { |
| // If we need to cache for retransmission, store data in a |
| // CacheAndWriteOutputStream. Otherwise write directly to the output stream. |
| OutputStream cout; |
| try { |
| try { |
| // cout = connection.getOutputStream(); |
| if (System.getSecurityManager() != null) { |
| try { |
| cout = AccessController.doPrivileged(new PrivilegedExceptionAction<OutputStream>() { |
| @Override |
| public OutputStream run() throws IOException { |
| return connection.getOutputStream(); |
| } |
| }); |
| } catch (PrivilegedActionException e) { |
| throw (IOException) e.getException(); |
| } |
| } else { |
| cout = connection.getOutputStream(); |
| } |
| } catch (ProtocolException pe) { |
| Boolean b = (Boolean)outMessage.get(HTTPURL_CONNECTION_METHOD_REFLECTION); |
| cout = connectAndGetOutputStream(b); |
| } |
| } catch (Exception e) { |
| if ("Socket Closed".equals(e.getMessage()) |
| || "HostnameVerifier, socket reset for TTL".equals(e.getMessage())) { |
| connection.connect(); |
| cout = connectAndGetOutputStream((Boolean)outMessage.get(HTTPURL_CONNECTION_METHOD_REFLECTION)); |
| } else { |
| throw e; |
| } |
| } |
| if (cachingForRetransmission) { |
| cachedStream = |
| new CacheAndWriteOutputStream(cout); |
| wrappedStream = cachedStream; |
| } else { |
| wrappedStream = cout; |
| } |
| } |
| |
| @Override |
| public void thresholdReached() { |
| if (chunking) { |
| connection.setChunkedStreamingMode( |
| URLConnectionHTTPConduit.this.getClient().getChunkLength()); |
| } |
| } |
| @Override |
| protected void onFirstWrite() throws IOException { |
| super.onFirstWrite(); |
| if (LOG.isLoggable(Level.FINE)) { |
| LOG.fine("Sending " |
| + connection.getRequestMethod() |
| + " Message with Headers to " |
| + url |
| + " Conduit :" |
| + conduitName |
| + "\n"); |
| } |
| } |
| protected void setProtocolHeaders() throws IOException { |
| new Headers(outMessage).setProtocolHeadersInConnection(connection); |
| } |
| |
| protected HttpsURLConnectionInfo getHttpsURLConnectionInfo() throws IOException { |
| connection.connect(); |
| return new HttpsURLConnectionInfo(connection); |
| } |
| protected void updateResponseHeaders(Message inMessage) { |
| Headers h = new Headers(inMessage); |
| h.readFromConnection(connection); |
| inMessage.put(Message.CONTENT_TYPE, connection.getContentType()); |
| cookies.readFromHeaders(h); |
| } |
| protected void handleResponseAsync() throws IOException { |
| handleResponseOnWorkqueue(true, false); |
| } |
| protected void updateCookiesBeforeRetransmit() { |
| Headers h = new Headers(); |
| h.readFromConnection(connection); |
| cookies.readFromHeaders(h); |
| } |
| |
| protected InputStream getInputStream() throws IOException { |
| InputStream in; |
| if (getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) { |
| in = connection.getErrorStream(); |
| if (in == null) { |
| try { |
| // just in case - but this will most likely cause an exception |
| in = connection.getInputStream(); |
| } catch (IOException ex) { |
| // ignore |
| } |
| } |
| } else { |
| in = connection.getInputStream(); |
| } |
| return in; |
| } |
| |
| |
| protected void closeInputStream() throws IOException { |
| //try and consume any content so that the connection might be reusable |
| InputStream ins = connection.getErrorStream(); |
| if (ins == null) { |
| ins = connection.getInputStream(); |
| } |
| if (ins != null) { |
| IOUtils.consume(ins); |
| ins.close(); |
| } |
| } |
| protected int getResponseCode() throws IOException { |
| try { |
| return AccessController.doPrivileged(new PrivilegedExceptionAction<Integer>() { |
| |
| @Override |
| public Integer run() throws IOException { |
| return connection.getResponseCode(); |
| } }); |
| } catch (PrivilegedActionException e) { |
| Throwable t = e.getCause(); |
| if (t instanceof IOException) { |
| throw (IOException) t; |
| } |
| throw new RuntimeException(t); |
| } |
| } |
| protected String getResponseMessage() throws IOException { |
| boolean b = MessageUtils.getContextualBoolean(this.outMessage, |
| SET_REASON_PHRASE_NOT_NULL, |
| SET_REASON_PHRASE); |
| if (connection.getResponseMessage() == null && b) { |
| //some http server like tomcat 8.5+ won't return the |
| //reason phrase in response, return a informative value |
| //to tell user no reason phrase in the response instead of null |
| return "no reason phrase in the response"; |
| } |
| return connection.getResponseMessage(); |
| } |
| protected InputStream getPartialResponse() throws IOException { |
| return ChunkedUtil.getPartialResponse(connection, connection.getResponseCode()); |
| } |
| protected boolean usingProxy() { |
| return connection.usingProxy(); |
| } |
| protected void setFixedLengthStreamingMode(int i) { |
| // [CXF-6227] do not call connection.setFixedLengthStreamingMode(i) |
| // to prevent https://bugs.openjdk.java.net/browse/JDK-8044726 |
| } |
| protected void handleNoOutput() throws IOException { |
| if ("POST".equals(getMethod())) { |
| connection.getOutputStream().close(); |
| } |
| } |
| protected void setupNewConnection(String newURL) throws IOException { |
| HTTPClientPolicy cp = getClient(outMessage); |
| Address address; |
| try { |
| if (defaultAddress.getString().equals(newURL)) { |
| address = defaultAddress; |
| } else { |
| address = new Address(newURL); |
| } |
| } catch (URISyntaxException e) { |
| throw new IOException(e); |
| } |
| setupConnection(outMessage, address, cp); |
| this.url = address.getURI(); |
| connection = (HttpURLConnection)outMessage.get(KEY_HTTP_CONNECTION); |
| } |
| |
| @Override |
| protected void retransmitStream() throws IOException { |
| Boolean b = (Boolean)outMessage.get(HTTPURL_CONNECTION_METHOD_REFLECTION); |
| OutputStream out = connectAndGetOutputStream(b); |
| cachedStream.writeCacheTo(out); |
| } |
| } |
| |
| } |