P2P Guide of Hyper Estraier Version 1 (Japanese)
Author: Mikio Hirabayashi
Description: P2P Guide of Hyper Estraier
Keywords: Hyper Estraier, Estraier, full-text search, API, Node, P2P

# P2Pガイド

Copyright (C) 2004-2007 Mikio Hirabayashi
Last Update: Tue, 06 Mar 2007 12:05:18 +0900
[English/Japanese] [HOME]

## 目次

1. はじめに
2. アーキテクチャ
3. チュートリアル
4. ノードマスタ用コマンド
5. プロトコル
6. ノードAPI
7. クライアント用コマンド
8. 疑似ノードマスタ
9. メタ検索ゲートウェイ
10. 助言

## はじめに

このガイドでは、Hyper EstraierのP2P機構の詳細な使い方を説明します。ユーザガイドをまだお読みでない場合は先にそちらに目を通しておいてください。

`estseek.cgi`で検索を行う場合、起動する度にデータベースを立ち上げ直すので、処理効率が悪いです。また、`estcmd`でデータベースの更新作業を行っている間は、データベースがロックされてしまうので検索ができません。これらの問題に対処するために、Hyper EstraierはC/S（クライアント/サーバ）方式のサーバプログラムを提供します。データベースを内包したプロセスをシステムに常駐させておいて、それをネットワーク経由で操作するものです。C/S方式には以下の利点があります。

- サーバとクライアントを別々のマシン上で動作させることができます。
- 単一のサーバに対する複数のクライアントが並列に処理を行えます。
- クライアントがクラッシュしてもデータベースが壊れません。
- 実装言語やAPIに依存しないでクライアントを実装できます。

C/S間のプロトコルはHTTPベースなので、一般的なWebブラウザをクライアントにすることが可能です。もちろん、独自のクライアントを実装していただいてもかまいませんし、WebブラウザにJavaScriptやFlashなどの技術を組み合わせることもできるでしょう。

複数のサーバが相互に通信することよって、P2P（Peer to Peer）方式の分散処理を行うこともできます。100万件の文書のインデックスを管理する10個のサーバを連携させれば、1000万件の文書を扱う検索システムを構築することができるというわけです。サーバ同士は対等の関係であるため、クライアントはどのサーバにアクセスしてもサービスを享受することができますし、どれかのサーバがダウンしてもシステム全体が停止することはありません。また、サーバ間の信頼度を元に検索精度を向上させる仕組みも備わっています。

C/S間のプロトコルを隠蔽する「ノードAPI」も提供されます。ノードAPIを使えば、ネットワークに関する詳細な知識がなくても、クライアントのプログラムを実装することができます。このガイドでは、ノードAPI（C言語）の使い方についても説明します。ノードAPIにはJava版とRuby版もあります。

## アーキテクチャ

ここでは、Hyper EstraierにおけるP2Pのアーキテクチャについて説明します。

### ノードマスタとノードサーバ

多数のインデックスを管理することを想定した場合、インデックス毎にサーバを起動するのは非効率です。そこで、単一のプロセスおよび単一のポートで複数のインデックスを管轄できる「ノードマスタ」というプログラムが提供されます。ノードマスタ内の個々のインデックスは独立したサービスを提供するので、ノードマスタは複数のサーバの集合体とみなすこともできます。そこで、個々のインデックスを管轄する仮想的なサーバを「ノードサーバ」と呼ぶことにします。各ノードサーバには別々のURLが割り当てられます。ユーザが操作するアプリケーションはノードサーバのクライアントとして位置づけられますが、接続先のノードサーバについてはURLだけ知っていればよく、ノードサーバがどのノードマスタ上に存在しているかをアプリケーション側で意識する必要はありません。

「ノード（node）」という用語をここではP2P機構における「ピア（peer）」とほぼ同じ意味で使っています。クライアントは、ノードマスタそのものに接続してノードの管理を行うとともに、各々のノードサーバに接続してインデックスに対する検索や文書の登録などの操作を行うことができます。

### メタ検索と信頼度

ノードサーバは別のノードサーバに対して一方的にリンクを張ることができます。クライアントがノードサーバに検索要求を出した場合、依頼を受けたノードサーバは自分がリンクを張っているノードサーバにもクエリを中継し、返された結果を自分のインデックスの結果とマージしてクライアントに返します。つまり、いわゆるメタ検索機能を全てのノードサーバが持つことによって、P2P型の分散処理を実現しているのです。

メタ検索は多階層的に行われます。経路の循環は自動的に検出されて抑止されますので、検索時には木構造のネットワークからデータを収集しているような挙動になります。この機構によって、検索対象となるノードの数を際限なく増やしていくことが可能になります。

ノード間の各リンクには、「信頼度」という数値が定義されます。ノードがメタ検索の結果をマージする際には、スコアの重みづけに信頼度が利用されます。信頼度が高いノードの結果は上位に来やすいということです。リンク作成や信頼度設定の指示はアプリケーションに任されますが、よく利用されるノードの信頼度を高めるように指示することによって、検索の精度が高めて行くことができます。

### 認証

クライアントがノードマスタやノードサーバに接続する際には、ユーザ名とパスワードを用いた認証が行われます。ユーザには、「スーパーユーザ」と「ノーマルユーザ」の2種類があります。前者はノードの管理やユーザの管理を行う権限を持つユーザで、後者はそれらの権限を持たないユーザです。また、ノードサーバの単位でも権限の付与が行われます。各ノードについて、インデックスの更新が可能な管理者ユーザのリストと、検索だけが可能なゲストユーザのリストが管理されます。なお、ノードマスタのスーパーユーザは配下のどのノードにも管理者としてアクセスできます。ユーザの管理はクライアントがノードマスタに指示を出すことで行います。

### アプリケーションの例

Hyper EstraierのノードAPIを使った最も興味深いアプリケーションとして、mod_estraierが挙げられます。Webサーバ（Apache）のモジュールとして動作して、プロクシとして仲介したコンテンツをインデックスに登録するソフトウェアです。フォワードプロクシとして使えば、自分や仲間が見たことのあるページのみを対象とした検索エンジンができます。リバースプロクシとして使えば、掲示板やWikiはもちろん、いかなるWebアプリケーションにも検索機能を組み込めます。このように、様々なアプリケーションと連携して高度な検索システムを簡単に作れるのがノードAPIの最大の特徴です。

## チュートリアル

P2Pの概念はなかなか難解なものですが、まずはコマンドを使ってみながら雰囲気を掴んで行きましょう。

### 起動と終了

ノードマスタを動作させる準備として、サーバルートディレクトリを作成します。設定ファイルやインデックスなどを格納するディレクトリです。以下のコマンドを実行すると、「`casket`」というディレクトリが作成されます。

    estmaster init casket

次に、ノードマスタを起動します。以下のようにします。

    estmaster start casket

ノードマスタを終了させる場合は、ノードマスタを実行している端末で`Ctrl-C`を入力するか、別の端末で以下のコマンドを実行します。

    estmaster stop casket

### 管理用インターフェイス

ノードマスタが起動しているならば、「`http://localhost:1978/master_ui`」というURLにWebブラウザでアクセスすることで、管理用インターフェイスを使うことができます。アクセスするとユーザ名とパスワードを聞かれるダイアログが出ますので、「`admin`」「`admin`」と入力してください。すると、管理用のメニューが表示されます。

「Manage Master」メニュー経由ではノードマスタの終了処理や同期処理を指示できますが、それらは今は放っておきましょう。

「Manage Users」を選んでください。今は「`admin`」というユーザでログインしていますが、新しいユーザを作っておきましょう。画面の下の方にある入力フォームに、右からユーザ名、パスワード、フラグ、本名、その他の情報を入力します。ユーザ名とパスワードは英数字とハイフンとアンダースコアとピリオドで指定します。とりあえずは、「`mikio`」「`oikim`」「`s`」「`平林幹雄`」「`管理用ユーザ`」としてみましょう。フラグに「`s`」を入力しているのが重要なところです。そのユーザが管理者権限を持つことを指示しています。

もはや「`admin`」ユーザは不要です。セキュリティの問題となるので、消してしまいましょう。「`admin`」の欄にある「DELE」を選択して、確認の画面で「sure」を選択してください。

次に、「Manage Nodes」を選択してください。もう「`admin`」ユーザが消えてしまったので、再びユーザ名とパスワードを聞かれます。先ほどの「`mikio`」「`oikim`」を入力すると進むことができます。今度は新しいノードを作ります。画面の下の方にある入力フォームにノード名とラベルを指定します。ノード名は英数字とハイフンとアンダースコアとピリオドで指定します。とりあえずは、「`test1`」「`テスト用ノードその壱`」というノードと、「`test2`」「`テスト用ノードその弍`」というノードを作ってください。

### 文書の登録

今度はコマンドラインの操作に戻ります。管理用インターフェイスの画面は閉じて構いません。ノードマスタを起動した端末はいろいろログが出て塞がれているでしょうから、別の端末を立ち上げてください。

ノードのインデックスに文書を登録してみましょう。登録対象の文書は文書ドラフト形式で表現する必要がありますので、予め以下のようなファイルを「`data001.est`」という名前で作成してください。

    @uri=data001
    @title=Material Girl

    Living in a material world
    And I am a material girl
    You know that we are living in a material world
    And I am a material girl

「`test1`」ノードに文書を登録するには、以下のコマンドを実行します。インデックスの更新には管理者権限が必要なので、`-auth`オプションで管理者のユーザ名とパスワードを指定しています。登録処理は一瞬で終わるので、本当に登録されているか疑いたくなるかもしれませんが、何もメッセージが出なければ成功ということです。

    estcall put -auth mikio oikim http://localhost:1978/node/test1 data001.est

メタ検索の説明のために、「`test2`」ノードにも文書を登録しておきましょう。以下のファイルを「`data002.est`」として作成してください。

    @uri=data002
    @title=Liberian Girl

    Liberian girl
    You came and you changed My world
    A love so brand new

そして、以下のコマンドを実行してください。

    estcall put -auth mikio oikim http://localhost:1978/node/test2 data002.est

文書ドラフト形式のファイルさえ作れば、リモートマシンからでも文書の登録ができるのがミソです。同じ要領で、いくつかの文書を登録してみてください。

### 文書の検索

では、登録した文書を対象とした検索を行ってみましょう。以下のコマンドを実行します。

    estcall search http://localhost:1978/node/test1 "material world"

すると、先ほど登録した文書の情報が表示されます。なお、日本語（UTF-8）が表示できない端末だと文字化けするかもしれません。

ノード間にリンクを張ることで、メタ検索を行うことができるようになります。試しに、「`test1`」から「`test2`」に対してリンクを張ってみましょう。リンク元のURL、リンク先のURL、表示用のラベル、信頼度の順で引数を指定します。

    estcall setlink -auth mikio oikim http://localhost:1978/node/test1 \
      http://localhost:1978/node/test2 TEST02 8000

ではまた検索してみましょう。今度は`-dpt`オプションでメタ検索の深度を指定します。

    estcall search -dpt 1 http://localhost:1978/node/test1 "girl"

「`test1`」に対して検索したのに、「`test2`」の結果もマージされて表示されます。これがP2P型のメタ検索です。「`test1`」と「`test2`」が別々のコンピュータにあれば、分散処理ができるわけです。

リンクの信頼度を高くすると、リンク先のノードが返した結果の方が表示順位が上がります。以下のコマンドを実行してから、先ほどと同じコマンドで検索してみてください。ちゃんと順位が入れ替わっていますよね。

    estcall setlink -auth mikio oikim http://localhost:1978/node/test1 \
      http://localhost:1978/node/test2 TEST02 12000

検索結果をXML形式で取得することもできます。以下のコマンドを実行してみてください。XMLの詳しい書式については`estresult.dtd`をご覧ください。

    estcall search -dpt 1 -vx http://localhost:1978/node/test1 "girl"

コマンドラインで検索しても大して面白くないですよね。でも、ノードサーバにはWebブラウザで検索できるインターフェイスも内蔵されています。「`http://localhost:1978/node/test1/search_ui`」にアクセスしてみてください。表示された画面で、「phrase」の欄に検索語を入れてから、「search」ボタンを押せば検索できます。「depth」の数を増やせばメタ検索もできます。左側に表示されたリンクを選択すると、そのノードを起点にして再検索が行われます。

### アプリケーションの開発

