New imagegen webapp, including

servlets for identicon and qr images.
Heavily modified from the example in the identicon package,
which is MIT licensed.
This commit is contained in:
zzz
2016-01-22 19:25:33 +00:00
parent 21e2600c40
commit 49d8235661
6 changed files with 500 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
Servlets based on the example from the identicon package.
License: See ../identicon/README.md

View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="imagegen">
<property name="project" value="imagegen" />
<property name="jetty" value="../../jetty/" />
<property name="lib" value="${jetty}/jettylib" />
<path id="cp">
<pathelement location="${lib}/javax.servlet.jar" />
<pathelement location="../identicon/build/identicon.jar" />
<pathelement location="../zxing/build/zxing.jar" />
<pathelement location="../../../build/i2p.jar" />
</path>
<target name="all" depends="war" />
<target name="build" depends="builddep, war" />
<target name="builddep">
<!-- run from top level build.xml to get dependencies built -->
</target>
<condition property="depend.available">
<typefound name="depend" />
</condition>
<target name="depend" if="depend.available">
<depend
cache="../../../build"
srcdir="./webapp/src/main/java"
destdir="./build/obj" >
</depend>
</target>
<!-- only used if not set by a higher build.xml -->
<property name="javac.compilerargs" value="" />
<property name="javac.version" value="1.6" />
<target name="compile" depends="depend">
<mkdir dir="./build" />
<mkdir dir="./build/WEB-INF" />
<mkdir dir="./build/WEB-INF/classes" />
<javac srcdir="./webapp/src/main/java" debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
includeAntRuntime="false"
classpathref="cp"
destdir="./build/WEB-INF/classes" >
<compilerarg line="${javac.compilerargs}" />
</javac>
</target>
<target name="listChangedFiles" if="shouldListChanges" >
<exec executable="mtn" outputproperty="workspace.changes" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="list" />
<arg value="changed" />
<arg value="." />
</exec>
<!-- \n in an attribute value generates an invalid manifest -->
<exec executable="tr" inputstring="${workspace.changes}" outputproperty="workspace.changes.tr" errorproperty="mtn.error2" failifexecutionfails="false" >
<arg value="-s" />
<arg value="[:space:]" />
<arg value="," />
</exec>
</target>
<target name="war" depends="compile, warUpToDate" unless="war.uptodate" >
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<!-- put the identicon and zxing classes in the war -->
<copy todir="build/WEB-INF/classes">
<fileset dir="../identicon/build/obj"/>
<fileset dir="../zxing/build/obj"/>
</copy>
<war destfile="build/${project}.war" webxml="webapp/src/main/webapp/WEB-INF/web.xml">
<fileset dir="build">
<include name="WEB-INF/**/*.class"/>
</fileset>
<fileset dir="webapp/src/main/webapp"/>
<manifest>
<attribute name="Implementation-Version" value="${full.version}" />
<attribute name="Built-By" value="${build.built-by}" />
<attribute name="Build-Date" value="${build.timestamp}" />
<attribute name="Base-Revision" value="${workspace.version}" />
<attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
</manifest>
</war>
</target>
<target name="warUpToDate">
<uptodate property="war.uptodate" targetfile="${project}.war">
<srcfiles dir= "." includes="WEB-INF/web-out.xml WEB-INF/**/*.class images/*.png css.css index.html WEB-INF/classes/${project}.properties" />
</uptodate>
<condition property="shouldListChanges" >
<and>
<not>
<isset property="war.uptodate" />
</not>
<isset property="mtn.available" />
</and>
</condition>
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./webapp/src/main/java" destdir="./build/javadoc"
packagenames="*"
use="true"
splitindex="true"
windowtitle="imagegen webapp" />
</target>
<target name="clean">
<delete dir="./build" />
<delete dir="./buildTest" />
</target>
<target name="cleandep" depends="clean">
</target>
<target name="distclean" depends="clean">
</target>
</project>

View File

