[ホーム] -> [Aache + PHP + PostgreSQL 実験室]

Apache の管理

設定項目

Apache の基本的な設定についてはすでに説明しましたが、もう少し説明しておいた方がいい項目があるので、ここで説明しておきましょう。もちろん、すべてを説明するには無理がありますので、独断と偏見で抜粋してあります。

Apache の設定項目は、「ディレクティブ」とか呼ばれています。これは英語の名称から来ているのですが。各ディレクティブの値にスペースが入っている場合は、ダブルクォーテーション「"」で囲む必要があります。また、設定値が長くて2行以上に分けたい場合は、改行の手前にバックスラッシュ「\」を置くことにより、複数行に分けることが出来ます。

前にした説明と重複する項目もありますが、こちらをリファレンス的に参照できるようにと考えています。機能別に振り分けましたので、それぞれを参照してみてください。

アクセス制御

ディレクティブでも説明しましたが、アクセス制御についてまとめてみます。

アクセス制御に関しては大きく二種類有り、アドレス認証と、ユーザ認証です。アドレス認証は、クライアントのアドレス(IP アドレス、ホスト名等)を元に制御するに対し、ユーザ認証はユーザ名とパスワードを入力してもらい、その情報を元に認証を行います。

アドレス認証

アドレス認証は、次のように Order, Allow, Deny ディレクティブを使います。

# ドメインが domain.com からのみ許可
Order Deny,Allow
Deny  from all
Allow from .domain.com
# この例は、見たとおりに一部から許可だとわかりやすい。

# これも同じで domain.com からのみ許可
Order Allow,Deny
Allow from .domain.com
# この例はデフォルト Deny に頼っている。

# domain.com は許可だけど、host1.domain.com は拒否
Order Allow,Deny
Allow from .domain.com
Deny  from host1.domain.com
# この例はデフォルト Deny に頼っている。
# ただし、複雑な指定はしやすい。

# domain.com は拒否だけど、host1.domain.com は許可
Order Deny,Allow
Deny  from .domain.com
Allow from host1.domain.com

一部だけに許可し、さらにその中の一部を拒否したい場合は Allow,Deny 、一部だけ拒否し、さらにその中の一部を許可したい場合は Deny,Allow を使います。例の上の二つのようにどちらでも表現できるものもありますが、どちらを使うかは管理者の好みなのでしょう。

ユーザ認証

ユーザ認証は、次のようにパスワードファイルを用意して行います。

# これはすべてで必要
AuthAuthoritative on
AuthType          Basic
AuthName          "Sample Authenticator"

# users.dat にあり、パスワードが正しければ全員許可
AuthUserFile  /www/data/users.dat
Require       valid-user

# users.dat にあり、パスワードが正しく、指定されユーザのみ許可
AuthUserFile  /www/data/users.dat
Require       user adminuser

# users.dat にあり、パスワードが正しく、
# そのユーザが groups.dat の該当グループにいれば許可
AuthUserFile  /www/data/users.dat
AuthGroupFile /www/data/groups.dat
Require       group admingroup

組み合わせ

また、アドレス認証とユーザ認証を組み合わせることも出来ます。

# .domain.com から許可するが、それ以外は users.dat にある、
# ユーザ認証をする必要がある
Satisfy Any
Order Deny,Allow
Deny  from all
Allow from .domain.com
AuthAuthoritative on
AuthType          Basic
AuthName          "Sample Authenticator"
AuthUserFile      /www/data/users.dat
Require           valid-user

# users.dat にあるユーザ認証をする必要があるが、
# .dame.com からはアクセスできない
Satisfy All
Order Deny,Allow
Deny  from .dame.com
AuthAuthoritative on
AuthType          Basic
AuthName          "Sample Authenticator"
AuthUserFile      /www/data/users.dat
Require           valid-user

組み合わせの場合、アドレス認証が先に行われるので、Satisfy All で、拒否されるアドレスからのアクセスの場合は、基本認証は行われません。

ログ

Apache のログには、エラーログとアクセスログがあります。

エラーログは ErrorLog ディレクティブで設定したファイルに出力されるログで、LogLevel により、出力されるログの種類が決定されます。このログファイルには、サーバ起動・終了した、ファイルが見つからないなどのエラーが記録されます。管理者は定期的に見るといいですが、ゴミも多いので、見るときはフィルターをかけるなど工夫してください。

