Title: lighttpd のコンフィグレーション言語

執筆途上 $Date: 2006/12/17 11:53:35 $ 近藤一弘 GEC01044@nifty.ne.jp

目次

1 はじめに

WEBサーバ lighttpd のコンフィグレーション言語について、ソースコードを元に詳細を追いか けてみようと思います。ここでのコンフィグレーション言語とは、lighttpd の各種設定を記述するためのファイル(lighttpd -f lighttpd.conf として起 動するときの lighttpd.conf)を解析するパーサ周辺を示します。 あくまでも、言語仕様に焦点をあてたものであり、運用上のケーススタディの ようなもの(例えば FastCGI の設定方法など)についてはふれません。

ところで、lighttpd の公式サイト<URL:http://www.lighttpd.net/>には、

If you prefer, call it just "Lighty". We want to keep your tongue intact. :)

とあるので、以降 lighttpd の代わりに Lighty と書きます。

1.1 留意事項

本文書は、Lighty のソースコードを元にした筆者なりの解釈を含んだ説明に なっています。公式ドキュメントの翻訳ではありません。 また、(注意は払っていますが)誤りが含まれている可能性もあります。 誤り等にお気付きの際は、ご指摘いただければ幸いです。

1.2 Lighty のバージョン

本文書の内容は Lighty 1.4.13 を前提としています。

1.3 コンフィグレーションファイルのデバグ

Lighty のコンフィグレーションファイルが期待通りに解釈されているかを確 認するには、コマンドラインから以下のように実行します。

lighttpd -t -p -f lighttpd.conf 

オプション -t を指定すると、シンタックスチェックのみを行い終了します。 さらに -p を指定すると、コンフィグレーションファイルを解析し内部表 現に変換した結果を、stdout へ出力します。 これは変数の展開やマージが期待通りに行われているかを、サーバとして起動 することなく簡単に確認できるため便利です。 実際、本文書の例を検証するために -t と -p の組合せを多用しました。 なお、-t -p -f の組合せは -tpf と略記することもできます。

2 Lightyコンフィグレーション言語ひとめぐり

まず、Lighty のコンフィグレーション言語について大まかな感触をつかめる ように、いくつかの例を示します。

2.1 基本は、項目名 = 値

Lighty のコンフィグレーションファイルは、基本的に、項目名 = 値 という 行の集まりです。 '#'以降行末まではコメントとして扱われ、コンフィグレーションに作用を及 ぼしません。

# リクエスト受信ポートの設定
server.port = 8080

# ドキュメントルートディレクトリの設定
server.document-root = "/home/httpd/html"

これらは、スカラー値(構造を持たない単純な値)の設定例です。 スカラー値は、整数または文字列に分かれます。 ここで、2つの例をあげたのは、データ型の違いを示すためです。 server.port には整数、server.document-root には文字列を設定しています。 文字列は両端を二重引用符で囲みます。

2.2 配列

複数の要素を値とする設定項目は、配列が使用されます。 配列は、開括弧と閉括弧の間に要素をカンマ区切りで並べて記述します。 行をまたいで記述できます。

以下の例では、ロードするモジュール名を要素とする配列を指定しています。

# ロードするモジュール
server.modules = ("mod_rewrite", "mod_redirect", "mod_access",
    "mod_fastcgi", "mod_userdir", "mod_cgi", "mod_accesslog")

Lighty の配列は、連想配列です。 連想配列とは、キー(文字列)をインデックスとする配列です。 上の例では、キーを省略していますが、その場合 Lighty が内部的にユニーク なキーを生成します(詳細は後述)。

以下は、拡張子をキー、対応する MIMEタイプを値とする連想配列の例です。

# 拡張子と MIMEタイプの対応付け。
mimetype.assign = (
    ".pdf"  => "application/pdf",
    ".gif"  => "image/gif",
    ".jpg"  => "image/jpeg",
    ".gz"   => "application/x-gzip",
    ".zip"  => "application/zip",
    ".html" => "text/html",
    ".text" => "text/plain")

配列の要素として配列を指定することもできます。

# アクセス制限の設定
auth.require = ( "/server-status" =>
                     (
                       "method"  => "digest",
                       "realm"   => "download archiv",
                       "require" => "user=jan"
                     ),
                 "/server-config" =>
                     (
                       "method"  => "digest",
                       "realm"   => "download archiv",
                       "require" => "valid-user"
                     )
               )