文書ドラフト形式のデータを作るのは面倒くさいし、付属の検索用インターフェイスもちょっと渋い感じかもしれません。さて、ここから先はあなたの出番です。`estcall`コマンドやノードAPIを使って小粋なアプリケーションを作ってみてください。このガイドの残りの項目では、そのためのノウハウについて説明します。

## ノードマスタ用コマンド

ノードマスタを管理するためのコマンドとして「`estmaster`」が提供されます。ここではその仕様を説明します。

### 書式

`estmaster`は多くのサブコマンドの集合体です。サブコマンドの名前は第1引数で指定されます。その他の引数はサブコマンドの種類に応じて解釈されます。rootdirという引数はサーバルートディレクトリのパスです。サーバルートディレクトリとは、ノードマスタの動作に必要な設定ファイルなどを格納するディレクトリツリーのトップのことです。

`estmaster init [-ex] rootdir`
  サーバルートディレクトリを作成します。
  -exを付けると、サンプルのユーザとノードを作成します。デフォルトでは、ユーザ名とパスワードがともに「admin」であるスーパーユーザのみが作成されます。

`estmaster start [-bg] [-ro] [-st] rootdir`
  ノードマスタを起動します。
  -bgをつけると、デーモンプロセスとしてバックグラウンドで実行します。
  -roをつけると、設定にかかわらず読み込み専用モードで動作します。
  -stをつけると、マルチスレッドを使わずにシングルスレッドで動作します。

`estmaster stop rootdir`
  起動中のノードマスタを終了させます。

`estmaster unittest rootdir`
  ユニットテストを行います。

`estmaster crypt key [hash]`
  指定した文字列の暗号用ハッシュ値を出力します。
  keyは処理対象の文字列を指定します。
  hashを指定すると、キーとハッシュが対応するかどうかを判定します。

全てのサブコマンドは、処理が正常に終了した場合には0を、そうでない場合は1を終了ステータスにします。起動中のノードマスタに1（SIGHUP）、2（SIGINT）、3（SIGQUIT）、15（SIGTERM）のどれかのシグナルを送ることにより、データベースを閉じて正常終了させることができます。デーモンとして稼働中のノードマスタの場合は、シグナル1（SIGHUP）を送ることにより、再起動して設定ファイルを読み込み直させることができます。

ノードマスタを終了させるにはコマンドラインによる方法や後述のネットワーク経由による方法がありますが、いずれにせよ、必ず規定の手順で終了させてください。さもなくばインデックスのデータが壊れる可能性があります。

### サーバルートディレクトリの構成

サーバルートディレクトリは以下のファイルやディレクトリを格納しています。

- `_conf` : 設定ファイル。詳細は後述します。
- `_user` : ユーザアカウントファイル。詳細は後述します。
- `_log` : ログファイル。サーバのイベントログや各クライアントのアクセスログを記録します。
- `_meta` : メタデータのデータベースファイル。重複起動防止のロック機構も兼ねます。
- `_pid` : プロセスIDファイル。起動中のノードマスタのプロセスIDが書かれます。
- `_stop` : 停止指示用ファイル。このファイルがあるとノードマスタは自分で停止しようとします。
- `_dfdb` : 文書頻度データベースのファイル。このファイルがあれば文書頻度の計算に利用されます。
- `_node/` : ノードディレクトリ。各ノードの転置インデックスを格納します。
- `_sess/` : セッションディレクトリ。各ユーザのセッション情報を格納します。現状では未使用です。

設定ファイルやユーザアカウントは任意のエディタで書き換えることができます。ただし、ユーザアカウントファイルは起動中のノードマスタが停止する際に更新されますので、ユーザアカウントファイルの編集はノードマスタを止めてから行ってください。

`estcmd`で作成したデータベースをノードディレクトリに入れてからノードマスタを起動すると、そのデータベースをノードとして利用することができるようになります。

### 設定ファイル

設定ファイルは、変数名と値を「`:`」で区切った形式の行を並べたものです。デフォルトでは、設定ファイルは以下のような内容になっています。

    bindaddr: 0.0.0.0
    portnum: 1978
    publicurl:
    runmode: 1
    authmode: 2
    recvmax: 1024
    maxconn: 30
    idleflush: 20
    idlesync: 300
    sessiontimeout: 600
    searchtimeout: 15
    searchmax: 1000
    searchdepth: 5
    rateuri: 1
    mergemethod: 2
    proxyhost:
    proxyport:
    logfile: _log
    loglevel: 2
    backupcmd:
    scalepred: 2
    scoreexpr: 2
    attrindex: @mdate{{!}}seq
    attrindex: @title{{!}}str
    docroot:
    indexfile:
    trustednode:
    denyuntrusted: 0
    cachesize: 64
    cacheanum: 8192
    cachetnum: 1024
    cachernum: 256
    specialcache:
    helpershift: 0.9
    wildmax: 256
    limittextsize: 128
    snipwwidth: 480
    sniphwidth: 96
    snipawidth: 96
    scancheck: 1
    smlrvnum: 32
    extdelay: 4096
    adminemail: magnus@hyperestraier.gov
    uireplace: ^file:///home/mikio/public_html/{{!}}http://localhost/
    uireplace: /index\.html?${{!}}/
    uiextattr: @author|Author
    uiextattr: @mdate|Modification Date
    uiphraseform: 2
    uismlrtune: 16 1024 4096

それぞれの変数の機能を以下に示します。

- `bindaddr` : サーバのアドレスを指定します。0.0.0.0はホストの全てのアドレスを意味します。
- `portnum` : サーバのポート番号を指定します。
- `publicurl` : サーバの公開URLを絶対URLで指定します。デフォルトでは、"http://"、ホスト名、":"、ポート番号から生成されます。
- `runmode` : 運用モードを指定します。1は通常モード、2は読み込み専用モードです。
- `authmode` : 認証モードを指定します。1は認証を一切しないモード、2は管理機能のみで認証を行うモード、3は全ての操作で認証を行うモードです。
- `recvmax` : 受信するデータの最大の長さをキロバイト単位で指定します。
- `maxconn` : 最大同時接続数を指定します。
- `idleflush` : キャッシュのフラッシュを開始するアイドル時間を秒単位で指定します。
- `idlesync` : データベースとディスクの同期処理を開始するアイドル時間を秒単位で指定します。
- `sessiontimeout` : セッションのタイムアウトを秒単位で指定します。未実装です。
- `searchtimeout` : 検索処理のタイムアウトを秒単位で指定します。
- `searchmax` : 送信する文書の最大数を指定します。
- `searchdepth` : メタ検索の最大深度を指定します。
- `rateuri` : 正数ならスコアリングの際にURIを評価します。
- `mergemethod` : メタ検索の結果のマージ方法を指定します。1ならスコアのみを用い、2ならスコアと順位を用い、3なら順位のみを用います。
- `proxyhost` : プロクシサーバのホスト名を指定します。
- `proxyport` : プロクシサーバのポート番号を指定します。
- `logfile` : ログファイルの名前を指定します。絶対パスか相対パスで指定します。
- `loglevel` : ログのレベルを指定します。1ならデバッグ、2なら通常、3なら警告、4ならエラー、5なら無しです。
- `docroot` : 通常のWebサーバとして公開するディレクトリの絶対パスを指定します。
- `backupcmd` : バックアップに用いるコマンドの絶対パスを指定します。
- `scalepred` : 各ノードのインデックスの規模に対する予測を指定します。1なら50000件未満、2なら300000件未満、3なら1000000件未満、4ならそれ以上です。
- `scoreexpr` : スコアの表現方法を指定します。1ならスコアを破棄し、2なら8ビットで保持し、3なら32ビットで保持し、4なら未調整で保持します。
- `attrindex` : 属性インデックスの属性名とデータ型を指定します。複数回指定できます。
- `indexfile` : 通常のWebサーバとして公開する際のディレクトリのインデックスファイルの名前を指定します。不要なら空文字列にします。
- `trustednode` : 認証を省略するノード（クライアント）のIPアドレスを10進数ドット付き表記で指定します。複数回指定可能です。
- `denyuntrusted` : 正数なら上記のノード以外からのアクセスを拒絶します。
- `cachesize` : インデックス用キャッシュのサイズをメガバイト単位で指定します。
- `cacheanum` : 文書の属性用のキャッシュのレコード数を指定します。
- `cachetnum` : 文書のテキスト用のキャッシュのレコード数を指定します。
- `cachernum` : 出現結果用のキャッシュのレコード数を指定します。
- `specialcache` : スペシャルキャッシュの属性名を指定します。
- `helpershift` : ヘルパを用いる下限のキャッシュ使用率を指定します。
- `wildmax` : ワイルドカード展開の最大語数を指定します。
- `limittextsize` : 登録文書のテキストのサイズ制限をキロバイト単位で指定します。
- `snipwwidth` : 検索結果のスニペットの全体の幅を指示します。
- `sniphwidth` : 検索結果のスニペットを作る際に文書の冒頭から取得する幅を指示します。
- `snipawidth` : 検索結果のスニペットを作る際に検索語の周辺から取得する幅を指示します。
- `scancheck` : 正数なら文書の内容と検索フレーズの精密検査をします。
- `smlrvnum` : 類似検索のベクトルの次元数を指定します。0なら類似検索が無効になります。
- `extdelay` : 類似検索のベクトル抽出を遅延させる文書数を指定します。負数を指定すると、遅延なしで形態素解析を適用します。
- `adminemail` : サイト管理者の電子メールアドレスを指定します。
- `uireplace` : ユーザインターフェイスで表示するURIを変換するための正規表現と置換文字列を「`{{!}}`」で区切って指定します。複数回指定できます。
- `uiextattr` : ユーザインターフェイスで表示する属性の名前とラベルを「`|`」で区切って指示します。複数回指定できます。
- `uiphraseform` : 検索条件式の種類指定します。1なら通常書式、2なら簡便書式、3なら粗略書式、4なら論理和書式、5なら論理積書式です。
- `uismlrtune` : ユーザインターフェイスからの類似検索のチューニングパラメータを指定します。キーワード数とキーワード毎の文書数と全体の候補の数を空白区切りで指定します。

`backupcmd`で指定したコマンドは、引数としてサーバルートディレクトリの絶対パスが渡されて呼び出されます。通常はシェルスクリプトを指定し、任意の方法でサーバルートディレクトリのバックアップを取ります。

gatherの「`-fx`」オプションで指定した外部コマンドは、第1引数に対象文書のパス、第2引数に出力先のパスが渡されて呼び出されます。すなわち、外部コマンドは第1引数のファイルを解析して文書ドラフトやプレーンテキストやHTMLやMIMEを生成し、第2引数のファイルに書き込むという機能を持つことが期待されます。なお、対象文書は一時ファイルとしてコピーされて渡されます。対象文書の元来のパスは環境変数`ESTORIGFILE`の値として渡されますが、決して元のファイルを変更してはいけません。`ESTORIGFILE`を使う場合はファイルを読み込む必要はありませんので、「`-fo`」オプションも同時につけるとよいでしょう。

`ui`で始まる名前の属性はユーザインターフェイスにのみ反映されます。APIを使う場合は、`uireplace`によって表示時にURLを加工することはできませんので、登録時に外部用URIを各文書の属性としてつけるようにしてください。

### ユーザアカウントファイル

ユーザアカウントファイルは、ユーザ名、暗号化されたパスワード、フラグ、フルネーム、雑多な情報をタブ区切りで並べた行を並べたものです。文字コードはUTF-8です。デフォルトでは以下のような内容になっています。

    admin   21232f297a57a5a743894a0e4a801fc3        s       Carolus Magnus  Administrator

パスワードはMD5のハッシュ値で表現されます。フラグには、管理者であることを示す「`s`」とアクセス禁止者であることを示す「`b`」を用いることができます。フラグとフルネームと雑多な情報は省略可能です。

### 内蔵ユーザインターフェイス

Webブラウザで、ノードマスタの相対URL「`/master_ui`」にアクセスすると、管理用のインターフェイスを使うことができます。管理用インターフェイスを使うには、スーパーユーザのアカウントでログインする必要があります。

Webブラウザで、ノードサーバのURLの後ろに「`/search_ui`」をつけたURLにアクセスすると、検索用のインターフェイスを使うことができます。検索結果の画面で、「`LINK#1`」といったラベルで表示されるのは、現在のノードからリンクされた他のノードです。リンクを選択すると、そのノードを中心として再検索が行われます。これを使って関連しそうなノードを次々とブラウズしていくことで、目的の文書を探すことができます。

