HTTPサーバ khttpsv v1.0.2

3 Sep, 2000
ksw <card_captor@geocities.co.jp>

はじめに

khttpsvは、オブジェクト指向スクリプト言語Rubyによって実装された HTTPサーバ(Webサーバ)です。 現在のところCGIは実装されていますが、SSIやユーザ認証機能は未実装です。

差分プログラミング(*1)によって各種設定を行いますので、 種々の設定ファイルが不要になっています。 これは、サーバを作るほうにとっても 設定ファイルの解析が不要になるというすばらしい副作用があります^^;;

(*1) 機能を追加する際、従来からの変更部分だけプログラミングすれば 実装が可能になってしまうという手法

とはいえ、スクリプト言語の常として、実行速度はあまり速くはありません。 高いパフォーマンスが要求されない個人的な学習用・実験用・組み込み型 Webサーバとしてお使いいただければ幸いです。


実行環境

RubyがインストールされているUNIX環境なら、たぶん動きます。

# いいかげんですいません ^^;;

なお、 Ruby1.4.6 / Linux 2.2.16(Debian GNU/Linux 2.1) で動作確認をしました。


配付

配付は、 http://www.geocities.co.jp/Technopolis/6855/khttpsv.tar.gz にて行っています。GPL2に従ってなんなりと。

# なんなりとナニするんだ…


設定方法

1. 最小限の設定

さきにも述べたとおり、設定ファイルは実行スクリプトでもあります。 まずは、以下の内容を持ったsample_template.rbというファイルを 適当な名前にコピーしてください。拡張子は.rbにしてください。

require 'khttpsv_simple.rb'

class Sample_server < Khttpsv_simple
end

Sample_server.start(8080)

もし8080番以外のポートで動かしたい場合は、 Sample_server.start(8080)の数値を変更してください。 また、コンテンツは/usr/local/www/htdocs/、 または~user/.public_html/以下に置いてください。

これらの準備がすんだら、あとは

% ruby ファイル名.rb
で実行するだけです。 ファイルに実行属性を付け、先頭行に#!/usr/local/bin/rubyなどの行を 付けても良いでしょう。

2. 設定の基本

このように、基本的にはsample_template.rbをコピーして "class 〜"から"end"までの間を書きたしていくことによって設定を行います。

Ruby(やその他のオブジェクト指向言語)をご存知の方なら、 これが「クラスの継承」によってなされていることにお気づきかと思います。 ですから、設定はRubyの文法が許す限りどんな処理でも書くことができます(*2)。

(*2) Rubyの詳細な文法はhttp://www.ruby-lang.org/から得ることができます。

3. サーバの自称を変える

インスタンス変数 @server_name
サーバソフトウェアの名前です。 initializeメソッド中で設定してください。
例
  def initialize(s)
    super
    @server_name = 'MyPrivateServer/2.2.16'
    (以下略)
  end
インスタンス変数 @server_addr
メソッドget_server_addrの帰り値が保存されます。 メソッドhook_translate_pathやメソッドdocument_rootが 呼ばれるまでにはセットされています。
メソッド get_server_addr
サーバのアドレスを示す文字列を返すメソッドです。 アドレスはDNS名、NIS名、IPアドレスのどれでも構いませんが、 少なくともクライアントが認識できる形式である必要があります。 存在しないアドレスを返してしまうと、 クライアントがハイパーリンクをたどれなくなります。

デフォルトでは、アクセスを受け付けたIPアドレスを返すように定義されています。 バーチャルホスティングを実施する場合は、 このメソッドを必ず再定義する必要があります。

例: バーチャルホスティングなしの設定
  def get_server_addr
    return 'www.tomoeda-es.ac.jp'
  end
