Chienomi

Ruby4.0からCGIが消えたので急遽LWMPを更新

開発::library

Ruby 4.0でCGIライブラリがdefault gemsから削除されたらしい。

これは非常に大きな話だ。

Rubyはもともと「大きな」言語を指向していた。 内包する生態系を重要とし、より機能を充実させることを良しとしていた。 しかしその方向性を維持することはできず、さらにweb分野からの影響力が強まる中、Rubyは「ウェブ言語的な」アプローチへ進んでいった。

もはや、RubyはUnixシステムのグルー言語として存在するわけではない。 そして一度書いたプログラムは毎日メンテナンスして然るべきものと化し、Rubyがあればそれだけで何でも書ける世界は終わりを迎えた。

そして、その最たる象徴がCGIであり、かつてのRubyの最後の砦だった。 実はcgi.rb2008年にはすでに問題視されていたりもする。

だが、それでもCGIがRubyに残り続けたのは、「Rubyで書かれたCGIスクリプト」が多数存在し、RubyにとってCGIは重要なものだったからだろう。

だがきっと、もはやRubyで新規にCGIスクリプトを書く人はほとんどいないだろう(私は書いているが)。 さらに、レンタルサーバーのRuby環境はほぼ放置されており、廃止したところでそれで泣く人はほとんど出ない可能性もある。

そうして維持され続けてきたCGIライブラリは、「Rubyだけがあれば動く」時代の砦だった。 それがなくなるということは、Rubyは「個々にライブラリごとパッケージングして閉じ込める言語」になったということだ。

私はRubyをウェブ言語として愛したわけではないので、極めて残念だ。

LWMPの改修

単に残念がっていられるわけではない。 私には公開・非公開両方にRuby CGIスクリプトがたくさんある。

困ったことに、これはcgi gemをインストールすれば解決する話ではない。 Rubyにcgiのダミーライブラリが含まれており、単にrequireしても読めないからだ。

もちろんBundleにすれば解決するが、CGIとBundleの相性があまりにも悪い。

とりあえず動かないLWMPは改修することにした。 幸いなことに、LWMPはCGIライブラリをクエリパラメータへのアクセスでしか使っていない。 bodyも読んではいるのだが、JSON APIである関係上、CGI.newする前に標準入力を呼んでparseしている。

  def cgi
    @db_dir = ENV["METADATA_DATABASE"]
    raise MetadataDisabledError unless @db_dir && !@db_dir.empty?
    stdin_data = $stdin.read
    body = JSON.load stdin_data

なので実際に使っているのはmediaplay.rbの2箇所だけ。

    val = dir(@cgi["path"])
    if @cgi["info"] == "true"

ということはCGI#[]さえあれば良い。 パラメータの解釈はURIでできるので、そこまで難しくはない。

# Home made CGI alternative class
class CGI
  def initialize
    query_string = ENV['QUERY_STRING'] || ""
    @params = URI.decode_www_form(query_string).to_h
  end

  def [](key)
    @params[key]
  end
end

状況の調査

CGI.methodsすると次のようになる。

!
!=
!~
<
<=
<=>
==
===
>
>=
__id__
__send__
alias_method
allocate
ancestors
attached_object
attr
attr_accessor
attr_reader
attr_writer
autoload
autoload?
class
class_eval
class_exec
class_variable_defined?
class_variable_get
class_variable_set
class_variables
clone
const_defined?
const_get
const_missing
const_set
const_source_location
constants
define_method
define_singleton_method
deprecate_constant
display
dup
enum_for
eql?
equal?
escape
escapeElement
escapeHTML
escapeURIComponent
escape_element
escape_html
escape_uri_component
extend
freeze
frozen?
h
hash
include
include?
included_modules
inspect
instance_eval
instance_exec
instance_method
instance_methods
instance_of?
instance_variable_defined?
instance_variable_get
instance_variable_set
instance_variables
is_a?
itself
kind_of?
method
method_defined?
methods
module_eval
module_exec
name
new
nil?
object_id
prepend
private_class_method
private_constant
private_instance_methods
private_method_defined?
private_methods
protected_instance_methods
protected_method_defined?
protected_methods
public_class_method
public_constant
public_instance_method
public_instance_methods
public_method
public_method_defined?
public_methods
public_send
refinements
remove_class_variable
remove_instance_variable
remove_method
respond_to?
send
set_temporary_name
singleton_class
singleton_class?
singleton_method
singleton_methods
subclasses
superclass
tap
then
to_enum
to_s
undef_method
undefined_instance_methods
unescape
unescapeElement
unescapeHTML
unescapeURIComponent
unescape_element
unescape_html
unescape_uri_component
yield_self