検索結果の画面で、「`Atom`」や「`RSS`」というリンクを選択すると、その検索結果を配信するためのAtom形式またはRSS形式のデータが得られます。このリンクのURLをAtom 1.0かRSS 1.0に対応したアプリケーション（RSSリーダ）に登録すると、定期的に検索結果を監視して、変化があった場合に通知させることができます。

GoogleやWikipediaなどの多くのサイトと連携したメタ検索を行うためのOpenSearchという規格があります。ノードサーバはOpenSearch 1.1のインターフェイスにも対応しています。Webブラウザで、ノードサーバのURLの後ろに「`/opensearch`」をつけたURLにアクセスすると、OpenSearch Descriptionと呼ばれるデータを取得することができます。これをOpenSearch対応のサイトやアプリケーションに読み込ませることで、そのノードをメタ検索の対象に含めることができます。

## プロトコル

ノード間およびクライアント・ノード間の通信はHTTPベースのプロトコルに基づいて行われます。ここでは、その具体的な仕様について見て行きます。

### 概要

ノードマスタおよびノードサーバはHTTP/1.0を実装しています。現状では、HTTP/1.1特有の機能であるキープアライブ接続やチャンクエンコーディングや各種のネゴシエーションには対応していません。

HTTPのメソッドはGETでもPOSTでも構いませんが、情報を取得するだけの場合はGET、ノードマスタやノードサーバに更新が発生する場合はPOSTを使うことが推奨されます。パラメータの文字コードはUTF-8ですが、URLに含められない文字にはURLエンコード（application/x-www-form-urlencoded）を施す必要があります。GETメソッドで送れるデータのサイズは8000バイトまでです。ユーザ名とパスワードの受け渡しはHTTPのBasic認証機構を使って行われます。

処理が成功した場合は原則的に200または202のステータスコードが返ります。エラーの場合は、以下のステータスコードが返ります。

- 400 : パラメータが不正です。
- 401 : 認証情報がないか、間違っています。
- 403 : 現在のアカウントにはアクセス権限がありません。
- 404 : 接続先のパスが存在しません。
- 500 : サーバに原因があるエラーが発生しました。

検索等の操作ではメッセージボディに結果のデータが含まれます。データ形式はUTF-8のテキストですが、タブや改行で構造化されている場合もあります。操作の種類によって結果の解釈方法は変わります。deflateエンコーディングに対応しているクライアントに対しては、圧縮したデータを送信します。

### ノードマスタの操作

ノードマスタに対する操作は「`/master`」というパスに接続することで行います。例えばホスト名が「`skyhigh.estraier.go.jp`」でポートが「`8888`」の場合は、「`http://skyhigh.estraier.go.jp:8888/master`」に接続することになります。ノードマスタの操作はスーパーユーザのみが行えます。ノードマスタの操作はいくつかのサブコマンドからなりますが、サブコマンドの名前は「`action`」パラメータで指定します。その他のパラメータはサブコマンドによって変わります。パラメータの順番は任意です。

`/master ? action=shutdown`
  ノードマスタをシャットダウンします。
  パラメータはありません。
  成功すれば202のステータスコードが返信されます。

`/master ? action=sync`
  全てノードのデータベースをディスクと同期させます。
  パラメータはありません。
  成功すれば202のステータスコードが返信されます。

`/master ? action=backup`
  データベースを同期させた上でバックアップコマンドを実行します。
  パラメータはありません。
  成功すれば202のステータスコードが返信されます。

`/master ? action=userlist`
  ユーザアカウントのリストを取得します。
  パラメータはありません。
  成功すれば200のステータスコードとともにTSV形式のリストが返信されます。各行が各ユーザの情報を表し、ユーザ名、暗号化されたパスワード、フラグ、フルネーム、その他の情報をタブ区切りのフィールドとして表現します。

`/master ? action=useradd & name=str & passwd=str & flags=str & fname=str & misc=str`
  ユーザアカウントを追加します。
  nameは新規のユーザ名を指定します。必須です。ユーザ名に利用できる文字はUS-ASCIIの英数字とハイフンとアンダースコアとピリオドのみです。既存のユーザ名と重複していた場合はエラーになります。
  passwdはパスワードを指定します。必須です。
  flagsはフラグを指定します。省略可能です。フラグが "s" を含めば管理者ユーザとなり、"b" を含めば禁止ユーザとなります。
  fnameはフルネームを指定します。省略可能です。
  miscは雑多な情報を指定します。省略可能です。
  成功すれば200のステータスコードが返信されます。
  パスワードを送信することになるため、パラメータはPOSTメソッドで送信すべきです。

`/master ? action=userdel & name=str`
  ユーザアカウントを削除します。
  nameはユーザ名を指定します。必須です。
  成功すれば200のステータスコードが返信されます。

`/master ? action=nodelist`
  ノードサーバのリストを取得します。
  パラメータはありません。
  成功すれば200のステータスコードとともにTSV形式のリストが返信されます。各行が各ノードの情報を表し、ノード名、ラベル、登録文書数、登録語数、サイズをタブ区切りのフィールドとして表現します。

`/master ? action=nodeadd & name=str & label=str`
  ノードサーバを追加します。
  nameは新規のノード名を指定します。必須です。ノード名に利用できる文字はUS-ASCIIの英数字とハイフンとアンダースコアとピリオドのみです。既存のノード名と重複していた場合はエラーになります。
  labelはラベルを指定します。省略可能です。省略した場合はノード名と同じラベルが付けられます。
  成功すれば200のステータスコードが返信されます。

`/master ? action=nodedel & name=str`
  ノードサーバを削除します。
  nameはノード名を指定します。必須です。
  成功すれば200のステータスコードが返信されます。

`/master ? action=nodeclr & name=str`
  ノードサーバに登録された文書を削除します。
  nameはノード名を指定します。必須です。
  成功すれば200のステータスコードが返信されます。ユーザとリンクの情報は保持されます。

`/master ? action=logrtt`
  ログファイルのローテーションを行います。
  パラメータはありません。
  成功すれば200のステータスコードが返信されます。既存のログファイルは空になり、内容はハイフンと「YYYYMMDDhhmmss」形式の日付表現を接尾させた名前のファイルとして退避されます。

### ノードサーバの操作

各ノードサーバに対する操作は「`/node/`」の後にノード名をつけたパスに接続することで行います。例えばホスト名が「`skyhigh.estraier.go.jp`」でポートが「`8888`」でノード名が「`foo`」の場合は、「`http://skyhigh.estraier.go.jp:8888/node/foo`」に接続することになります。ノードマスタの操作はいくつかのサブコマンドからなりますが、サブコマンドの名前はノード名の後に「`/`」を挟んで指定します。パラメータはサブコマンドによって変わります。パラメータの順番は任意です。ノードマスタの操作は検索系と更新系の二つに大別できますが、前者はどのユーザでも実行でき、後者はそのノードの管理者のみが実行できます。

`/node/name/inform`
  ノードの情報を取得します。
  パラメータはありません。
  成功すれば200のステータスコードとともにTSV形式のデータが返信されます。第1行目は、ノード名、ラベル、登録文書数、登録語数、サイズをタブ区切りのフィールドとして表現したものです。空行を挟んで、次の空行までの各行は管理者のユーザ名のリストです。空行を挟んで、次の空行までの各行はゲストのユーザ名のリストです。空行を挟んで、以降はリンク情報のリストです。リンク情報の各行はURLとラベルと信頼度をタブ区切りのフィールドで表現したものです。

`/node/name/cacheusage`
  ノードのキャッシュ使用率を取得します。
  パラメータはありません。
  成功すれば200のステータスコードとともにキャッシュの使用率を10進小数で表現したデータが返信されます。

`/node/name/search ? phrase=str & attr=str & order=str & max=num & options=num & auxiliary=num & distinct=str & depth=num & wwidth=num & hwidth=num & awidth=num & skip=num & mask=num`
  検索を実行します。
  phraseは検索フレーズを指定します。省略可能です。書式はコアAPIのものと同じです。
  attrは属性条件を指定します。省略可能です。`attr1`から`attr9`を使って複数の属性条件を指定することも可能です。書式はコアAPIのものと同じです。
  orderはソート条件を指定します。省略可能です。書式はコアAPIのものと同じです。
  maxは取得件数を指定します。省略可能です。デフォルトは10です。
  optionsは検索条件のオプションを指定します。省略可能です。値はコアAPIのものと同じです。
  auxiliaryは補助インデックスの結果を採用する許可を指定します。省略可能です。デフォルトは32です。
  distinctは属性重複除去フィルタを指定します。省略可能です。
  depthはメタ検索の深度を指定します。省略可能です。デフォルトは0です。
  wwidthはスニペットの全体の幅を指示します。省略可能です。デフォルトはノードマスタの設定に従います。0にするとスニペットを送信しません。負数にするとスニペットの代わりに本文全体を送信します。
  hwidthはスニペットを作る際に文書の冒頭から取得する幅を指示します。省略可能です。デフォルトはノードマスタの設定に従います。
  awidthはスニペットを作る際に検索語の周辺から取得する幅を指示します。省略可能です。デフォルトはノードマスタの設定に従います。
  skipは取得をスキップする件数を指定します。省略可能です。デフォルトは0件です。
  maskは検索対象のマスクを指定します。省略可能です。1は自分自身、2は1番目のリンク先、4は2番目のリンク先、8は3番目のリンク先といった2の累乗の値の合計で検索を抑止する対象を指定します。あるいは、`mask0=on`から`mask9=on`までの式で指定することもできます。
  成功すれば200のステータスコードとともに検索結果のデータが返信されます。詳細は後述します。

`/node/name/list ? max=num & prev=str`
  文書の一覧を取得します。
  maxは取得件数を指定します。省略可能です。デフォルトは10です。
  prevは繰り返しにおける前の要素のURLを指定します。省略可能です。
  成功すれば200のステータスコードとともに文書の一覧のデータが返信されます。詳細は後述します。

`/node/name/get_doc ? id=num & uri=str`
  文書の情報を取得します。
  idは対象文書のID番号を指定します。省略可能です。
  uriは対象文書のURIを指定します。省略可能です。
  成功すれば200のステータスコードとともに文書ドラフト形式のデータが返信されます。

`/node/name/get_doc_attr ? id=num & uri=str & attr=str`
  文書の属性値を取得します。
  idは対象文書のID番号を指定します。省略可能です。
  uriは対象文書のURIを指定します。省略可能です。
  attrは属性名を指定します。必須です。
  成功すれば200のステータスコードとともに属性値のデータが返信されます。

`/node/name/etch_doc ? id=num & uri=str`
  文書のキーワードを抽出します。
  idは対象文書のID番号を指定します。省略可能です。
  uriは対象文書のURIを指定します。省略可能です。
  成功すれば200のステータスコードとともにキーワードとそのスコアのデータが返信されます。書式はTSVです。

`/node/name/uri_to_id ? uri=str`
  URIに対応する文書のID番号を取得します。
  uriは対象文書のURIを指定します。必須です。
  成功すれば200のステータスコードとともに該当文書のID番号が返信されます。

`/node/name/put_doc ? draft=str`
  文書を登録します。管理者のみが利用できます。
  draftは対象文書の内容を文書ドラフト形式で指定します。必須です。
  成功すれば200のステータスコードが返信されます。

`/node/name/out_doc ? id=num & uri=str`
  文書を削除します。管理者のみが利用できます。
  idは対象文書のID番号を指定します。省略可能です。
  uriは対象文書のURIを指定します。省略可能です。
  成功すれば200のステータスコードが返信されます。

`/node/name/edit_doc ? draft=str`
  文書の属性を編集します。管理者のみが利用できます。
  draftは対象文書の内容を文書ドラフト形式で指定します。必須です。
  成功すれば200のステータスコードが返信されます。

`/node/name/sync`
  データベースの更新内容を同期させます。管理者のみが利用できます。
  パラメータはありません。
  成功すれば200のステータスコードが返信されます。

`/node/name/optimize`
  データベースを最適化します。管理者のみが利用できます。
  パラメータはありません。
  成功すれば200のステータスコードが返信されます。

