at.spardat.xma.boot.transport.HTTPTransport.java Source code

Java tutorial

Introduction

Here is the source code for at.spardat.xma.boot.transport.HTTPTransport.java

Source

/*******************************************************************************
 * 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 10852 2013-06-28 10:34:13Z 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<String, String> redirectCache = new HashMap<String, String>();

    {
        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("<local>", "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 <code>HTTPTransport</code>.
     *
     */
    public HTTPTransport() {
        if (log_ == null)
            log_ = Logger.getLogger("boot.httpTransport"); //$NON-NLS-1$
    }

    /**
     * Converts a <code>java.util.Date</code> to a String using the encoding specified by
     * the HTTP-Specification. We have to wrap this task, cause we may have to change it. <p>
     * see also SUN-Bug_ID: 4397096 <p>
     *
     * @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. <p>
     * see also SUN-Bug_ID: 4397096 <p>
     *
     * @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<URL> redirectLoopPreventionSet = new HashSet<URL>();

        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.WARNING, 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 < len) {
                    read = serverOut.read(buffer, all, len - all);
                    if (read > -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<byte[]> bufferList = null;
                final int defLen = 1024 * 8;
                int lastLength = 0;

                bufferList = new ArrayList<byte[]>();
                for (; read > -1; all += lastLength) {
                    buffer = new byte[defLen];
                    for (lastLength = 0; read > -1 && lastLength < defLen;) {
                        read = serverOut.read(buffer, lastLength, defLen - lastLength);
                        if (read > -1)
                            lastLength += read;
                    }
                    bufferList.add(buffer);
                }
                byte[] result = new byte[all];
                for (int i = 0; i < bufferList.size() - 1; i++) {
                    System.arraycopy(bufferList.get(i), 0, result, i * defLen, defLen);
                }
                System.arraycopy(bufferList.get(bufferList.size() - 1), 0, result, (bufferList.size() - 1) * defLen,
                        lastLength);
                return result;
            }

        } finally {
            if (serverOut != null)
                serverOut.close();
        }
    }

    /**
     * Get the used port of the url. If no port is defined in the url,
     * the default port of the protocol is returned.
     */
    int getPort(URL url) {
        int port = url.getPort();
        if (port == -1) {
            port = url.getDefaultPort();
        }
        return port;
    }

    /**
     * Return the httpState for the session.
     * 
     * To support multiple sessions override this method. 
     */
    protected HttpState getHttpState(IRtXMASessionClient session) {
        return httpState;
    }

    /**
      * Returns all cookies which are stored for the given url.
     * @deprecated Use getCookies(URL url, IRtXMASessionClient session) instead.
     */
    public Cookie[] getCookies(URL url) {
        return getCookies(url, null);
    }

    /**
      * Returns all cookies which are stored for the given url and session.
      */
    public Cookie[] getCookies(URL url, IRtXMASessionClient session) {
        Cookie[] cookies = getHttpState(session).getCookies();
        if (cookies != null && cookies.length > 0) {
            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 < cookies.length; j++) {
                    if ("JSESSIONID".equals(cookies[j].getName())) {
                        session.setId(cookies[j].getName() + "=" + cookies[j].getValue());
                    }
                }
            }
        }
    }

    /**
     *  Parses the cookies from the given connection and stores them in httpState.
     *  Invalid cookies are ignored and logged.
     */
    private void readCookies(IRtXMASessionClient session, URL url, HttpURLConnection conn) {
        String headerName = "";
        for (int i = 1; headerName != null; i++) {
            headerName = conn.getHeaderFieldKey(i);
            if (Statics.HTTP_SET_COOKIE.equals(headerName)) {
                try {
                    Cookie[] cookies = cookieSpec.parse(url.getHost(), getPort(url), url.getPath(),
                            "https".equals(url.getProtocol()), conn.getHeaderField(i));
                    if (cookies != null) {
                        for (int j = 0; j < cookies.length; j++) {
                            try {
                                cookieSpec.validate(url.getHost(), getPort(url), url.getPath(),
                                        "https".equals(url.getProtocol()), cookies[j]);
                                getHttpState(session).addCookie(cookies[j]);
                                if (session != null && "JSESSIONID".equals(cookies[j].getName())) {
                                    session.setId(cookies[j].getName() + "=" + cookies[j].getValue());
                                }
                            } catch (MalformedCookieException e) {
                                log_.log(LogLevel.WARNING, "cookie rejected: \""
                                        + cookieSpec.formatCookie(cookies[j]) + "\". " + e.getMessage());
                            }
                        }
                    }
                } catch (MalformedCookieException e) {
                    log_.log(LogLevel.WARNING,
                            "Invalid cookie header: \"" + conn.getHeaderField(i) + "\". " + e.getMessage());
                }
            }
        }
    }

}