もう一つのアクセスログは、いろいろカスタマイズできるので、ここで説明しておきます。カスタマイズ対象は、大きく二つあり、ログのフォーマットと、出力先です。ファイルに保存して、ログ解析ツールなどを利用する場合は、あまりフォーマットは変更せず、すでに定義済みの combined などを使うといいと思います。ここでは、ログを PostgreSQL に記録する説明をします。

概要は、Apache が出力するログのフォーマットを insert 文にし、その出力を psql に渡して insert を実行する、というものです。

まず、ログのフォーマットですが、LogFormat ディレクティブで設定します。マニュアルを見るといろいろな書式が使えることが分かります。ここでは、PostgreSQL に記録できるようにするために、次のようにします。

LogFormat "insert into apache_log values \
 ('%h','%>l','%u','%{%Y-%m-%d %H:%M:%S %Z}t','%r',%>s,%B,\
 '%{Referer}i','%{User-Agent}i')" \
 pgsql

これでログの出力フォーマットが insert 文になりました。次に CustomLog ディレクティブを利用して、この insert 文を psql に流すようにします。

CustomLog "| sed -n 's/;/\\\\;/g; p' \
|/usr/local/pgsql/bin/psql -q -S wwwdb nobody" pgsql

wwwdb がデータベース名で、nobody がデータベースに接続するユーザ名です。ご自分の環境に合わせて修正してください。この方法だと、psql がパスワードを聞いてくる場合はうまくいきませんpg_hba.conf を修正して、パスワードなしでも接続できるように設定をゆるめる必要があります。

