<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Christoph Bünte &nbsp;&raquo; Software Entwicklung Berlin</title>
	<atom:link href="http://www.christophbuente.de/kategorie/software-entwicklung/java-jee/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.christophbuente.de</link>
	<description>Software Entwicklung</description>
	<lastBuildDate>Tue, 07 Dec 2010 11:30:07 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.5</generator>
		<item>
		<title>Stripes Framework &#8211; Tests für ActionBeans im Wizard Modus</title>
		<link>http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/</link>
		<comments>http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/#comments</comments>
		<pubDate>Thu, 24 Jan 2008 22:44:17 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Java JEE]]></category>
		<category><![CDATA[Web Entwicklung]]></category>
		<category><![CDATA[actionBean]]></category>
		<category><![CDATA[anleitung]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JEE]]></category>
		<category><![CDATA[junit]]></category>
		<category><![CDATA[mock]]></category>
		<category><![CDATA[MockRoundTrip]]></category>
		<category><![CDATA[MockServletContext]]></category>
		<category><![CDATA[stripes]]></category>
		<category><![CDATA[stripes framework]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[tests]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[unit tests]]></category>
		<category><![CDATA[wizard mode]]></category>
		<category><![CDATA[wizard modus]]></category>
		<category><![CDATA[__fp]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/</guid>
		<description><![CDATA[Bei meinem aktuellen Kunden wurde das Stripes Framework verwendet, um eine Java basierte Webanwendung zu erstellen. Für mehrseitige Formulare bietet Stripes ActionBeans mit einen Wizard Modus an. Dieser zeichnet sich dadurch aus, dass alle bereits ausgefüllten Formularfelder als versteckte Felder in alle folgenden Formulare eingefügt werden. Dieser Modus erspart bei mehrseitigen Formularen viel Arbeit, aber [...]]]></description>
			<content:encoded><![CDATA[<p>Bei meinem aktuellen Kunden wurde das <a href="http://www.stripesframework.org/">Stripes Framework</a> verwendet, um eine Java basierte Webanwendung zu erstellen. Für mehrseitige Formulare bietet Stripes ActionBeans mit einen <a href="http://www.stripesframework.org/display/stripes/Wizard+Forms">Wizard Modus</a> an. Dieser zeichnet sich dadurch aus, dass alle bereits ausgefüllten Formularfelder als versteckte Felder in alle folgenden Formulare eingefügt werden. Dieser Modus erspart bei mehrseitigen Formularen viel Arbeit, aber wie lässt sich dieser Wizard Modus am besten Testen? In diesem Artikel wird beschrieben, wie man mit Hilfe dem Stripes eigenen MockServletContexts sinnvolle Unit tests schreiben kann.</p>
<p><span id="more-31"></span></p>
<p>Die Dokumentation für <a href="http://www.stripesframework.org/display/stripes/Unit+Testing#UnitTesting-Approach2%3AMockContainerUsage">Stripes Unit tests</a> beschreibt sehr ausführlich, wie man mit Hilfe des MockServletContext eine Testinfrastruktur aufbaut, die es ermöglicht die ActionBeans ausserhalb des <acronym title="Java Enterprise Edition" lang="en">JEE</acronym>-Containers zu testen. Dies funktioniert für klassische ActionBeans ganz wunderbar. Doch beim schreiben von Tests für ActionBeans im Wizard Mode fiel auf, dass die Simulation eines Submits nicht funktionierte. Im Wizard Modus erzeugt das <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/tag/FormTag.html">Stripes FormTag</a> ein zusätzliches verstecktes Formularfeld mit dem Namen &#8220;__fp&#8221;, welches alle Feldnamen des aktuellen Formulars in verschlüsselter Form enthält. Ohne dieses Feld verweigert der Stripes Dispatcher die Arbeit! Nimmt man nun einen beliebigen Wert und legt ihn in diesem Feld ab, so erzeugt das ebenfalls einen Fehler:<small>(Der Rechtschreibfehler ist tatsächlich aus dem Framework.)</small></p>
<p><code>Stripes attmpted and failed to decrypt the non-null value in the 'fields present' field.</code></p>
<p>Auch ist es nicht sinnvoll, einen beliebigen Text mit Hilfe des <code>CryptoUtil</code> zu verschlüsseln. Der Submit kann dann zwar erfolgreich simuliert werden, jedoch werden sämtliche Validierungsregel ignoriert. Mit folgenden Zeilen Code, kann ein korrekter Wert für das &#8220;__fp&#8221; Feld erzeugt werden:</p>
<pre lang="java">// Alle Feldnamen des aktuellen Formulars in eine Liste aufnehmen
   List fields = new ArrayList();
   fields.add("username");
   fields.add("password");
// Alle Feldnamen mit Trennzeichen zusammenfassen
   String hiddenFieldValue = HtmlUtil.combineValues(fields);
// servlet engine configurieren
   MockServletContext ctx = TestFixture.getServletContext();
   MockRoundtrip trip = new MockRoundtrip(this.ctx, YourCustomActionBean.class);
   HttpServletRequest request = (HttpServletRequest) trip.getRequest();
   hiddenFieldValue = CryptoUtil.encrypt(hiddenFieldValue, request);
   trip.setParameter(StripesConstants.URL_KEY_FIELDS_PRESENT, hiddenFieldValue);</pre>
<p>Für das Testen der Feldvalidierung ist es wichtig, dass die Feldnamen mit dem Wert des <code>&lt;input name="" /&gt;</code> Name Attributes in dem jeweiligen Formular übereinstimmen. Denn es werden tatsächlich nur die Felder validiert, die aus dem decodiertem Inhalt des &#8220;__fp&#8221; Feldes ausgelesen werden können.</p>
<p>Angenommen der oben abgebildete Code wird in der <code>setup()</code> Methode eines Unit Tests ausgeführt und speichert das <code>MockRoundtrip</code> Object in einem privaten Member, könnte ein Beispieltest könnte dann so aussehen:</p>
<pre lang="java">@Test
public void testLogin() throws Exception {
	trip.setParameter("username", "christophbuente");
	trip.setParameter("password", "secret");
	trip.execute("login");
	YourCustomActionBean bean = trip.getActionBean(YourCustomActionBean.class);
	Assert.assertTrue("Destination", "/account", trip.getDestination());
}</pre>
<p>Ich hoffe, dass diese Informationen ausreichen, um sinnvolle Unit Tests für ActionBeans im Wizard Modus zu schreiben. Bei Fragen helfe ich gerne weiter. Und wie immer sind Kommentare sehr willkommen.</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2007-10-23-bots-aussperren-mit-captcha/" rel="bookmark" class="crp_title">captcha tutorial &#8211; Bots aussperren</a></li><li><a href="http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/" rel="bookmark" class="crp_title">canoo webtest &#8211; Web-Anwendungen automatisch testen</a></li><li><a href="http://www.christophbuente.de/2009-05-16-remarkable-rails-anwendungen-automatisch-testen/" rel="bookmark" class="crp_title">Remarkable &#8211; Rails Anwendungen automatisch testen</a></li><li><a href="http://www.christophbuente.de/2007-11-24-advancing-rails-ein-workshop-mit-david-a-black/" rel="bookmark" class="crp_title">Advancing Rails &#8211; Ein Workshop mit David A. Black</a></li><li><a href="http://www.christophbuente.de/2007-08-05-objekte-und-beziehungen/" rel="bookmark" class="crp_title">Objekte und Beziehungen</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>canoo webtest &#8211; Web-Anwendungen automatisch testen</title>
		<link>http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/</link>
		<comments>http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/#comments</comments>
		<pubDate>Sat, 17 Nov 2007 19:53:56 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Java JEE]]></category>
		<category><![CDATA[Software Entwicklung]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[ant]]></category>
		<category><![CDATA[automatisches testen]]></category>
		<category><![CDATA[canoo webtest]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[deployment]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[J2EE]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JEE]]></category>
		<category><![CDATA[junit]]></category>
		<category><![CDATA[unit test]]></category>
		<category><![CDATA[user acceptance test]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/</guid>
		<description><![CDATA[In der Softwareentwicklung haben sich in den letzten Jahren diverse Praktiken des automatisierten Testens etabliert. Die bekannteste Methode dürfte der Unit Test sein. Dabei wird jede Komponente einer Anwendung in einer wohldefinierten Umgebung einzeln abgetestet. In Java lässt sich diese Art Tests sehr einfach mit JUnit und Ant bewerkstelligen. Sollen aber sogenannte Usecases einer Web-Anwendung [...]]]></description>
			<content:encoded><![CDATA[<p>In der Softwareentwicklung haben sich in den letzten Jahren diverse Praktiken des automatisierten Testens etabliert. Die bekannteste Methode dürfte der <a href="http://de.wikipedia.org/wiki/Modultest">Unit Test</a> sein. Dabei wird jede Komponente einer Anwendung in einer wohldefinierten Umgebung einzeln abgetestet. In Java lässt sich diese Art Tests sehr einfach mit <a href="http://www.junit.org/">JUnit</a> und <a href="http://ant.apache.org/">Ant</a> bewerkstelligen. Sollen aber sogenannte Usecases einer Web-Anwendung überprüft werden, stößt man schnell an die Grenzen des machbaren. Schließlich werden Dienste vorausgesetzt, die nur der <acronym title="Java Enterprise Edition" lang="en">JEE</acronym>-Container ( <a href="http://tomcat.apache.org/">Tomcat</a>, <a href="http://www.jboss.org/">JBoss</a>) zur Verfügung stellt. Diese Dienste sind beim Testen einer Einheit ausserhalb des Containers nicht zwangsläufig vorhanden. Oft wird aus diesen Gründen das Testen mit echten Personen durchgeführt. Doch steigt mit der Anzahl der Usecases der Testaufwand exponentiell, was dazu führt, dass nicht alle Tests auf jede neue Version angewendet werden. In diesem Artikel werde ich zeigen, wie eine Web-Anwendung mit Hilfe von <a href="http://webtest.canoo.com">Canoo Webtest</a> automatisch abgetestet werden kann.</p>
<p><span id="more-19"></span></p>
<p>Canoo Webtest bietet dem Benutzer die Möglichkeit, eine Web-Anwendung automatisch zu testen. Basierend auf der <a href="http://ant.apache.org/">Ant</a> Technologie können Tests in einem menschenlesbaren <acronym title="Extensible Markup Language"><a href="http://de.wikipedia.org/wiki/Extensible_Markup_Language">XML</a></acronym>-Dialekt deklariert werden. Ein simples Beispiel könnte so aussehen:</p>
<pre escaped="true" lang="xml">&lt;steps&gt;
  &lt;invoke
    description="Loginseite aufrufen"
    url="/login" /&gt;
  &lt;verifyTitle
    description="Seitentitel sollte folgender sein: "
    text="Anmeldung" /&gt;
  &lt;setInputField
    description="Setze Benutzernamen"
    name="username"
    value="max.mustermann" /&gt;
  &lt;setInputField
    description="Setze Passwort"
    name="password"
    value="mypassword" /&gt;
  &lt;clickButton
    description="Klick den login Knopf"
    name="login" /&gt;
  &lt;verifyTitle
    description="Seitentitel sollte folgender sein:"
    text="Willkommen"  /&gt;
&lt;/steps&gt;</pre>
<p>Doch als erstes muss Canoo Webtest <a href="http://webtest.canoo.com/webtest/manual/Downloads.html">heruntergeladen</a> und installiert werden. Keine Angst, das geht schnell und unkompliziert. Für den produktiven Einsatz empfehle ich die neueste, stabile Version. Von den Snapshots muss ich leider abraten, da beim erzeugen eines Projektes wichtige Dateien fehlten. Nach dem herunterladen der <code>build.zip</code> Datei kann das Archiv in ein beliebiges Verzeichnis entpackt werden. In diesem Verzeichnis muss sich danach ein <code>bin</code> Verzeichnis befinden, welches die Ausführungsskripte für Windows und Unix beinhaltet.</p>
<p>Dieses Verzeichnis muss dem Ausführungspfad bekannt gemacht werden, sodass Canoo Webtest einwandfrei verwendet werden kann.</p>
<ul>
<li>Unter Windows geht das wie folgt:
<ul>
<li>In den Systemeinstellungen (Windows-Taste + Pause) wählt man den Reiter &#8220;Erweitert&#8221;.</li>
<li>Dort drückt man den Knopf &#8220;Umgebungsvariablen&#8221; und fügt eine neue Variable WEBTEST_HOME mit der entsprechenden Pfadangabe hinzu: &#8220;C:\Programme\Webtest&#8221; hinzu.</li>
<li>Anschließend bearbeitet man die Variable &#8220;Path&#8221; und fügt am Ende folgendes an: &#8220;;%WEBTEST_HOME%\bin&#8221;.</li>
</ul>
</li>
<li>Unter Linux/Unix:
<ul>
<li> <code>setenv WEBTEST_HOME /usr/local/webtest</code><br />
<code>setenv PATH $PATH:$WEBTEST_HOME/bin</code> in der <code>~/.login (tcsh)</code></li>
<li> <code>export WEBTEST_HOME=/usr/local/webtest</code><br />
<code>export PATH=$PATH:$WEBTEST_HOME/bin</code> in der <code>~/.bashrc</code> oder <code>~/.bash_login (bash)</code></li>
</ul>
</li>
</ul>
<p>Canoo Webtest generiert auf Wunsch ein Beispielprojekt, mit dem man sofort loslegen kann. Auf der Kommandozeile führt man folgenden Befehl aus:</p>
<p><code>webtest.sh -f pfad/zur/webtest/installation/webtest.xml wt.createProject</code></p>
<p>Während der Ausführung wird man nach einem Projektnamen gefragt. Ein gleichnamiges Verzeichnis wird nach der Eingabe im aktuellen Verzeichnis angelegt. Das neu angelegte Projekt enthält ein paar Beispieltests und Vorlagen. Um die Tests auszuprobieren ruft man lediglich <kbd>webtest.sh</kbd> im Verzeichnis des neu erstellten Projektes auf. Nach Abschluß aller Tests wird im Verzeichnis <code>results</code> ein HTML Report erzeugt, der automatisch angezeigt wird.</p>
<p>Um eigene Tests zu verwenden, löscht man im Verzeichnis <code>tests</code> am besten alle Dateien bis auf <code>allTests.xml</code> und eine weitere beliebige Datei. In der Datei <code>allTests.xml</code> werden alle vorhandenen Testdateien aufgerufen. Sie stellt das Verzeichnis aller auszuführenden Tests dar.</p>
<pre lang="xml">&lt;?xml version="1.0"?&gt;
&lt;project name="allTests" default="test"&gt;
  &lt;target name="test" description="führt alle tests aus"&gt;
    &lt;ant antfile="loginTest.xml"/&gt;
  &lt;/target&gt;
&lt;/project&gt;</pre>
<p>Der anderen Datei gibt man einen sprechenden Namen, in diesem Fall <code>loginTest.xml</code>. In dieser Datei wird ein Testfall deklariert, welcher so aussehen könnte:</p>
<pre escaped="true" lang="xml">&lt;?xml version="1.0"?&gt;
&lt;!DOCTYPE project SYSTEM "../dtd/Project.dtd"&gt;
&lt;project name="loginTest" default="test"&gt;
  &lt;target name="test"&gt;
    &lt;webtest name="loginTest"&gt;
      &amp;config;
      &lt;steps&gt;
        &lt;invoke
          description="Loginseite aufrufen"
          url="/login" /&gt;
        &lt;verifyTitle
          description="Seitentitel sollte folgender sein: "
          text="Anmeldung" /&gt;
        &lt;setInputField
          description="Setze Benutzernamen"
          name="username"
          value="max.mustermann" /&gt;
        &lt;setInputField
          description="Setze Passwort"
          name="password"
          value="mypassword" /&gt;
        &lt;clickButton
          description="Klick den login Knopf"
          name="login" /&gt;
        &lt;verifyTitle
          description="Seitentitel sollte folgender sein:"
          text="Willkommen"  /&gt;
      &lt;/steps&gt;
    &lt;/webtest&gt;
  &lt;/target&gt;
&lt;/project&gt;</pre>
<p>Sicher ist aufgefallen, dass in dieser Datei kein Server deklariert ist, der getestet werden soll. Jedoch steht als erstes Element im &lt;webtest&gt; Element folgender Text: <code>&amp;config;</code><br />
Dieser verweist auf die Datei <code>includes/config.xml</code>, welche alle Einstellungen enthält. Hier können nun der Name, Port und Protokoll des zu testenden Server eingetragen werden:</p>
<pre lang="xml">&lt;!--
Wie alle Dateien dieses Ordners, ist dieser Inhalt als DTD Entität verfügbar (in diesem Fall &amp;config;)
--&gt;;
&lt;config
  host="www.meinserver.de"
  port="8080"
  protocol="http"
  basepath=""
  summary="true"
  saveresponse="true"
  resultfile="${wt.resultfile}"
  resultpath="${wt.resultpath}"
  haltonfailure="false"
  haltonerror="false" /&gt;</pre>
<p>Um eine möglichst vollständige Testabdeckung zu erreichen, fügt man in separaten Dateien für alle erdenklichen Anwendungsfälle Tests hinzu und trägt Sie in die Datei <code>allTests.xml</code> ein. Eine Dokumentation der zur Verfügung stehenden Schritte (Steps) findet man in der Anleitung für die <a href="http://webtest.canoo.com/webtest/manual/syntaxCore.html">Canoo Webtest Core Steps</a>. Über mehr oder weniger generische Anwendungsfälle und deren sinnvolle Abtestung kann gerne in den Kommentaren berichtet werden.</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/" rel="bookmark" class="crp_title">Stripes Framework &#8211; Tests für ActionBeans im Wizard Modus</a></li><li><a href="http://www.christophbuente.de/2007-12-21-voiceglue-konfiguration-server-fur-sprachanwendungen-einrichten/" rel="bookmark" class="crp_title">VoiceGlue Konfiguration &#8211; Server für Sprachanwendungen einrichten</a></li><li><a href="http://www.christophbuente.de/2007-11-25-ruby-on-rails-auf-mac-os-x-5-minuten-kurzanleitung/" rel="bookmark" class="crp_title">Ruby on Rails auf Mac OS X &#8211; 5 Minuten Kurzanleitung</a></li><li><a href="http://www.christophbuente.de/2007-11-24-advancing-rails-ein-workshop-mit-david-a-black/" rel="bookmark" class="crp_title">Advancing Rails &#8211; Ein Workshop mit David A. Black</a></li><li><a href="http://www.christophbuente.de/2010-07-20-rails-hosting-bei-rocket-rentals-auch-mit-staging-umgebung/" rel="bookmark" class="crp_title">Rails Hosting bei Rocket Rentals &#8211; auch mit Staging Umgebung</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>captcha tutorial &#8211; Bots aussperren</title>
		<link>http://www.christophbuente.de/2007-10-23-bots-aussperren-mit-captcha/</link>
		<comments>http://www.christophbuente.de/2007-10-23-bots-aussperren-mit-captcha/#comments</comments>
		<pubDate>Tue, 23 Oct 2007 07:30:55 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Java JEE]]></category>
		<category><![CDATA[Software Entwicklung]]></category>
		<category><![CDATA[bot aussperren]]></category>
		<category><![CDATA[captcha]]></category>
		<category><![CDATA[integration]]></category>
		<category><![CDATA[J2EE]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[jcaptcha]]></category>
		<category><![CDATA[JEE]]></category>
		<category><![CDATA[jsp]]></category>
		<category><![CDATA[session id]]></category>
		<category><![CDATA[Web Entwicklung]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/2007/10/23/bots-aussperren-mit-captcha/</guid>
		<description><![CDATA[Vergangene Woche habe ich für einen aktuellen Kunden eine Funktion implementiert, die es dem Nutzer einer Webseite erlaubt, anderen diese Seite per E-Mail zu empfehlen. Dies öffnete Spammern potentiell Tür und Tor. Dem Kunden war besonders daran gelegen, sogenannte Bots auszusperren, die diese Funktion in sehr hoher Frequenz ausführen können. [ad#vert-banner] Als Mittel der Wahl [...]]]></description>
			<content:encoded><![CDATA[<p>Vergangene Woche habe ich für einen aktuellen Kunden eine Funktion implementiert, die es dem Nutzer einer Webseite erlaubt, anderen diese Seite per E-Mail zu empfehlen. Dies öffnete Spammern potentiell Tür und Tor. Dem Kunden war besonders daran gelegen, sogenannte Bots auszusperren, die diese Funktion in sehr hoher Frequenz ausführen können.</p>
<p><span id="more-16"></span></p>
<p>[ad#vert-banner]</p>
<p>Als Mittel der Wahl hat sich seit Jahren das sogenannte <acronym title="Completely Automated Public Turing Test to tell Computer and Humans Apart" lang="en"><a href="http://de.wikipedia.org/wiki/Captcha">CAPTCHA</a></acronym> etabliert. Dabei werden Bilder erzeugt, die auf unebendem Hintergrund verzerrte Zeichen darstellen. Der Benutzer muss diese sogenannte Challenge lösen, in dem er die Zeichenfolge in ein Formularfeld eingibt. Stimmen die eingegebenen Zeichen und die Zeichen auf dem Bild übereinstimmen. Hier macht man sich zu nutze, dass der Mensch besser abstrahieren kann, und die Buchstaben trotz widriger Umstände erkennen kann, wo eine Maschine scheitert.</p>
<p>Da ich das Rad nicht neu erfinden will, habe ich mich einer Bibliothek bedient, die diese Funktionalität zur Verfügung stellt, und sehr einfach in das aktuelle Projekt einzubinden ist. Meine Wahl fiel auf <a href="http://jcaptcha.sourceforge.net/">JCaptcha</a>. Wie der Name vermuten läßt ist JCaptcha eine Java basierte Implementierung. Sie läßt sich in Java basierte Webprojekte sehr einfach integrieren.</p>
<p>Folgende Schritte sind notwendig:</p>
<ol>
<li>Implementierung eines eigenen Servlets, welches mit der Auslieferung der generierten Bilder beauftragt wird.</li>
<li> Eintrag des Servlets und des Mappings in die web.xml</li>
<li> Implementierung eines Singleton, welches den CaptchaService zur Verfügung stellt.</li>
<li>Erstellen einer validierungsmethode zum Überprüfen der Benutzereingabe.</li>
</ol>
<p>Der generelle Ablauf ist recht einfach. Das in der JSP mit Hilfe des &lt;img src=&#8221;img.captcha&#8221;&gt; referenzierte Bild wird von unserem Servlet bedient. Dieses erzeugt mit Hilfe des CaptchaService ein neues Bild und gibt es als jpg zurück.  Der CaptchaService speichert in einer internen Map unter dem Schlüssel der jeweiligen session id das captcha response. Die Methode validateResponseForID des CaptchaService prüft, ob der eingegebene Text mit dem Bild übereinstimmt.</p>
<p>Das Servlet wird wie folgt implementiert:</p>
<pre lang="java">
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Locale;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;
import com.octo.captcha.service.CaptchaServiceException;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/**
 * @author Christoph Bünte
 *
 * @version $Revision: 1.5 $
 *
 * @created 11.10.2007
 *
 */
public class CaptchaServlet extends HttpServlet {

/**
  * The serial id for this class
  */
 private static final long serialVersionUID = -5555311841435084305L;

@Override
 public void init(ServletConfig servletConfig) throws ServletException {

super.init(servletConfig);
}

/**
  * Generiert das Captcha image in form eines bytearrays
  *
  * @param token
  *            SessionId
  * @param loc
  *            Locale Object für die Sprache des Benutzers
  * @return
  */
 private synchronized byte[] getCaptchaChallenge(String token, Locale loc) {

byte[] captchaChallenge = null;

try {
 		// create the captcha challenge
 		BufferedImage challenge = CaptchaServiceSingleton.getInstance()
 				.getImageChallengeForID(token, loc);

		// transform image data into jpeg byte array
 		ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();

		JPEGImageEncoder jpegEncoder = JPEGCodec
 				.createJPEGEncoder(jpegOutputStream);

		jpegEncoder.encode(challenge);

		captchaChallenge = jpegOutputStream.toByteArray();

 	} catch (IOException e) {
 		//TODO: Fehlerbehandlung
 	} catch (CaptchaServiceException e) {
 		//TODO: Fehlerbehandlung
 	}

	return captchaChallenge;

 }

@Override
 protected void doGet(HttpServletRequest request,

 		HttpServletResponse response) throws ServletException, IOException {

			// Hier benutzen wir die session id und das Locale Objekt des requests
			String token = request.getSession().getId();
 			Locale loc = request.getLocale();

			// hole das CAPTCHA challenge als JPEG
			byte[] captchaAsJpeg = getCaptchaChallenge(token, loc);

			// falls nicht verfügbar, sende http code 404 (resource not available)
			if (captchaAsJpeg == null) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
			}

			// gibt ein response Objekt mit dem jpeg bild zurück
			// (für den browser: benutze keinen cache und verwirf den inhalt sofort)
			// set header

			response.setHeader("Cache-Control", "no-store");
			response.setHeader("Pragma", "no-cache");
			response.setDateHeader("Expires", 0);
			response.setContentType("image/jpeg");

			// sende bilddaten
			ServletOutputStream responseOutputStream = response.getOutputStream();
			responseOutputStream.write(captchaAsJpeg);
			responseOutputStream.flush();
			responseOutputStream.close();
 		}

}</pre>
<p>Der Eintrag in die web.xml sieht so aus:</p>
<pre class="xml">
&lt;servlet&gt;
	&lt;servlet-name&gt;jcaptcha&lt;/servlet-name&gt;
	&lt;display-name&gt;JCaptcha&lt;/display-name&gt;
	&lt;servlet-class&gt;your.package.path.CaptchaServlet&lt;/servlet-class&gt;
	&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
&lt;/servlet&gt;</pre>
<pre class="xml">&lt;servlet-mapping&gt;
	&lt;servlet-name&gt;jcaptcha&lt;/servlet-name&gt;
	&lt;url-pattern&gt;*.captcha&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;</pre>
<pre class="xml"></pre>
<p>Das CaptchaService als Singleton implementiert:</p>
<pre lang="java">
import com.octo.captcha.engine.image.ListImageCaptchaEngine;
import com.octo.captcha.service.captchastore.CaptchaStore;
import com.octo.captcha.service.captchastore.FastHashMapCaptchaStore;
import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;
import com.octo.captcha.service.image.ImageCaptchaService;</pre>
<pre lang="java">/**
 *
 * @author Christoph Bünte
 *
 * @version $Revision: 1.6 $
 *
 * @created 11.10.2007
 *
 */
public class CaptchaServiceSingleton {

	private static ImageCaptchaService instance = new DefaultManageableImageCaptchaService();

	/**
	  * Gibt die einzige Instanz einnes CaptchaService zurück.
	  *
	  * @return
	  */
	 public static ImageCaptchaService getInstance() {

		return instance;
	 }
}</pre>
<p>Im Formular für die Eingabe der Captcha Response, wird das Bild wie folgt eingebunden:</p>
<pre class="html">
&lt;img src="img.captcha"  class="captcha"&gt;</pre>
<p>Je nachdem, welches Webframework man verwendet, differiert die Implementierung einer Validierungsmethode. Letztlich könnte sie wie folgt aussehen. Es ist ein Beispiel, wie es im <a href="http://mc4j.org/confluence/display/stripes/Home">stripes framework</a> verwendet wird.</p>
<pre lang="java">/**
 * Validating capture input
 *
 * @param errors
 */
@ValidationMethod(on = { "sendRecommendation" }, when = ValidationState.ALWAYS)

public void validateCaptcha(ValidationErrors errors) {
 try {
 	String sessionId = context.getRequest().getSession().getId();
 	if (!CaptchaServiceSingleton.getInstance().validateResponseForID(
 			sessionId, captchaResponse)) {
 		errors.add("captchaResponse", new LocalizableError(
 				"captchaResponse.invalidValue"));
 	}
 } catch (CaptchaServiceException e) {
 	// ungültige Session id, Fehlerbehandlung
 }
}</pre>
<p>Das Ergebnis ist schon ganz ansehnlich. Im Formular wird mit der Default Engine ein mehr oder weniger schönes Captcha Bild erzeugt. Jedoch fällt Ästheten sofort auf, dass das Bild überhaupt nicht in das Design der Seite passt. Um solche Anforderungen zu erfüllen, implementiert man am besten eine CaptchaEngine. Dazu müssen wir die Singleton Klasse noch etwas modifizieren, doch das schnell erledigt. Wird sind doch agil, oder?</p>
<p>Die unten gezeigte Engine erzeugt ein Captcha Bild mit hellblauem Hintergrund und grauer und schwarzer Schrift. Für die Schrift werden nur Großbuchstaben verwendet. Man hätte als Alternative auch die Informationen zum Rendern des Bildes auch direkt in der Singleton Klasse zu einer vorhandenen Engine hinzufügen können. Unangenehmer Nebeneffekt ist aber, dass es immer eine Default engine gibt. Beim Rendern der Bilder wird dann eine verfügbare Engine zufällig ausgewählt, so dass in unregelmässigen Abständen eine Bild mit grün-rot-gelb gesprenkeltem Hintergrund erscheint. Dieses, für mich anfänglich, merkwürde Verhalten hat mich zwei Stunden aufgehalten. Wer unterschiedliche Captcha Bilder erzeugen möchte implenentiert entsprechend mehr Klassen. Hier ein Beispiel:</p>
<pre lang="java">
import java.awt.Color;import com.octo.captcha.component.image.backgroundgenerator.BackgroundGenerator;
import com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator;
import com.octo.captcha.component.image.fontgenerator.DeformedRandomFontGenerator;
import com.octo.captcha.component.image.fontgenerator.FontGenerator;
import com.octo.captcha.component.image.textpaster.RandomTextPaster;
import com.octo.captcha.component.image.textpaster.TextPaster;
import com.octo.captcha.component.image.wordtoimage.ComposedWordToImage;
import com.octo.captcha.component.image.wordtoimage.WordToImage;
import com.octo.captcha.component.word.wordgenerator.RandomWordGenerator;
import com.octo.captcha.component.word.wordgenerator.WordGenerator;
import com.octo.captcha.engine.CaptchaEngine;
import com.octo.captcha.engine.image.ListImageCaptchaEngine;
import com.octo.captcha.image.ImageCaptchaFactory;
import com.octo.captcha.image.gimpy.GimpyFactory;

/**
 * Das ist unsere spezielle {@link CaptchaEngine} Implementierung.
 * @author Christoph Bünte
 *
 * @version $Revision: 1.2 $
 *
 * @created 12.10.2007
 *
 */
final public class CustomCaptchaEngine extends ListImageCaptchaEngine {

@Override
 protected void buildInitialFactories() {

// RGB: #4C4E42
 	Color textColor = new Color(0x4c, 0x4e, 0x42);
 	Color textColor2 = Color.BLACK;
 	Color[] colors = new Color[2];
 	colors[0]=textColor;
 	colors[1]=textColor2;

// RGB: #F4F8FB
 	Color backgroundColor = new Color(0xf4, 0xf8, 0xfb);

// Einheitlicher Hintergrund mit der gegeben Breite, Höhe und Farbe
 	BackgroundGenerator bgGenerator = new UniColorBackgroundGenerator(250,
 			70, backgroundColor);

// Der Text hat die Min-und Maxlänge mit den gegeben Farben
 	TextPaster paster = new RandomTextPaster(6, 6, colors);

// Schriftgrößen 10 bis 30
 	FontGenerator fontGenerator = new DeformedRandomFontGenerator(
 			10, 30);

// Benutze einen einfachen Mechanismus zum Mischen von Hintergrund und Text
 	WordToImage wordToImage = new ComposedWordToImage(fontGenerator,
 			bgGenerator, paster);

// Benutze zufällige Worte aus den gegebenen Buchstaben
 	WordGenerator wordGenerator = new RandomWordGenerator(
 			"ABCDEFGHIJKLMNOPQRSTUVWXYZ");

ImageCaptchaFactory factory = new GimpyFactory(wordGenerator,
 			wordToImage);

 	this.addFactory(factory);
 }
}</pre>
<p>Und hier die neue Singleton Implementierung:</p>
<pre lang="java">
import com.octo.captcha.engine.image.ListImageCaptchaEngine;
import com.octo.captcha.service.captchastore.CaptchaStore;
import com.octo.captcha.service.captchastore.FastHashMapCaptchaStore;
import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;
import com.octo.captcha.service.image.ImageCaptchaService;

/**
 *
 * @author Christoph Bünte
 *
 * @version $Revision: 1.6 $
 *
 * @created 11.10.2007
 *
 */

public class CaptchaServiceSingleton {

	private static ImageCaptchaService instance = initializeService();

	/**
	  * Gibt die einzige Instanz einnes CaptchaService zurück.
	  *
	  * @return
	  */
	 public static ImageCaptchaService getInstance() {
	 	return instance;
	 }

	/**
	  * Initialisiert den {@link ImageCaptchaService}
	  *
	  * @return
	  */
	 private static ImageCaptchaService initializeService() {

	// Wir brauchen eine Instanz unser eigenen Engine
	 	ListImageCaptchaEngine engine = new CustomCaptchaEngine();

		CaptchaStore captchaStore = new FastHashMapCaptchaStore();
 		captchaStore.empty();

		ImageCaptchaService service = new DefaultManageableImageCaptchaService(
 			captchaStore, engine, 180, 100000, 75000);

		return service;

 	}

}</pre>
<p>Kommentare und Anregungen sind wie immer willkommen. Beispiele für die kreativsten Bilder nehme ich auch gerne entgegen. Viel Spass beim ausprobieren.</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/" rel="bookmark" class="crp_title">Stripes Framework &#8211; Tests für ActionBeans im Wizard Modus</a></li><li><a href="http://www.christophbuente.de/2007-11-10-paypal-subscriptions-abonnements-in-java-integrieren/" rel="bookmark" class="crp_title">Paypal subscriptions &#8211; Abonnements in Java integrieren</a></li><li><a href="http://www.christophbuente.de/2007-12-21-voiceglue-konfiguration-server-fur-sprachanwendungen-einrichten/" rel="bookmark" class="crp_title">VoiceGlue Konfiguration &#8211; Server für Sprachanwendungen einrichten</a></li><li><a href="http://www.christophbuente.de/2008-08-04-bilder-versehentlich-geloscht-urlaubserinnerungen-ganz-leicht-zuruckholen/" rel="bookmark" class="crp_title">Bilder versehentlich gelöscht &#8211; Urlaubserinnerungen ganz leicht zurückholen</a></li><li><a href="http://www.christophbuente.de/2009-10-29-opensocial-gadgets-apps-fur-studivz-selbst-entwickeln/" rel="bookmark" class="crp_title">Opensocial Gadgets &#8211; Apps für StudiVZ selbst entwickeln</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2007-10-23-bots-aussperren-mit-captcha/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Minified using memcached (Feed is rejected)
Page Caching using memcached
Database Caching 10/31 queries in 0.049 seconds using memcached
Object Caching 968/1037 objects using memcached

Served from: www.christophbuente.de @ 2012-02-08 10:26:43 -->