`/node/name/_set_user ? name=str & mode=num`
  ユーザのアクセス権限を設定します。管理者のみが利用できます。
  nameはユーザ名を指定します。必須です。
  modeは操作内容を指定します。必須です。0なら指定したユーザのアクセス権限を剥奪します。1なら指定したユーザを管理者として登録します。2なら指定したユーザをゲストとして登録します。
  成功すれば200のステータスコードが返信されます。

`/node/name/_set_link ? url=str & label=str & credit=num`
  他のノードへのリンクを設定します。管理者のみが利用できます。
  urlはリンク先のノードサーバのURLを指定します。必須です。既存のリンクと重複していた場合はラベルと信頼度が再設定されます。
  labelはリンク先のノードサーバのラベルを指定します。必須です。
  creditはリンクの信頼度を指定します。省略可能です。省略した場合はリンクが削除されます。
  成功すれば200のステータスコードが返信されます。

ノードマスタのスーパーユーザは各ノードの管理権限を持ちますが、各ノードの管理者はノードマスタのスーパーユーザとは限らないということに注意してください。また、各ノードのゲストは、認証モードを3（all）にした場合にのみ意味を持ちます。

### 検索結果の書式

`search`コマンドの応答のエンティティボディは、MIMEのマルチパートに似た形式をとります。以下に例を示します。

    --------[2387AD2E34554FFF]--------
    VERSION 1.0
    NODE    http://localhost:1978/node/sample1
    HIT     2
    HINT#1  give    2
    DOCNUM  2
    WORDNUM 31
    TIME    0.006541
    TIME#i  0.000058
    TIME#0  0.002907
    TIME#1  0.001578
    LINK#0  http://localhost:1978/node/sample1      Sample1 10000   2       31      2731304 2
    LINK#1  http://localhost:1978/node/sample2      Sample2 4000    3       125     8524522 1
    VIEW    SNIPPET

    --------[2387AD2E34554FFF]--------
    #nodelabel=Sample Node One
    #nodescore=7823432
    #nodeurl=http://localhost:1978/node/sample1
    @id=1
    @uri=http://localhost/foo.html
    %VECTOR give    8502    dispose 7343    griefs  5932    king    2343    void    1232

    You may my glories and my state dispose, But not my griefs; still am I king of those. (
    Give    give
     it u

    p, Yo!
    Give    give
     it up, Yo!)

    --------[2387AD2E34554FFF]--------
    #nodelabel=Sample Node One
    #nodescore=5623772
    #nodeurl=http://localhost:1978/node/sample1
    @id=2
    @uri=http://localhost/bar.html
    %VECTOR faster  9304    give    7723    griefs  6632    go      5343    you     3289

    The faster I go, the behinder I get. (
    Give    give
     it up, Yo!
    Give    give
     it up, Yo!)

    --------[2387AD2E34554FFF]--------:END

改行コードは単一のLFです。第1行目は区切り文字列の定義です。ここで定義された文字列の行で、パートが区切られます。最後の区切り文字列の末尾には「`:END`」という文字列がつきます。最初のパートはメタ部です。それ以降のパートは文書部です。

メタ部は、TSV形式をとります。各行の意味は第1フィールドの文字列で識別できます。以下の種類があります。

- `VERSION` : プロトコルのバージョンを示します。
- `NODE` : ノードのURLを示します。
- `HIT` : 該当文書数の総計を示します。
- `HINT#n` : 各検索語の該当数を示します。第2フィールドが語で、第3フィールドが該当数です。
- `DOCNUM` : 登録文書数の総計を示します。
- `WORDNUM` : 登録語数の総計を示します。
- `TIME` : 全体の処理にかかった時間を秒単位で示します。
- `TIME#n` : 各ノードの処理にかかった時間を秒単位で示します。「`TIME#i`」はローカルの転置インデックスの処理時間を示します。
- `LINK#n` : 各ノードの情報を示します。第2フィールドはURL、第3フィールドはラベル、第4フィールドは信頼度、第5フィールドは登録文書数、第6フィールドは登録語数、第8フィールドはデータベースのサイズ、第8フィールドは該当文書数です。「`LINK#0`」はそのノード自身の情報を示します。
- `VIEW` : 文書パートの形式を示します。現状では「`SNIPPET`」のみが定義されています。

文書部は、該当文書の属性情報とスニペットを示します。最初の空行までが属性情報で、それ以降がスニペットです。属性情報の形式は文書ドラフトと同じです。キーワードが付与されている場合は`%VECTOR`制御命令で表現され、スコアが指定されている場合は`%SCORE`制御命令で表現されます。スニペットの形式はTSVです。その各行は表示すべき文字列です。ほとんどの行は単一のフィールドしか持ちませんが、いくつかは二つのフィールドを持ちます。もし第2フィールドが存在したならば、第1フィールドはハイライトして表示すべき文字列で、第2フィールドはその正規化された文字列です。

`search`コマンドや`get_doc`コマンドの操作を行った際に返される文書情報には、以下の疑似属性が付加されます。

- `#nodeurl` : その文書が登録されているノードのURL。
- `#nodescore` : その文書のノードローカルなスコア。
- `#nodelabel` : その文書が登録されているノードのラベル。

### 文書一覧の書式

`list`コマンドの応答のエンティティボディは、TSV形式です。以下に例を示します。実際には、「...」の後にも文字列は続きます。

    181     http://localhost/data/ihaveadream.xml   31e51df5f33131943dda22bd0fd755a0 ...
    1       http://localhost/prog/hyperestraier-1.0.2/doc/index.html        45368fa3c...
    2       http://localhost/prog/hyperestraier-1.0.2/doc/index.ja.html     0e9edf4ae...
    3       http://localhost/prog/hyperestraier-1.0.2/doc/intro-en.html     ec622d19a...
    18      http://localhost/prog/hyperestraier-1.0.2/doc/intro-ja.html     96f743fa6...
    5       http://localhost/prog/hyperestraier-1.0.2/doc/javanativeapi/allclasses-fr...
    25      http://localhost/prog/hyperestraier-1.0.2/doc/javanativeapi/allclasses-no...
    26      http://localhost/prog/hyperestraier-1.0.2/doc/javanativeapi/constant-valu...
    20      http://localhost/prog/hyperestraier-1.0.2/doc/javanativeapi/estraier/Cmd....
    1022    http://localhost/prog/hyperestraier-1.0.2/doc/javanativeapi/estraier/Docu...

改行コードは単一のLFです。各行が各文書のデータを表し、14個のシステム属性を表すフィールドを持ちます。左から、「`@id`」「`@uri`」「`@digest`」「`@cdate`」「`@mdate`」「`@adate`」「`@title`」「`@author`」「`@type`」「`@lang`」「`@genre`」「`@size`」「`@weight`」「`@misc`」です。該当の属性が定義されていない場合は、そのフィールドは空文字列になります。

### 文書登録の特殊形式

`put_doc`コマンドと`edit_doc`コマンドでは大きなサイズのパラメータを送ることが多いですが、それをURLエンコードすると効率が悪くなります。POSTメソッドのContent-Typeヘッダの値を「text/x-estraier-draft」にすると、エンティティボディとして文書ドラフトそのものを送ることができます。例えば、以下のようなリクエストを送信します。

    POST /node/foo/put_doc HTTP/1.0
    Content-Type: text/x-estraier-draft
    Content-Length: 138

    @uri=http://gogo.estraier.go.jp/sample.html
    @title=Twinkle Twinkle Little Star

    Twinkle, twinkle, little star,
    How I wonder what you are.

## ノードAPI

ノードサーバを操作する際にHTTPを駆使するのが面倒な場合はノードAPIを使うと便利です。ここではその詳細について説明します。

### 概要

ノードAPIを使えば、TCP/IPやHTTPについての低レベルな処理を気にすることなく、ノードサーバと通信を行うことができます。コアAPIと比べると通信の分のオーバーヘッドがかかりますが、リモートホストから実行できたり、リーダやライタの区別を気にせずに並列処理ができるといった利点は重要です。

ノードAPIを使うアプリケーションのソースコードでは、`estraier.h`と`estnode.h`と`cabin.h`と`stdlib.h`をインクルードしてください。

    #include <estraier.h>
    #include <estnode.h>
    #include <cabin.h>
    #include <stdlib.h>

アプリケーションをビルドする際には、以下ようなコマンドを実行してください。コアAPIのビルド方法と全く同じです。

    gcc `estconfig --cflags` -o foobar foobar.c `estconfig --ldflags` `estconfig --libs`

ノードAPIを利用する際には、コアAPIで定義されている機能も利用することになりますので、プログラミングガイドをまだお読みでない場合は先にそちらに目を通しておいてください。

### 初期化のためのAPI

ノードAPIのネットワーク機能を使う前提として、プログラムの冒頭でネットワーク環境を初期化してください。また、プログラムが終了する際にはネットワーク環境を破棄してください。

ネットワーク環境を初期化するには、関数 `est_init_net_env' を用います。

`int est_init_net_env(void);`
  戻り値は成功なら真、エラーなら偽です。同一プログラム内でこの関数を複数回実行しても構いませんが、同じ回数だけ `est_free_net_env' も実行してください。

ネットワーク環境を破棄するには、関数 `est_free_net_env' を用います。

`void est_free_net_env(void);`
  パラメータや戻り値はありません。

### ノードを扱うAPI

構造体型 ``ESTNODE`' は、ノードとの接続を抽象化したものです。各ノードはURLで識別されます。``ESTNODE`' の実体が直接参照されることはなく、必ずポインタを介して間接参照されます。このポインタおよびその参照先を総じてノード接続オブジェクトと呼びます。ノード接続オブジェクトは関数 ``est_node_new`' によって生成され、``est_node_delete`' によって破棄されます。生成されたノード接続オブジェクトは必ず破棄してください。

ノード接続オブジェクトの典型的なライフサイクルを以下に示します。

    ESTNODE *node;

    /* 生成する */
    node = est_node_new("http://estraier.gov:1978/node/foo");

    /* プロクシとタイムアウトと認証情報を設定する */
    est_node_set_proxy(node, "proxy.qdbm.go.jp", 8080);
    est_node_set_timeout(node, 5);
    est_node_set_auth(node, "mikio", "oikim");

      /* ここで文書を登録したり、検索を行ったりする */

    /* 破棄する */
    est_node_delete(node);

ノード接続オブジェクトを生成するには、関数 `est_node_new' を用います。

`ESTNODE *est_node_new(const char *url);`
  `url' はノードのURLを指定します。戻り値はノード接続オブジェクトです。