2.3 リクエスト条件による設定の切替え

2.4 環境変数とユーザ定義変数

2.5 インクルード

3 データ型

データ型は大別して、スカラーデータ型と配列型があります。 スカラーデータ型には、整数、文字列、および Booleanがあります。

3.1 スカラーデータ

3.1.1 整数

整数は、Lighty をコンパイルしたC言語処理系の int 型に相当します。 ただし、構文上は先頭にマイナス文字を付けた定数の記述(例えば -1 のよう な書き方)ができず、整数の減算式も書けないため、実質的には 0 以 上の整数ということになります。 int が 32ビットの環境では(おそらく多くの環境では) 、0 から 2147483647 の範囲になります。

ちなみに、構文解析時点で整数のオーバーフローチェックは行われていないの で、最大値を超える値を指定してもエラーにはなりませんが、正しい値は設定 されません。 例えば筆者の環境では以下のようになりました。

max = 2147483647           # 最大値
min = max + 1              # -2147483648 になる
minus_1 = max + max + 1    # -1 になる

3.1.2 文字列

文字列は、可変長のバイト列です。

コンフィグレーションファイル上では、二重引用符で囲んで表します。

"Hello World."

文字列内に文字として二重引用符を含める場合は、バックスラッシュ \ に 続けて二重引用符を記述します。

例(二重引用符1文字の文字列)

"\""

二重引用符内のバックスラッシュ \ は、二重引用符の直前に置いた場合にの み特別な意味を持ちます。 バックスラッシュ自身を文字列に含める場合は、そのまま記述するだけです。

例(a\bという3文字の文字列)

"a\b"

字句解析の実装上、バックスラッシュ1文字の文字列は記述できないようです。 以下はいずれもエラーになります。

"\"     # エラー
"\\"    # エラー

3.1.3 Boolean

Boolean は "enable" または "disable" いずれかの値をとります。 ただし、Boolean は実質的に文字列であり、構文上も文字列の規則が適用され ます。

3.2 配列

配列は、0個以上の要素を保持できる1次元のデータ列です。 任意のデータ型を配列の要素にできます。 配列の要素として配列を再帰的に持つこともできます。

3.2.1 配列要素のキー

Lighty の配列は連想配列です。 連想配列とは、キー(文字列)をインデックスとする配列です。

以下は、キー key0 に対する値 value0、 キー key1 に対する値 value1 とい う2つの要素を持つ配列を定義しています。

var.a = ("key0" => "value0", "key1" => "value1")

Perl や Ruby のようなスクリプト言語では、連想配列のことを ハッシュ(Hash)と呼んでいます。 実際、Lighty でキーを明示した配列定義は、Perl のハッシュ定義構文とよく 似ています。 ただ、それらのスクリプト言語で連想配列のことをハッシュと呼ぶ背景には、 連想配列が Hash Table を使用して実装されるという前提があるようです。 Lighty では、連想配列の実装に Hash Table は使用されておらず、代わりに バイナリーサーチアルゴリズムでキーから値を検索するようになっています。

3.2.2 キーの自動払い出し

配列要素のキーは省略して記述することもできます。 その場合、自動的にキーが払い出されます。 自動的に払い出されるキーは、0 を起点として要素ごとにインクリメントした 値(文字列化した数値)になります。

例えば

("A", "B", "C")

と記述すると、それぞれの要素に対してキー "0", "1", "2" が払い出されま す。 これは

("0" => "A", "1" => "B", "2" => "C")

とほぼ等価です。 ただし完全に等価ではありません。 内部的には、キーが明示的に指定されたか自動的に払い出されたのかが区別さ れており、前者と後者では配列同士のマージを適用した際の結果が異なります。

キーを省略した要素と、キーを明示した要素を混在させることもできます。

("A", "key" => "B", "C")

このように記述すると、内部的には

("0" => "A", "key" => "B", "1" => "C")

のようにキーが割り当てられます。 キーを省略した要素にのみキーの自動払い出しとインクリメントが 行われるため、2 番目の値 "C" に付けられるキーは "2" ではなく "1" にな ります。

3.2.3 配列定義の構文

3.3 データ型の変換

3.3.1 整数から文字列への変換規則

文字列が期待される箇所に整数を指定すると、整数から文字列への変換が行わ れます。文字列に変換された後のフォーマットは、変換前の整数をゼロサプレ スした十進表記となります。