例: 名前ベースのバーチャルホスティング
  def get_server_addr
    # name-based virtual hosting
    if @req_headers['Host'] then
      if @req_headers['Host'][0] =~ /^www.domain1.co.jp:/i then
	return 'www.domain1.co.jp'
      elsif @req_headers['Host'][0] =~ /^www.domain2.co.jp:/i then
	return 'www.domain2.co.jp'
      end
    end
    return 'www.default.co.jp'
  end
ホスト名ベースのバーチャルドメインを作る場合、 ネットワークインターフェースに複数のIPアドレスを割り当てる必要はありません。 ただし、古いHTTPクライアントを使っている場合は これらのバーチャルドメインを区別して扱うことができません。
例: アドレスベースのバーチャルホスティング
  def get_server_addr
    # IP-based virtual hosting
    if @sock.addr[3] == '192.168.2.1' then
      return 'www.domain1.co.jp'
    elsif @sock.addr[3] == '192.168.2.2' then
      return 'www.domain2.co.jp'
    else
      return 'www.default.co.jp'
    end
  end
コンピュータが複数のIPを持っている場合、 アドレスベースのバーチャルホスティングを実施することができます。 この場合、複数のネットワークインターフェース(NIC)を持つか、 ひとつのNICに複数のIPアドレスを割り当てることになります。

4. ドキュメントがある場所の設定

