at.spardat.xma.boot.transport.HTTPTransport Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
* All rights reserved. This program and the accompanying materials
* are 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:
* s IT Solutions AT Spardat GmbH - initial API and implementation
*******************************************************************************/
/*
* Created on 16.05.2003
*/
package at.spardat.xma.boot.transport;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.SimpleTimeZone;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.cookie.CookieSpec;
import org.apache.commons.httpclient.cookie.CookieSpecBase;
import org.apache.commons.httpclient.cookie.MalformedCookieException;
import at.spardat.xma.boot.BootRuntime;
import at.spardat.xma.boot.Statics;
import at.spardat.xma.boot.comp.CCLoader;
import at.spardat.xma.boot.component.IRtXMASessionClient;
import at.spardat.xma.boot.logger.LogLevel;
import at.spardat.xma.boot.logger.Logger;
/**
* This class implements low level transport on http transport using suns UrlConnection implementation.
*
* @author s2877, s3595
* @version $Id: HTTPTransport.java 10965 2013-08-22 11:23:48Z dschwarz $
*
*/
public class HTTPTransport extends Transport {
/** used to format if-modified-since correctly */
private static DateFormat httpdate_;
private static Logger log_;
private static HostnameVerifier hostnameVerifier;
private static CookieSpec cookieSpec;
private static HttpState httpState = new HttpState();
private HashMap redirectCache = new HashMap();
{
httpdate_ = new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US ); //$NON-NLS-1$
httpdate_.setTimeZone(new SimpleTimeZone(0, "GMT")); //$NON-NLS-1$
}
/**
* Initializes the underlaying http-protocol-provider from the given Propterties.
* This sets the tcp-timeouts, the proxy-settings and ssl-settings.
* @param prop containing properties for http and https protocol
*/
public static void init( Properties prop ){
log_ = Logger.getLogger( "boot.transport.http" ); //$NON-NLS-1$
// proxy properties
String strProxyEnable = prop.getProperty( Statics.CFG_PROP_PROXYENABLE );
if( strProxyEnable != null && Boolean.valueOf(strProxyEnable).booleanValue()) {
String strProxyServer = prop.getProperty( Statics.CFG_PROP_PROXYSERVER );
String strProxyPort = prop.getProperty( Statics.CFG_PROP_PROXYPORT );
if( strProxyServer!=null && strProxyPort!=null ) {
System.setProperty( "proxySet", "true" );
System.setProperty( "http.proxyHost", strProxyServer );
System.setProperty( "http.proxyPort", strProxyPort );
log_.log(LogLevel.FINE, "transport proxy is: {0}:{1}", new Object[] { strProxyServer, strProxyPort } );
}
String strSecureProxyServer = prop.getProperty( Statics.CFG_PROP_SECUREPROXYSERVER );
String strSecureProxyPort = prop.getProperty( Statics.CFG_PROP_SECUREPROXYPORT );
if(strSecureProxyPort!=null&&strSecureProxyServer!=null) {
System.setProperty("https.proxyHost",strSecureProxyServer);
System.setProperty("https.proxyPort",strSecureProxyPort);
log_.log(LogLevel.FINE, "secure transport proxy is: {0}:{1}", new Object[] { strSecureProxyServer, strSecureProxyPort } );
}
String strProxyOverride = prop.getProperty( Statics.CFG_PROP_PROXYOVERRIDE );
if(strProxyOverride!=null) {
strProxyOverride = strProxyOverride.replace(';','|'); // documented delimiter for IE
strProxyOverride = strProxyOverride.replace(',','|'); // IE supports ',' as delimiter, too
strProxyOverride = strProxyOverride.replace(' ','|'); // IE supports blank as delimiter, too
strProxyOverride = strProxyOverride.replace('\t','|'); // IE supports tab as delimiter, too
strProxyOverride = strProxyOverride.replace('\r','|'); // IE supports carriage return as delimiter, too
strProxyOverride = strProxyOverride.replace('\n','|'); // IE supports newline as delimiter, too
strProxyOverride = strProxyOverride.replaceAll("","localhost|127.0.0.1");
System.setProperty("http.nonProxyHosts",strProxyOverride);
log_.log(LogLevel.FINE, "proxy not used for: {0}", strProxyOverride );
}
} else {
log_.log(LogLevel.FINE, "no transport proxy is used" );
}
// timeout properties
String strConnectTimeout = prop.getProperty(Statics.CFG_PROP_CONNECTTIMEOUT);
if(strConnectTimeout!=null) {
System.setProperty("sun.net.client.defaultConnectTimeout",strConnectTimeout);
log_.log(LogLevel.FINE, "http connect timeout: "+strConnectTimeout+" milliseconds");
}
String strReadTimeout = prop.getProperty(Statics.CFG_PROP_READTIMEOUT);
if(strReadTimeout!=null) {
System.setProperty("sun.net.client.defaultReadTimeout",strReadTimeout);
log_.log(LogLevel.FINE, "http read timeout: "+strReadTimeout+" milliseconds");
}
// ssl properties
String strTrustStore = prop.getProperty(Statics.CFG_PROP_SECURECERTS);
if(strTrustStore!=null) {
log_.log(LogLevel.FINE,"using trusted certificates file "+strTrustStore);
File trustFile = new File(strTrustStore);
if(!trustFile.exists()) {
log_.log(LogLevel.SEVERE,"trusted certificates file '"+trustFile.getAbsolutePath()+"' not found");
}
System.setProperty("javax.net.ssl.trustStore",strTrustStore);
}
hostnameVerifier = new HostnameVerifierImpl(prop.getProperty(Statics.CFG_PROP_HOSTNAMEVERIFYIGNORE));
// cookie handling policy
Class> cookiePolicyClass = null;
String strCookiePolicy = prop.getProperty(Statics.CFG_PROP_COOKIEPOLICY);
if(strCookiePolicy!=null) {
try {
cookiePolicyClass = Class.forName(strCookiePolicy);
} catch (ClassNotFoundException e) {
log_.log(LogLevel.WARNING,"configured cookiePolicy '"+strCookiePolicy+"' not found, using default");
}
}
if(cookiePolicyClass==null) {
cookiePolicyClass = CookieSpecBase.class;
}
log_.log(LogLevel.FINE,"using cookiePolicy "+cookiePolicyClass.getName());
CookiePolicy.registerCookieSpec(CookiePolicy.DEFAULT, cookiePolicyClass);
cookieSpec=CookiePolicy.getDefaultSpec();
}
/**
* logs the proxy settings at the given level.
*/
public void logProxyInfo(LogLevel level) {
StringBuffer result = new StringBuffer();
String proxyHost = System.getProperty("http.proxyHost");
if(proxyHost!=null) {
result.append("http proxy is: "+proxyHost);
String proxyPort = System.getProperty("http.proxyPort");
if(proxyPort!=null) {
result.append(":"+proxyPort);
}
log_.log(level,result.toString());
} else {
log_.log(level,"no http proxy used");
}
result = new StringBuffer();
proxyHost = System.getProperty("https.proxyHost");
if(proxyHost!=null) {
result.append("https proxy is: "+proxyHost);
String proxyPort = System.getProperty("https.proxyPort");
if(proxyPort!=null) {
result.append(":"+proxyPort);
}
log_.log(level,result.toString());
} else {
log_.log(level,"no https proxy used");
}
String nonProxyHosts = System.getProperty("http.nonProxyHosts");
if(nonProxyHosts!=null) {
log_.log(level,"no proxy used for: "+nonProxyHosts);
}
}
/**
* Constructs a HTTPTransport
.
*
*/
public HTTPTransport() {
if( log_==null)
log_ = Logger.getLogger( "boot.httpTransport" ); //$NON-NLS-1$
}
/**
* Converts a java.util.Date
to a String using the encoding specified by
* the HTTP-Specification. We have to wrap this task, cause we may have to change it.
* see also SUN-Bug_ID: 4397096
*
* @param date the date to be formated
* @return String the formated representation
*/
public static String httpDate(Date date) {
return httpdate_.format(date);
}
/**
* Converts a long containing a date to a String using the encoding specified by
* the HTTP-Specification. We have to wrap this task, cause we may have to change it.
* see also SUN-Bug_ID: 4397096
*
* @param ldate the milliseconds since January 1, 1970, 00:00:00 GMT.
* @return String the formated representation
*/
public static String httpDate(long ldate ) {
return httpdate_.format( new Date(ldate));
}
/* (non-Javadoc)
* @see at.spardat.xma.transport.Transport#getResource(at.spardat.xma.transport.XMA_URI, long)
*/
public Result getResource(final IRtXMASessionClient session, final XMA_URI resource, final long modifiedSince, final String etag) throws CommunicationException {
CCLoader swtCLoader = BootRuntime.getInstance().getAppManager().getSWTClassLoader();
if(swtCLoader!=null) {
class Inner implements Runnable {
Result result;
CommunicationException exc;
public void run() {
try {
result = getResourceImpl(session,resource,modifiedSince,etag);
} catch(CommunicationException exc) {
this.exc=exc;
}
}
}
Inner inner = new Inner();
try { // call BusyIndicator.showWhile over reflection from the SWT-Classloader
Class> busyIndicatorClass = swtCLoader.loadClass("org.eclipse.swt.custom.BusyIndicator");
Class> displayClass = swtCLoader.loadClass("org.eclipse.swt.widgets.Display");
Method method = busyIndicatorClass.getMethod("showWhile",new Class[]{displayClass,Runnable.class});
method.invoke(null,new Object[]{null,inner});
} catch (Exception exc) {
throw new RuntimeException(exc);
}
if(inner.exc!=null) throw inner.exc;
return inner.result;
} else {
return getResourceImpl(session,resource,modifiedSince,etag);
}
}
/* (non-Javadoc)
* @see at.spardat.xma.boot.transport.Transport#getRedirection(at.spardat.xma.boot.transport.XMA_URI)
*/
@Override
public XMA_URI getRedirection(XMA_URI resource) {
String resourceHostApp = resource.getHostApp();
String translated = redirectCache.get(resourceHostApp);
if (translated != null) {
String newUrl = resource.toString().replace(resourceHostApp, translated);
try {
return new XMA_URI(newUrl);
} catch (MalformedURLException e) {
log_.info("Can't translate URL: " + newUrl + ": " + e.toString());
}
}
return null;
}
private Object callRedirectAware(XMA_URI resource, RedirectCallback callback) throws CommunicationException {
Set redirectLoopPreventionSet = new HashSet();
URL url = resource.getHTTP_URI();
String initialResourceHostApp = resource.getHostApp();
do {
XMA_URI redirectedResource = getRedirection(resource);
if (redirectedResource != null) {
url = redirectedResource.getHTTP_URI();
log_.log(LogLevel.FINE, "Using redirect cache: " + resource + " -> " + redirectedResource);
}
redirectLoopPreventionSet.add(url);
try {
return callback.call(url);
} catch (RedirectException re) {
log_.log(LogLevel.INFO, re.getMessage());
try {
url = new URL(re.getLocation());
String resourceHostApp = resource.getHostApp();
resource = new XMA_URI(url);
String newHostApp = resource.getHostApp();
if (!resourceHostApp.equals(newHostApp)) {
redirectCache.put(resourceHostApp, newHostApp);
// be aware of multiple redirects
redirectCache.put(initialResourceHostApp, newHostApp);
log_.log(LogLevel.FINE, "Adding redirect cache: " + resourceHostApp + " -> " + newHostApp);
}
} catch (MalformedURLException e) {
throw new ServerException("Illegal HTTP redirect location: " + re.getLocation(), re, re.getReturnCode());
}
}
} while (!redirectLoopPreventionSet.contains(url) && redirectLoopPreventionSet.size() < 5);
throw new ServerException("HTTP redirect loop detected at " + url);
}
public Result getResourceImpl(final IRtXMASessionClient session, XMA_URI resource, final long modifiedSince, final String etag) throws CommunicationException {
RedirectCallback callback = new RedirectCallback() {
public Object call(URL url) throws CommunicationException {
return getResourceImpl(session, url, modifiedSince, etag);
}
};
return (Result) callRedirectAware(resource, callback);
}
private Result getResourceImpl(IRtXMASessionClient session, URL url, long modifiedSince, String etag) throws CommunicationException {
/* locals ---------------------------------- */
Result result = new Result();
int code = 0;
HttpURLConnection conn;
/* locals ---------------------------------- */
try {
conn = (HttpURLConnection) url.openConnection();
if(conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setHostnameVerifier(hostnameVerifier);
}
sendCookies(session,url,conn);
if( etag != null ){
conn.setRequestProperty("If-None-Match", etag); //$NON-NLS-1$
}
String strUrl = url.toExternalForm();
if( url.getQuery()==null && ( strUrl.endsWith( ".jar") || strUrl.endsWith( ".xml")) ) {
conn.setRequestProperty(Statics.HTTP_CACHE_CONTROL, Statics.HTTP_MAX_AGE + "=0"); //$NON-NLS-1$
}
if(modifiedSince>0) {
// see sun bugid: 4397096
// if HTTP_Util library is used, the original method may also be used.
// conn.setIfModifiedSince(modifiedSince);
conn.setRequestProperty( Statics.strIfModifiedSince, HTTPTransport.httpDate(modifiedSince));
}
conn.setRequestProperty( Statics.HTTP_ACCEPT,"*/*"); //$NON-NLS-1$
conn.setRequestProperty( Statics.HTTP_USER_AGENT, Statics.HTTP_USER_AGENT_NAME);
} catch (IOException exc) {
log_.log(LogLevel.WARNING, "error loading '"+url.toString()+"' form server:", exc); //$NON-NLS-1$
throw new ConnectException("error loading '"+url.toString()+"' form server:",exc);
}
try {
code = conn.getResponseCode();
if(code==HttpURLConnection.HTTP_NOT_MODIFIED) {
result.contentLength_ = 0;
result.lastModified_ = conn.getLastModified();
if(result.lastModified_<=0) {
result.lastModified_ = modifiedSince;
}
result.expirationDate_ = conn.getExpiration();
result.etag_ = conn.getHeaderField( Statics.strEtag );
if(result.etag_==null) {
result.etag_ = etag;
}
log_.log(LogLevel.FINE, "resource not modified: {0}", url.toExternalForm()); //$NON-NLS-1$
} else if(code==HttpURLConnection.HTTP_OK){
result.contentLength_ = conn.getContentLength();
result.lastModified_ = conn.getLastModified();
result.expirationDate_ = conn.getExpiration();
result.etag_ = conn.getHeaderField( Statics.strEtag );
result.transformations_= conn.getHeaderField(Statics.TRANSFORM_HEADER);
result.setBuffer( this.readOutput(conn) );
if(result.contentLength_<0) {
result.contentLength_=result.buffer_.length;
}
} else if (code==HttpURLConnection.HTTP_MOVED_TEMP || code==HttpURLConnection.HTTP_MOVED_PERM) {
String location = conn.getHeaderField(Statics.HTTP_LOCATION);
throw new RedirectException("redirect received from " + url.toString() + " to " + location, code, location);
} else {
if(code<500) throw new ConnectException("error loading '"+url.toString()+"' from the server:",code);
else throw new ServerException("error loading '"+url.toString()+"' from the server:",code);
}
readCookies(session,url,conn);
} catch (RedirectException re) {
throw re;
} catch (CommunicationException ce) {
if(code!=0) log_.log(LogLevel.WARNING,"http returncode: {0}",Integer.toString(code)); //$NON-NLS-1$
log_.log(LogLevel.WARNING, "error loading '"+url.toString()+"' from the server:", ce); //$NON-NLS-1$
throw ce;
} catch (Exception ex) {
if(code!=0) log_.log(LogLevel.WARNING,"http returncode: {0}",Integer.toString(code)); //$NON-NLS-1$
log_.log(LogLevel.WARNING,"error loading '"+url.toString()+"' from the server:", ex); //$NON-NLS-1$
if(code<500) throw new ConnectException("error loading '"+url.toString()+"' from the server:",ex);
else throw new ServerException("error loading '"+url.toString()+"' from the server:",ex);
}
return result;
}
public byte[] callServerEvent(final IRtXMASessionClient session,final XMA_URI eventHandler,final byte[] input) throws CommunicationException {
return callServerEvent(session, eventHandler, input, false);
}
/* (non-Javadoc)
* @see at.spardat.xma.transport.Transport#callServerEvent(at.spardat.xma.session.XMASessionClient, at.spardat.xma.transport.XMA_URI, java.io.InputStream)
*/
public byte[] callServerEvent(final IRtXMASessionClient session,final XMA_URI eventHandler,final byte[] input, final boolean handleRedirect) throws CommunicationException {
CCLoader swtCLoader = BootRuntime.getInstance().getAppManager().getSWTClassLoader();
if(swtCLoader!=null) {
class Inner implements Runnable {
byte[] result;
CommunicationException exc;
public void run() {
try {
result = callServerEventImpl(session,eventHandler,input, handleRedirect);
} catch(CommunicationException exc) {
this.exc=exc;
}
}
}
Inner inner = new Inner();
try { // call BusyIndicator.showWhile over reflection from the SWT-Classloader
Class> busyIndicatorClass = swtCLoader.loadClass("org.eclipse.swt.custom.BusyIndicator");
Class> displayClass = swtCLoader.loadClass("org.eclipse.swt.widgets.Display");
Method method = busyIndicatorClass.getMethod("showWhile",new Class[]{displayClass,Runnable.class});
method.invoke(null,new Object[]{null,inner});
} catch (Exception exc) {
throw new RuntimeException(exc);
}
if(inner.exc!=null) throw inner.exc;
return inner.result;
} else {
return callServerEventImpl(session,eventHandler,input, handleRedirect);
}
}
private byte[] callServerEventImpl(final IRtXMASessionClient session, XMA_URI eventHandler, final byte[] input, boolean handleRedirect) throws CommunicationException {
if (handleRedirect) {
RedirectCallback callback = new RedirectCallback() {
public Object call(URL url) throws CommunicationException {
return callServerEventImpl(session, url, input, true);
}
};
return (byte[]) callRedirectAware(eventHandler, callback);
} else {
return callServerEventImpl(session, eventHandler.getHTTP_URI(), input, false);
}
}
private byte[] callServerEventImpl(IRtXMASessionClient session, URL url, byte[] input, boolean handleRedirect) throws CommunicationException {
OutputStream serverIn;
int code = 0;
HttpURLConnection conn;
byte[] buffer = null;
try {
conn = (HttpURLConnection) url.openConnection();
if(conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setHostnameVerifier(hostnameVerifier);
}
conn.setDoOutput(true);
conn.setRequestMethod("POST"); //$NON-NLS-1$
sendCookies(session,url,conn);
conn.setRequestProperty( Statics.HTTP_CONTENT_TYPE,"application/octet-stream"); //$NON-NLS-1$
conn.setRequestProperty( Statics.HTTP_ACCEPT,"application/octet-stream"); //$NON-NLS-1$
conn.setRequestProperty( Statics.HTTP_USER_AGENT , Statics.HTTP_USER_AGENT_NAME );
serverIn = conn.getOutputStream();
} catch (IOException exc) {
log_.log(LogLevel.WARNING,"error calling '"+url.toString()+"' at the server:", exc); //$NON-NLS-1$
throw new ConnectException("error calling '"+url.toString()+"' at the server:",exc);
}
try {
serverIn.write(input);
serverIn.close();
code = conn.getResponseCode();
// if requested, we allow redirect also on POST requests, therewith violating RFC 2616 section 10.3!
if (handleRedirect && code==HttpURLConnection.HTTP_MOVED_TEMP || code==HttpURLConnection.HTTP_MOVED_PERM) {
String location = conn.getHeaderField(Statics.HTTP_LOCATION);
throw new RedirectException("redirect received from " + url.toString() + " to " + location, code, location);
}
buffer = this.readOutput(conn);
readCookies(session,url,conn);
return buffer;
} catch (RedirectException re) {
throw re;
} catch (CommunicationException ce) {
if(code!=0) log_.log(LogLevel.WARNING,"http returncode: {0}",Integer.toString(code)); //$NON-NLS-1$
log_.log(LogLevel.WARNING, "error calling '"+url.toString()+"' at the server:", ce); //$NON-NLS-1$
throw ce;
} catch (Exception ex) {
if(code!=0) log_.log(LogLevel.WARNING,"http returncode: {0}",Integer.toString(code)); //$NON-NLS-1$
log_.log(LogLevel.WARNING,"error calling '"+url.toString()+"' at the server:", ex); //$NON-NLS-1$
if(code<500) throw new ConnectException("error calling '"+url.toString()+"' at the server:",ex);
else throw new ServerException("error calling '"+url.toString()+"' at the server:",ex);
}
}
/**
* get server output into a buffer
*
* @param conn connection to read from
* @return byte[] server output
* @throws ServerException content lenght errro
* @throws IOException for read erros
*/
private byte[] readOutput( HttpURLConnection conn ) throws IOException {
InputStream serverOut = null;
byte[] buffer = null;
try{
serverOut = conn.getInputStream();
int len = conn.getContentLength();
int read = 0;
int all = 0;
if(len>-1) {
buffer = new byte[len];
while(read>-1&&all-1) all+=read;
}
if(read<0) {
throw new ServerException("Server reported contentLength "+len+" but send only "+all+" bytes of data");
}
return buffer;
} else {
// if no content length was send, use default size and grow dynamically
List bufferList = null;
final int defLen = 1024*8;
int lastLength = 0;
bufferList = new ArrayList();
for(;read>-1;all+=lastLength) {
buffer = new byte[defLen];
for(lastLength=0;read>-1&&lastLength-1) lastLength+=read;
}
bufferList.add(buffer);
}
byte [] result = new byte[all];
for(int i=0;i0) {
cookies = cookieSpec.match(url.getHost(),getPort(url),url.getPath(),"https".equals(url.getProtocol()),cookies);
}
return cookies;
}
/**
* Takes the cookies from the httpState which match the given url and creates the
* corresponsing cookie-header on the given connection.
*/
private void sendCookies(IRtXMASessionClient session, URL url,HttpURLConnection conn) {
Cookie[] cookies = getCookies(url,session);
if(cookies!=null&&cookies.length>0) {
String cookieHeader = cookieSpec.formatCookies(cookies);
conn.setRequestProperty(Statics.HTTP_COOKIE,cookieHeader);
if(session!=null && session.getId()==null) { // server side http session was established before client side session was created
for(int j=0;j