例えば

001

と記述した整数を文字列に変換すると

"1"

となります。

3.3.2 整数を連想配列のキーに適用

整数を連想配列のキーに指定した場合、キーに対して「整数から文字列への変 換規則」が適用されます。

例えば、

var.a = ( 001 => "value" )

var.a = ( "1" => "value" )

と等価です。

3.4 マージ規則

'+' 演算子により、スカラーデータ型同士、または配列同士のマージを行うこ とができます。

3.4.1 整数同士のマージ

整数同士のマージは、整数の加算になります。

例えば

1 + 2

の評価結果は 3 になります。

3.4.2 文字列同士のマージ

文字列同士のマージは、文字列の連結になります。

例えば

"a" + "b"

の評価結果は "ab" になります。

3.4.3 整数と文字列のマージ

一方が整数で他方が文字列の場合、整数が文字列に変換されたうえで、 文字列の連結になります。

例えば

1 + "2"

の評価結果は "12" になります。

3.4.4 結合規則

'+' 演算子は左結合です。

例えば

1 + 2 + "4"

は、まず 1 + 2 が整数同士のマージ(数値加算)として解釈され 3 になます。 次に 3 + "4" が整数と文字列のマージ(文字列連結)として解釈され "34" となります。

ポピュラーなプログラミング言語でよく行うような、括弧を使っての優先順位の 変更はできません。

3.4.5 配列同士のマージ

配列のマージは、基本的にマージ元の全要素をマージ先に追加することになり ます。

キーを明示しない配列同士のマージは、単純な連結になります。

# 以下の2行は等価
v = ("a", "b", "c") + ("d", "e", "f")
v = ("a", "b", "c", "d", "e", "f")

# 以下の2行は等価
v = ((1, 2), (3, 4)) + ((5, 6), (7, 8))
v = ((1, 2), (3, 4), (5, 6), (7, 8))

キーを明示した配列で、かつ両配列中に重複するキーが含まれない場合も、単 純な連結になります。

# 以下の2行は等価
v = ("k1" => "a") + ("k2" => "b")
v = ("k1" => "a", "k2" => "b")

キーを明示した配列で、かつ両配列中に重複するキーが含まれる場合、 重複するキーのそれぞれの値が文字列の場合のみ、カンマとスペースを挟ん で連結されます。

# 以下の2行は等価
v = ("k1" => "a") + ("k1" => "b")
v = ("k1" => "a, b")

ただし、この連結ルールが適用されるのは、両者の値が文字列型の場合のみです。 それ以外の場合は、右辺の要素が捨てられます。

# 以下の2行は等価
v = ("k1" => "a") + ("k1" => 20)
v = ("k1" => "a")

この場合、値 20 は整数型なので、捨てられます。 整数から文字列への自動型変換は適用されない点に注意して下さい。

また、同一キー要素の配列同士がマージされることはありません。

# 以下の2行は等価
v = ("k1" => (1, 2)) + ("k1" => (3, 4))
v = ("k1" => (1, 2))

この場合、右辺の要素は捨てられます。

一方の配列がキーを明示した配列で、他方がキーを明示しない配列であった場 合、キーの明示/非明示は左辺の配列に合わされます。

# 以下の2行は等価
v = ("0" => "a", "1" => "b") + ("A", "B")
v = ("0" => "a, A", "1" => "b, B")

# 以下の2行は等価
v = ("A", "B") + ("0" => "a", "1" => "b") 
v = ("a, A", "b, B")

3.4.6 配列の生成と、配列のマージ

1つの配列は、配列同士のマージ規則を作用させながら生成されるようになっ ています。

例えば

v = (1, 2, 3)

という記述に対して、内部的に配列が生成される際、

v = (1) + (2) + (3) 

と記述した際のマージと同じ処理が行われます。

このことから、1つの配列を記述する場合でも、マージ規則を意識しなけ ればならない場合があります。

例えば、

("A", 001 => "B", "C")

と記述した結果、実際に生成される配列は、

("A", "1" => "B, C")

になります。

前者の記述 ("A", 001 => "B", "C") は、Lighty 内で次のような手順で 処理され、結果として ("A", "1" => "B, C") になります。

001 というキーを "001" のように文字列として記述すれば、"001" => "B" "1" => "C" のようにマージされず別々の要素になります。

