phex.bootstrap.GWebCacheConnection.java Source code

Java tutorial

Introduction

Here is the source code for phex.bootstrap.GWebCacheConnection.java

Source

/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2009 Phex Development Group
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  --- SVN Information ---
 *  $Id: GWebCacheConnection.java 4471 2009-07-03 21:35:21Z gregork $
 */
package phex.bootstrap;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import phex.common.PhexVersion;
import phex.common.address.DestAddress;
import phex.common.address.MalformedDestAddressException;
import phex.connection.ProtocolNotSupportedException;
import phex.http.HTTPHeaderNames;
import phex.http.HttpClientFactory;
import phex.net.repres.PresentationManager;
import phex.ProxyPrefs;
import phex.security.AccessType;
import phex.security.PhexSecurityManager;
import phex.util.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * This class implements the basic functionality of the Gnutella Web Cache (GWebCache)
 * invented from the Gnucleus Team.
 * <p>
 * To access a GWebCache you should send a ping request first. To verify if it is
 * working.
 * <p>
 * Supports Version 1.3 of GWebCache specification.
 */
public class GWebCacheConnection {
    private static final String PING_QUERY = "ping=1";
    private static final String HOST_FILE_QUERY = "hostfile=1";
    private static final String URL_FILE_QUERY = "urlfile=1";
    private static final String IP_QUERY = "ip=";
    private static final String URL_QUERY = "url=";

    private static final String QUERY_POSTFIX = "&client=PHEX&version=" + PhexVersion.getFullVersion();

    private static final Logger logger = LoggerFactory.getLogger(GWebCacheConnection.class);
    public static final int MAX_LENGTH = 100000;

    /**
     * The GWebCache base URL.
     */
    private GWebCacheHost gWebCache;

    /**
     * The connection to the GWebCache. The connection is opened when accessing
     * the GWebCache the first time.
     */
    private HttpMethod method;

    /**
     * The reader of the connection.
     */
    private BufferedReader reader;

    /**
     * Indicates if a cache is bad.
     */
    private boolean isCacheBad;

    /**
     * @param aGWebCache the GWebCache. Must be http protocol.
     * @throws ProtocolNotSupportedException in case a different protocol then http is used
     */
    public GWebCacheConnection(GWebCacheHost aGWebCache) throws ProtocolNotSupportedException {
        if (!aGWebCache.getUrl().getProtocol().equals("http")) {
            throw new ProtocolNotSupportedException("Only http URLs are supported for a GWebCacheConnection");
        }
        this.gWebCache = aGWebCache;
        isCacheBad = false;
    }

    /**
     * @return the {@link GWebCacheHost} behind this connection.
     */
    public GWebCacheHost getGWebCache() {
        return gWebCache;
    }

    /**
     * Sends a ping request to the GWebCache. The method returns true if a PONG
     * was received otherwise it returns false and set the cache to be a bad cache.
     */
    public boolean sendPingRequest() {
        try {
            URL requestURL = new URL(gWebCache.getUrl(),
                    gWebCache.getUrl().getPath() + '?' + PING_QUERY + QUERY_POSTFIX);
            if (requestURL.getHost() == null) {
                logger.warn("Host is null");
                isCacheBad = true;
                return false;
            }
            openConnection(requestURL);
            String line = reader.readLine();
            if (line != null && line.startsWith("PONG")) {
                return true;
            } else {
                isCacheBad = true;
                return false;
            }
        } catch (IOException exp) {
            logger.debug(exp.getMessage(), exp);
            isCacheBad = true;
            return false;
        } finally {
            closeConnection();
        }
    }

