Firefox 2.0 検索プラグインの作り方(Suggest の実現)
さあ、Firefox検索プラグインの記事も大詰め。いよいよ Firefox 2.0 特有の機能である Suggest (検索後候補の表示)機能の仕組みに迫ってみたいと思います。そして、最後は(多少変則的なものではありますが ^^:) Suggest 機能付きのFirefox検索プラグインを実装してみたいと思います。実は意外と応用範囲の広い機能だと思いますので、ぜひ読んでみてください。
今回はサーバー側で CGI を組む必要がある関係上、 Ruby を使用しています。それほど複雑なことはしていないので、 Perl などの言語に読み替えることは難しくないと思いますが、ご了承くださいませ。
なお、毎回の但し書きで申し訳ありませんが(^^;、この記事の執筆時点では Firefox 2.0 の Beta 1 しかリリースされていません。正式版でなんらかの変更が加わる場合もありますので、その際はご容赦ください。
Suggest 機能の仕組み
まずは、 Suggest 機能が有効な検索エンジンに検索語が入力された際に、 Firefox がどのようにサーバーと通信しているかを理解しておきましょう。 CGI スクリプトが作れる程度の知識があれば簡単ですので、お気軽に。
ユーザーが Suggest をサポートした検索プラグインを選択して検索窓になんらかの文字列を打ち込んだとき、 Firefox は以下のような方法でサジェストを表示しています。
- 検索プラグインの Url タグの情報をもとにして、サーバーに HTTP リクエストを送る(必ず GET メソッドが使われます)。
- リクエストを受けたサーバーは、リクエストに含まれる検索語とそれに対応する Suggest の内容をレスポンスとして返信する。
- レスポンスを受け取った Firefox は、レスポンスに含まれる検索語が現在検索窓にある文字列と一致するかを検証する。もし一致しなかった場合、そのレスポンスは破棄する。
- 一致したなら、レスポンスに含まれる Suggest の内容を検索窓のドロップダウンメニューとして表示する。
(1) はとくに問題ないでしょう。 Suggest に使用する Url タグの記述方法は後述します。最初のポイントは (2) のサーバーからのレスポンスの形式です。詳細は後述しますが、BODY に以下のような JSON 記法の文字列を格納することになっています。
[ "検索語", [ "Suggest1", "Suggest2", ...] ]
検索語はクライアントから渡された検索文字列、 Suggest1 などは Suggest として検索窓のドロップダウンに表示する文字列です。とてもシンプルですね。
もうひとつのポイントは (3) でレスポンスに含まれる検索語が検証されることです。レスポンスできちんと検索語を返さないと、なんの表示もなく無視されてしまうので注意してください。ここで検証を入れる理由は、サーバーからのレスポンスが返る前に検索窓の内容が変更された場合に、無意味な Suggest を表示しないためだと思います。この検証をパスすれば、 (4) で Suggest が表示され、めでたく処理完了です。
以上が、 Firefox の Suggest 機能の概要です。実際に解析したわけではないので嘘が混じっているかもしれませんが、概ねこんな感じのはずです(笑)。この流れを頭に入れておけば、実装作業でトラブルがあった場合にも対処しやすいと思います。
検索プラグインでの Suggest の実装方法
だいたいの仕組みがわかったところで、実装方法をご紹介していきましょう。この節では、どのプラグインでも共通の概念部分をご説明し、次の節で実際にプラグインを実装する例をご紹介します。ということで、まずは概念的なところから。
通常、 Suggest 機能をサポートしたFirefox検索プラグインの実装は、以下の手順で進めることになると思います。
- Suggest 機能なしのFirefox検索プラグインを作成する。
- 作ったプラグインに Suggest 用の Url タグを追加する。
- サーバー側で Suggest のクエリーに応答する CGI スクリプトを実装する。
(1) の作業については基礎編、実践編でご紹介していますので、ここでは (2) 以降について具体的な内容を見ていこうと思います。
Suggest 用の Url タグを追加する
Suggest 機能をサポートしたFirefox検索プラグインには、 Url タグを 2 つ記述する必要があります。ひとつは type="text/html" で、通常の検索結果の表示に使う URL を指定します。これについてはこれまでに何度も出てきていますので、ここでは触れません。
もうひとつが Suggest 用の Url タグで、概ね以下のように記述します。
<Url type="application/x-suggestions+json" method="GET" template="クエリーURL" />
Suggest 用の Url タグは、検索結果表示用のものとは以下の点で異なっています。
- type 属性には "application/x-suggestions+json" を指定する。
- method 属性は "GET" に固定。 POST メソッドによる Suggest クエリーはサポートされていない。
- 上記のとおり Param タグは使わないので、子要素は存在しません。
とくに重要な相違点は (3) の CGI パラメータの指定方法です。例えば、通常のクエリーでは以下のように記述するような URL であれば、
<Url type="text/html" method="GET" template="http://www.site.com/"> <Param name="q" value="{searchTerms}"/> <Param name="p1" value="v 1"/> <Param name="p2" value="v 2"/> </Url>
Suggest 用の Url に記述する場合はこのようになります。
<Url type="application/x-suggestions+json" method="GET" template="http://www.site.com/suggest.cgi?q={searchTerms}&p1=v+1&p2=v+2"/>
上記の例でも行っていますが、 Suggest 用の指定では CGI パラメータの名前、値の両者を URL エンコードしなければいけません。空白は '+' に、一部の記号や日本語などは "%nn" に・・・っていう、アレですね。半角英数字のみであれば問題ありませんが、記号や日本語が含まれる場合は注意してください。検索語の文字コード変換(InputMethod タグで指定した文字コードに変換されます)や URL エンコードは自動で処理されますのでご心配なく。
Suggest クエリーに応答する CGI を用意する
次は、 Suggest のクエリーに応答する CGI スクリプトを用意します。こちらの実装は検索プラグインによってまちまちなので、具体的な実装例は次節を参照してください。ご自分で実装するときには、以下の点に留意すると良いと思います。
- HTTP レスポンスヘッダの Content-type は "text/javascript; charset=文字コード" にする。文字コードはプラグインの InputEncoding に合わせること。
- レスポンスの中身には前述の JSON 記法の配列のみを記述し、タグなどの余計な文字は含めない。
- クエリーで渡される検索語は URL エンコードされているので、レスポンスにはそれをデコードしてたものを渡す必要がある。
- 文字列に含まれる二重引用符や "\" は適切にエスケープすること。
最も簡単なレスポンスの例として、検索語が 「abc」、 Suggest が 「dir\abc」、「dir\"abc"」、「"dir\abc"」の 3 つで、文字コードが UTF-8 だった場合は以下のようになります。
Content-type: text/javascript; charset=utf-8 [ "abc" [ "dir\\abc", "dir\\\"abc\"", "\"dir\\abc\"" ] ]
実際にはさらにいくつかのヘッダが追加されると思います。ヘッダの出力や文字列のエスケープなどは、使用するスクリプト言語にサポート機能があれば、そちらを使うのが賢明でしょう。
実装してみる
ここまでで Suggest 機能の実装に必要な知識は揃ったことと思います。今度は実際に Suggest 機能付きのFirefox検索プラグインを作ってみましょう。とはいえ、 Suggest 機能付きの検索サービスなんて、まともに作るのは途方もなく大変です。とてもこの記事だけでご紹介できるような作業ではありません。ここは発想を転換しましょう。 Suggest 機能と名が付いていても、別に検索語の候補表示にしか使ってはいけないという法はありません。前述のとおり、実体は単なる HTTP リクエストにすぎませんから、「ある文字列を受け取って他の(いくつかの)文字列を返答する」というサービスであればなんでも Suggest 機能で表示することができます。
こう考えればできることはいくらでも思いつきますが、 Ruby で簡単に実現できるということで、与えられた文字列を HTML エスケープして返すというサービスを実装してみることにしました。「R&R」を「R&R」に変換、とかいう感じですね。 Ruby なら CGI.escapeHTML メソッド一発ですし、他の言語でも同様な関数は存在すると思われるので、移植も楽だと思います。それでは、さっそく実装に入りましょう。
検索プラグイン
まずはFirefox検索プラグインの XML ファイルを作成します。基本部分の作成方法は基礎編、実践編をご参照ください。さらに 2 番目の Url タグとして Suggest 用の URL を指定してします。全体のソースは以下のような感じです。
<?xml version="1.0" encoding="UTF-8"?> <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> <ShortName>Suggestionの実験</ShortName> <InputEncoding>UTF-8</InputEncoding> <Url type="text/html" method="GET" template="http://www.sourcewalker.com/blog_files/50835797/suggestion.cgi"> <Param name="q" value="{searchTerms}"/> </Url> <Url type="application/x-suggestions+json" template="http://www.sourcewalker.com/blog_files/50835797/suggestion.cgi?s={searchTerms}"/> </SearchPlugin>
今回は、通常の検索リクエストと Suggest 用のリクエストを同じ CGI スクリプトで処理することにしました。 CGI パラメータとして "q" (通常の検索)と "s" (Suggest)のどちらかをとり、それによって通常のページを返すか、 JSON 記法の Suggest を返すかを CGI スクリプト側で判断しています。もちろん、これらを別々のスクリプトで処理してもかまいません。
CGI スクリプト
次に、サーバー側の CGI スクリプトを実装しましょう。ソースファイルは以下のようになります。このスクリプトをFirefox検索プラグインで指定した URL で公開するだけで、 Suggest 機能による HTML エスケープサービスが実現できます。
#!/usr/local/bin/ruby $KCODE='UTF8' require 'cgi' cgi = CGI.new('html4') mime = 'text/plain; charset=utf-8' r = '' # この関数で、クエリー内容をSuggestに変換しています。 # 入力 s : クエリー文字列 # 出力 Suggest 文字列の配列 def suggest(s) [CGI.escapeHTML(s)] end if cgi.has_key?('s') mime = 'text/javascript; charset=utf-8' query = CGI.unescape(cgi['s']) r = [ query, suggest(query) ].inspect elsif cgi.has_key?('q') query = CGI.unescape(cgi['q']) r = [ query, suggest(query) ].inspect end cgi.out(mime) { r }
実際にクエリー文字列から Suggest を生成しているのが suggest 関数です。この関数の内容を変更して任意の文字列配列を返すだけで、 Suggest 結果を変更することができます。例えば "[CGI.escapeHTML(s)]" を "[s.upcase, s.downcase]" とするだけで、与えられた文字列のアルファベットをすべて大文字および小文字にしたものを Suggest するサービスになります。
説明するまでもないと思いますが、一応処理の流れをご紹介しておきます。
- まず、必要なライブラリの読み込みや変数の初期化を行っています。 KCODE は文字コードの指定、 "require 'cgi'" は CGI 処理ライブラリの読み込みです。
- 次は suggest 関数の定義です。内部の処理は与えられた文字列 s を HTML エスケープし、それを唯一の要素とした配列を作って返しているだけです。
- 最後にレスポンスを出力しています。 cgi.out は、ブロック(中括弧の内部)で最後に評価された文字列に引数で指定された HTTP レスポンスヘッダを付加して出力するメソッドです。
Ruby オブジェクトから JSON 記法への変換を inspect メソッドで済ましている点は、微妙に自信のないところです(^^ゞ。このメソッドに副作用があるとは思えないので、サーバー側のセキュリティーは大丈夫なはずですが、クライアントに変な結果を返してしまうことはあるかもしれません。もしサービスとして広く公開するような場合は、もっときちんと処理した方が良いかと思います。 JSON の Wiki ページ にいくつか Ruby 用のライブラリをリンクしてありますので、ご参照ください。
サンプル
今回も、上記の検索プラグインを以下のページで追加できるようにしておきました。Firefox 2.0 で閲覧中の方は、よろしければお試しください。
http://www.sourcewalker.com/blog_files/50835797/
上記のページでFirefox検索プラグインを追加し、それを選択して適当な文字列を検索窓に打ち込むと、下図のように HTML エスケープした内容が Suggest として表示されます。
上記のページにも書いてありますが、 Suggest 機能は HTTP リクエストを頻繁に投げるため、サーバーへの負荷が大きくなりがちです。私が使っているサーバーは VALUE DOMAIN の共用サーバーなので、あまり負荷をかけると怒られてしまいます。あくまでお試し用ということで、動作が確認できたらプラグインを削除するようにお願いします m(_ _)m
Suggest 機能のデバッグ
Suggest 機能を実装していて、大変だったのがデバッグです。なにか間違いがあると単に Suggest が表示されなくなってしまうので、原因がよくわかりません。そんなときに活躍するのがパケットキャプチャと呼ばれるソフトウェアです。私は Ethereal というソフトを常用しています。こちらの記事でレビューしていますので、よろしければご覧ください。
と、これだけではなんなので、パケットをキャプチャして HTTP のパケットだけを表示させる手順をまとめておきます。インストールはすんでいると仮定していますので、インストール方法は上記記事をご参照ください。
- Ethereal を起動する。
- メインメニューから [Capture]-[Options] を選択する。
- 「Ethereal Capture Options」のダイアログが開くので、一番上の「Interface」コンボボックスでパケットをキャプチャするネットワークカードを選択する。
- ダイアログ右下にある「Start」ボタンをクリックすれば、キャプチャが開始される。
- Firefox の検索窓になにか入力して、 Suggest のクエリーを発生させる。
- キャプチャ開始時に表示されたダイアログの「Stop」ボタンを押す。キャプチャが終了し、メインウインドウにキャプチャしたパケットが表示される。
- メインウインドウのツールバーの下にある「Filter」というテキストボックスに「http」と入力し、 Enter キーを押す。
- HTTP のパケットのみがメインウインドウに表示される。
あとは表示されたパケットの内容を地道に検証していきましょう。また、既存の Suggest 機能付き検索エンジンのパケットを覗くのも参考になります。 Google はレスポンスを圧縮して返すので、 Yahoo! のほうがお勧めです。
以上、 3 回に渡って Firefox 2.0 の検索プラグイン作成方法をご紹介してきました。これにて完結です。ここまで読んでくださった方、本当にありがとうございます m(_ _)m
それにしても、最後の Suggest 機能はなかなか面白いですね。その気になれば、検索窓を数式電卓にしたり(実際にできるようです)、英和辞書にしたりと、いろいろ考えられます。検索の枠を超えて、ミニミニ Web サービスのクライアントとして活用できるわけですね。この方向で発展すれば、今まで思いつかなかったようなサービスが生まれる気がします。皆さんもユニークなサービスを実装して、 Firefox の可能性をどんどん広げていきましょう!(^^)
詳しくはこちらの記事をどうぞ!
この記事にコメントする