Fessの検索画面は、これまでJSPベースで提供してきました。デザインを大きく変えようとするとJSPやアクションに手を入れる必要があり、フロントエンドだけで完結させるのが難しい構成でした。
そこでFess 15.7で、統一されたWeb API /api/v2 と、HTML/CSS/JSだけで検索画面を構築できる静的テーマ機能を追加しました。これにより、Fessをバックエンドとして扱い、検索画面をシングルページアプリケーション(SPA)として自由に作れるようになります。
API v2 について
/api/v2 は、これまで機能ごとに分かれていたREST APIを単一のディスパッチャに統合したものです。検索・サジェスト・ラベル・お気に入り・クリックログ・認証・RAGチャットなど、検索画面を作るのに必要なエンドポイントがひと通り揃っています。
統一されたレスポンス形式
すべてのレスポンスは以下のように response でラップされた形で返ってきます。
{
"response": {
"status": 0,
"...": "..."
}
}
エラー時は以下のようになります。
{
"response": {
"status": 1,
"error": {
"code": "invalid_request",
"message": "..."
}
}
}
status は 0(成功)、1(クライアントエラー)、9(システムエラー)の3種類です。error.code は invalid_request auth_required forbidden not_found rate_limited などの安定したコードになっており、クライアント側はメッセージ文字列ではなくこのコードで分岐・ローカライズします。JSONのキーはリクエスト・レスポンスともにすべてスネークケースで統一しています。
主なエンドポイント
代表的なものを挙げると以下の通りです。
| メソッド + パス | 用途 |
|---|---|
GET /api/v2/search | ドキュメント検索 |
GET /api/v2/suggest-words | サジェスト |
GET /api/v2/labels | ラベル一覧 |
GET /api/v2/popular-words | 人気の検索ワード |
GET /api/v2/ui/config | UI初期化用の設定とCSRFトークン |
GET /api/v2/auth/me | ログイン中のユーザー情報 |
POST /api/v2/auth/login | ログイン |
POST /api/v2/click | クリックログの記録 |
POST /api/v2/chat/stream | RAGチャット(SSEストリーミング) |
GET /api/v2/documents/all | スクロール検索(NDJSON) |
GET /api/v2/health | クラスタのヘルスチェック |
検索の例は以下のような感じです。
$ curl "http://localhost:8080/api/v2/search?q=fess&num=20"
GET /api/v2/search は q(クエリ)、start / num(ページング)、sort、lang、facet.field などのパラメータを受け取り、data(ドキュメント配列)、record_count、page_count、facet_field、related_query などをまとめて返します。
UI初期化とCSRF
SPAはまず GET /api/v2/ui/config を匿名で呼び出します。ここでサイト名・機能フラグ・ソートやページサイズの選択肢・テーマ情報、そしてCSRFトークンを取得します。
状態を変更するPOST/PUT/DELETEには X-Fess-CSRF-Token ヘッダーが必須です(/auth/login のみ例外)。トークンはログイン・ログアウト・パスワード変更のたびにローテーションされます。CSRF検証は認証より前に走るため、トークンなしの更新系リクエストは401ではなく403になります。
静的テーマ機能
検索画面は「テーマ」としてファイルシステムから直接配信されます。JSPやアクションを経由しないため、ブラウザのアドレスバーのURLもそのまま保持されます。
テーマは theme.yml というマニフェストを持ちます。
apiVersion: fess.codelibs.org/v1
kind: StaticTheme
name: bootstrap
displayName: "Bootstrap"
version: "1.0.0"
author: "CodeLibs Project"
minFessVersion: "15.7"
supportedLocales: [en, ja, de, es, fr, ko, pt-BR, zh-CN]
entry: index.html
spaFallback: true
type: static
thumbnail: thumbnail.png
/themes/{name}/... でアセットが配信され、/search /help /profile /cache /chat などのSPA用パスではエントリの index.html が返されます。一方で /admin/ や /api/ などはこれまで通りFess側のルートに渡されるため、管理画面やAPIがSPAに飲み込まれることはありません。
テーマは管理画面に追加された /admin/theme/ から、ZIPでアップロードして管理します。アップロード時にはZipSlipやzip爆弾などへの対策が入っています。有効化は管理画面で theme.default にテーマ名を設定するか、バーチャルホストに紐付けて行います。
Bootstrapリファレンステーマ
標準で、Bootstrap 5ベースのリファレンステーマを同梱しています。これは既存のJSP検索画面をそのまま再現したもので、バニラのES2022モジュールで書かれています。独自テーマを作る場合は、このテーマをコピーして始めるのがおすすめです。
中心となるのが api.js で、これがそのまま使い回せるAPIラッパーになっています。
init()…GET /ui/configを呼び、設定とCSRFトークンをキャッシュget(path, params)/post(path, body)… エンベロープを検証し、status !== 0ならApiErrorを投げる。POST時はCSRFトークンを自動付与sseStream(path, body, onEvent, onError)…/chat/stream向けのfetchベースのSSE(標準のEventSourceはGET限定でCSRFヘッダーを送れないため独自実装)
チャットのストリーミングはこんな感じで使えます。
import * as api from "./api.js";
const ctrl = api.sseStream("/chat/stream", { q: "質問内容" }, (event) => {
if (event.type === "chunk") bubble.textContent += event.data.content ?? "";
if (event.type === "done") ctrl.abort();
}, (err) => console.error(err));
なお、テーマ内では動的な文字列の innerHTML を使わず、検索結果カードやファセット、ページネーションなどはすべて document.createElement と textContent で組み立てています。XSS対策として、独自テーマでもこの方針を踏襲することをおすすめします。
検索画面を自作する流れ
独自の検索画面を作る場合は、ざっくり以下の流れになります。
- 同梱の
bootstrapテーマのディレクトリをコピーし、theme.ymlのnameを変更する index.html(Bootstrap 5 + セマンティックなHTML)とstyles.css、各JSモジュールを編集する。エンベロープ・CSRF・SSEの処理はapi.jsをそのまま使い回せる- ZIPにまとめて
/admin/theme/からアップロードし、有効化する - SPAは
api.init()→GET /ui/config(匿名)で起動し、機能フラグとCSRFトークンを読み込んでから/api/v2を通して検索・認証・お気に入り・チャットを動かす
v1 APIについての注意
今回の変更にあわせて、従来の /api/v1 のJSON検索APIとチャットAPIはFess本体から削除し、fess-webapp-v1-api という別プラグインに切り出しました。これは破壊的変更になります。新規の連携は /api/v2 を対象にしてください。
まとめ
API v2と静的テーマ機能により、FessをヘッドレスなバックエンドとしてHTML/CSS/JSだけで検索画面を構築できるようになりました。同梱のBootstrapテーマをベースに、自分のサイトに合わせた検索画面を作ってみてください。