001/* 002 * Shredzone Commons 003 * 004 * Copyright (C) 2012 Richard "Shred" Körber 005 * http://commons.shredzone.org 006 * 007 * This program is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU Library General Public License as 009 * published by the Free Software Foundation, either version 3 of the 010 * License, or (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU Library General Public License 018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 019 */ 020 021package org.shredzone.commons.gravatar; 022 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030import javax.servlet.http.HttpServletRequest; 031import javax.servlet.http.HttpServletResponse; 032 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035import org.springframework.beans.BeansException; 036import org.springframework.util.FileCopyUtils; 037import org.springframework.web.servlet.FrameworkServlet; 038 039/** 040 * A servlet that proxies requests to the Gravatar server, and caches the image results. 041 * <p> 042 * By using this servlet instead of immediate requests to Gravatar, page loading speed is 043 * kept high even if the Gravatar servers are currently under high load or unreachable. 044 * Additionally, privacy is kept because the visitor's IP and browser headers are kept 045 * hidden from the Gravatar servers. 046 * 047 * @author Richard "Shred" Körber 048 */ 049public class GravatarCacheServlet extends FrameworkServlet { 050 private static final long serialVersionUID = 5962372398781412921L; 051 052 private static final Logger LOG = LoggerFactory.getLogger(GravatarCacheServlet.class); 053 private static final Pattern HASH_PATTERN = Pattern.compile("/([0-9a-f]{32})"); 054 055 @Override 056 protected void doService(HttpServletRequest req, HttpServletResponse resp) 057 throws Exception { 058 try { 059 Matcher m = HASH_PATTERN.matcher(req.getPathInfo()); 060 if (! m.matches()) { 061 resp.sendError(HttpServletResponse.SC_NOT_FOUND); 062 return; 063 } 064 065 String hash = m.group(1); 066 067 GravatarService gs = getWebApplicationContext().getBean("gravatarService", GravatarService.class); 068 File gravatarFile = gs.fetchGravatar(hash); 069 070 long modifiedSinceTs = getIfModifiedSince(req); 071 if (modifiedSinceTs >= 0 072 && (modifiedSinceTs / 1000L) == (gravatarFile.lastModified() / 1000L)) { 073 // The image has not been modified since last request 074 resp.sendError(HttpServletResponse.SC_NOT_MODIFIED); 075 return; 076 } 077 078 long size = gravatarFile.length(); 079 if (size > 0 && size <= Integer.MAX_VALUE) { 080 // Cast to int is safe 081 resp.setContentLength((int) size); 082 } 083 084 resp.setContentType("image/png"); 085 resp.setDateHeader("Date", System.currentTimeMillis()); 086 resp.setDateHeader("Last-Modified", gravatarFile.lastModified()); 087 088 try (InputStream in = new FileInputStream(gravatarFile)) { 089 FileCopyUtils.copy(in, resp.getOutputStream()); 090 } 091 } catch (IOException | BeansException ex) { 092 LOG.error("Failed to send Gravatar icon", ex); 093 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); //NOSONAR 094 } 095 } 096 097 /** 098 * Reads the If-Modified-Since header. 099 * 100 * @return date returned in the If-Modified-Since header, or -1 if not set or not 101 * readable 102 */ 103 private long getIfModifiedSince(HttpServletRequest req) { 104 try { 105 return req.getDateHeader("If-Modified-Since"); 106 } catch (IllegalArgumentException ex) { 107 // As stated in RFC2616 Sec. 14.25, an invalid date will just be ignored. 108 LOG.debug("Ignored a bad If-Modified-Since header", ex); 109 return -1; 110 } 111 } 112 113}