@@ -0,0 +1,171 @@
package net.i2p.imagegen;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.docuverse.identicon.IdenticonCache;
import com.docuverse.identicon.IdenticonRenderer;
import com.docuverse.identicon.IdenticonUtil;
import com.docuverse.identicon.NineBlockIdenticonRenderer2;
import net.i2p.data.Hash;
import net.i2p.util.ConvertToHash;
/**
* This servlet generates <i>identicon</i> (visual identifier) images ranging
* from 16x16 to 512x512 in size.
*
* <h5>Supported Image Formats</h5>
* <p>
* Currently only PNG is supported because <code>javax.imageio</code> package
* does not come with built-in GIF encoder and PNG is the only remaining
* reasonable format.
* </p>
* <h5>Initialization Parameters:</h5>
* <blockquote>
* <dl>
* <dt>inetSalt</dt>
* <dd>salt used to generate identicon code with. must be fairly long.
* (Required)</dd>
* <dt>cacheProvider</dt>
* <dd>full class path to <code>IdenticonCache</code> implementation.
* (Optional)</dd>
* </dl>
* </blockquote>
* <h5>Request ParametersP</h5>
* <blockquote>
* <dl>
* <dt>code</dt>
* <dd>identicon code to render. If missing, requester's IP addresses is used
* to generated one. (Optional)</dd>
* <dt>size</dt>
* <dd>identicon size in pixels. If missing, a 16x16 pixels identicon is
* returned. Minimum size is 16 and maximum is 64. (Optional)</dd>
* </dl>
* </blockquote>
*
* @author don
* @since 0.9.25
*/
public class IdenticonServlet extends HttpServlet {
private static final long serialVersionUID = -3507466186902317988L;
private static final String INIT_PARAM_VERSION = "version";
private static final String INIT_PARAM_CACHE_PROVIDER = "cacheProvider";
private static final String PARAM_IDENTICON_SIZE_SHORT = "s";
private static final String PARAM_IDENTICON_CODE_SHORT = "c";
private static final String IDENTICON_IMAGE_FORMAT = "PNG";
private static final String IDENTICON_IMAGE_MIMETYPE = "image/png";
private static final long DEFAULT_IDENTICON_EXPIRES_IN_MILLIS = 24 * 60 * 60 * 1000;
private int version = 1;
private final IdenticonRenderer renderer = new NineBlockIdenticonRenderer2();
private IdenticonCache cache;
private long identiconExpiresInMillis = DEFAULT_IDENTICON_EXPIRES_IN_MILLIS;
@Override
public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
// Since identicons cache expiration is very long, version is
// used in ETag to force identicons to be updated as needed.
// Change veresion whenever rendering codes changes result in
// visual changes.
if (cfg.getInitParameter(INIT_PARAM_VERSION) != null)
this.version = Integer.parseInt(cfg
.getInitParameter(INIT_PARAM_VERSION));
String cacheProvider = cfg.getInitParameter(INIT_PARAM_CACHE_PROVIDER);
if (cacheProvider != null) {
try {
Class cacheClass = Class.forName(cacheProvider);
this.cache = (IdenticonCache) cacheClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String codeParam = request.getParameter(PARAM_IDENTICON_CODE_SHORT);
boolean codeSpecified = codeParam != null && codeParam.length() > 0;
if (!codeSpecified) {
response.setStatus(403);
return;
}
String sizeParam = request.getParameter(PARAM_IDENTICON_SIZE_SHORT);
int size = 32;
if (sizeParam != null) {
try {
size = Integer.parseInt(sizeParam);
if (size < 16)
size = 16;
else if (size > 512)
size = 512;
} catch (NumberFormatException nfe) {}
}
String identiconETag = IdenticonUtil.getIdenticonETag(codeParam.hashCode(), size,
version);
String requestETag = request.getHeader("If-None-Match");
if (requestETag != null && requestETag.equals(identiconETag)) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
} else {
// we try to interpret the codeParam parameter as:
// 1) a number
// 2) a base32 or base64 hash, which we take the Java hashcode of
// 3) a string, which we take the Java hashcode of
int code;
try {
code = Integer.parseInt(codeParam);
} catch (NumberFormatException nfe) {
Hash h = ConvertToHash.getHash(codeParam);
if (h != null)
code = Arrays.hashCode(h.getData());
else
code = codeParam.hashCode();
}
byte[] imageBytes = null;
// retrieve image bytes from either cache or renderer
if (cache == null
|| (imageBytes = cache.get(identiconETag)) == null) {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
RenderedImage image = renderer.render(code, size);
ImageIO.write(image, IDENTICON_IMAGE_FORMAT, byteOut);
imageBytes = byteOut.toByteArray();
if (cache != null)
cache.add(identiconETag, imageBytes);
} else {
response.setStatus(403);
return;
}
// set ETag and, if code was provided, Expires header
response.setHeader("ETag", identiconETag);
if (codeSpecified) {
long expires = System.currentTimeMillis()
+ identiconExpiresInMillis;
response.addDateHeader("Expires", expires);
}
// return image bytes to requester
response.setContentType(IDENTICON_IMAGE_MIMETYPE);
response.setContentLength(imageBytes.length);
response.getOutputStream().write(imageBytes);
}
}
}

