前回「
独自 CAPTCHA アプリケーションの開発」へ
CAPTCHA を Web サービスから配布または呼び出す方法
Webサービスとは、URIでアドレスを指定できるソフトウェアコンポーネントである。Webサービスでは、さまざまなニッチな需要を満たす多様なサービスを提供することができる。ここでは、他のアプリケーションのためにCAPTCHA画像を提供するWebサービスの作成方法について説明する。具体的には、Webメソッドの公開、BLOB(binary large object)またはBase64エンコードデータの転送、Webメソッドの呼び出しを行うためのテクニックを紹介する。
これから説明するのはcaptchaWebServiceというWebサービスである。このWebサービスには、getCaptcha、selectWord、generateImageという3つの公開メソッド(Webメソッド)が含まれている。.NET Frameworkには、メソッドシグネチャを表すasmxファイルを調べる機能がある。invoke.aspxと同様に、このWebサービスは、画像を使ったユーザー確認を必要とするどのWebアプリケーションからでも実行できる。この機能はASP.NET以外からも利用可能である。これはWebサービスであるため、実質的にすべてのプログラミング言語およびプラットフォームに対するクロスプラットフォームサポートを提供している。
このプラットフォーム非依存のコンポーネントアーキテクチャには、もう1つ大きな利点がある。それは、CAPTCHアルゴリズムはボットに対抗して一元的に修正、拡張、強化することができ、すべてのクライアントがその恩恵を受けることができるということだ。CAPTCHAアプリケーションサービスプロバイダは、このサービスを低価格で提供してもよいし、定額制または従量制で提供してもよい。どのような形で提供するかはビジネスプロセスモデルしだいである。
関数に関するWSDL(web service description language)ファイルについてはここを参照。このファイルには、関数シグネチャとパラメータの詳細がそれぞれのデータ型と共に記されている。
関数getCaptcha、selectWord、generateImageはそれぞれ個別に実行できるが、内部的にはお互いを呼び出しながら処理を行っている。
selectWord()
[WebMethod(Description="Get an Word from OGDEN’s dictionary")]
public String selectWord ()
このWebメソッドは、既に紹介したものによく似ている。唯一異なるのは、WebMethod属性を公開して、外部から呼び出せるようにしている点だ。このメソッドは辞書データベースに接続し、次の図のようにランダムな単語を文字列として返す。
図3.5:selectWordメソッドを呼び出した結果
selectWord Webメソッドに関するSOAP要求および応答の詳細を見るには、ここをクリック。このメソッドはGET、POST、SOAP要求を通じて個別に呼び出すことができる。
selectWordを再度呼び出した結果は次の通り。返される単語が変わっていることに注意。
図3.4:selectWordメソッドを再び呼び出した結果
generateImage()
[WebMethod(Description="Generates a CAPTCHA Image and returns filename")]
public String generateImage ()
このメソッドは画像を生成し、物理的にディスクに格納する。画像の生成に成功すると、次の図のようにファイル名を返す。
getCaptcha()
[WebMethod(Description="Returns a CAPTCHA Image in Base64-Encoding")]
publicbyte[] getCaptcha()
getCaptchaは、このWebサービスの統合機能を実現するコアメソッドである。CAPTCHAの機能を必要とするアプリケーションは、このメソッドを呼び出すことになる。このメソッドは、クライアントに対して画像をストリーミングするバイト配列を返す。invoke.aspxはこのWebメソッドを呼び出して、提供されたCAPTCHAを取得する。
図3.7:invoke.aspxとgetCaptchaメソッドの使用例
次の図は、invoke.aspxを何度か実行した結果である。さまざまな種類のCAPTCHA画像が描画されることに注目してほしい。
本稿ではこのWebサービスのすべてのソースコードを提供しているので、自由に実装したり、試したりしてほしい。ただし、このサンプルではプロキシクラスとアセンブリをprocess.batで生成している。これは、Visual Studio.NETを使わずにプロキシクラスを生成する人にとっては便利な方法である。このプロセスは次のようになる。
次にprocess.bat、invoke.aspx、captchaWebService.asmxのコードリストを示す。コードの詳細についてはコメントを見てほしい。
リスト:process.bat
wsdl /l:cs /o:captchaWebService.cs http://localhost/captchawebservice/captchaWebService.asmx?WSDL
/n:captchaWebService
csc /out:captchaWebService.dll /t:library /r:system.web.dll,system.xml.dll,system.web.services.dll
captchaWebService.cs
リスト:invoke.aspx
<%@ Page Language="c#" debug="True" %>
<%@ Import namespace="captchaWebService" %>
<script language="c#" runat="server">
publicvoid Page_Load(System.Object sender,System.EventArgs e)
{
Page.Response.BinaryWrite(new captchaWebService().getCaptcha());
}
</script>
リスト:captchaWebService.asmx
<%@ webservice class="captchaWebService" language="c#" %>
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using System.IO;
using System.Data.OleDb;
using System.Drawing;
using System.Drawing.Imaging;
///<summary>
/// This is the basic Service for CAPTCHA provision.
///</summary>
[WebService(Namespace="http://axisebusiness.com/webservices/")]
publicclass captchaWebService : System.Web.Services.WebService
{
[WebMethod(Description="Returns a CAPTCHA Image in Base64-Encoding")]
publicbyte[] getCaptcha()
{
return getBytesFromRaster(Server.MapPath(generateImage()));
}
// Returns byte from Image file
publicbyte[] getBytesFromRaster(string filename)
{
if(File.Exists(filename))
{
try
{
FileStream s =File.OpenRead(filename);
byte[] bytes = newbyte[s.Length];
s.Read(bytes, (int)0, (int)s.Length);
return bytes;
}
catch(Exception e)
{
returnnewbyte[0];
}
}
else
{
returnnewbyte[0];
}
}
[WebMethod(Description="Generates a CAPTCHA Image and returns filename")]
public String generateImage ()
{
//Reading the parameter from session this time
String strText = selectWord ();// = Session("param")
//Create the memory map
Bitmap raster;
System.Drawing.Imaging.PixelFormat pixFormat =
System.Drawing.Imaging.PixelFormat.Format32bppArgb;
// Select an memory image from file of 290x80px
// in the backgrounds folder named backX.jpg
Graphics graphicsObject;
System.Drawing.Image imageObject =
System.Drawing.Image.FromFile(Server.MapPath(@"backgroundsack" +
new Random().Next(9) + ".jpg"));
// Creating the raster image object
raster = new Bitmap (imageObject);
//Creating graphics object
graphicsObject = Graphics.FromImage(raster);
// Instantiate object of brush with black color
SolidBrush objBrush = new SolidBrush(Color.Black);
Font objFont;
int a;
String myFont, str;
//Creating an array for most readable yet cryptic fonts for OCR’s
// This is entirely up to developer’s discretion
String[] crypticFonts = new String[11];
crypticFonts [0] = "Arial";
crypticFonts [1] = "Verdana";
crypticFonts [2] = "Comic Sans MS";
crypticFonts [3] = "Impact";
crypticFonts [4] = "Haettenschweiler";
crypticFonts [5] = "Lucida Sans Unicode";
crypticFonts [6] = "Garamond";
crypticFonts [7] = "Courier New";
crypticFonts [8] = "Book Antiqua";
crypticFonts [9] = "Arial Narrow";
crypticFonts [10] = "Estrangelo Edessa";
//Loop to write the characters on image
// with different fonts.
for (a=0; a<=strText.Length-1; a++)
{
myFont = crypticFonts[new Random().Next(a)];
objFont = new Font(myFont, 20, FontStyle.Bold);
str = strText.Substring(a, 1);
graphicsObject.DrawString(str, objFont, objBrush, a*20, 35);
graphicsObject.Flush();
}
String filename= new Random().Next().ToString() + ".gif";
raster.Save(Server.MapPath(filename), System.Drawing.Imaging.ImageFormat.Gif);
raster.Dispose();
graphicsObject=null;
return filename;
} // End of Function
// *************************************
// Select word web method
// Return type String of random word from dictionary
// Dictionary is based on OGDEN’s BASIC ENGLISH
// http://ogden.basic-english.org/basiceng.html
[WebMethod(Description="Get an Word from OGDEN’s dictionary")]
public String selectWord ()
{
// The Connection string referencing the MDB file
String ConnectionString = "Provider=Microsoft.Jet.OleDb.4.0;Data Source="
+ Server.MapPath("dictionary.mdb") + ";";
// Datareader object
OleDbDataReader objReader;
// Creating an array of 26 characters (alphabets in dictionary database as columns)
char[] columns = newchar[26];
// Adding the column names in the array
// uses the ASCII character conversion for selecting values
// from A- Z
for (int a=65; a<65+26; a++)
columns[a-65] = (char)a;
// Query String for selecting a random column from spelling list database
String QuerySQL = "SELECT " + columns[(new Random().Next(26))] +
" FROM spellList";
// Opening the connection
OleDbConnection objConn = new OleDbConnection(ConnectionString);
// Creating new command object
OleDbCommand objCmd = new OleDbCommand();
// Assigning command text
objCmd.CommandText = QuerySQL;
// Assigning the connection to command object connection attribute
objCmd.Connection = objConn;
// Instantiating a random class object
Random randomSeed = new Random();
// Creating a random seed selector
int randomSeedSelector=0;
// An string character with maximum capacity for dictionary column
String[] selectedIndex = new String[700];
String str = "";
// This code segment opens the connection and read the dictionary
try
{
objConn.Open();
objReader = objCmd.ExecuteReader();
while (objReader.Read())
{
str = objReader.GetValue(0).ToString();
if (str.Length != 0)
{
selectedIndex[randomSeedSelector] =str;
randomSeedSelector++;
}
}// Ends While
str = selectedIndex[randomSeed.Next(randomSeedSelector)];
} // Ends Try
catch (Exception Err)
{
// The Error Catching operations
}
finally
{
objConn.Close();
}
// Returns the selected string
return (str);
}// ends web method
} // Ends Class
次回「
まとめ」へ