4 変数

4.1 変数の種類

変数には、大別して以下の種類があります。

4.1.1 設定項目変数

設定項目変数は、Lighty の各モジュールから参照されます。

4.1.2 環境変数 env.*

env.<環境変数名> とすることで、Lighty起動時の環境変数を参照できます。

プレフィックス env. で始まる変数は、読み取り専用です。 元々未定義であっても定義することはできず、定義済みの環境変数にマージを 適用することもできません。

例えば、

env.undefined_variable = 1
env.HOME += "/foo"

という記述はエラーになります。

4.1.3 ユーザ定義変数 var.*

プレフィックス var. で始まる変数は、ユーザがワーク領域として利用できる 変数です。プレフィックス var. は省略できます。 省略した場合は、Lighty が内部的に var. をおぎないます。

例えば

var.foo

foo

は同じ変数です。

ただし、Lighty は変数名の先頭から最初のドットまでをプレフィックスとみ なすので、

var.foo.bar

foo.bar

は異なる変数です。 後者の foo.bar は foo. がプレフィックスとして解釈されるため、 ユーザ定義変数ではありません。

4.1.4 その他の予約変数(var.PID, var.CWD)

4.2 変数の定義と初期値の設定(=)

<変数名> = <値> という形式で変数を定義と初期値の設定を行います。

server.port = 80

同一コンテキスト内では、一度定義された変数の再定義はできません。

server.port = 80
server.port = 8080    # エラー! 再定義はできない。

コンテキストが異なれば、同じ名前の変数をそれぞれで定義できます。

server.document-root = "/var/www/htdocs"
$HTTP["host"] == "localhost" {
    server.document-root = "/var/www/local-docs"   # OK
}

変数の再定義はできませんが、変数値に対してマージを適用して更新すること は可能です。

4.3 マージ(+=)

4.4 変数のスコープ

5 コンフィグレーションファイルの分割

5.1 include

5.2 include_shell

6 コンテキスト

6.1 グローバルコンテキスト

6.2 条件コンテキスト

6.3 リテラル

6.4 変数参照

7 構文規則(BNF)

以下に lighttpd のソースコード configparser.y から抽出した BNF(Backus Naur Form)を示します。

<input>         ::= <metalines>
<metalines>     ::= <metalines> <metaline>
                 |  
<metaline>      ::= <varline>
                 |  <global>
                 |  <condlines> <EOL>
                 |  <include>
                 |  <include_shell>
                 |  <EOL>
<varline>       ::= <key> '=' <expression>
                 |  <key> '+=' <expression>
<key>           ::= <LKEY>
<expression>    ::= <expression> '+' <value>
                 |  <value>
<value>         ::= <key>
                 |  <STRING>
                 |  <INTEGER>
                 |  <array>
<array>         ::= '(' ')'
                 |  '(' <aelements> ')'
<aelements>     ::= <aelements> ',' <aelement>
                 |  <aelements> ','
                 |  <aelement>
<aelement>      ::= <expression>
                 |  <stringop> '=>' <expression>
<eols>          ::= <EOL>
                 |  
<globalstart>   ::= 'global'
<global>        ::= <globalstart> '{' <metalines> '}'
<condlines>     ::= <condlines> <eols> 'else' <condline>
                 |  <condline>
<condline>      ::= <context> '{' <metalines> '}'
<context>       ::= '$' <SRVVARNAME> '[' <stringop> ']' <cond> <expression>
<cond>          ::= '=='
                 |  '=~'
                 |  '!='
                 |  '!~'
<stringop>      ::= <expression>
<include>       ::= 'include' <stringop>
<include_shell> ::= 'include_shell' <stringop>

<INTEGER>       ::= [0-9]+
<LKEY>          ::= [-.0-9A-Za-z_]+
<EOL>           ::= NL
<SRVVARNAME>    ::= SERVER | HTTP
<STRING>        ::= "[^"]*"

補足

Lighty のソースファイル configparser.y は Lemon Parser Generator という LALR(1) パーサージェネレーター用の構文定義ファイルです (拡張子が .y ですが、yacc/bison ではありません)。 このファイルに対して、UNIX コマンドラインで

grep ::= configparser.y

とするだけでも、構文定義部分を取り出せますが、より読みやすくするため、 簡単な ruby スクリプト で、以下のような加工を施してあります。

8 参考資料