CGI.new.methodsするとクラスに関するメソッドがないだけで、インスタンスにしかないメソッドはない。エンコード関係はインスタンスも持っている。

!
!=
!~
<=>
==
===
__id__
__send__
class
clone
define_singleton_method
display
dup
enum_for
eql?
equal?
escape
escapeElement
escapeHTML
escapeURIComponent
escape_element
escape_html
escape_uri_component
extend
freeze
frozen?
h
hash
inspect
instance_eval
instance_exec
instance_of?
instance_variable_defined?
instance_variable_get
instance_variable_set
instance_variables
is_a?
itself
kind_of?
method
methods
nil?
object_id
private_methods
protected_methods
public_method
public_methods
public_send
remove_instance_variable
respond_to?
send
singleton_class
singleton_method
singleton_methods
tap
then
to_enum
to_s
unescape
unescapeElement
unescapeHTML
unescapeURIComponent
unescape_element
unescape_html
unescape_uri_component
yield_self

つまり、エンコード/デコード目的で使っていたのであれば、次のメソッドはあるため支障はない。

escape
escapeElement
escapeHTML
escapeURIComponent
escape_element
escape_html
escape_uri_component
unescape
unescapeElement
unescapeHTML
unescapeURIComponent
unescape_element
unescape_html
unescape_uri_component

cgiライブラリをCGIのために使うことができなくなったという話だ。 なので、cgiライブラリを使っていたがCGIは使っていない人の多くは気にしなくて大丈夫だろう。

XXWMPには次のようなコードがあるが

@path = path.sub(%r:^/+:, "").gsub(%r:/{2,}:, "/").split("/").map {|i| CGI.unescape(i)}.join("/")

これは改修不要ということだ。

Ruby/CGIの状況

需要がどれくらいあるかは怪しいが、まとめるとこうなる。

  • cgi.rb でCGIするのはもうやめたほうがいい
    • レンタルサーバー相手を想定する場合実際は動いてしまう可能性はそれなりにあるものの、開発環境を用意するのもしんどい
    • レンサバで動くCGIを意識するなら、PHPが一番無難
  • Rack/CGIもやめたほうがいい
    • 私は「将来的なアプリケーションサーバーへの移行のため」としてRack/CGIを使ったやつもある (主にConoHa WING時代のもの)
    • しかしCGIはアプリケーションサーバーと事情が違いすぎて、Rack/CGIをアプリケーションサーバー環境に移植するときの作業はかなり多い
    • それだったらSintraなりRodaなりで書き直したほうがマシだったりする
    • しかもRack/CGIも将来性はない
  • だがそれはRuby/CGIの終焉を意味しない
    • 実はcgi.rbの機能はCGIするにしても使い勝手が悪い、もしくは使いたい気持ちが薄いものが多い
    • 出力系などは古い時代のものという感じが強い
    • なのでCGIするためにcgi.rbを使っていても、cgi.rbの機能を使わずに自前実装することは少なくなかった (私のJSONパーサーとかそう)
    • そしてcgi.rbを使っている部分は、意外と簡単に自力実装できる
    • なのでRuby/CGIする上でのcgi.rbの影響というのは、作業は発生するけれど甚大ではない
    • だいたいにしてCGIライブラリが揃っている言語なんて相当少ないので、PerlやPHPの土俵から落ちて他の言語と同じになっただけ、とも言える
    • だから「Rubyの立ち位置と歴史」という意味でのターニングポイントとしての意味が大きく、実用的な意味ではいろんな意味で「もはや使われていない(使う必要がない)」になっていた

おまけ

そういう感じで必要な機能を追加するためのGemをリリースした。

私が使う用の簡易ものなので、どれほど使えるかは分からないが……