Java tutorial
/******************************************************************************* * Copyright (c) 2013-2014 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package com.openshift.internal.restclient.http; import java.io.IOException; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.ProtocolException; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.MessageFormat; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.openshift.restclient.ISSLCertificateCallback; import com.openshift.restclient.authorization.IAuthorizationStrategy; import com.openshift.restclient.authorization.URLConnectionRequest; import com.openshift.restclient.http.IHttpClient; import com.openshift.restclient.http.IHttpConstants; import com.openshift.restclient.model.IResource; import com.openshift.restclient.utils.SSLUtils; /** * @author Andre Dietisheim * @author Nicolas Spano * @author Corey Daley * @author Sean Kavanagh */ public class UrlConnectionHttpClient implements IHttpClient { private static final Logger LOGGER = LoggerFactory.getLogger(UrlConnectionHttpClient.class); protected String userAgent; protected String acceptedMediaType; protected String acceptedVersion; protected ISSLCertificateCallback sslAuthorizationCallback; protected Integer configTimeout; private String excludedSSLCipherRegex; private IAuthorizationStrategy authStrategy; public UrlConnectionHttpClient(String userAgent, String acceptedMediaType, String version) { this(userAgent, acceptedMediaType, version, null, null, null); } public UrlConnectionHttpClient(String userAgent, String acceptedMediaType, String version, ISSLCertificateCallback callback, Integer configTimeout, String excludedSSLCipherRegex) { this.userAgent = userAgent; this.acceptedMediaType = acceptedMediaType; this.acceptedVersion = version; this.sslAuthorizationCallback = callback; this.configTimeout = configTimeout; this.excludedSSLCipherRegex = excludedSSLCipherRegex; } @Override public void setSSLCertificateCallback(ISSLCertificateCallback callback) { this.sslAuthorizationCallback = callback; } @Override public ISSLCertificateCallback getSSLCertificateCallback() { return sslAuthorizationCallback; } @Override public void setAuthorizationStrategy(IAuthorizationStrategy strategy) { this.authStrategy = strategy; } @Override public String get(URL url, int timeout) throws HttpClientException, SocketTimeoutException { return request(HttpMethod.GET, url, null, timeout); } @Override public String head(URL url, int timeout) throws HttpClientException, SocketTimeoutException { return request(HttpMethod.HEAD, url, null, timeout); } public String put(URL url, IMediaType mediaType, int timeout, Parameter... parameters) throws HttpClientException, SocketTimeoutException, EncodingException { return request(HttpMethod.PUT, url, mediaType, timeout, parameters); } @Override public String put(URL url, int timeout, IResource resource) throws HttpClientException, SocketTimeoutException, EncodingException { return request(HttpMethod.PUT, url, timeout, resource); } @Override public String post(URL url, int timeout, IResource resource) throws HttpClientException, SocketTimeoutException, EncodingException { return request(HttpMethod.POST, url, timeout, resource); } public String delete(URL url, IMediaType mediaType, int timeout, Parameter... parameters) throws HttpClientException, SocketTimeoutException, EncodingException { return request(HttpMethod.DELETE, url, mediaType, timeout, parameters); } @Override public String delete(URL url, int timeout) throws HttpClientException, SocketTimeoutException, EncodingException { return delete(url, null, timeout); } protected String request(HttpMethod httpMethod, URL url, IMediaType requestMediaType, int timeout, Parameter... parameters) throws SocketTimeoutException, HttpClientException { return request(httpMethod, url, requestMediaType, timeout, new ParameterValueMap(parameters)); } protected String request(HttpMethod httpMethod, URL url, IMediaType requestMediaType, int timeout, ParameterValueMap parameters) throws SocketTimeoutException, HttpClientException { HttpURLConnection connection = null; try { connection = createConnection(url, userAgent, acceptedVersion, acceptedMediaType, sslAuthorizationCallback, timeout); if (httpMethod == HttpMethod.POST || httpMethod == HttpMethod.PUT) { setContentTypeHeader(acceptedVersion, acceptedMediaType, connection); } // PATCH not yet supported by JVM setRequestMethod(httpMethod, connection); if (!parameters.isEmpty()) { connection.setDoOutput(true); setRequestMediaType(requestMediaType, connection); requestMediaType.writeTo(parameters, connection.getOutputStream()); } return IOUtils.toString(connection.getInputStream(), "UTF-8"); } catch (SocketTimeoutException e) { throw e; } catch (IOException e) { throw createException(e, connection); } finally { disconnect(connection); } } protected String request(HttpMethod httpMethod, URL url, int timeout, IResource resource) throws SocketTimeoutException, HttpClientException { HttpURLConnection connection = null; try { connection = createConnection(url, userAgent, acceptedVersion, acceptedMediaType, sslAuthorizationCallback, timeout); if (httpMethod == HttpMethod.POST || httpMethod == HttpMethod.PUT) { setContentTypeHeader(acceptedVersion, acceptedMediaType, connection); } // PATCH not yet supported by JVM setRequestMethod(httpMethod, connection); if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("Request Properties: %s", connection.getRequestProperties())); LOGGER.debug(String.format("Request Method: %s", connection.getRequestMethod())); } if (resource != null) { if (LOGGER.isDebugEnabled()) LOGGER.debug(resource.toJson(false)); connection.setDoOutput(true); PrintWriter writer = new PrintWriter(connection.getOutputStream()); writer.write(resource.toString()); writer.flush(); } return IOUtils.toString(connection.getInputStream(), "UTF-8"); } catch (SocketTimeoutException e) { throw e; } catch (IOException e) { throw createException(e, connection); } finally { disconnect(connection); } } private void setRequestMethod(HttpMethod httpMethod, HttpURLConnection connection) throws ProtocolException { if (httpMethod == HttpMethod.PATCH) { httpMethod = HttpMethod.POST; connection.setRequestProperty("X-Http-Method-Override", "PATCH"); } connection.setRequestMethod(httpMethod.toString()); } private void disconnect(HttpURLConnection connection) { if (connection != null) { connection.disconnect(); } } private HttpClientException createException(IOException ioe, HttpURLConnection connection) throws SocketTimeoutException { try { int responseCode = connection.getResponseCode(); String errorMessage = createErrorMessage(ioe, connection); switch (responseCode) { case IHttpConstants.STATUS_INTERNAL_SERVER_ERROR: return new InternalServerErrorException(errorMessage, ioe); case IHttpConstants.STATUS_BAD_REQUEST: return new BadRequestException(errorMessage, ioe); case IHttpConstants.STATUS_UNAUTHORIZED: return new UnauthorizedException(errorMessage, ioe); case IHttpConstants.STATUS_NOT_FOUND: return new NotFoundException(errorMessage, ioe); default: return new HttpClientException(errorMessage, ioe, responseCode); } } catch (SocketTimeoutException e) { throw e; } catch (IOException e) { return new HttpClientException(e); } } protected String createErrorMessage(IOException ioe, HttpURLConnection connection) throws IOException { String errorMessage = IOUtils.toString(connection.getErrorStream()); if (!StringUtils.isEmpty(errorMessage)) { return errorMessage; } StringBuilder builder = new StringBuilder("Connection to ").append(connection.getURL()); String reason = connection.getResponseMessage(); if (!StringUtils.isEmpty(reason)) { builder.append(": ").append(reason); } return builder.toString(); } private boolean isHttps(URL url) { return "https".equals(url.getProtocol()); } protected HttpURLConnection createConnection(URL url, String userAgent, String acceptedVersion, String acceptedMediaType, ISSLCertificateCallback callback, int timeout) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); if (isHttps(url)) { HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; SSLContext sslContext = setSSLCallback(sslAuthorizationCallback, url, httpsConnection); setFilteredCiphers(excludedSSLCipherRegex, sslContext, httpsConnection); } setAuthorization(connection); connection.setUseCaches(false); connection.setDoInput(true); connection.setAllowUserInteraction(false); setConnectTimeout(NO_TIMEOUT, connection); setReadTimeout(timeout, connection); // wont work when switching http->https // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 connection.setInstanceFollowRedirects(true); setUserAgent(userAgent, connection); setAcceptHeader(acceptedVersion, acceptedMediaType, connection); return connection; } private void setUserAgent(String userAgent, HttpURLConnection connection) { if (!StringUtils.isEmpty(userAgent)) { connection.setRequestProperty(IHttpConstants.PROPERTY_USER_AGENT, userAgent); } } private void setAcceptHeader(String acceptedVersion, String acceptedMediaType, HttpURLConnection connection) { if (StringUtils.isEmpty(acceptedMediaType)) { throw new HttpClientException(MessageFormat.format("Accepted media type (ex. {0}) is not defined", IHttpConstants.MEDIATYPE_APPLICATION_JSON)); } StringBuilder builder = new StringBuilder(acceptedMediaType); if (acceptedVersion != null) { builder.append(IHttpConstants.SEMICOLON).append(IHttpConstants.SPACE).append(IHttpConstants.VERSION) .append(IHttpConstants.EQUALS).append(acceptedVersion); } connection.setRequestProperty(IHttpConstants.PROPERTY_ACCEPT, builder.toString()); } private void setContentTypeHeader(String version, String mediaType, HttpURLConnection connection) { if (StringUtils.isEmpty(mediaType)) { throw new HttpClientException(MessageFormat.format("Accepted media type (ex. {0}) is not defined", IHttpConstants.MEDIATYPE_APPLICATION_JSON)); } StringBuilder builder = new StringBuilder(mediaType); if (acceptedVersion != null) { builder.append(IHttpConstants.SEMICOLON).append(IHttpConstants.SPACE).append(IHttpConstants.VERSION) .append(IHttpConstants.EQUALS).append(version); } connection.setRequestProperty(IHttpConstants.PROPERTY_CONTENT_TYPE, builder.toString()); } protected final void setAuthorization(HttpURLConnection connection) { if (authStrategy != null) { authStrategy.authorize(new URLConnectionRequest(connection)); return; } } private SSLContext setSSLCallback(ISSLCertificateCallback sslAuthorizationCallback, URL url, HttpsURLConnection connection) { X509TrustManager trustManager = null; if (sslAuthorizationCallback != null) { connection.setHostnameVerifier(new CallbackHostnameVerifier()); trustManager = createCallbackTrustManager(sslAuthorizationCallback, connection); } try { SSLContext sslContext = SSLUtils.getSSLContext(trustManager); connection.setSSLSocketFactory(sslContext.getSocketFactory()); return sslContext; } catch (GeneralSecurityException e) { LOGGER.warn("Could not install trust manager callback", e); ; return null; } } /** * Returns the callback trustmanager or <code>null</code> if it could not be created. * * @see ISSLCertificateCallback */ private X509TrustManager createCallbackTrustManager(ISSLCertificateCallback sslAuthorizationCallback, HttpsURLConnection connection) { X509TrustManager trustManager = null; try { trustManager = getCurrentTrustManager(); if (trustManager == null) { LOGGER.warn("Could not install trust manager callback, no trustmanager was found.", trustManager); } else { trustManager = new CallbackTrustManager(trustManager, sslAuthorizationCallback); } } catch (GeneralSecurityException e) { LOGGER.warn("Could not install trust manager callback.", e); ; } return trustManager; } /** * Sets a ssl socket factory that sets a filtered list of ciphers based on * the #excludedSSLCipherRegex to the given connection. * * @param sslContext * * @param sslContext * the ssl context that shall be used * @param url * the url we are connecting to * @param connection * the connection that the cipher filter shall be applied to */ protected SSLContext setFilteredCiphers(String excludedSSLCipherRegex, SSLContext sslContext, HttpsURLConnection connection) { if (excludedSSLCipherRegex != null) { connection.setSSLSocketFactory(new EnabledCiphersSSLSocketFactory( SSLUtils.filterCiphers(excludedSSLCipherRegex, getSupportedCiphers(sslContext)), sslContext.getSocketFactory())); } return sslContext; } protected String[] getSupportedCiphers(SSLContext sslContext) { return sslContext.getSupportedSSLParameters().getCipherSuites(); } private void setConnectTimeout(int timeout, URLConnection connection) { if (getTimeout(timeout) != NO_TIMEOUT) { connection.setConnectTimeout(getTimeout(timeout)); } } private void setReadTimeout(int timeout, URLConnection connection) { if (getTimeout(timeout) != NO_TIMEOUT) { connection.setReadTimeout(getTimeout(timeout)); } } private int getTimeout(int timeout) { if (timeout == NO_TIMEOUT) { if (configTimeout != null) { timeout = this.configTimeout; } } return timeout; } private void setRequestMediaType(IMediaType mediaType, HttpURLConnection connection) { if (mediaType == null || StringUtils.isEmpty(mediaType.getType())) { throw new HttpClientException(MessageFormat.format("Request media type (ex. {0}) is not defined", IHttpConstants.MEDIATYPE_APPLICATION_FORMURLENCODED)); } connection.setRequestProperty(IHttpConstants.PROPERTY_CONTENT_TYPE, mediaType.getType()); } private X509TrustManager getCurrentTrustManager() throws NoSuchAlgorithmException, KeyStoreException { TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); X509TrustManager x509TrustManager = null; for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { if (trustManager instanceof X509TrustManager) { x509TrustManager = (X509TrustManager) trustManager; break; } } return x509TrustManager; } @Override public void setUserAgent(String userAgent) { this.userAgent = userAgent; } @Override public void setAcceptVersion(String version) { this.acceptedVersion = version; } @Override public void setAcceptedMediaType(String acceptedMediaType) { this.acceptedMediaType = acceptedMediaType; } public class CallbackTrustManager implements X509TrustManager { private X509TrustManager trustManager; private ISSLCertificateCallback callback; private CallbackTrustManager(X509TrustManager currentTrustManager, ISSLCertificateCallback callback) throws NoSuchAlgorithmException, KeyStoreException { this.trustManager = currentTrustManager; this.callback = callback; } public X509Certificate[] getAcceptedIssuers() { return trustManager.getAcceptedIssuers(); } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { trustManager.checkServerTrusted(chain, authType); } catch (CertificateException e) { if (!callback.allowCertificate(chain)) { throw e; } } } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkServerTrusted(chain, authType); } } private class CallbackHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return sslAuthorizationCallback.allowHostname(hostname, session); } } /** * SSL socket factory that wraps a given socket factory and sets given ciphers * to the socket that the wrapped factory creates. * * @see http://stackoverflow.com/questions/6851461/java-why-does-ssl-handshake-give-could-not-generate-dh-keypair-exception/16686994#16686994 */ private static class EnabledCiphersSSLSocketFactory extends SSLSocketFactory { private String[] enabledCiphers; private SSLSocketFactory socketFactory; EnabledCiphersSSLSocketFactory(String[] enabledCiphers, SSLSocketFactory socketFactory) { this.enabledCiphers = enabledCiphers; this.socketFactory = socketFactory; } @Override public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException { return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port)); } @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port)); } @Override public String[] getSupportedCipherSuites() { if (enabledCiphers == null) { return socketFactory.getSupportedCipherSuites(); } else { return enabledCiphers; } } @Override public String[] getDefaultCipherSuites() { return socketFactory.getDefaultCipherSuites(); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { return setEnabledCiphers((SSLSocket) socketFactory.createSocket(socket, host, port, autoClose)); } private SSLSocket setEnabledCiphers(SSLSocket socket) { if (enabledCiphers == null) { return socket; } socket.setEnabledCipherSuites(enabledCiphers); return socket; } } }