https://www.codelibs.org を作り直してみました。以前はgithubへのリンクを置いておいただけだったのですが、せっかくなので、Claude Codeに今風な感じのページにしてもらって、各プロジェクトへのリンクを置くなどして、見た感じの印象の改善を試みた感じです。まぁ、それなりな感じのページになった気はします。スター数はHTMLにハードコードされているので、定期的にClaude Codeに更新してもらう必要はあるので、もう少しこのページも有効活用できればいいな、とは考えています。
FessのRAGチャットでMarkdownレンダリングに対応
FessのRAGチャット機能で、LLMからの応答をMarkdownとしてレンダリングできるようにしました。これにより、見出しやテーブル、コードブロックなどが整形された状態で表示されるようになります。また、ストリーミング中の途中の応答も適切にMarkdownとして表示されます。
背景
これまでFessのチャット画面では、LLMからの応答をプレーンテキストとして表示していました。LLMの応答にはMarkdown形式で見出しやリスト、コードブロックなどが含まれることが多いため、そのまま表示すると読みにくい状態でした。特にストリーミング応答中は、Markdownのソースがそのまま見えてしまう問題がありました。
変更内容
marked.jsとDOMPurifyの導入
クライアントサイドでMarkdownをHTMLに変換するために、marked.js(v17.0.4)を導入しました。また、XSS対策としてDOMPurifyを組み合わせて使用しています。
サニタイズポリシーは、サーバーサイドのMarkdownRenderer(OWASPサニタイザー)と同等の設定にしています。
- 許可するHTMLタグを限定(見出し、リスト、テーブル、コードブロックなど)
- リンクには
rel="nofollow"を自動付与 class属性はcode、pre、span、div要素のみに制限data-*属性は不許可- URIは
https?://のみ許可
ストリーミング対応
ストリーミング応答では、受信中のテキストに対してrenderMarkdown()を呼び出すことで、途中の応答もMarkdownとしてレンダリングされます。従来の.text()による表示を.html(renderMarkdown())に変更し、リアルタイムにMarkdown変換を行っています。
CSSスタイルの追加
レンダリングされたMarkdown要素に対して、チャットUIに適したスタイルを追加しました。
- 見出し(h1〜h6)のフォントサイズとマージン
- テーブルのボーダーとパディング
- 引用ブロックの左ボーダー
- リンクの色とホバースタイル
フォールバック
marked.jsやDOMPurifyの読み込みに失敗した場合は、従来どおりHTMLエスケープしたプレーンテキストとして表示するフォールバック処理を実装しています。
関連リンク
FessクローラーのURL処理をjava.net.URIからjava.net.URLに戻した
FessのクローラーでURL処理に使っていたjava.net.URLをjava.net.URIに置き換える対応を以前行いましたが、URIでは対応できない文字が多く問題が発生したため、java.net.URLに戻しました。
背景
Javaではjava.net.URLとjava.net.URIの2つのURLを扱うクラスがあります。java.net.URLは古くからあるクラスで、java.net.URIはRFC 2396に準拠したより厳密なクラスです。一般的にはURIの使用が推奨されていますが、Webクローラーの用途では事情が異なります。
Webの世界には、RFCに準拠していない非標準的なURLが数多く存在します。URI.create()はこれらの非標準URLをIllegalArgumentExceptionで拒否してしまいますが、java.net.URLはより寛容に処理できます。
変更内容
PR #3066で、FessXpathTransformerとProtocolHelperの2つのクラスを中心に、URIからURLへの変更を行いました。
FessXpathTransformer
主な変更点は以下の通りです。
java.net.URI/URISyntaxExceptionをjava.net.URL/MalformedURLExceptionに変更getURI()メソッドをgetURL()に、getBaseUri()をgetBaseUrl()にリネームaddChildUrlFromTagAttribute()の引数の型をURIからURLに変更- 相対URL解決を
URI.resolve()からnew URL(base, spec)コンストラクタに変更
new URL(base, spec)コンストラクタは相対URLの解決をネイティブに処理できるため、URI使用時に必要だった手動でのフォールバック処理(//で始まるURL、?や#で始まるURL、相対パスなど)を大幅に簡素化できました。
// 変更前: URI.resolve()を使用
final URI childUri = uri.resolve(resolveTarget);
u = encodeUrl(normalizeUrl(childUri.toString()), encoding);
// 変更後: new URL(base, spec)を使用
final URL childUrl = new URL(url, urlValue.startsWith(":") ? url.getProtocol() + urlValue : urlValue);
String childUrlStr = childUrl.toExternalForm();
一方で、/../で始まるパスの正規化処理は引き続き必要なため、その部分は残しています。
ProtocolHelper
ProtocolHelperでは、リソースのプロトコルチェックで不要なURI変換を削除し、URL.getProtocol()を直接使用するようにしました。URIへの変換はFileコンストラクタがURIを要求する箇所でのみ残しています。
URIで問題が起きた理由
java.net.URIはRFC 2396に厳密に準拠しているため、以下のような文字を含むURLを処理できません。
- ブラケット(
[、]) - 一部のUnicode文字
- パーセント記号の不正なエンコーディング
- HTMLエンティティを含むURL
Webクローラーは様々なサイトのHTMLを解析するため、このような非標準的なURLに頻繁に遭遇します。URI.create()がIllegalArgumentExceptionを投げると、そのURLはクロール対象から外れてしまい、取得漏れの原因になります。
テストの追加
今回の変更に合わせて、特殊文字を含むURLに対するテストも追加しました。
FessXpathTransformerTest:getBaseUrl()のテストをリネームし、URLAPIに合わせてアサーションを更新CrawlingInfoHelper、IndexExportJob、DocumentUtilにブラケット、パーセント記号、Unicode、HTMLエンティティを含むURLのテストを追加
まとめ
java.net.URIはRFC準拠の厳密なURL処理には適していますが、Webクローラーのように非標準的なURLを扱う必要がある場面ではjava.net.URLの方が実用的です。今回の変更により、new URL(base, spec)による相対URL解決のおかげでコードも簡素化でき、クローラーの堅牢性も向上しました。