WEBRick を使ってプラグインを公開する方法

WEBRick の問題点

プラグインを自動的にインストールできる plugin コマンドにはいつもお世話になっている。このコマンドの実体は、(Rails gem home directory)/lib/commands/plugin.rb にある。ソースコードをつらつらと眺めていると、

% ruby script/plugin install http://www.example.com/plugins/foobar/

なとどした場合、普通に http でファイルを取ってくるだけのようだ。しかし、plugin コマンドはなかなか賢くて、ウェブサーバが返すディレクトリのファイルリストを元にサブフォルダもろともごっそりと、RAILS/vendor/plugin/foobar/ 以下にダウンロードしてくれる。

WEBRick を使ってプラグインを公開することもできるのだが、その場合、1つ問題点がある。WEBrick が表示するディレクトリのファイルリストは、ファイル名とか項目ごとにソートができるようになっていて、その部分ハイパーリンクが plugin コマンドを誤作動させるのだ。それを避ける方法を考えてみた。

要点は、いらないハイパーリンクを消すだけ。

# WEBrick::HTTPServlet::FileHandler#set_dir_list
...
   #res.body << " <A HREF=\"?N=#{d1}\">Name</A>                          "
   res.body << " Name                          "
   #res.body << "<A HREF=\"?M=#{d1}\">Last modified</A>         "
   res.body << "Last modified         "
   #res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
   res.body << "Size\n"
...

ごくシンプルなウェブサーバを作ってみたので、参考にしてほしい。

サンプルコード

#!/usr/local/bin/ruby
require 'webrick'

module WEBrick
  module HTTPServlet
    class FileHandler
      def set_dir_list(req, res)
        redirect_to_directory_uri(req, res)
        unless @options[:FancyIndexing]
          raise HTTPStatus::Forbidden, "no access permission to `#{req.path}'"
        end
        local_path = res.filename
        list = Dir::entries(local_path).collect{|name|
          next if name == "." || name == ".."
          next if nondisclosure_name?(name)
          st = (File::stat(local_path + name) rescue nil)
          if st.nil?
            [ name, nil, -1 ]
          elsif st.directory?
            [ name + "/", st.mtime, -1 ]
          else
            [ name, st.mtime, st.size ]
          end
        }
        list.compact!

        if    d0 = req.query["N"]; idx = 0
        elsif d0 = req.query["M"]; idx = 1
        elsif d0 = req.query["S"]; idx = 2
        else  d0 = "A"           ; idx = 0
        end
        d1 = (d0 == "A") ? "D" : "A"

        if d0 == "A"
          list.sort!{|a,b| a[idx] <=> b[idx] }
        else
          list.sort!{|a,b| b[idx] <=> a[idx] }
        end

        res['content-type'] = "text/html"

        res.body = <<-_end_of_html_
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
  <HEAD><TITLE>Index of #{HTMLUtils::escape(req.path)}</TITLE></HEAD>
  <BODY>
    <H1>Index of #{HTMLUtils::escape(req.path)}</H1>
        _end_of_html_

        res.body << "<PRE>\n"
        #res.body << " <A HREF=\"?N=#{d1}\">Name</A>                          "
        res.body << " Name                          "
        #res.body << "<A HREF=\"?M=#{d1}\">Last modified</A>         "
        res.body << "Last modified         "
        #res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
        res.body << "Size\n"
        res.body << "<HR>\n"
       
        list.unshift [ "..", File::mtime(local_path+".."), -1 ]
        list.each{ |name, time, size|
          if name == ".."
            dname = "Parent Directory"
          elsif name.size > 25
            dname = name.sub(/^(.{23})(.*)/){ $1 + ".." }
          else
            dname = name
          end
          s =  " <A HREF=\"#{HTTPUtils::escape(name)}\">#{dname}</A>"
          s << " " * (30 - dname.size)
          s << (time ? time.strftime("%Y/%m/%d %H:%M      ") : " " * 22)
          s << (size >= 0 ? size.to_s : "-") << "\n"
          res.body << s
        }
        res.body << "</PRE><HR>"

        res.body << <<-_end_of_html_    
    <ADDRESS>
     #{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
     at #{req.host}:#{req.port}
    </ADDRESS>
  </BODY>
</HTML>
        _end_of_html_
      end
    end
  end
end

include WEBrick

mime_types = HTTPUtils::DefaultMimeTypes
mime_types['rhtml'] = 'text/html'

s = HTTPServer.new( 
	:Port => 2000,
	:MimeTypes => mime_types,
	:DocumentRoot => File.expand_path(File.dirname(".")) + "/htdocs"
)

trap("INT"){ s.shutdown }
s.start