FaviconServlet.java 7.48 KB
Newer Older
Gaston Dombiak's avatar
Gaston Dombiak committed
1 2 3 4 5 6 7 8 9 10 11 12 13
/**
 * $RCSfile$
 * $Revision: $
 * $Date: $
 *
 * Copyright (C) 2006 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.util;

14 15 16 17
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
18
import org.jivesoftware.util.cache.Cache;
19
import org.jivesoftware.util.cache.CacheFactory;
20

Gaston Dombiak's avatar
Gaston Dombiak committed
21 22 23 24 25 26
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
27
import java.io.*;
Gaston Dombiak's avatar
Gaston Dombiak committed
28 29 30 31 32 33 34 35 36
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

/**
 * Servlet that gets favicons of webservers and includes them in HTTP responses. This
 * servlet can be used when getting a favicon can take some time so pages can use this
 * servlet as the image source to let the page load quickly and get the favicon images
 * as they are available.<p>
37
 *
Gaston Dombiak's avatar
Gaston Dombiak committed
38 39 40 41 42 43
 * This servlet expects the web application to have the <tt>images/server_16x16.gif</tt>
 * file that is used when no favicon is found.
 *
 * @author Gaston Dombiak
 */
public class FaviconServlet extends HttpServlet {
44

Gaston Dombiak's avatar
Gaston Dombiak committed
45 46 47
    /**
     * The content-type of the images to return.
     */
48
    private static final String CONTENT_TYPE = "image/x-icon";
49 50 51
    /**
     * Bytes of the default favicon to return when one was not found on a host.
     */
Gaston Dombiak's avatar
Gaston Dombiak committed
52
    private byte[] defaultBytes;
53 54 55
    /**
     * Pool of HTTP connections to use to get the favicons
     */
56
    private HttpClient client;
57 58 59
    /**
     * Cache the domains that a favicon was not found.
     */
60 61 62 63 64
    private Cache<String, Integer> missesCache;
    /**
     * Cache the favicons that we've found.
     */
    private Cache<String, byte[]> hitsCache;
Gaston Dombiak's avatar
Gaston Dombiak committed
65 66 67

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
68 69 70
        // Create a pool of HTTP connections to use to get the favicons
        client = new HttpClient(new MultiThreadedHttpConnectionManager());
        HttpConnectionManagerParams params = client.getHttpConnectionManager().getParams();
71 72
        params.setConnectionTimeout(2000);
        params.setSoTimeout(2000);
73
        // Load the default favicon to use when no favicon was found of a remote host
Gaston Dombiak's avatar
Gaston Dombiak committed
74 75 76 77 78 79 80
        try {
            URL resource = config.getServletContext().getResource("/images/server_16x16.gif");
            defaultBytes = getImage(resource.toString());
        }
        catch (MalformedURLException e) {
            e.printStackTrace();
        }
81
        // Initialize caches.
82 83
        missesCache = CacheFactory.createCache("Favicon Misses");
        hitsCache = CacheFactory.createCache("Favicon Hits");
Gaston Dombiak's avatar
Gaston Dombiak committed
84 85 86 87 88
    }

    /**
     * Retrieve the image based on it's name.
     *
89
     * @param request the httpservletrequest.
Gaston Dombiak's avatar
Gaston Dombiak committed
90 91 92 93 94
     * @param response the httpservletresponse.
     * @throws javax.servlet.ServletException
     * @throws java.io.IOException
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
95 96
            throws ServletException, IOException
    {
Gaston Dombiak's avatar
Gaston Dombiak committed
97
        String host = request.getParameter("host");
98 99
        // Check special cases where we need to change host to get a favicon
        host = "gmail.com".equals(host) ? "google.com" : host;
Gaston Dombiak's avatar
Gaston Dombiak committed
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

        byte[] bytes = getImage(host, defaultBytes);
        if (bytes != null) {
            writeBytesToStream(bytes, response);
        }
    }

    /**
     * Writes out a <code>byte</code> to the ServletOuputStream.
     *
     * @param bytes the bytes to write to the <code>ServletOutputStream</code>.
     */
    private void writeBytesToStream(byte[] bytes, HttpServletResponse response) {
        response.setContentType(CONTENT_TYPE);

        // Send image
        try {
            ServletOutputStream sos = response.getOutputStream();
            sos.write(bytes);
            sos.flush();
            sos.close();
        }
        catch (IOException e) {
            // Do nothing
        }
    }

    /**
     * Returns the favicon image bytes of the specified host.
     *
     * @param host the name of the host to get its favicon.
     * @return the image bytes found, otherwise null.
     */
    private byte[] getImage(String host, byte[] defaultImage) {
134 135 136
        // If we've already attempted to get the favicon twice and failed,
        // return the default image.
        if (missesCache.get(host) != null && missesCache.get(host) > 1) {
137 138 139
            // Domain does not have a favicon so return default icon
            return defaultImage;
        }
140 141 142 143
        // See if we've cached the favicon.
        if (hitsCache.containsKey(host)) {
            return hitsCache.get(host);
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
144 145
        byte[] bytes = getImage("http://" + host + "/favicon.ico");
        if (bytes == null) {
146 147 148 149 150 151 152 153
            // Cache that the requested domain does not have a favicon. Check if this
            // is the first cache miss or the second.
            if (missesCache.get(host) != null) {
                missesCache.put(host, 2);
            }
            else {
                missesCache.put(host, 1);
            }
Gaston Dombiak's avatar
Gaston Dombiak committed
154 155 156
            // Return byte of default icon
            bytes = defaultImage;
        }
157 158 159 160
        // Cache the favicon.
        else {
            hitsCache.put(host, bytes);
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
161 162 163 164 165
        return bytes;
    }

    private byte[] getImage(String url) {
        try {
166 167 168
            // Try to get the fiveicon from the url using an HTTP connection from the pool
            // that also allows to configure timeout values (e.g. connect and get data)
            GetMethod get = new GetMethod(url);
169 170 171 172 173 174 175 176 177 178
            get.setFollowRedirects(true);
            int response = client.executeMethod(get);
            if (response < 400) {
                // Check that the response was successful. Should we also filter 30* code?
                return get.getResponseBody();
            }
            else {
                // Remote server returned an error so return null
                return null;
            }
179
        }
180
        catch (IllegalStateException e) {
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
            // Something failed (probably a method not supported) so try the old stye now
            try {
                URLConnection urlConnection = new URL(url).openConnection();
                urlConnection.setReadTimeout(1000);

                urlConnection.connect();
                DataInputStream di = new DataInputStream(urlConnection.getInputStream());

                ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                DataOutputStream out = new DataOutputStream(byteStream);

                int len;
                byte[] b = new byte[1024];
                while ((len = di.read(b)) != -1) {
                    out.write(b, 0, len);
                }
                di.close();
                out.flush();

                return byteStream.toByteArray();
            }
            catch (IOException ioe) {
                // We failed again so return null
                return null;
Gaston Dombiak's avatar
Gaston Dombiak committed
205 206
            }
        }
207
        catch (IOException ioe) {
208
            // We failed so return null
209 210
            return null;
        }
Gaston Dombiak's avatar
Gaston Dombiak committed
211 212
    }

213
}