その前の sed は、アクセス文字列に不正な文字があった場合の最低限の対処です。もしこれがないと、別の SQL 文を実行することが可能になってしまいます。この設定をしていても、シングルクォーテーション「('」が入っていると、ログに記録されません。また、sed が出力をバッファリングするようで、ある一定以上ログがたまらないと psql にデータを渡してくれません・・・。

設定はこれだけなので、apachectl restart として再起動してみましょう。もし何かエラーがれば、error_log に記録されているはずです。

特にエラーが出ていないようでしたら、何かページにアクセスしてみて、うまくテーブルにデータが追加されているか確認しましょう。バッファリングされているので、何度も何度も(数十回)アクセスしてみてください。一気にログに出力されるはずです。

セキュリティを考慮したとき、危険な文字列は「');」という文字列です。この文字の後ろに SQL 文を書かれると、それが実行されています。上の sed でセミコロン「;」だけ対処して、複数の SQL 文が実行されないようにしています。本当は、シングルクォーテーションをエスケープするのが正解ですが、もっと複雑な仕組みが必要なので、やめにしました。ちなみに、「ブラウザからはこの文字列は投げられない」と思った人は不正解です。別にブラウザを使わなくても、直接ポート80に投げれば(telnet とか使って)どんな文字でも遅れます。400 Bad Request が返ってきますが、結局その文字列自体がログに記録されてしまうので、サーバ側で何か対処しざるを得ません。

もし、上記の sed 対応ではまずいと思った人や、別の方法を知っている方がいらっしゃったら、ご教授ください。

仮想ホスト

Apache の機能の一つに、仮想ホスト(バーチャルホスト)の機能があります。これは、一つのサーバで、複数のサーバが動いているように見せかける機能です。例えば、DNS の設定で同じ IP アドレスに対し、www.foo.comwww.bar.com の二つの名前を付けたとします(A でも CNAME でも)。このとき、特に設定しないと /index.html へのアクセスはどちらの名前でアクセスしても同じファイルを参照します。しかし、仮想ホストの機能を使えば、これを別々に出来ます。

仮想ホストには名前ベースと IP ベースの二種類あります。まずは IP ベースの設定をしてみましょう。

IP ベース仮想ホスト

IP ベースとは、一つのマシンに複数の IP アドレスを持っていて、それぞれで別々の設定を行う場合に使います。例えば 192.168.0.5110.3.0.11 という IP アドレスを持っていたとしましょう。httpd.conf に次のように設定します。

<VirtualHost 10.3.0.11>
  ServerName   project.foo.com
  DocumentRoot /www/project
</VirtualHost>
<VirtualHost 192.168.0.51>
  ServerName   personal.foo.com
  DocumentRoot /www/personal
</VirtualHost>

これで、それぞれ別の設定になりました。ここでは ServerName, DocumentRoot ディレクティブしか設定していませんが、他のディレクティブを指定して、ログファイルを分けたりとか、アクセス制御を分けたりとか出来ます。IP アドレスの部分が _default_ の仮想ホスト(<VirtualHost _default_>)を設定でき、アクセスが他の仮想ホストの IP アドレスにマッチしない場合、この _default_ ホストの設定が利用されます。つまり、この例の場合は IP アドレスを 3 つ以上持っていた場合に _default_ の設定が利用されます。もし _default_ ホストが無いと、大元の設定(つまり httpd.conf で普通に設定したやつ)が利用されます。

名前ベース仮想ホスト

次は名前ベースの仮想ホストです。こちらは、一つの IP アドレスに対して複数の名前が付いているとき、設定を分けるのに利用します。アクセスされた IP アドレスではなく、アクセスするときに利用したホスト名が利用されるのです。これは次のようにします。

NameVirtualHost *
<VirtualHost *>
  ServerName   opaq.foo.com
  DocumentRoot /www/opaq
</VirtualHost>
<VirtualHost *>
  ServerName   tepco.foo.com
  DocumentRoot /www/tepco
</VirtualHost>

NameVirtualHost ディレクティブの設定は必須です。アクセスするときに利用したホスト名が ServerName に指定した値と同じ場合に、その仮想ホストの設定が利用されます。もし、一つの仮想ホストに複数の名前を割り当てたい場合は、ServerAlias ディレクティブを利用します。

NameVirtualHost *
<VirtualHost *>
  ServerName   project.foo.com
  ServerAlias  internal.foo.com
  DocumentRoot /www/project
</VirtualHost>

定義されている名前以外でアクセスがあると、一番最初に定義した名前ベースの仮想ホストの設定が利用されます。

組み合わせの例

NameVirtualHost ディレクティブは、名前ベースの仮想ホストで利用する IP アドレスを指定します。もし IP アドレスを複数持っていて、IP ベースの仮想ホストと組み合わせたい場合は、* ではなく、利用する IP アドレスを指定します。例えば次のようになります。

# Host A
<VirtualHost _default_>
  ServerName   internal.foo.com
  DocumentRoot /www/internal
</VirtualHost>
# Host B
<VirtualHost 192.168.0.51>
  ServerName   personal.foo.com
  DocumentRoot /www/personal
</VirtualHost>
NameVirtualHost 10.3.0.11
# Host C
<VirtualHost 10.3.0.11>
  ServerName   opaq.foo.com
  DocumentRoot /www/opaq
</VirtualHost>
# Host D
<VirtualHost 10.3.0.11>
  ServerName   tepco.foo.com
  DocumentRoot /www/tepco
</VirtualHost>

この設定で、このマシンが 192.168.0.51, 192.168.0.55, 10.3.0.11, 10.3.0.15 の4つの IP アドレスを持っていて、名前を次のように持っていた場合に利用される仮想ホストの対応表です。

personal.foo.com    192.168.0.51   Host B
seep.foo.com        192.168.0.51   Host B
calgon.foo.com      192.168.0.55   Host A
opaq.foo.com        10.3.0.11      Host C
tepco.foo.com       10.3.0.11      Host D
sonet.foo.com       10.3.0.11      Host C
totte.foo.com       10.3.0.15      Host A

この仮想ホストの設定を確認したい場合は、httpd -S とコマンドを打ってみてください。仮想ホストの分析結果が表示されます。思った通りに設定できているか確認してみてください。

名前ベースの仮想ホストは、HTTP リクエストヘッダーの Host: を見て判断しています。古いブラウザなどはこの値を送らないものがあるそうです(お目にかかったことがないので分からない)。もし、Host: を送ってこないと、どのホスト名でアクセスしようとしても、デフォルトホスト(一番最初に定義した仮想ホスト)の設定になってしまいます。その場合は、ServerPath ディレクティブを指定します。

ServerName   www.foo.com
DocumentRoot /www/root
NameVirtualHost *
<VirtualHost *>
  ServerName   project.foo.com
  DocumentRoot /www/project
  ServerPath   "/project"
</VirtualHost>

この様に設定しておくと、http://www.foo.com/project/ へのアクセスが、http://project.foo.com/ へのアクセスされたのと同じ設定で動作します(仮想ホストの設定が使われる)。つまり、Host: ヘッダーを送らないブラウザが、http://project.foo.com/ にアクセスすると、http://www.foo.com/ へのアクセスと同じになってしまいますが、そこでそのページに http://www.foo.com/project/ へのリンクが書かれていれば、そちらからアクセスできるようにまります。ただし、この場合気を付けなければならない点が一つあり、project.foo.com の中で利用しているリンクのパスを、すべて相対パスで指定しないといけない点です。リンクを ../images/log.png とか指定していればいいですが、/images/log.png としておくと、http://www.foo.com/project/ からアクセスした場合にリンクが正しくなくなってしまいます。

名前ベースは、かなり便利なので、一度使ってみてください。私は、いろいろなサイト構築の仕事をしたりしますが、それぞれのプロジェクトで開発用のマシンを用意できない場合は、一つのマシンに仮想ホストの設定して開発したりしています。それぞれの仮想ホストでルートディレクトリが変えられるのはありがたいです。

コンテントネゴシエーション

コンテントネゴシエーションとは、簡単に言うとクライアント(ブラウザ)が送ってきた情報を元に、適切なドキュメントを返す仕組みです。コンテントネゴシエーションを有効に知るには、Options +MultiViews と指定して有効になっていないといけません。どのドキュメントを返すかを判断する材料となるのは、次の4つがあります。

これらを元に判断するわけですが、これらの機能を使うには、mod_mime モジュールが利用でくるようになっていないといけません。それと、以下の説明で出てくる拡張子についてです。例えば、index.html.ja というファイルがあったとすると、この拡張子は htmlja です。この拡張子の順番には、特にどちらが先でもいいですし、拡張子の数も二つ以上あっても問題ありません。

コンテントネゴシエーションを使ってアクセスする場合は、拡張子を指定せずに(/index.html ではなく、/index)アクセスしますが、もし拡張子も指定してアクセスした場合、それ以降の拡張子がコンテントネゴシエーションで使われます。例えば、/index.html とアクセスした場合、index.html.ja, index.html.en などが対象になりますが、index.ja.html は対象になりません。

また、ブラウザが送ってきた条件にあったファイルが見つからないけれど、他の候補がある場合は 406 Not Acceptable が返ってきて、他の候補を選択できる一覧が表示されます。これが返ってきた場合は、ブラウザの条件に合うファイルがなかったことを意味しますので、Apache の設定、ブラウザの設定、用意したファイルの名前を確認してください。

言語

この中で一番よく使われると思うのは言語でしょう。ブラウザは大抵、「言語の優先順位」とか「表示する言語の順序」などの設定が出来ます。ブラウザはこの値を元に、Accept-Language: という HTTP リクエストヘッダーを生成し、サーバに送ります。サーバ側ではこのヘッダーの値を元に、どのドキュメントを返すか決定します。

Accept-Language: ヘッダーには、例えば「ja, en;q=0.50, fr;q=0.20」という風に設定されています。ja, en, fr は言語名です。各言語名の後ろに付いている、;q=0.50 とかは、優先度です。0 から 1 の値を取り、1 の方が優先度が高いです。省略されている場合は 1 が指定されたのと同じことになります。つまりこの例だと、日本語、英語、フランス語の順番の優先度が付いています。

この状態で、/index というファイル(拡張子は指定しません!)にアクセスすると、サーバ側では拡張子に言語名の付いたファイルを検索します。index.html.ja, index.ja.html, index.html.en, index.en.html ... と検索されていき、最初に存在するものを返します。

複数拡張子が付いていても、その中のどれかに当てはまればいいので、.html.ja, .ja.html のどちらでも問題ありません(両方ある場合は、言語名が最後に付いているファイルが優先されるみたいです)。また、言語を表す拡張子が複数付いていた場合は、どちらの言語を指定されても、そのファイルは対象とまります。

試しに次のようにファイルを作り、ブラウザの言語の設定を変えてアクセスしてみてください。httpd.conf.htaccessOptions +MultiViews と指定するのを忘れないでください。

> echo Japanese > lang.html.ja
> echo English > lang.html.en
> echo French > lang.html.fr
> echo Unknown > lang.html

というファイルを作り、lang に(拡張子は指定しません!)アクセスしてみてください。ブラウザの言語の優先順位を変えていろいろアクセスしてみてください。うまくいきましたか? うまくいかない人もいるかもしれます。と言うのは、これ以外でいくつかの設定に原因があるのです。

# 言語名と拡張子を結びつける
AddLanguage en .en
AddLanguage ja .ja
AddLanguage fr .fr
# 優先度が設定されていない場合の優先順位
LanguagePriority en ja
# 言語名の拡張子が付いていないファイルの言語名
DefaultLanguage en

大きく上記の三つで、利用する言語が AddLanguage ディレクティブで定義されているか確認してください。最近の Apahce はデフォルトで多くの言語設定がされているので問題ないはずです。LanguagePriority(mod_negotiation)は、Accept-Language: ヘッダーに複数の言語が指定されているにもかかわらず、それらの言語に優先度(;q=)が指定されていない場合に、サーバ側で判断する優先順位です。しばらく前のブラウザ(HTTP/1.1 に沿っていないやつ)は、これの優先度を付けないのがあるので、注意が必要です。DefaultLanguage は、拡張子に言語名が含まれていない場合に、そのファイルがどの言語のファイルかを指定するものです。

ファイルタイプ

次にファイルタイプですが、文章なら HTML ファイルかテキストファイルか、画像なら PNG か JPEG か GIF かなどを決定するものです。

Apache は、ブラウザが送ってくる Accept: ヘッダーを見て判断しますが、大抵のブラウザはこの値をカスタマイズすることは出来ません。例えば、Mozilla 1.0 ブラウザは、次のような値を送ります。(適当に改行しています)

Mozilla 1.0 の場合
Accept: text/xml,application/xml,application/xhtml+xml,
        text/html;q=0.9,
        text/plain;q=0.8,
        video/x-mng,
        image/png,image/jpeg,
        image/gif;q=0.2,
        text/css,
        */*;q=0.1
Internet Explorer 6.0 の場合
Accept: */*

この値を見ると、Mozilla の場合、文章のは XML > HTML > テキスト で、画像は PNG,JPEG > GIF の優先度なのが分かりますね。ちなみに Accept: ヘッダーで指定されている値は、MIME タイプですので、この MIME タイプに対応する拡張子が登録されていないといけません。TypesConfig ディレクティブで指定されたファイルに MIME タイプと拡張子の対応が書かれているか確認してください。もし無い場合は追加するか、AddType ディレクティブを使って定義する必要があります。

# MIME タイプと拡張子を関連付け
#(以下の3つは TypesConfig で指定されているはずなので普通はいらない)
AddType text/xml   .xml
AddType text/html  .html
AddType text/plain .txt
# 拡張子がない場合のデフォルトの MIME タイプ
DefaultType text/plain

この様な設定で type.xml.en, type.html.en, type.txt.en というファイルを作って確認してみてください。全部存在すれば type.xml.en、それがなければ type.html.en と言う風に表示されるはずです。ちなみに言語の拡張子を指定していますが、この言語拡張子を付けておかないと DefaultLanguage で指定された言語ファイルだと認識されます。

ファイルタイプを表す拡張子が複数付いていた場合は、右側(後ろ側)の拡張子が優先されます。

エンコーディング

エンコーディングは Accept-Encoding: ヘッダーの値を見て行われます。現在このエンコーディングは、gzipcompress による圧縮くらいしか使い道がありません。このヘッダー自体、IE は送ってきませんし、ユーザがこの値を直接設定できるブラウザもあまり無いでしょう。ちなみ Mozilla 1.0 だと、次のようになります(Mozilla は、prefs.js でこの値を変えられます)。

Accept-Encoding: gzip,deflate,compress,identity

試してみるには、次のようにしてファイルを用意してみましょう。

> echo gzip file | gzip > enc.gz.html.en
> echo compress file | compress > enc.Z.html.en
> echo normal file > enc.html.en

Apache の設定は次のようになります。最初から指定してある設定は、x- が付いているので、注意してください。

AddEncoding compress Z
AddEncoding gzip     gz

この様にして、enc にアクセスすると、enc.gz.html.en の値が返ってくるのが分かると思います。ちなみに、デフォルトだと .gzapplication/x-gzip という MIME タイプに関連付けされているので(TypesConfig 内で)、.html より右側に .gz を付けてしまうと、このファイルの MIME タイプが text/html ではなく、application/x-gzip と判断されてしまうので注意してください。この enc.gz.html.en は、「英語で書かれ text/html 形式のファイルで、gzip によりエンコードされている」ファイルなのです。

このエンコーディングを表す拡張子が複数付いていた場合は、どちらのエンコーディングを指定されても、そのファイルは対象となります。

DefaultEncoding というディレクティブは無いようで、Accept-Encoding: ヘッダーの条件に合わない場合は、エンコーディング拡張子なしのファイルが使われます。

キャラクタセット

キャラクタセットとは、一般に文字コードと呼ばれているやつで(正確には文字コードとキャラクタセットは意味が違う、この場合の文字コードという言い方は間違い)、例えば EUC-JP やら、Shift_JIS、ISO-2022-JP がそれに当たります。

つまり、同じ文章でも Shift_JIS や EUC-JP で保存したファイルを用意しておき、どれかしか読めない場合はそのファイルを返すようにするのがこの設定です。ブラウザの Accept-Charset: ヘッダーを見て判断します。やはり、この値をユーザが設定できるブラウザは少ないようです。Apache の設定は次のようになります。

AddCharset ISO-2022-JP .jis
AddCharset Shift_JIS   .sjis
AddCharset EUC-JP      .euc-jp
AddCharset UTF-8       .utf-8

この場合も、DefaultCharset というディレクティブは無いようで、Accept-Charset: ヘッダーの条件に合わない場合は、エンコーディング拡張子なしのファイルが使われます。

それでは、次のようにしてファイルを用意してみましょう。

echo "日本語 ISO-2022-JP" | iconv -f EUC-JP -t ISO-2022-JP > char.jis.html.ja
echo "日本語 Shift_JIS" | iconv -f EUC-JP -t SHIFT_JIS > char.sjis.html.ja
echo "日本語 EUC" > char.euc-jp.html.ja
echo "日本語 UTF-8" | iconv -f EUC-JP -t UTF-8 > char.utf-8.html.ja
echo "Japanese Unknown" > char.html.ja

この例は端末が EUC-JP の場合です、Shift_JIS の場合などは、-f SHIFT_JIS などとしてください。iconv が無い場合は、nkf(UTF-8 は作れないか)や、jvim, emacs エディタなどで作ってみてください。

Mozilla 1.0(Windows 版)の場合、次のようなヘッダーを送っています。

Accept-Charset: Shift_JIS, utf-8;q=0.66, *;q=0.66

このことから、Shift_JIS > その他(UTF-8 と * が同じ優先度)という優先度だと分かります。テスト結果はうまくいったでしょうか?

キャラクタセットを表す拡張子が複数付いていた場合は、右側(後ろ側)の拡張子が優先されます。

コンテントネゴシエーションのまとめ

4種類あるのを説明しましたが、現実的に言うと、普通に使って問題ないのは言語のみで、ファイルタイプはかなり限定されますね。エンコーディングは微妙です。キャラクタセットはあまり必要を感じません(普通のブラウザなら、どのキャラクタセットでも問題ない気がします)。

エンコーディングは IE の場合 Accept-Encoding: ヘッダーを送りませんが、gzip 形式で送ってもきちんと解釈できます。gzip 形式にすると、転送するデータ量が減るため、ネットワークの帯域を有効利用できる、レスポンスが速くなる、というメリットはありますね。だから対応したブラウザのために gzip 形式も用意しておくと得するかもしれません(転送料によって課金されるタイプの ISP などを使っている人は)。

言語はきちんと用意さえすれば、ユーザにあった言語を表示できる仕組みなので便利だとは思います。例えば、DefaultLanguage en としておいて、サポートしたい言語を、index.html.ja, index.html.fr などと作っておけば、/index.html に対して適切な言語のファイルを返すでしょう。この場合、一見言語の切り替えをしているようには見えないので、非常にスマートだと思います。

ただし、日本人で英語版のブラウザを使っていたりすると、Accept-Language: ヘッダーで en しか送ってこなかったりもするので、言語を切り替える説明のページとかが必要だと思います。

ホームへ