ノード接続オブジェクトを破棄するには、関数 `est_node_delete' を用います。

`void est_node_delete(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。

ノード接続オブジェクトにプロクシの情報を設定するには、関数 `est_node_set_proxy' を用います。

`void est_node_set_proxy(ESTNODE *node, const char *host, int port);`
  `node' はノード接続オブジェクトを指定します。`host' はプロクシサーバのホスト名を指定します。`port' はプロクシサーバのポート番号を指定します。

ノード接続オブジェクトにタイムアウトの情報を設定するには、関数 `est_node_set_timeout' を用います。

`void est_node_set_timeout(ESTNODE *node, int sec);`
  `node' はノード接続オブジェクトを指定します。`sec' は接続のタイムアウト時間を秒単位で指定します。

ノード接続オブジェクトに認証情報を設定するには、関数 `est_node_set_auth' を用います。

`void est_node_set_auth(ESTNODE *node, const char *name, const char *passwd);`
  `node' はノード接続オブジェクトを指定します。`name' は認証情報のユーザ名を指定します。`passwd' は認証情報のパスワードを指定します。

ノード接続オブジェクトで直前に起きたステータスコードを取得するには、関数 `est_node_status' を用います。

`int est_node_status(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はノード接続で直前に起きたステータスコードです。-1は接続に失敗したことを意味します。

ノードのデータベースの更新内容を同期させるには、関数 `est_node_sync' を用います。

`int est_node_sync(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値は成功なら真、エラーなら偽です。

ノードのデータベースを最適化するには、関数 `est_node_optimize' を用います。

`int est_node_optimize(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値は成功なら真、エラーなら偽です。

ノードに文書を追加するには、関数 `est_node_put_doc' を用いる。

`int est_node_put_doc(ESTNODE *node, ESTDOC *doc);`
  `node' はノード接続オブジェクトを指定します。`doc' は文書オブジェクトを指定します。文書オブジェクトはURI属性を持っていなければなりません。戻り値は成功なら真、エラーなら偽です。指定された文書オブジェクトのURI属性がノード内の既存の文書と一致する場合、既存の方は削除されます。

ノードから文書を削除するには、関数 `est_node_out_doc' を用います。

`int est_node_out_doc(ESTNODE *node, int id);`
  `node' はノード接続オブジェクトを指定します。`id' は登録文書のID番号を指定します。戻り値は成功なら真、エラーなら偽です。

ノードからURIで指定した文書を削除するには、関数 `est_node_out_doc_by_uri' を用います。

`int est_node_out_doc_by_uri(ESTNODE *node, const char *uri);`
  `node' はノード接続オブジェクトを指定します。`uri' は登録文書のURIを指定します。戻り値は成功なら真、エラーなら偽です。

ノード内の文書の属性を編集するには、関数 `est_node_edit_doc' を用います。

`int est_node_edit_doc(ESTNODE *node, ESTDOC *doc);`
  `node' はノード接続オブジェクトを指定します。`doc' は文書オブジェクトを指定します。文書オブジェクトはURI属性を持っていなければなりません。戻り値は成功なら真、エラーなら偽です。ID属性を変更することはできません。変更したURI属性が他の登録文書と重なる場合はエラーとなります。

ノードから文書を取得するには、関数 `est_node_get_doc' を用います。

`ESTDOC *est_node_get_doc(ESTNODE *node, int id);`
  `node' はノード接続オブジェクトを指定します。`id' は登録文書のID番号を指定します。戻り値は文書オブジェクトか、エラーなら `NULL' です。戻り値のオブジェクトは `est_doc_new' で生成されているので、不要になったら `est_doc_close' で破棄してください。

ノードからURIで指定した文書を取得するには、関数 `est_node_get_doc_by_uri' を用います。

`ESTDOC *est_node_get_doc_by_uri(ESTNODE *node, const char *uri);`
  `node' はノード接続オブジェクトを指定します。`uri' は登録文書のURIを指定します。戻り値は文書オブジェクトか、エラーなら `NULL' です。戻り値のオブジェクトは `est_doc_new' で生成されているので、不要になったら `est_doc_close' で破棄してください。

ノードから文書の属性値を取得するには、関数 `est_node_get_doc_attr' を用います。

`char *est_node_get_doc_attr(ESTNODE *node, int id, const char *name);`
  `node' はノード接続オブジェクトを指定します。`id' は登録文書のID番号を指定します。`name' は属性名を指定します。戻り値は該当の属性値か、無ければ `NULL' です。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

ノードからURIで指定した文書の属性値を取得するには、関数 `est_node_get_doc_attr_by_uri' を用います。

`char *est_node_get_doc_attr_by_uri(ESTNODE *node, const char *uri, const char *name);`
  `node' はノード接続オブジェクトを指定します。`uri' は登録文書のURIを指定します。`name' は属性名を指定します。戻り値は該当の属性値か、無ければ `NULL' です。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

ノードから文書のキーワードを取得するには、関数 `est_node_etch_doc' を用います。

`CBMAP *est_node_etch_doc(ESTNODE *node, int id);`
  `node' はノード接続オブジェクトを指定します。`id' は登録文書のID番号を指定します。戻り値はキーワードとその10進数表現のスコアを格納したマップオブジェクトか、エラーなら `NULL' です。戻り値の領域は `cbmapopen' で生成されているので、不要になったら `cbmapclose' で破棄してください。

ノードからURIで指定した文書のキーワードを取得するには、関数 `est_node_etch_doc_by_uri' を用います。

`CBMAP *est_node_etch_doc_by_uri(ESTNODE *node, const char *uri);`
  `node' はノード接続オブジェクトを指定します。`uri' は登録文書のURIを指定します。戻り値はキーワードとその10進数表現のスコアを格納したマップオブジェクトか、エラーなら `NULL' です。戻り値の領域は `cbmapopen' で生成されているので、不要になったら `cbmapclose' で破棄してください。

URIに対応する文書のID番号を取得するには、関数 `est_node_uri_to_id' を用います。

`int est_node_uri_to_id(ESTNODE *node, const char *uri);`
  `node' はノード接続オブジェクトを指定します。`uri' は登録文書のURIを指定します。戻り値は文書のID番号であるか、エラーなら-1です。

ノードの名前を取得するには、関数 `est_node_name' を用います。

`const char *est_node_name(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はデータベースの名前か、エラーなら `NULL' です。戻り値の文字列の寿命はノード接続オブジェクトのそれと同期します。

ノードのラベルを取得するには、関数 `est_node_label' を用います。

`const char *est_node_label(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はデータベースのラベルか、エラーなら `NULL' です。戻り値の文字列の寿命はノード接続オブジェクトのそれと同期します。

ノードに登録された文書の数を取得するには、関数 `est_node_doc_num' を用います。

`int est_node_doc_num(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はデータベースに登録された文書の数か、エラーなら-1です。

ノードに登録された異なり語の数を取得するには、関数 `est_node_word_num' を用います。

`int est_node_word_num(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はデータベースに登録された異なり語の数か、エラーなら-1です。

ノードのデータベースのサイズを取得するには、関数 `est_node_size' を用います。

`double est_node_size(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はノードのデータベースのサイズか、エラーなら-1.0です。

ノードのキャッシュ使用率を取得するには、関数 `est_node_cache_usage' を用います。

`double est_node_cache_usage(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はノードのキャッシュ使用率か、エラーなら-1.0です。

ノードの管理者名のリストを取得するには、関数 `est_node_admins' を用います。

`const CBLIST *est_node_admins(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はノードの管理者名のリストオブジェクトか、エラーなら `NULL' です。戻り値のオブジェクトの寿命はノード接続オブジェクトのそれと同期します。

ノードのユーザ名のリストを取得するには、関数 `est_node_users' を用います。

`const CBLIST *est_node_users(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はノードのユーザ名のリストオブジェクトか、エラーなら `NULL' です。戻り値のオブジェクトの寿命はノード接続オブジェクトのそれと同期します。

ノードのリンク表現のリストを取得するには、関数 `est_node_links' を用います。

`const CBLIST *est_node_links(ESTNODE *node);`
  `node' はノード接続オブジェクトを指定します。戻り値はノードのリンク表現のリストオブジェクトか、エラーなら `NULL' です。戻り値の各要素はTSVの文字列で、URLとラベルとスコアの3つのフィールドを持ちます。戻り値のオブジェクトの寿命はノード接続オブジェクトのそれと同期します。

検索条件に該当する文書の一覧を取得するには、関数 `est_node_search' を用います。

`ESTNODERES *est_node_search(ESTNODE *node, ESTCOND *cond, int depth);`
  `node' はノード接続オブジェクトを指定します。`cond' は検索条件オブジェクトを指定します。`depth' はメタ検索の深度を指定します。戻り値はノード結果オブジェクトか、エラーなら `NULL' です。戻り値のオブジェクトは不要になったら `est_noderes_delete' で破棄してください。

検索結果のスニペットの幅を設定するには、関数 `est_node_set_snippet' を用います。

`void est_node_set_snippet_width(ESTNODE *node, int wwidth, int hwidth, int awidth);`
  `node' はノード接続オブジェクトを指定します。`wwidth' は結果全体の幅（≒文字数）を指定します。デフォルトは480です。0にするとスニペットを送信しません。負数にするとスニペットの代わりに本文全体を送信します。`hwidth' は本文の冒頭から抽出する幅を指定します。デフォルトは96です。`awidth' はハイライトされる語の周辺から抽出する幅を指定します。デフォルトは96です。

ノードのユーザアカウントを管理するには、関数 `est_node_set_user' を用います。

`int est_node_set_user(ESTNODE *node, const char *name, int mode);`
  `node' はノード接続オブジェクトを指定します。`name' はユーザ名を指定します。`mode' は操作モードを指定します。0ならアカウントを削除し、1ならアカウントを管理者にし、2なら通常ユーザにします。戻り値は成功なら真、エラーなら偽です。

ノードのリンクを管理するには、関数 `est_node_set_link' を用います。

`int est_node_set_link(ESTNODE *node, const char *url, const char *label, int credit);`
  `node' はノード接続オブジェクトを指定します。`url' はリンク対象のノードのURLを指定します。`label' はリンクのラベルを指定します。`credit' はリンクの信頼度を指定します。負数の場合は該当のリンクを削除します。戻り値は成功なら真、エラーなら偽です。

### ノードからの検索結果を扱うAPI

構造体型 ``ESTNODERES`' は、ノードからの検索結果を抽象化したものです。結果には、該当文書の情報のリストとヒント情報が含まれます。``ESTNODERES`' の実体が直接参照されることはなく、必ずポインタを介して間接参照されます。このポインタおよびその参照先を総じてノード結果オブジェクトと呼びます。ノード結果オブジェクトは関数 ``est_node_search`' によって生成され、``est_noderes_delete`' によって破棄されます。生成されたノード結果オブジェクトは必ず破棄してください。

構造体型 ``ESTRESDOC`' は、ノードからの検索結果の各文書の情報を抽象化したものです。文書情報には、複数の属性と一つのスニペットが含まれます。``ESTRESDOC`' の実体が直接参照されることはなく、必ずポインタを介して間接参照されます。このポインタおよびその参照先を総じて結果文書オブジェクトと呼びます。結果文書オブジェクトは関数 ``est_noderes_get_doc`' によって参照できますが、実体はノード結果オブジェクトの内部で管理されるため、破棄する必要はありません。

ノード結果オブジェクトと結果文書オブジェクトの典型的なライフサイクルを以下に示します。

    ESTNODERES *nres;
    CBMAP *hints;
    ESTRESDOC *rdoc;
    int i;

    /* ノード結果オブジェクトを生成する */
    nres = est_node_search(node, cond, 1);

    /* ヒントを取り出す */
    hints = est_noderes_hints(nres);

       /* ここでヒントを表示する */

    /* 該当文書のリストを走査する */
    for(i = 0; i < est_noderes_doc_num(nres); i++){

      /* 結果文書オブジェクトを取り出す */
      rdoc = est_noderes_get_doc(nres, i);

      /* ここで文書情報を表示する */

    }

    /* ノード結果オブジェクトを破棄する */
    est_noderes_delete(nres);

ノード結果オブジェクトを破棄するには、関数 `est_noderes_delete' を用います。

`void est_noderes_delete(ESTNODERES *nres);`
  `nres' はノード結果オブジェクトを指定します。

ノード結果オブジェクトからヒントのマップオブジェクトを取得するには、関数 `est_noderes_hints' を用いる。

`CBMAP *est_noderes_hints(ESTNODERES *nres);`
  `nres' はノード結果オブジェクトを指定します。戻り値はヒントのマップオブジェクトです。キーには "VERSION"、"NODE"、"HIT"、"HINT#n"、"DOCNUM"、"WORDNUM"、"TIME"、"TIME#n"、"LINK#n"、"VIEW" があります。戻り値のオブジェクトの寿命はノード結果オブジェクトのそれと同期します。

ノード結果オブジェクト内の類似した文書を隠蔽するには、関数 `est_noderes_eclipse' を用います。

`void est_noderes_eclipse(ESTNODERES *nres, int num, double limit);`
  `nres' はノード結果オブジェクトを指定します。`num' は表示すべき文書の数を指定します。0を越えない値を指定すると隠蔽を解除します。`limit' は隠蔽される文書の下限の類似度を0.0から1.0までの値で指定します。

ノード結果オブジェクトに含まれる文書情報の数を取得するには、関数 `est_noderes_doc_num' を用います。

`int est_noderes_doc_num(ESTNODERES *nres);`
  `nres' はノード結果オブジェクトを指定します。戻り値はノード結果オブジェクトに含まれる文書情報の数です。

ノード結果オブジェクトから個々の結果文書オブジェクトを取得するには、関数 `est_noderes_get_doc' を用います。

`ESTRESDOC *est_noderes_get_doc(ESTNODERES *nres, int index);`
  `nres' はノード結果オブジェクトを指定します。`index' は取り出す要素のインデックスを指定します。戻り値は文書オブジェクトか、`index' が要素数と等しいか大きければ `NULL' です。戻り値のオブジェクトの寿命はノード結果オブジェクトのそれと同期します。

結果文書オブジェクトからURIを取得するには、関数 `est_resdoc_uri' を用います。

`const char *est_resdoc_uri(ESTRESDOC *rdoc);`
  `rdoc' は結果文書オブジェクトを指定します。戻り値は結果文書オブジェクトのURIです。戻り値の文字列の寿命は結果文書オブジェクトのそれと同期します。

結果文書オブジェクトの属性名のリストを取得するには、関数 `est_resdoc_attr_names' を用います。

`CBLIST *est_resdoc_attr_names(ESTRESDOC *rdoc);`
  `rdoc' は結果文書オブジェクトを指定します。戻り値は結果文書オブジェクトの属性名のリストです。戻り値のオブジェクトは `cblistopen' で生成されているので、不要になったら `cblistclose' で破棄してください。

結果文書オブジェクトの属性の値を取得するには、関数 `est_resdoc_attr' を用います。

`const char *est_resdoc_attr(ESTRESDOC *rdoc, const char *name);`
  `rdoc' は結果文書オブジェクトを指定します。`name' は属性名を指定します。戻り値は属性値ですが、該当する属性がない場合は `NULL' が返されます。戻り値の文字列の寿命は結果文書オブジェクトのそれと同期します。

結果文書オブジェクトのスニペットを取得するには、関数 `est_resdoc_snippet' を用います。

`const char *est_resdoc_snippet(ESTRESDOC *rdoc);`
  `rdoc' は結果文書オブジェクトを指定します。戻り値は結果文書オブジェクトのスニペットの文字列です。その形式はタブ区切り文字列（TSV）です。その各行は表示すべき文字列です。ほとんどの行は単一のフィールドしか持ちませんが、いくつかは二つのフィールドを持ちます。もし第2フィールドが存在したならば、第1フィールドはハイライトして表示すべき文字列で、第2フィールドはその正規化された文字列です。戻り値の文字列の寿命は結果文書オブジェクトのそれと同期します。

検索結果オブジェクトのキーワードベクトルを取得するには、関数 `est_resdoc_keywords' を用います。

`const char *est_resdoc_keywords(ESTRESDOC *rdoc);`
  `rdoc' は結果文書オブジェクトを指定します。戻り値は結果文書オブジェクトのキーワードの文字列です。その形式はタブ区切り文字列（TSV）です。各フィールドには、キーワードとそのスコアが交互に表れます。戻り値の文字列の寿命は結果文書オブジェクトのそれと同期します。

結果文書オブジェクトに隠蔽された文書の配列を取得するには、関数 `est_resdoc_shadows' を用います。

`ESTRESDOC **est_resdoc_shadows(ESTRESDOC *rdoc, int *np);`
  `rdoc' は結果文書オブジェクトを指定します。`np' は戻り値の配列の要素数を格納する変数へのポインタを指定します。戻り値は隠蔽された結果文書オブジェクトの配列です。戻り値の配列とその要素の寿命は結果文書オブジェクトのそれと同期します。

隠蔽された検索結果オブジェクトの代表文書との類似度を取得するには、関数 `est_resdoc_similarity' を用います。

`double est_resdoc_similarity(ESTRESDOC *rdoc);`
  `rdoc' は結果文書オブジェクトを指定します。戻り値は代表文書との類似度ですが、その結果オブジェクトが隠蔽されていない場合は-1.0です。

### 並列性

ノード接続オブジェクト、ノード結果オブジェクト、結果文書オブジェクトのいずれも、スレッド間で共有してはなりません。マルチスレッドを用いる場合は、各スレッドで別々のオブジェクトを使うようにしてください。その条件さえ守れば、ノードAPIの各関数はマルチスレッドセーフなものとして扱えます。

### ギャザラのサンプル

最も単純なギャザラの実装を以下に示します。コアAPIのギャザラとの違いは、ネットワーク環境の初期化が必要なことと、データベースオブジェクトの代わりにノード接続オブジェクトを使うことです。

    #include <estraier.h>
    #include <estnode.h>
    #include <cabin.h>
    #include <stdlib.h>
    #include <stdio.h>

    int main(int argc, char **argv){
      ESTNODE *node;
      ESTDOC *doc;
      /* ネットワーク環境を初期化する */
      if(!est_init_net_env()){
        fprintf(stderr, "error: network is unavailable\n");
        return 1;
      }
      /* ノード接続オブジェクトを生成して設定する */
      node = est_node_new("http://localhost:1978/node/test1");
      est_node_set_auth(node, "admin", "admin");
      /* 文書オブジェクトを生成する */
      doc = est_doc_new();
      /* 文書オブジェクトに属性を追加する */
      est_doc_add_attr(doc, "@uri", "http://estraier.gov/example.txt");
      est_doc_add_attr(doc, "@title", "Over the Rainbow");
      /* 文書オブジェクトに本文を追加する */
      est_doc_add_text(doc, "Somewhere over the rainbow.  Way up high.");
      est_doc_add_text(doc, "There's a land that I heard of once in a lullaby.");
      /* 文書オブジェクトをノードに登録する */
      if(!est_node_put_doc(node, doc))
        fprintf(stderr, "error: %d\n", est_node_status(node));
      /* 文書オブジェクトを破棄する */
      est_doc_delete(doc);
      /* ノード接続オブジェクトを破棄する */
      est_node_delete(node);
      /* ネットワーク環境を破棄する */
      est_free_net_env();
      return 0;
    }

### サーチャのサンプル

最も単純なサーチャの実装を以下に示します。コアAPIでは検索結果から文書オブジェクト（ESTDOC）を取得していましたが、ここでは結果文書オブジェクト（ESTRESDOC）を取得していることに注意してください。

    #include <estraier.h>
    #include <estnode.h>
    #include <cabin.h>
    #include <stdlib.h>
    #include <stdio.h>

    int main(int argc, char **argv){
      ESTNODE *node;
      ESTCOND *cond;
      ESTNODERES *nres;
      ESTRESDOC *rdoc;
      int i;
      const char *value;
      /* ネットワーク環境を初期化する */
      if(!est_init_net_env()){
        fprintf(stderr, "error: network is unavailable\n");
        return 1;
      }
      /* ノード接続オブジェクトを生成する */
      node = est_node_new("http://localhost:1978/node/test1");
      /* 検索条件オブジェクトを生成する */
      cond = est_cond_new();
      /* 検索条件オブジェクトに検索式を設定する */
      est_cond_set_phrase(cond, "rainbow AND lullaby");
      /* ノード結果オブジェクトを取得する */
      nres = est_node_search(node, cond, 0);
      if(nres){
        /* 各文書を取得して表示する */
        for(i = 0; i < est_noderes_doc_num(nres); i++){
          /* 結果文書オブジェクトを取得する */
          rdoc = est_noderes_get_doc(nres, i);
          /* 属性を表示する */
          if((value = est_resdoc_attr(rdoc, "@uri")) != NULL)
            printf("URI: %s\n", value);
          if((value = est_resdoc_attr(rdoc, "@title")) != NULL)
            printf("Title: %s\n", value);
          /* スニペットを表示する */
          printf("%s", est_resdoc_snippet(rdoc));
        }
        /* ノード結果オブジェクトを破棄する */
        est_noderes_delete(nres);
      } else {
        fprintf(stderr, "error: %d\n", est_node_status(node));
      }
      /* 検索条件オブジェクトを破棄する */
      est_cond_delete(cond);
      /* ノード接続オブジェクトを破棄する */
      est_node_delete(node);
      /* ネットワーク環境を破棄する */
      est_free_net_env();
      return 0;
    }

## クライアント用コマンド

ここでは、ノード管理用コマンド「`estcall`」の詳細な仕様を説明します。検索もできるので、`estcall`をスクリプト言語から呼び出せばそれなりのアプリケーションが簡単に作れます。

### 書式

`estcall`は多くのサブコマンドの集合体です。サブコマンドの名前は第1引数で指定されます。その他の引数はサブコマンドの種類に応じて解釈されます。nurlという引数は操作対象のノードのURLです。`-proxy`オプションはプロクシのホスト名とポート番号を指定します。`-tout`はタイムアウトの時間を秒単位で指定します。`-auth`は認証のユーザ名とパスワードを指定します。

`estcall put [-proxy host port] [-tout num] [-auth user pass] nurl [file]`
  文書ドラフト形式のファイルを登録します。
  fileは対象のファイルを指定しますが、省略した場合は標準入力が読み込まれます。

`estcall out [-proxy host port] [-tout num] [-auth user pass] nurl expr`
  特定の文書の情報をインデックスから削除します。
  exprは対象のID番号かURIを指定します。

`estcall edit [-proxy host port] [-tout num] [-auth user pass] nurl expr name [value]`
  特定の文書の属性を更新します。
  exprは対象のID番号かURIを指定します。
  nameは属性の名前を指定します。
  valueは属性の値を指定します。省略した場合その属性は削除されます。

`estcall get [-proxy host port] [-tout num] [-auth user pass] nurl expr [attr]`
  特定の文書の情報を文書ドラフト形式で出力します。
  exprは対象のID番号かURIを指定します。
  attrを付けると、その属性の値のみを出力します。

`estcall etch [-proxy host port] [-tout num] [-auth user pass] nurl expr`
  特定の文書のキーワードとスコアをTSV形式で出力します。
  exprは対象のID番号かURIを指定します。

`estcall uriid [-proxy host port] [-tout num] [-auth user pass] nurl uri`
  指定したURIの文書のID番号を出力します。
  uriは対象のURIを指定します。

`estcall inform [-proxy host port] [-tout num] [-auth user pass] [-ia|-iu|-il] nurl`
  ノードの名前とラベルと登録文書数と登録語数とキャッシュ使用率を出力します。
  -iaを付けると、管理者名のリストを出力します。
  -iuを付けると、ユーザ名のリストを出力します。
  -ilを付けると、リンク表現のリストを出力します。

`estcall sync [-proxy host port] [-tout num] [-auth user pass] nurl`
  ノードのデータベースの更新内容を同期させます。

`estcall optimize [-proxy host port] [-tout num] [-auth user pass] nurl`
  ノードのデータベースを最適化します。

`estcall search [-proxy host port] [-tout num] [-auth user pass] [-vu|-vx] [-kw] [-ec rn] [-sf] [-attr expr] [-ord expr] [-max num] [-sk num] [-aux num] [-dis name] [-dpt num] [-mask num] nurl [phrase]`
  ノードに登録された文書を検索します。
  phraseは全文検索の検索式を指定します。
  -vuを付けると、URIとノードの情報をタブ区切りにした形式で結果を出力します。
  -vxを付けると、XML形式にして結果を出力します。
  -kwを付けると、キーワードベクトルを取得します。
  -ecは類似隠蔽の下限類似度を0.0から1.0までの実数で指定します。
  -sfを付けると、検索式を簡便書式として扱います。
  -attrは絞り込みの属性条件を指定します。複数指定可能です。
  -ordはソート条件を指定します。デフォルトはスコアの降順です。
  -maxは最大表示件数を指定します。負数にすると無制限になります。デフォルトは10件です。
  -skは取得をスキップする件数を指定します。デフォルトは0件です。
  -auxは補助インデックスの結果を採用する許可を指定します。0を越えない値を指定すると補助インデックスは使われません。デフォルトは32件です。
  -disは重複回避属性の名前を指定します。
  -dptはメタ検索の深度を指定します。デフォルトは0です。
  -maskはメタ検索のマスクを指定します。デフォルトは0です。

`estcall list [-proxy host port] [-tout num] [-auth user pass] nurl`
  ノードに登録された全ての文書の一覧を取得します。

`estcall setuser [-proxy host port] [-tout num] [-auth user pass] nurl name mode`
  ユーザ権限を設定します。
  nameはユーザ名を指定します。
  modeが1ならそのユーザを管理者にし、2ならそのユーザを通常ユーザにし、0ならユーザ権限を削除します。

`estcall setlink [-proxy host port] [-tout num] [-auth user pass] nurl url label credit`
  リンクを設定します。
  urlはリンク先のノードのURLを指定します。
  labelはラベルを指定します。
  creditは信頼度を指定します。値が負数の場合はそのリンクを削除します。

`estcall raw [-proxy host port] [-tout num] [-auth user pass] [-np] [-eh expr] url [file]`
  HTTPリクエストを直接制御してレスポンスを出力します。
  urlは操作対象のURLを指定します。
  fileが指定された場合はその内容をPOSTメソッドで送信します。指定されなかった場合はGETメソッドを用います。"-" を指定すると標準入力を読み込みます。
  -npを付けると、レスポンスヘッダも含めて出力します。
  -ehは追加するHTTPヘッダを指定します。デフォルトではHostとConnectionとUser-AgentContent-Lengthが付加されます。

全てのサブコマンドは、処理が正常に終了した場合には0を、そうでない場合は1を終了ステータスにします。

### 応用例：ノードマスタの操作

ノードマスタ自体の操作はAPIにはありませんので、rawサブコマンドを用いてください。例えば、ノードマスタをシャットダウンするには、以下のようなコマンドを実行します。

    estcall raw -auth admin admin \
      'http://localhost:1978/master?action=shutdown'

ユーザを追加するには、以下のようなコマンドを実行します。

    estcall raw -auth admin admin \
      'http://localhost:1978/master?action=useradd&name=mikio&passwd=iloveyou'

POSTメソッドを使うには、以下のようにします。

    echo -n 'action=useradd&name=mikio&passwd=iloveyou' |
      estcall raw -auth admin admin \
        -eh 'Content-Type: application/x-www-form-urlencoded' \
        'http://localhost:1978/master' -

## 疑似ノードマスタ

ここでは、疑似ノードマスタ`estfraud.cgi`の詳細な仕様を説明します。

### 構成

デーモンプロセスであるノードマスタを実際に運用するには、起動および終了の処理を行うスクリプトを用意するなどの手間がかかります。また、共用サーバなどでデーモンプロセスの利用が禁止されていることもあります。そのような場合には、ノードサーバのプロトコルを模倣するCGIスクリプトである疑似ノードマスタが役に立つかもしれません。Webサーバが動いていて、CGIスクリプトが利用できる環境であれば、通常のインデックスをノードサーバに見立てて、ノードAPIを使ってリモートから検索を行うことができます。疑似ノードマスタの上で動く仮想的なノードサーバを疑似ノードサーバと呼びます。

疑似ノードマスタはノードの数が非常に多い場合にも役立ちます。ファイルディスクリプタを開いたまま常駐する通常のノードサーバと違い、疑似ノードサーバはリクエスト毎にプロセスを生成してデータベースに接続するので、ファイルディスクリプタの枯渇を心配する必要はありません。通常のノードマスタの上で動かせるノードサーバは実用的には30個程度が上限ですが、疑似ノードマスタの上では1000個以上の疑似ノードサーバを動かしても問題はありません。

疑似ノードサーバを利用するには、CGIスクリプト`estfraud.cgi`とその設定ファイル`estfraud.conf`をCGIスクリプトが利用できる場所に設置してください。

### 設定ファイル

設定ファイルは、変数名と値を「`:`」で区切った形式の行を並べたものです。デフォルトでは、設定ファイルは以下のような内容になっています。

    indexdir: .
    runmode: 2
    pidxsuffix: -pidx
    pidxdocmax: 256
    pidxdocmin: 0
    lockindex: 0
    searchmax: 1000
    rateuri: 1
    mergemethod: 2
    scoreexpr: 2
    wildmax: 256
    snipwwidth: 480
    sniphwidth: 96
    snipawidth: 96
    scancheck: 1
    smlrvnum: 32
    extdelay: 4096

それぞれの変数の機能を以下に示します。

- `indexdir` : インデックスを置いたディレクトリのパスを指定します。
- `runmode` : 運用モードを指定します。1は通常モード、2は読み込み専用モードです。
- `pidxsuffix` : 疑似インデックスの接尾辞を指定します。
- `pidxdocmax` : 疑似インデックス内の文書の最大数を指定します。
- `pidxdocmin` : 疑似インデックス内の文書の最小数を指定します。
- `lockindex` : 正数ならインデックスにファイルロックをかけます。
- `searchmax` : 送信する文書の最大数を指定します。
- `rateuri` : 正数ならスコアリングの際にURIを評価します。
- `mergemethod` : メタ検索の結果のマージ方法を指定します。1ならスコアのみを用い、2ならスコアと順位を用い、3なら順位のみを用います。
- `scoreexpr` : スコアの表現方法を指定します。1ならスコアを破棄し、2なら8ビットで保持し、3なら32ビットで保持し、4なら未調整で保持します。
- `wildmax` : ワイルドカード展開の最大語数を指定します。
- `snipwwidth` : 検索結果のスニペットの全体の幅を指示します。
- `sniphwidth` : 検索結果のスニペットを作る際に文書の冒頭から取得する幅を指示します。
- `snipawidth` : 検索結果のスニペットを作る際に検索語の周辺から取得する幅を指示します。
- `scancheck` : 正数なら文書の内容と検索フレーズの精密検査をします。
- `smlrvnum` : 類似検索のベクトルの次元数を指定します。0なら類似検索が無効になります。
- `extdelay` : 類似検索のベクトル抽出を遅延させる文書数を指定します。負数を指定すると、遅延なしで形態素解析を適用します。

疑似ノードサーバによって扱いたいインデックスを「`/home/mikio/myindex`」の下に置く場合、`indexdir`の値には「`/home/mikio/myindex`」を指定します。このディレクトリの中に置いたインデックスは、全て検索対象として公開されることになります。`estfraud.cgi`が「`http://abc.def/ghi/estfraud.cgi`」としてアクセスできるならば、「`/home/mikio/myindex/foo`」は「`http://abc.def/ghi/estfraud.cgi/foo`」というURLの疑似ノードサーバとして公開されます。

### 制限

疑似ノードサーバによってインデックスの更新を行う場合、以下の条件を満たす必要があります。

- 疑似ノードマスタの運用モードが通常モードであること（「`runmode: 1`」にする）。
- 疑似インデックスが存在すること（インデックス名の後に「`-pidx`」をつけたディレクトリを作る）。
- CGIスクリプトの実行ユーザがインデックスおよび疑似インデックスの書き込み権限を持っていること。

疑似ノードサーバ経由で文書登録を行った場合、その文書のデータは文書ドラフトとして疑似インデックスに格納されます。疑似インデックス内のファイル数が`pidxdocmax`で指定した数に達した時点で、疑似インデックスの内容がインデックスにマージされ、疑似インデックスは空にされます。この仕組みによって、常駐しないCGIスクリプトのプロセスでも効率的にインデックスの更新を行うことができます。`pidxdocmax`の値を大きくするほど更新性能は上がりますが、検索性能は下がります。疑似インデックスのマージを明示的に行うには、インデックスの同期（`est_node_sync`）を行ってください。

疑似ノードサーバは、他のノードサーバへのクエリのリレーをサポートしません。つまり、疑似ノードサーバに対する検索クエリでは、`depth`パラメータは無視されます。また、疑似ノードサーバは登録文書の編集（`est_node_edit_doc`）によるURI属性の変更をサポートしません。

## メタ検索ゲートウェイ

ここでは、論理積メタ検索ゲートウェイ`estscout.cgi`および論理和メタ検索ゲートウェイ`estsupt.cgi`の詳細な仕様を説明します。

### 構成

複数のインデックスに対する全文検索の結果を要素とする、URI属性等を識別子にした論理積（積集合）を取得するには、論理積メタ検索ゲートウェイを使うと便利です。例えば社員名簿から名前と住所の両方で絞りこむ場合、名前を本文として社員番号をURIにした文書群のインデックスと、住所を本文として社員番号をURIにした文書群のインデックスに対して論理積メタ検索を行い、両方に含まれる社員番号のリストを得ることができます。さらに、複数回の論理積メタ検索の結果の論理和（和集合）を得るための論理和メタ検索ゲートウェイも提供されます。

論理積メタ検索ゲートウェイおよび論理和メタ検索ゲートウェイはCGIスクリプトとして実装され、GETメソッドのURLでパラメータを指定して利用されます。APIなどの抽象化されたインターフェイスは提供されません。メタ検索はマルチスレッドおよびマルチプロセスで行わるので、分散処理によるスケーラビリティの向上が期待できます。

### 論理積メタ検索ゲートウェイ

論理積メタ検索ゲートウェイを利用するには、CGIスクリプト`estscout.cgi`とその設定ファイル`estscout.conf`をCGIスクリプトが利用できる場所に設置してください。

設定ファイルは、変数名と値を「`:`」で区切った形式の行を並べたものです。デフォルトでは、設定ファイルは以下のような内容になっています。

    indexname: casket-1
    indexname: casket-2
    indexname: casket-3
    lockindex: 1
    condgstep: 2
    dotfidf: true
    scancheck: 3
    phraseform: 2
    wildmax: 256
    stmode: 0
    idattr: @uri
    idsuffix:
    ordexpr: @uri STRA
    dupcheck: 0
    union: 0
    score: 0
    tmpdir: /tmp
    cclife: 300
    logfile:
    logformat: {time}\t{REMOTE_ADDR}:{REMOTE_PORT}\t{cond}\t{hnum}\n

それぞれの変数の機能を以下に示します。

- `indexname` : インデックスのパスを指定します。複数回指定できます。
- `lockindex` : 正数ならインデックスにファイルロックをかけます。
- `condgstep` : N-gramの検査精度を指定します。「`1`」だと全ての語を調べ、「`2`」だと1個置き、「`3`」だと2個置き、「`4`」だと3個置きに調べます。
- `dotfidf` : TF-IDF法を適用するかどうかを指定します。「`true`」か「`false`」です。
- `scancheck` : 文書の内容と検索フレーズの精密検査をする数を指定します。
- `phraseform` : 検索条件式の種類指定します。「`1`」だと通常書式で、「`2`」だと簡便書式で、「`3`」だと粗略書式で、「`4`」だと論理和書式で、「`5`」だと論理積書式です。
- `wildmax` : ワイルドカード展開の最大語数を指定します。
- `stmode` : 正数ならシングルスレッドモードになります。
- `idattr` : 文書の識別子として用いる属性名を指定します。空文字列の場合はスコアが識別子として用いられます。
- `idsuffix` : 文書の識別子の接尾辞として用いられる属性名を指定します。複数回指定できます。
- `ordexpr` : 属性検索の際に用いられる順序指定式を指定します。または、「`$`」に続けて属性名を書くと常にその属性値の降順でソートされます。
- `dupcheck` : 正数なら識別子の重複検査を行います。
- `union` : 正数なら論理和メタ検索を行います。
- `score` : 論理演算時のスコアの演算方法を指定します。「`0`」だと合計値、「`1`」だと最大値、「`2`」だと最小値、「`3`」だと平均値を採用します。
- `tmpdir` : 一時ファイルを格納するディレクトリのパスを指定します。
- `cclife` : キャッシュファイルの寿命を秒数で指定します。負数なら無期限です。
- `logfile` : ログファイルのパスを指定します。
- `logformat` : ログの書式を指定します。「`{time}`」は現在時刻に変換され、「`{cond}`」は検索条件に置換され、「`{hnum}`」は検索の該当件数に置換され、それ以外で「`{}`」で括ったものはその名前の環境変数の値に置換されます。「`\t`」はタブに、「`\n`」は改行に置換されます。

論理積メタ検索ゲートウェイは以下のパラメータを受け付けます。順序指定はできません。

- `phrasen` : nには1から9までの数字をとり、対応するインデックスに対する全文検索条件を指定します。
- `attrn` : nには1から9までの数字をとり、最初のインデックスに対する属性検索条件を指定します。
- `max` : 結果の表示件数を指定します。デフォルトは10件です。
- `distinct` : 重複回避属性の名前を指定します。
- `fresh` : 正数ならば、そのクエリにはキャッシュを使いません。

例えば、「`http://localhost/estscout.cgi`」として設置した論理積メタ検索ゲートウェイに対して、1番目のインデックスにおいて本文に「`mikio`」を含み、2番目のインデックスにおいて本文に「`tokyo`」を含み、1番目のインデックスにおいてURI属性に「`123`」を含むものを取得するには、以下のURLのHTTPリクエストを発行します。

    http://localhost/estscout.cgi?phrase1=mikio&phrase2=tokyo&attr1=%40title%20STRINC%20123

出力の最初の行には該当数の概算が出力されます。それ以降の行は、該当文書の識別子とスコアがタブ区切りで出力されます。例えば以下のようになります。

    256
    file:///home/mikio/tako.html   12561
    file:///home/mikio/ika.html    11624
    file:///home/mikio/uni.html    9232
    file:///home/mikio/kani.html   8293
    file:///home/mikio/ebi.html    8312

### 論理和メタ検索ゲートウェイ

論理和メタ検索ゲートウェイを利用するには、CGIスクリプト`estsupt.cgi`とその設定ファイル`estsupt.conf`をCGIスクリプトが利用できる場所に設置してください。

設定ファイルは、変数名と値を「`:`」で区切った形式の行を並べたものです。デフォルトでは、設定ファイルは以下のような内容になっています。

    targeturl: http://searcher1/estscout.cgi
    targeturl: http://searcher2/estscout.cgi
    stmode: 0
    tmpdir: /tmp
    cclife: 1800
    #shareurl: http://merger1/estsupt.cgi
    #shareurl: http://merger2/estsupt.cgi
    failfile:
    logfile:
    logformat: {time}\t{REMOTE_ADDR}:{REMOTE_PORT}\t{cond}\t{hnum}\n

それぞれの変数の機能を以下に示します。

- `targeturl` : 論理積メタ検索ゲートウェイのURLを指定します。複数回指定できます。
- `stmode` : 正数ならシングルスレッドモードになります。
- `score` : 論理演算時のスコアの演算方法を指定します。「`0`」だと合計値、「`1`」だと最大値、「`2`」だと最小値、「`3`」だと平均値を採用します。
- `tmpdir` : 一時ファイルを格納するディレクトリのパスを指定します。
- `cclife` : キャッシュファイルの寿命を秒数で指定します。負数なら無期限です。
- `shareurl` : 共有キャッシュ用の論理和メタ検索ゲートウェイのURLを指定します。複数回指定できます。
- `failfile` : 失敗確率ファイルのパスを指定します。
- `logfile` : ログファイルのパスを指定します。
- `logformat` : ログの書式を指定します。「`{time}`」は現在時刻に変換され、「`{cond}`」は検索条件に置換され、「`{hnum}`」は検索の該当件数に置換され、それ以外で「`{}`」で括ったものはその名前の環境変数の値に置換されます。「`\t`」はタブに、「`\n`」は改行に置換されます。

論理和メタ検索ゲートウェイのパラメータおよび出力の形式は論理積メタ検索ゲートウェイのものと全く同じです。

論理和メタ検索ゲートウェイはリモートに設置した論理積メタ検索ゲートウェイの検索結果を収集することができます。したがって、論理積メタ検索ゲートウェイを複数のマシンに設置することで分散処理が可能となります。

失敗確率ファイルは0.0から1.0までの小数式を内容とするファイルで、その値の確率で検索処理を失敗させるためにあります。システムの負荷が高すぎる場合にはこのファイルの内容を調整して、タイムアウトによる輻輳を避けて運用することができます。

## 助言

ここでは、Hyper EstraierのP2P機構を活用するためのコツをいくつか紹介します。

### パラメータの詳細

メタ検索の深度は0が起点です。深度が0なら自前のインデックスのみを対象とし、1ならリンク先のノードも対象とし、2ならその先も対象とし、3ならさらにその先も対象とします。それ以上の値も指定できます。クライアントが指定した深度がノードマスタの設定ファイルで指定した深度より大きい場合は、ノードマスタで指定した方に抑えられます。

設定ファイルで`rateuri`の値を正数にした場合は、各文書のURI属性に含まれるディレクトリ階層の深さがスコアに反映されます。階層が浅い位置にあるほど重要な文書で、深い位置にあるほど瑣末な文書だという考え方に基づいています。具合的には、以下の値を計算します。

- score * (8 / (4 + depth))

例えば、「http://foo.go.jp/bar/baz.html」の階層の深さは2ですので、元来のスコアに1.333が掛けられます。「http://abc.gov/def/ghi/jkl/mno/pqr.html」の階層の深さは5ですので、元来のスコアに0.888が掛けられます。

リンクの信頼度は検索結果の各文書の順位を決定する際に利用されます。各ノードから収集した結果をマージする際には、各文書について以下の値が計算され、それが大きいものから提示されます。scoreは各文書のノードにおけるスコアです。maxは表示件数です。rankはノード内の表示順位です。creditは各ノードの信頼度を10000で割ったものです。

- score * (max * 2 - rank) * credit

例えば、信頼度15000のノードに対して10件の文書を問い合わせた結果の4位の文書のスコアが1000だった場合、調整後のスコアは「1000 * (10 * 2 - 4) * (15000 / 10000)」で24000になります。なお、設定ファイルで`mergemethod`の値を1にした場合は「score * credit」という式が用いられ、3にした場合は「(max - rank) * credit」という式が用いられます。ただし、`scoreexpr`が4（asis）の場合はスコアの調整は一切行われません。

各文書において`@weight`という属性が定義されていた場合、その値が調整後のスコアに掛けられます。これによって特定の文書に重みづけを行うことができます。値は小数で指定できます。例えば調整後のスコアが24000で`@weight`が1.5の場合は36000になります。

自前のインデックスの信頼度は10000で固定です。したがって、他のノードへのリンクの信頼度は、その結果を自前のインデックスの結果より上位にしたい場合は10000より大きくし、下位にしたい場合は10000より小さくするとよいでしょう。

### 認証機構の詳細

認証機構のモードは3種類あり、設定ファイルで指定されます。デフォルトは2です。以下の表では、各種の操作に対して匿名を許可する場合は「○」、認証を行う場合は「×」を示します。ノードサーバの管理操作とは、`put_doc`、`out_doc`、`_set_user`、`_set_link`のことです。ノードサーバに対するそれ以外の操作は通常操作です。

| 1（none） | 2（admin） | 3（all）
--- | --- | --- | ---
ノードマスタの操作 | × | × | ×
ノードサーバの管理操作 | ○ | × | ×
ノードサーバの通常操作 | ○ | ○ | ×

認証機構があっても平文をネットワーク上に流すのではセキュリティとしては不十分です。特にHTTPの基本認証のセキュリティは低く、邪悪な人達には全く無力と言っても過言ではありません。したがって、重要なデータを扱う際には、通信を暗号化することをお薦めします。ただし、Hyper Estraier自体は暗号通信の機能を提供しませんので、Apache等をリバースプロクシとして利用してください。同時接続数が多い場合は市販のSSLアクセラレータを利用するとよいでしょう。

このような認証機構が気に入らない場合は、フロントエンドのクライアントを実装して、そこでアカウント管理と認証を行ってください。フロントエンドがバックエンドであるノードマスタやノードサーバにアクセスする際には常にスーパーユーザを用いるとよいでしょう。Apacheをフロントエンドにすれば、mod_auth_ldapを使ってLDAPによる認証を行ったり、mod_auth_radiusを使ってRADIUSによる認証を行ったりできます。Locationディレクティブなどを駆使すれば各メソッドのアクセス権限を詳細に設定することもできます。

### 類似検索

設定ファイルの`smlrvnum`の値を1以上にすると、類似検索が有効になります。この状態でノードサーバに文書を登録すると、類似検索用のキーワードが抽出されて内部で管理されるようになります。

キーワードの選定にはTF-IDF法を用いますが、デフォルトでは、文書頻度（DF）の算出にはそのノードの既存の登録文書を用います。したがって、はじめの方に登録した文書と後の方に登録した文書とではTF-IDF法の強さに差が出てきます。これを回避するために、予め生成した文書頻度のデータベースを用いることができます。`estcmd words`コマンドで生成したデータベースをサーバルートディレクトリの直下に「`_dfdb`」というファイル名で置いてください。

### 通常のWeb機能

ノードマスタは通常のWebサーバとしての機能も備えています。この機能は検索対象文書の実体を公開する場合に便利でしょう。例えば、「`/home/www/public_html`」以下を公開したい場合は、設定ファイルに以下のように書きます。

    docroot: /home/www/public_html
    indexfile: index.html

### バックアップ

設定ファイルの`backupcmd`に任意のコマンドを指定することにより、ノードマスタの稼働中にデータベースのバックアップを取ることができます。ノードマスタ組み込みのユーザインターフェイスから「BACKUP」を実行すると、全てのノードのデータベースをデバイスに同期させた上で、`backupcmd`で指定したバックアップコマンドが呼び出されます。バックアップコマンドの第1引数にはサーバルートディレクトリの絶対パスが指定されます。通常は、以下のようなシェルスクリプトを用意して、そのパスを指定するとよいでしょう。

    #! /bin/sh
    cd "$1"
    name=`printf '%s' ${PWD##*/}`
    cd ..
    find "./$name" -depth | grep -v './_pid' |
    cpio -o > "$name.`date -I`.cpio"

`_pid`ファイルは不要ですので、アーカイブに含めないようにするか、アーカイブを展開した後に削除するようにしてください。なお、CPIO形式のファイルは「cpio -idv < casket.xxx.cpio」などとすると展開できます。

### 負荷テスト用コマンド

各種のWebアプリケーションに対する負荷テストのためのユーティリティとして、`estload`コマンドが提供されます。以下の書式を持ちます。

`estload [-t num] [-l num] [-i num] [-p] [-q] [file|url]`
  fileを指定した場合、そのファイルの各行に含まれるURLのリクエストを発行します。urlを指定した場合、そのURLのリクエストを発行します。何も指定しなかった場合、標準入力の各行に含まれるURLのリクエストを発行します。
  -tは並行して動かすスレッドの数を指定します。デフォルトは1です。
  -lはリストを繰り返す回数を指定します。デフォルトは1です。
  -iはリクエストを発行してからスリープする時間をミリ秒単位で指定します。デフォルトは0です。
  -pを指定すると、レスポンスを表示します。
  -qを指定すると、進捗状態を表示しません。

### 初期導入の効率化

大規模なサイトの立ち上げを素早く行いたい場合、`estcmd`を利用するかアプリケーションを作成するかして、コアAPIのレベルでインデックスを作った方がよいでしょう。そうして作ったインデックスは、ノードディレクトリに置くことでノードサーバとして利用することができます。例えば、「`/home/mikio`」以下の文書を登録した、名前が「`mikio`」でラベルが「`Mikio Hirabayashi`」のノードサーバを設置するには、以下のようにします。

    estmaster init casket
    estcmd gather -sd -il ja casket/_node/mikio /home/mikio
    estcmd meta casket/_node/mikio label "Mikio Hirabayashi"

類似検索を有効にする場合は、以下のコマンドも実行してキーワードデータベースを作ってください。

    estcmd extkeys casket/_node/mikio

ユーザやリンクの設定は、ノードマスタを起動させた上で、ノードAPIや`estcall`コマンドを使って行ってください。なお、`estcall`には`estcmd gather`のようなギャザラ用のサブコマンドは用意されていません。`find`や`estcmd draft`などを組み合わせれば同等のことは可能です。最も簡単には、以下のようにします。

    find /home/mikio/public_html -type f -name '*.html' |
    while read file ; do
      estcmd draft -fh -il ja "$file" |
      estcall put -auth admin admin http://localhost:1978/node/test1
    done

### Windowsサービス

Windows版のバイナリパッケージには、ノードマスタのWindowsサービス版も同梱されます。ログオンしていない状態でもノードマスタを動かしたい場合にはWindowsサービスが役立ちます。ここでは、「`C:\hyperestraier`」の中にHyper EstraierのコマンドやDLLの一式を置いて、そこにパスを通してあることを前提に、Windowsサービス版の導入手順を説明します。

まず、サーバルートディレクトリを作成します。コマンドプロンプトで以下を実行します。

    estmaster init C:\hyperestraier\casket

ノードマスタのサービスを登録します。コマンドプロンプトで以下を実行します。なお、行末の「`^`」はコマンドプロンプトのエスケープ文字です。

    scmutil install estraier ESTRAIER ^
      C:\hyperestraier\estmaster-sv.exe C:\hyperestraier\casket

「コントロールパネル」の「管理ツール」の「サービス」を開くと、「`estraier`」というサービスが登録されているはずです。それを開始すれば、ノードマスタが起動します。あとは、通常のWindowsサービスと同様に扱うことができます。ログオフしてもノードマスタは動作し続けますし、シャットダウンの際には正常終了します。システム起動時にノードマスタも自動起動するように設定することもできます。

`scmutil`はWindowsサービスのインストールとアンインストールのためのコマンドです。以下の書式を持ちます。

`scmutil install name label command ...`
  サービスをインストールします。
  nameはサービスの名前を指定します。
  labelはサービスのラベルを指定します。
  commandはサービスの起動コマンドを指定します。コマンドやファイルのパスには絶対パスを用います。

`scmutil uninstall name`
  サービスをアンインストールします。
  nameはサービスの名前を指定します。

`estmaster-sv.exe`はサービス専用のコマンドです。起動時の第1引数でサーバルートディレクトリを指定します。