    /**
     * Request a list of hosts from the GWebCache. The returned list is either
     * null if a problem occurred, empty if the GWebCache does not have any hosts
     * stored, or filled with IPs.
     */
    public DestAddress[] sendHostFileRequest(PhexSecurityManager securityService) {
        URL url = gWebCache.getUrl(), requestURL = null;
        try {
            requestURL = new URL(url, url.getPath() + '?' + HOST_FILE_QUERY + QUERY_POSTFIX);
            /*if (requestURL.getHost() == null) {
            logger.warn("Host is null");
            isCacheBad = true;
            return null;
            }*/
            openConnection(requestURL);
            String line = reader.readLine();
            // the first line might contain an error
            if (line != null && line.startsWith("ERROR")) {
                isCacheBad = true;
                return null;
            }

            DestAddress destAddress;
            boolean falseHostFound = false;
            List<DestAddress> hostFileList = new ArrayList<DestAddress>(20);
            PresentationManager presentationMgr = PresentationManager.getInstance();
            while (line != null) {
                try {
                    // The line requires to have a valid port. If no port could
                    // be found the default port of -1 should ensure the
                    // DestAddress is invalid when no port is given.
                    destAddress = presentationMgr.createHostAddress(line, -1);
                    if (!destAddress.isIpHostName() || !destAddress.isValidAddress()) {
                        throw new MalformedDestAddressException("Invalid address.");
                    }

                    AccessType access = securityService.controlHostAddressAccess(destAddress);
                    switch (access) {
                    case ACCESS_DENIED:
                    case ACCESS_STRONGLY_DENIED:
                        // skip host address...
                        break;
                    case ACCESS_GRANTED:
                        hostFileList.add(destAddress);
                        break;
                    }
                } catch (MalformedDestAddressException exp) {
                    logger.debug("Invalid host found: {}", line);
                    falseHostFound = true;
                    line = reader.readLine();
                    continue;
                }
                line = reader.readLine();
            }
            // all returned hosts where false, cache is bad.
            if (hostFileList.size() == 0 && falseHostFound) {
                isCacheBad = true;
                return null;
            }
            DestAddress[] hostFileArr = new DestAddress[hostFileList.size()];
            hostFileList.toArray(hostFileArr);
            logger.info("found hosts: {}", hostFileArr.length);
            return hostFileArr;
        } catch (IOException exp) {
            logger.debug("request host {}: {}", requestURL != null ? requestURL : url, exp.getMessage());
            isCacheBad = true;
            return null;
        } finally {
            closeConnection();
        }
    }

    /**
     * Request a list of GWebCache URLs from the GWebCache. The returned list is
     * either null if a problem occurred, empty if the GWebCache does not have any
     * URLs stored, or filled with URLs.
     */
    public URL[] sendURLFileRequest() {
        try {
            URL requestURL = new URL(gWebCache.getUrl(),
                    gWebCache.getUrl().getPath() + '?' + URL_FILE_QUERY + QUERY_POSTFIX);
            if (requestURL.getHost() == null) {
                logger.warn("Host is null");
                isCacheBad = true;
                return null;
            }
            openConnection(requestURL);
            String line = reader.readLine();
            // the first line might contain an error
            if (line != null && line.startsWith("ERROR")) {
                isCacheBad = true;
                return null;
            }

            boolean falseURLFound = false;
            List<URL> urlFileList = new ArrayList<URL>(20);
            int counter = 0;
            // don't apply more then 20 URL
            while (line != null && counter < 20) {
                try {
                    NormalizableURL helpUrl = new NormalizableURL(line);
                    helpUrl.normalize();
                    URL url = new URL(helpUrl.toExternalForm());
                    if (!url.getProtocol().equals("http")) {
                        throw new ProtocolNotSupportedException(
                                "Only http URLs are supported for a GWebCacheConnection");
                    }
                    urlFileList.add(url);
                } catch (MalformedURLException exp) {//ignore false url
                    falseURLFound = true;
                }
                line = reader.readLine();
                counter++;
            }
            // all returned urls where false, cache is bad.
            if (urlFileList.size() == 0 && falseURLFound) {
                isCacheBad = true;
                return null;
            }

            URL[] urlFileArr = new URL[urlFileList.size()];
            urlFileList.toArray(urlFileArr);
            logger.debug("URLs found: {}", urlFileArr.length);
            return urlFileArr;
        } catch (IOException exp) {
            logger.debug(exp.getMessage(), exp);
            isCacheBad = true;
            return null;
        } finally {
            closeConnection();
        }
    }

