001: /**
002: * $RCSfile$
003: * $Revision: $
004: * $Date: $
005: *
006: * Copyright (C) 2006 Jive Software. All rights reserved.
007: *
008: * This software is published under the terms of the GNU Public License (GPL),
009: * a copy of which is included in this distribution.
010: */package org.jivesoftware.util;
011:
012: import org.apache.commons.httpclient.HttpClient;
013: import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
014: import org.apache.commons.httpclient.methods.GetMethod;
015: import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
016: import org.jivesoftware.util.cache.Cache;
017: import org.jivesoftware.util.cache.CacheFactory;
018:
019: import javax.servlet.ServletConfig;
020: import javax.servlet.ServletException;
021: import javax.servlet.ServletOutputStream;
022: import javax.servlet.http.HttpServlet;
023: import javax.servlet.http.HttpServletRequest;
024: import javax.servlet.http.HttpServletResponse;
025: import java.io.*;
026: import java.net.MalformedURLException;
027: import java.net.URL;
028: import java.net.URLConnection;
029:
030: /**
031: * Servlet that gets favicons of webservers and includes them in HTTP responses. This
032: * servlet can be used when getting a favicon can take some time so pages can use this
033: * servlet as the image source to let the page load quickly and get the favicon images
034: * as they are available.<p>
035: *
036: * This servlet expects the web application to have the <tt>images/server_16x16.gif</tt>
037: * file that is used when no favicon is found.
038: *
039: * @author Gaston Dombiak
040: */
041: public class FaviconServlet extends HttpServlet {
042:
043: /**
044: * The content-type of the images to return.
045: */
046: private static final String CONTENT_TYPE = "image/x-icon";
047: /**
048: * Bytes of the default favicon to return when one was not found on a host.
049: */
050: private byte[] defaultBytes;
051: /**
052: * Pool of HTTP connections to use to get the favicons
053: */
054: private HttpClient client;
055: /**
056: * Cache the domains that a favicon was not found.
057: */
058: private Cache<String, Integer> missesCache;
059: /**
060: * Cache the favicons that we've found.
061: */
062: private Cache<String, byte[]> hitsCache;
063:
064: public void init(ServletConfig config) throws ServletException {
065: super .init(config);
066: // Create a pool of HTTP connections to use to get the favicons
067: client = new HttpClient(
068: new MultiThreadedHttpConnectionManager());
069: HttpConnectionManagerParams params = client
070: .getHttpConnectionManager().getParams();
071: params.setConnectionTimeout(2000);
072: params.setSoTimeout(2000);
073: // Load the default favicon to use when no favicon was found of a remote host
074: try {
075: URL resource = config.getServletContext().getResource(
076: "/images/server_16x16.gif");
077: defaultBytes = getImage(resource.toString());
078: } catch (MalformedURLException e) {
079: e.printStackTrace();
080: }
081: // Initialize caches.
082: missesCache = CacheFactory.createCache("Favicon Misses");
083: hitsCache = CacheFactory.createCache("Favicon Hits");
084: }
085:
086: /**
087: * Retrieve the image based on it's name.
088: *
089: * @param request the httpservletrequest.
090: * @param response the httpservletresponse.
091: * @throws javax.servlet.ServletException
092: * @throws java.io.IOException
093: */
094: public void doGet(HttpServletRequest request,
095: HttpServletResponse response) throws ServletException,
096: IOException {
097: String host = request.getParameter("host");
098: // Check special cases where we need to change host to get a favicon
099: host = "gmail.com".equals(host) ? "google.com" : host;
100:
101: byte[] bytes = getImage(host, defaultBytes);
102: if (bytes != null) {
103: writeBytesToStream(bytes, response);
104: }
105: }
106:
107: /**
108: * Writes out a <code>byte</code> to the ServletOuputStream.
109: *
110: * @param bytes the bytes to write to the <code>ServletOutputStream</code>.
111: */
112: private void writeBytesToStream(byte[] bytes,
113: HttpServletResponse response) {
114: response.setContentType(CONTENT_TYPE);
115:
116: // Send image
117: try {
118: ServletOutputStream sos = response.getOutputStream();
119: sos.write(bytes);
120: sos.flush();
121: sos.close();
122: } catch (IOException e) {
123: // Do nothing
124: }
125: }
126:
127: /**
128: * Returns the favicon image bytes of the specified host.
129: *
130: * @param host the name of the host to get its favicon.
131: * @return the image bytes found, otherwise null.
132: */
133: private byte[] getImage(String host, byte[] defaultImage) {
134: // If we've already attempted to get the favicon twice and failed,
135: // return the default image.
136: if (missesCache.get(host) != null && missesCache.get(host) > 1) {
137: // Domain does not have a favicon so return default icon
138: return defaultImage;
139: }
140: // See if we've cached the favicon.
141: if (hitsCache.containsKey(host)) {
142: return hitsCache.get(host);
143: }
144: byte[] bytes = getImage("http://" + host + "/favicon.ico");
145: if (bytes == null) {
146: // Cache that the requested domain does not have a favicon. Check if this
147: // is the first cache miss or the second.
148: if (missesCache.get(host) != null) {
149: missesCache.put(host, 2);
150: } else {
151: missesCache.put(host, 1);
152: }
153: // Return byte of default icon
154: bytes = defaultImage;
155: }
156: // Cache the favicon.
157: else {
158: hitsCache.put(host, bytes);
159: }
160: return bytes;
161: }
162:
163: private byte[] getImage(String url) {
164: try {
165: // Try to get the fiveicon from the url using an HTTP connection from the pool
166: // that also allows to configure timeout values (e.g. connect and get data)
167: GetMethod get = new GetMethod(url);
168: get.setFollowRedirects(true);
169: int response = client.executeMethod(get);
170: if (response < 400) {
171: // Check that the response was successful. Should we also filter 30* code?
172: return get.getResponseBody();
173: } else {
174: // Remote server returned an error so return null
175: return null;
176: }
177: } catch (IllegalStateException e) {
178: // Something failed (probably a method not supported) so try the old stye now
179: try {
180: URLConnection urlConnection = new URL(url)
181: .openConnection();
182: urlConnection.setReadTimeout(1000);
183:
184: urlConnection.connect();
185: DataInputStream di = new DataInputStream(urlConnection
186: .getInputStream());
187:
188: ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
189: DataOutputStream out = new DataOutputStream(byteStream);
190:
191: int len;
192: byte[] b = new byte[1024];
193: while ((len = di.read(b)) != -1) {
194: out.write(b, 0, len);
195: }
196: di.close();
197: out.flush();
198:
199: return byteStream.toByteArray();
200: } catch (IOException ioe) {
201: // We failed again so return null
202: return null;
203: }
204: } catch (IOException ioe) {
205: // We failed so return null
206: return null;
207: }
208: }
209:
210: }
|