View File

@@ -0,0 +1,138 @@
package net.i2p.imagegen;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.docuverse.identicon.IdenticonCache;
import com.docuverse.identicon.IdenticonUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
/**
* This servlet generates QR code images.
*
* @author modiied from identicon
* @since 0.9.25
*/
public class QRServlet extends HttpServlet {
private static final long serialVersionUID = -3507466186902317988L;
private static final String INIT_PARAM_VERSION = "version";
private static final String INIT_PARAM_CACHE_PROVIDER = "cacheProvider";
private static final String PARAM_IDENTICON_SIZE_SHORT = "s";
private static final String PARAM_IDENTICON_CODE_SHORT = "c";
private static final String IDENTICON_IMAGE_FORMAT = "PNG";
private static final String IDENTICON_IMAGE_MIMETYPE = "image/png";
private static final long DEFAULT_IDENTICON_EXPIRES_IN_MILLIS = 24 * 60 * 60 * 1000;
private int version = 1;
private IdenticonCache cache;
private long identiconExpiresInMillis = DEFAULT_IDENTICON_EXPIRES_IN_MILLIS;
@Override
public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
// Since identicons cache expiration is very long, version is
// used in ETag to force identicons to be updated as needed.
// Change veresion whenever rendering codes changes result in
// visual changes.
if (cfg.getInitParameter(INIT_PARAM_VERSION) != null)
this.version = Integer.parseInt(cfg
.getInitParameter(INIT_PARAM_VERSION));
String cacheProvider = cfg.getInitParameter(INIT_PARAM_CACHE_PROVIDER);
if (cacheProvider != null) {
try {
Class cacheClass = Class.forName(cacheProvider);
this.cache = (IdenticonCache) cacheClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String codeParam = request.getParameter(PARAM_IDENTICON_CODE_SHORT);
boolean codeSpecified = codeParam != null && codeParam.length() > 0;
if (!codeSpecified) {
// TODO 404
codeParam="http://stats.i2p/?i2paddresshelper=Okd5sN9hFWx-sr0HH8EFaxkeIMi6PC5eGTcjM1KB7uQ0ffCUJ2nVKzcsKZFHQc7pLONjOs2LmG5H-2SheVH504EfLZnoB7vxoamhOMENnDABkIRGGoRisc5AcJXQ759LraLRdiGSR0WTHQ0O1TU0hAz7vAv3SOaDp9OwNDr9u902qFzzTKjUTG5vMTayjTkLo2kOwi6NVchDeEj9M7mjj5ySgySbD48QpzBgcqw1R27oIoHQmjgbtbmV2sBL-2Tpyh3lRe1Vip0-K0Sf4D-Zv78MzSh8ibdxNcZACmZiVODpgMj2ejWJHxAEz41RsfBpazPV0d38Mfg4wzaS95R5hBBo6SdAM4h5vcZ5ESRiheLxJbW0vBpLRd4mNvtKOrcEtyCvtvsP3FpA-6IKVswyZpHgr3wn6ndDHiVCiLAQZws4MsIUE1nkfxKpKtAnFZtPrrB8eh7QO9CkH2JBhj7bG0ED6mV5~X5iqi52UpsZ8gnjZTgyG5pOF8RcFrk86kHxAAAA";
//response.setStatus(403);
//return;
}
String sizeParam = request.getParameter(PARAM_IDENTICON_SIZE_SHORT);
// very rougly, number of "modules" is about 4 * sqrt(chars)
// (assuming 7 bit) default margin each side is 4
// assuming level L
// min modules is 21x21
// shoot for 2 pixels per module
int size = Math.max(50, (2 * 4) + (int) (2 * 5 * Math.sqrt(codeParam.length())));
if (sizeParam != null) {
try {
size = Integer.parseInt(sizeParam);
if (size < 40)
size = 40;
else if (size > 512)
size = 512;
} catch (NumberFormatException nfe) {}
}
String identiconETag = IdenticonUtil.getIdenticonETag(codeParam.hashCode(), size,
version);
String requestETag = request.getHeader("If-None-Match");
if (requestETag != null && requestETag.equals(identiconETag)) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
} else {
byte[] imageBytes = null;
// retrieve image bytes from either cache or renderer
if (cache == null
|| (imageBytes = cache.get(identiconETag)) == null) {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
QRCodeWriter qrcw = new QRCodeWriter();
BitMatrix matrix;
try {
matrix = qrcw.encode(codeParam, BarcodeFormat.QR_CODE, size, size);
} catch (WriterException we) {
throw new IOException("encode failed", we);
}
MatrixToImageWriter.writeToStream(matrix, IDENTICON_IMAGE_FORMAT, byteOut);
imageBytes = byteOut.toByteArray();
if (cache != null)
cache.add(identiconETag, imageBytes);
} else {
response.setStatus(403);
return;
}
// set ETag and, if code was provided, Expires header
response.setHeader("ETag", identiconETag);
if (codeSpecified) {
long expires = System.currentTimeMillis()
+ identiconExpiresInMillis;
response.addDateHeader("Expires", expires);
}
// return image bytes to requester
response.setContentType(IDENTICON_IMAGE_MIMETYPE);
response.setContentLength(imageBytes.length);
response.getOutputStream().write(imageBytes);
}
}
}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>WebAppExample - Identicon</display-name>
<servlet>
<servlet-name>net.i2p.imagegen.IdenticonServlet</servlet-name>
<servlet-class>net.i2p.imagegen.IdenticonServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>net.i2p.imagegen.QRServlet</servlet-name>
<servlet-class>net.i2p.imagegen.QRServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>net.i2p.imagegen.RandomArtServlet</servlet-name>
<servlet-class>net.i2p.imagegen.RandomArtServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- precompiled servlets -->
<servlet-mapping>
<servlet-name>net.i2p.imagegen.IdenticonServlet</servlet-name>
<url-pattern>/id</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.imagegen.QRServlet</servlet-name>
<url-pattern>/qr</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.imagegen.RandomArtServlet</servlet-name>
<url-pattern>/ra</url-pattern>
</servlet-mapping>
<!-- this webapp doesn't actually use sessions or cookies -->
<session-config>
<session-timeout>
30
</session-timeout>
<cookie-config>
<http-only>true</http-only>
</cookie-config>
</session-config>
</web-app>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Identicon Canvas Test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h2>Server-side ID</h2>
<img src="id?c=-2044886870&amp;s=15" width=15 height=15>
<img src="id?c=-2044886870&amp;s=21" width=21 height=21>
<img src="id?c=-2044886870&amp;s=30" width=30 height=30>
<img src="id?c=-2044886870&amp;s=48" width=48 height=48>
<img src="id?c=-2044886870&amp;s=64" width=64 height=64>
<img src="id?c=-2044886870&amp;s=128" width=128 height=128>
<h2>Server-side QR</h2>
<img src="qr?c=https%3a%2f%2fgeti2p.net%2f&amp;s=128" width=128 height=128>
<img src="qr?c=https%3a%2f%2fgeti2p.net%2f&amp;s=128" width=128 height=128>
</body>
</html>