    /**
     * Sends a update request to update the GWebCache. You can provide a fullHostName
     * and a cacheURL or only one of them by settings the other one to null.
     */
    public boolean updateRequest(String fullHostName, String cacheURL) {
        if (fullHostName == null && cacheURL == null) {
            throw new IllegalArgumentException("Must provide at least one of hostIP or cacheURL.");
        }

        try {
            StringBuffer queryBuffer = new StringBuffer();
            if (fullHostName != null) {
                queryBuffer.append(IP_QUERY);
                queryBuffer.append(URLCodecUtils.encodeURL(fullHostName));
            }
            if (cacheURL != null) {
                if (fullHostName != null) {
                    queryBuffer.append('&');
                }
                queryBuffer.append(URL_QUERY);
                queryBuffer.append(URLCodecUtils.encodeURL(cacheURL));
            }

            URL requestURL = new URL(gWebCache.getUrl(),
                    gWebCache.getUrl().getPath() + '?' + queryBuffer + QUERY_POSTFIX);
            if (requestURL.getHost() == null) {
                logger.warn("Host is null");
                isCacheBad = true;
                return false;
            }
            openConnection(requestURL);
            String line = reader.readLine();
            if (line != null && line.startsWith("OK")) {
                return true;
            } else {
                isCacheBad = true;
                return false;
            }
        } catch (IOException exp) {
            logger.debug(exp.getMessage(), exp);
            isCacheBad = true;
            return false;
        } finally {
            closeConnection();
        }
    }

    /**
     * Returns if a cache is bad.
     * <p>
     * Bad caches are those that return:<br>
     * - nothing - those that cannot be accessed at all (timeouts, invalid hostnames, etc.)<br>
     * - HTTP error codes (400-599)<br>
     * - responses that cannot be parsed by a client<br>
     * - ERROR response<p>
     *
     * @return true if the cache is bad, false otherwise
     */
    public boolean isCacheBad() {
        return isCacheBad;
    }

    /**
     * Opens a connection to the request url and checks the response code.
     * 3xx are automatically redirected.
     * 400 - 599 response codes will throw a ConnectException.
     *
     * @throws IOException if an IO error occurs
     */
    private void openConnection(URL requestURL) throws IOException {
        HttpClient client = HttpClientFactory.createHttpClient();

        if (ProxyPrefs.UseHttp.get().booleanValue() && !StringUtils.isEmpty(ProxyPrefs.HttpHost.get())) {
            client.getHostConfiguration().setProxy(ProxyPrefs.HttpHost.get(), ProxyPrefs.HttpPort.get().intValue());
        }

        method = new GetMethod(requestURL.toExternalForm());
        method.setFollowRedirects(false);
        method.addRequestHeader("Cache-Control", "no-cache");
        // be HTTP/1.1 compliant
        method.addRequestHeader(HTTPHeaderNames.CONNECTION, "close");

        logger.debug("Open GWebCache connection to {}.", requestURL);
        int responseCode = client.executeMethod(method);
        logger.debug("GWebCache {} returned response code: {}", requestURL, responseCode);

        // we only accept the status codes 2xx all others fail...
        if (responseCode < 200 || responseCode > 299) {
            throw new ConnectException("GWebCache service not available, response code: " + responseCode);
        }

        InputStream bodyStream = method.getResponseBodyAsStream();
        if (bodyStream != null) {
            // ensure we never read a body size larger then 100K to reduce misuse 
            reader = new BufferedReader(
                    new InputStreamReader(new LengthLimitedInputStream(bodyStream, MAX_LENGTH)));
        } else {
            throw new ConnectException("Empty response.");
        }
    }

    private void closeConnection() {
        IOUtil.closeQuietly(reader);
        reader = null;

        if (method != null) {
            method.releaseConnection();
            method = null;
        }

        logger.debug("Releasing HTTP method.");
    }
}