JavaのCaptchaライブラリのサンプルでは、画像を返すServletをサンプル実装としていることが多いですが、Liftのような独自のURLマップを持つフレームワークだといろいろ面倒。
ここでは、Liftらしい方法でCaptchaを実装します。
Liftでバイナリデータをレスポンスする方法
LiftWiki:StreamingResponse
に書いてありますが、これだけではなんのことかわからない。。
あるURLに対してあるレスポンスを返すには、
LiftRules.dispatch.appendを使います。
class Boot{
LiftRules.dispatch.append {
case Req( "captcha" :: Nil, _, _ ) => Captcha.challenge _
}
}
LiftRules.dispatch.appendの引数の型は、
PartialFunction[Req, () => Box[LiftResponse]]
URLパターンのcaseに対して、"Box[LiftResponse]を返す関数"を指定してやればよい。LiftResponseのサブタイプにはいろいろ便利ラッパがあるのでチェック。バイナリデータを応答するには、StreamingResponse(BasicResponseのサブタイプ)を使う。
(後述のCaptcha#challengeがそのサンプル)
※ちなみにこのDispatchingの仕組みを使った、RESTの実装方法がまとまっているので要チェックです。
LiftWiki:REST Web Services
Captchaの実装
JCaptchaを利用した。
JCaptcha: 5 minutes application integration tutorial
但し、バージョン1.0付属のCaptchaエンジンでは(泣けるほど)ろくな画像を生成してくれないので、バージョン2.0-alphaを使う。
JCaptcha:Simple Servlet Integration documentation
の下の「Jcaptcha 2.0 jars」からダウンロード。HotmailEngineか、GmailEngineあたりが見やすいのではないでしょうか。
上記の情報を総合して、Lift+Scalaを使った、Captchaの実装はこんな感じになります。
import com.octo.captcha.service.image._
import com.octo.captcha.service.captchastore._
import com.octo.captcha.engine.image.gimpy._
import com.sun.image.codec.jpeg._
import java.io._
import java.util.Locale._
import net.liftweb.common._
import net.liftweb.http._
object Captcha {
lazy val service =
new DefaultManageableImageCaptchaService(
new FastHashMapCaptchaStore, new GmailEngine,
180,100,75)
def getCaptcha:Box[ Array[Byte] ] = S.session match {
case Full(session)=>
val jpeg = new ByteArrayOutputStream
val id = session.uniqueId
val challenge = service.getImageChallengeForID(id, JAPAN)
JPEGCodec.createJPEGEncoder(jpeg).encode(challenge)
Full( jpeg.toByteArray )
case _ => Empty
}
//Captchaを生成してレスポンス
def challenge: Box[LiftResponse] = {
getCaptcha match{
case Full( capture ) =>
val headers = ("Content-type" -> "image/jpeg") ::
("Cache-Control" -> "no-store") ::
("Pragma", "no-cache") ::
("Expires", "0" ) ::
("Content-length" -> capture.length.toString) :: Nil
Full(StreamingResponse( new java.io.ByteArrayInputStream(capture),
() => {}, capture.length, headers, Nil, 200) )
case _ => Empty
}
}
//ユーザー入力が正しいかチェック
def validate(response:String):Boolean = S.session match{
case Full(session) =>
service.validateResponseForID(session.uniqueId,response)
.asInstanceOf[Boolean]
case _ => false
}
}