インスタンス変数 @index_file
URLとしてディレクトリが指定された場合に、 代わりに表示するファイル名を指定します (たとえば、URLとしてhttp://server/dir/が指定された時、 http://server/dir/index.htmlが指定されたかのように振舞います)。 デフォルトでは'index.html'です。 ここで指定されたファイルが存在しない場合、 ディレクトリ内容の表示が許可されていればそれを表示します。 initializeメソッド中で設定してください。
インスタンス変数 @user_document_dir
URLとしてユーザディレクトリが指定されている場合に、 どこにwebのコンテンツが置いてあるか指定します。 これはそのユーザのホームディレクトリからの 相対パスでなければなりません。 デフォルトは'.public_html'です。 initializeメソッド中で設定してください。
例
  def initialize(s)
    super
    # like an NCSA httpd
    @index_file = 'welcome.html'
    # http://server/~foo/bar/baz.html seeks for ~foo/.www/bar/baz.html
    @user_document_dir = '.www'
    ...
  end
メソッド document_root
ドキュメントが置かれているディレクトリ(ドキュメントルート)を指定します。 バーチャルホスティングを実施している場合は、 どの仮想サーバにアクセス要求が送られているかによって ドキュメントルートを変化させます。 ユーザディレクトリを指定しているURL (http://server/~user/...という形式のURL)は このメソッドの影響を受けません。
例: バーチャルドメインを使わない場合
  def document_root
    return '/var/www'
  end
常に/var/wwwディレクトリをドキュメントルートとします。
例: バーチャルホスティングを使う場合
  def document_root
    case @server_addr
    when 'www.domain1.co.jp'
      return '/var/www/dir1'
    when 'www.domain2.co.jp'
      return '/var/www/dir2'
    else
      return '/var/www'
    end
  end
この例の場合、 クライアントが"www.domain1.co.jp"にアクセスしていると思っているなら/var/www/dir1を、 クライアントが"www.domain2.co.jp"にアクセスしていると思っているなら/var/www/dir2を それぞれドキュメントルートとします。
メソッド hook_translate_path
URL(URI)から実際のパスへの変換において、特殊な処理をここに記述します。 もし変換が必要なら文字列を返し、不要(デフォルトの処理)ならnilを返します。 Apache等でのScriptAliasなどに相当する機能を実現します。
例
  def hook_translate_path(s)
    if s.sub!(%r{^/cgi-bin/}, '/usr/lib/cgi-bin/') then
      # translate uri into path, for exapmle
      # http://server/cgi-bin/foo/bar.cgi -> /usr/lib/cgi-bin/foo/bar.cgi
      return s
    end
    if s.sub!(%r{^/icons/}, '/usr/X11/lib/X11/pixmap/') then
      return s
    end
    return super
  end
この例では、 http://server/cgi-bin/...というURLが要求されると/usr/lib/cgi-bin/...というパスに、 http://server/icons/...というURLが要求されると/usr/X11/lib/X11/pixmap/...というパスに、 それぞれ置き換えます。

5. 動作ポリシーを変える

メソッド allow?
機能の使用ポリシーを決定します。 デフォルトでは、ユーザディレクトリへのアクセス('user_dir')、 ディレクトリのリスティング('dir_indexing')を許可するようになっています。 このメソッドは、機能を示す文字列におうじて許可を示すtrueか 不許可を表すfalseのどちらかを返します。

なお、Rubyではメソッド名に'?'を含めることができます。

例
  def allow?(a)
    return true if a == 'cgi_exec'
    return super
  end
この例では、CGIの実行('cgi_exec')を明示的に許可し、 その他はデフォルトの動作に従うようになっています。
メソッド cgi?
インスタンス変数@translated_pathで示されるファイルが CGIであるかを判断するメソッドです。再定義の必要はおそらくありません。 デフォルトでは、拡張子がcgi(大文字小文字を区別しない)である 実行可能なファイルをCGIであると判断しています。
例
  def cgi?
    if @translated_path[-4,4] == '.asp' && FileTest.executable?(@translated_path) then
      return true
    elsif @translated_path[-4,4] == '.cgi' && FileTest.executable?(@translated_path) then
      return true
    end
    return false
  end
aspまたはcgiという拡張子をもつ実行ファイルをCGIであるとしています。 ちなみに、Rubyでは文字列クラスに[-4,4]という演算子を適用すると、 「うしろから数えて4バイト目から4バイトを取り出した文字列」という意味になります。
メソッド get_mime_type
インスタンス変数@translated_pathで示されるファイルの MIMEタイプを決定します。再定義の必要はおそらくありません。 デフォルトでは、MIME_mapというHashを利用して拡張子で決定します。 該当するMIMEタイプがない場合は、text/plainであるとしています。

MIME_mapはmime_type.rbというファイルで定義されているので、 メソッドを再定義しなくてもこちらを変更すればよい場合があります。

例
  def get_mime_type
    if @translated_path[-4,4].downcase == '.doc'
      return 'text/plain'
    else
      return super
    end
  end
この例では、拡張子がdocの場合(MS-Wordではなく)plain textであるとし、 それ以外の場合はデフォルトの動作を引き継ぎます。
メソッド Khttpsv_template.logger
ログを記録するメソッドです。 このメソッドは、メッセージの文字列と、重要度を示す数値を引数とします。 重要度は定数で表され、重篤な順からLOG_ERR, LOG_WARN, LOG_NOTICE, LOG_INFO, LOG_DEBUGとなっています。

デフォルトでは、LOG_INFOより重要なメッセージを、 スレッドIDと一緒に標準出力に表示します (たぶん、再定義したいでしょう)。

例
  def Khttpsv_template.logger(s, lev = LOG_INFO)
    pri = nil
    case lev
    when LOG_NOTICE
      pri = 'local0.notice'
    when LOG_WARN
      pri = 'local0.warning'
    when LOG_ERR
      pri = 'local0.error'
    end
    if pri then
      system('logger', '-t', 'khttpsv', '-p', pri, "#{Thread.current.id}: #{s}")
    end
    STDERR.print "#{Thread.current.id}: #{s}\n"
  end
この例では、LOG_INFOより重要なメッセージをsyslogに出力します。 また、すべてのメッセージを標準エラーに出力します。
メソッド logger
こちらは、個々のサーバスレッドごとのログを記録するメソッドです。 デフォルトの定義では、LOG_INFO以上のメッセージに対して Khttpsv_template.loggerを呼び出します。 おそらく再定義の必要はありません。

ksw <card_captor@geocities.co.jp>
1