タイトルの意味(≒この記事の概要)
- およそ10年ぶりに個人サイト1を更新してみた。
- 全てのページをPerlによるCGIで生成していたが、Swiftで一から書き直した。
- しかし、サーバやCSS, HTMLに関する知識は10年前から変わっていない2。
- 即ち10年前からタイムスリップしてきたことと同じだった(誇張)。
それを踏まえてSwiftでCGIプログラムを書いた経緯をインタビュー形式で記事にします。なぜインタビュー形式なのか、それは永遠の謎です。
一応、CGIプログラムについて基礎的なことから書いてあります。Swiftに限らずいろんな言語でCGIを書くきっかけになれば…。
SwiftでCGI
そもそもCGIって…
通りすがりの知ったかぶりインタビュアー(以下I): CGIという言葉自体が死語になりつつあるようですが?
10年前の世界からやってきたオッサンことYOCKOW(以下Y): そうなんですか?昔は自分のサイトに掲示板(死語)やチャット(死語)を設置するためにフリーのCGIプログラムをもらってくることが流行った気がします。でも、自分が借りてるレンタルサーバーの環境に合わなくてプログラムを修正しなきゃいけなかったり、細かい仕様が気に入らなかったり…。それなら自分で最初から作っちゃえば良いじゃん、ってなってCGIのプログラムを書き書きし始める…というのが一般的な流れでした(N=1)。そういう意味でCGIという言葉はサイト運営をする上で切っても切り離せないものでした。
I: もちろん今もCGIという仕組みが無くなった訳ではないですが、動的なページを作るのにCGIの仕様を意識しなければいけない環境は減っていると言えます。Webアプリケーションのバックエンドを作るにしても便利なフレームワークがありますし、クラウド・アプリケーション・プラットフォームなどというものまでありますからね。
Y: ただ、個人サイトを作るだけなら、そんな大層なソフトウェアを使わなくても…という気はします(10年前の感覚)。だって、CGIは標準出力に文字列(データ)を書き出すことができればそれで良い訳ですから。もちろんPOST
を処理するなら標準入力も扱えないといけないですけど。なんならbashでだってCGIは書けます3。
I: ところで、10年前にはSwiftは存在していませんでした4。当時は何の言語でCGIを?
Y: 当然Perlですね。CGIといえばPerl、PerlといえばCGIという時代でした。次点でPHPでしょうか。Yahoo!知恵袋に**「CGIとPerlって何が違うんですか?」**みたいな質問がいくつもありました5。
I: 「法律と警察って何が違うんですか?」みたいな質問ですね。
Y: 確かに質問として本来成り立たないはずのものですが、それだけ**CGIといえばPerlという“常識”**があったのでしょう。
CGIプログラムの実際
I: “CGIは標準出力に文字列を書き出すことができれば良い”という話がありましたが、具体的にはどうすればいいのでしょうか?
Y: 厳密な話はRFC3875を読んでいただくとして、最低限Content-Type
ヘッダ行と0バイト以上のボディーさえあればOKです。Perlで書くと
#!/usr/bin/perl
print "Content-Type: text/plain; charset=utf-8\n";
print "\n";
print "Hello, CGI.";
Y: これだけで"Hello, CGI."という文字を表示するCGIの完成です。ちなみに、HTTPヘッダの改行は必ずCR+LF
でなくてはなりませんが、CGIプログラムの段階での改行コードはLF
で問題ありません。なぜなら、Webサーバが必ず改行文字をCR+LF
に変換してくれるからです6。
I: ところで、上記のコードではHTTPレスポンスコードが指定されていませんが、どうなっているのでしょうか?
Y: 基本的に、CGIが正常終了すれば200
、異常終了すれば500
ですね。
I: え?それだけですか?
Y: まぁ、もちろん、それだけではないです。CGIではStatus
という特別なフィールドが規定されています7。他のHTTPヘッダと同じようにStatus: 200 OK
などと出力すれば、Webサーバが良きに計らってくれて適切なHTTPレスポンスに変換されます。もちろん404ならStatus: 404 Not Found
とします。Reason Phraseを自分で決めることもできます。Status: 500 I have given up
とかでも問題ありません8。
I: 文字列を出力するだけでいいなら、確かにどんな言語でも良さそうですね。
Y: そうですね。もちろんSwiftでもCGIプログラムを書くことが可能です。
#!/usr/bin/swift
print("""
Status: 200 Swift is also OK
Content-Type: text/plain; charset=utf-8
Hello, Swift CGI.
""")
I: Swiftでもshebangが使えるんですね。
Y: そうなんです9。ただ、shebangを使うと単一ファイルのコードしか実行できないですし、Swiftはお世辞にもコンパイルが早いとは言えないので(小声)、予めコンパイルした実行ファイルをCGIプログラムとして起動することをお勧めします。
CGIに使える言語
I: 今回CGIを一から作り直すにあたって、最初から言語をSwiftにすると決めていたのでしょうか?
Y: それを話すと少し長くなりますが良いですか?
I: (え、めんどくさい…)
Y: 私が個人サイト1を立ち上げたのは1999年の春でした。確か「フリーティケットシアター」(freett.com)10を利用していましたね。
I: (話し始めちゃった…)
Y: そして、CGIが使えるレンタルサーバに移ったのが2002年頃です。その頃から一部のページをCGIで表示するようになり、遅くとも2003年にはほぼ全てのページをCGIで出力するようにしました。たぶん。
I: 先の話にあったように、その時使っていた言語はPerlだった訳ですね。
Y: そうです。最初の頃はとほほのWWW入門にお世話になりました。
I: 今や老舗のサイトですね。
Y: 当時、Mac OS X(macOSの当時の名前)用のソフトウェアを公開することも自分のサイトの目的の一つでした。そして、Objective-Cを触っているうちに気づいてしまったのです。
I: と言いますと?
Y: オブジェクト指向は素晴らしい!
I: あぁー…。
Y: ただ、今思い返すと、オブジェクト指向そのものが素晴らしいというよりは、Cocoa (= Foundation + AppKit)が素晴らしいということだったとは思いますけどね。ちなみに、木下誠氏11の書籍やWeb連載が好きでした。
I: なるほど。
Y: 本当にFoundationが好きすぎて、PerlでFoundationを再実装するような行為に勤しんでいました。もちろん、完コピではなく自分が必要とする部分だけの実装でしたが。たとえば、NSString
に倣って書いたHTB::String
(HTBは当時のサイト名にちなんだprefix)では、
sub stringByReplacingOccurrencesOfString_withString_options_range {
my $self = shift;
my $target = shift;
my $replacement = shift;
my $options = shift;
my $searchRange = shift;
# 以下実際のコード
# :
}
Y: というようにメソッド(Perl用語ではサブルーチン)の名前を付けていました。
I: Foundationにおけるネーミングそのままですね。
Y: ただ、Perl(Perl 5)は「オブジェクト指向のような書き方ができる」程度で、言語レベルでクラス定義などをサポートしていませんでした。…ですよね?当時、"Perl 6"が出る出ると言われて全然出なかったこともあり12、Perl以外の言語も考えていました。しかし、2008年ごろから現実世界が忙しくなってプログラミングに時間を割けなくなりました。さらに、2010年頃はゆるキャラや某音ゲーのことしか考えていませんでした。こうしてプログラミングのブランクが始まったのです。2012年に一念発起してCGIを一からPerlで書き直したのですが8割方完成したところで諸事情により再び中断することになりました。
I: (途中のよくわからない話は無視して)次にプログラミングを始めるのは?
Y: まずは2016年にVPS(Virtual Private Server)を借りることにしました。多少(時間や金銭に)余裕ができたので。レンタルサーバが公園の砂場なら、VPSは無人島13というところでしょうか。『何もないから、なんでもできる』みたいな。でも、すぐに何かをしたいというよりは、いつか何かできたらいいなという感じでした。最初、CGIに使う言語としてはRubyを考えていました14。日本発の言語で日本語の資料も豊富にあったというのも理由です。Rubyでのプログラミングは実際楽しかったです。PerlのAUTOLOAD
15をRubyでも実現させてみたり。でも、『Rubyは遅い』みたいな話もあったりして。ここらへんは結局宗教論争のようなところはあるので、どうでもいいと言えばどうでもよかったのですが…。そもそも速さを求めるようなサイトではないですし。それでもなんとなく速さのことが気になって…。そこで、速さのことを考えるならそもそもインタプリタ方式ではなくてコンパイラ方式にすればええやんと思ったのです。VPSなら色んなコンパイラを導入できますしね。
I: なるほど、それでSwiftに?
Y: いいえ、C言語 です。
I: まさかのC。…なぜSwiftではなかったのでしょう?Swiftは2015年末にはオープンソース化もされ、名目上Linux対応も謳っていましたが。
Y: ただ単にSwiftを知らなかったのです。Mac OS X用のソフトウェアを作ったのは2005年が最後でした16。そこからはMacソフト開発からは遠ざかっていたので、Swiftなどというものの存在に触れる機会がなかった訳です。結果的に、まずはC言語を試すことになりました。
I: そういえば、かの「2ちゃんねる」(現・5ちゃんねる)も軽量化のためにかつてPerlからC言語に移行したなんていうこともありました[要出典]。
Y: C言語でまずやり始めたのは…またFoundationの猿真似でした。2005年頃にPerlでやっていたことを再度始めたのでした。これも今思い返してみれば、Core Foundationを使えばそれで良かったんですよね。Objective-CのFoundationはプロプライエタリですが、C言語で書かれたCore Foundationはオープンソースかつクロスプラットフォームですからね。どうしても車輪の再発明が大好きな質なので、一から作ることを先に考えちゃうんですよね。まぁ、それはそれで楽しかったです。メモリ管理とか、ディクショナリのためのハッシュ関数を自作したりとか。楽しかったけど、もちろん、面倒でした。なので、C言語から別の言語を使うことを考え始めて…。
I: そこでSwift?
Y: いいえ、Objective-Cです。
I: おっと。もしかしてサーバをMacに…?
Y: Linuxです。Objective-Cは決してMac専用の言語ではありません。その誕生は1983年(または1984年?)と古く、GCCも標準でObjective-Cをサポートしています。もちろんFoundationは前出の通りプロプライエタリなのでLinuxでは使えませんが、GNUstepがあります。GNUstepはNeXTのOPENSTEPと互換性を持たせたフリーソフトウェアです17。ご存知の通り、NeXTはのちにAppleに買収され、OPENSTEPのObjective-CライブラリはCocoaと名前を変え開発が進められることになります。GNUstepはAppleのCocoaに追随しており、Mac, Linux両方で開発を進められると思っていたのです…。
I: 思って*「いた」*?
Y: はい。実際初期のコードはMacでもLinuxでも動作したのですが、FoundationのAPIをいろいろ調べているうちに違和感を覚えるようになったのです。…違和感の正体はObjective-C 2.0でした。Objective-C 2.0はMac OS X v10.5 Leopardとともに登場したものです。つまり2007年登場です。当時(2016年頃)で既に登場から10年ほど経っているので、Objective-C 2.0は当たり前のように使われていました。しかし、前述の通り私が最後にMac OS X用のソフトウェアを作ったのは2005年のことです。Objective-C 2.0などというものを知る由もありませんでした。調べていくと、Linuxでもlibobjc2を使えばObjective-C 2.0が利用できると分かったのですが、自分の中でObjective-C 2.0は新しい言語を覚えるような気分でした。一方で、FoundationのAPIをApple Developerのサイトで調べているときに気づいたことがありました。
I: それは…?
Y: ページの右上に"Language"を選択するメニューがあったのです。Languageといっても日本語や英語という意味ではなく、プログラミングの言語を選択するメニューです。…Objective-CとSwiftが並んでいました。そこから「Swiftって何だろう?」と調べるようになりました。その時、Swiftのバージョンは2.2で、もうすぐ3が登場するだろうというタイミングでした。最初に見た時は、一見してスクリプト言語のような印象だったので、AppleScriptの進化版か何かかと思っていました。でも、Qiitaで@koherさんや@omochimetaruさんの記事を読んだりして、思ったのです。**「SwiftはObjective-Cを置き換えようとしている」**と。実際にAppleがどういう意図をもってSwiftを開発しているのかはわかりませんでしたが、そう思った瞬間、自分のCGIプログラムの言語はSwiftにすることに決まりました。Linuxでも使えるということはもちろん、今後、Darwinプラットフォーム(macOSやiOS)のソフトウェアを開発するときにも必ず役に立つと考えたからです18。
I: やっとSwiftに辿り着きましたね(長かった…)。
CGIとSwift
Y: まずはLinuxでSwiftコンパイラを動かすところから始めました。当時はCentOSを使っていたのですが、CentOS用のバイナリが配布されておらず、Swiftを2時間ぐらいかけてビルドした記憶があります。しかもFoundation(Swiftで書かれたFoundation)のビルドに失敗してやり直しになるという…。のちにUbuntuに乗り換えたのですが、今はCentOS以外にもAmazon Linux 2用のバイナリまで配布されるなど、便利になりました。
I: 今はLinuxどころかWindows用までありますからね19。
Y: 当時既にSwiftはオープンソースになっていたのですが、Swiftで検索してもDarwin以外のプラットフォームで動かす情報が少なかったように思います。そこで、少しずつ自分が得た知見を共有しようと、Qiitaの記事もたまに書くようになりました。初めて書いた記事は、『C言語とSwiftを一つのプロジェクトで混在させてみる(OS X, Linux)』(2016年5月)です。Swift Package Managerがまだ無かった時代の記事ですね。ちなみに、これまでに投稿したSwiftに関する記事は全てDarwinでもLinuxでも動作するコードを前提に記載しています。
I: CGIだけであればSwiftUIとかは関係ないですからね。
Y: そしてSwiftに多少慣れたタイミングでCGIプログラムを作るためのライブラリを作り始めました。その名もSwiftCGIResponder。CGIResponder
という単純なstructを用意して、CGIとしての出力を任せるというものです。
I: 一応、その頃にはKituraもVaporも登場済みだとは思いますが…。
Y: そうだったんですね。全然気にしてませんでした。Perlの時代と同じように、一からCGIプログラムを書くことしか考えていませんでした。ただ、Perlの時代とは大きく違うことがありました。それは、Foundationが最初から存在するということです。PerlのときもRubyのときもCのときも、まず自分でFoundationっぽい何かを作るところから始めていました。Foundation大好きっ子ですからね。それがSwiftではLinuxでもFoundationが使えるのです。
I: Foundationがあれば充分だった、と。
Y: その通りです。ですが、それはそれで単純ではありませんでした。Darwinでは、Swiftで使うFoundationはObjective-CのFoundationをインポートする形です。即ちCocoaを(原則)そのままSwiftでも使えます。しかし、Darwin以外のプラットフォームでは、Swiftで書かれたFoundation20を使うことになります。そのSwiftFoundationが、バグと未実装の嵐だったのです。
I: となると、Swift以外の言語へ移ることも検討を?
Y: そんなことはしません。Foundation大好きっ子なので(2回目)。バグがあるなら直せば良いじゃないですか。幸いにもSwiftはオープンソースです。GitHubでPRを送ることもできます。というわけで、あると困るバグは自分で直しました。こうしてFoundationを使ってCGIプログラムを作るという、夢のような世界に到達できたのです。今のところ、SwiftCGIResponderを使うと、
import CGIResponder
var responder = CGIResponder()
responder.status = .ok
responder.contentType = ContentType(pathExtension: .txt, parameters: ["charset": "UTF-8"])!
responder.content = .string("Hello, World!\n", encoding: .utf8)
try! responder.respond()
// -- Output --
// Status: 200 OK
// Content-Type: text/plain; charset=UTF-8
//
// Hello, World!
//
Y: のように書くことができます。基本的にCGIResponder
は単純で、上のコードを見るだけだと「直接文字列を出力すればいいのでは?」となりそうですが、その裏では、HTTP Headerを適切に扱うような仕組み・国際化ドメイン名を含めたドメインの処理・HTTP CookieとPublic Suffixの処理・XHTMLパーサ/ジェネレータなどを実装してあります。ただ、コードサイズが大きくなってしまったので、結果として、少しずつ機能を切り離して独立したパッケージにしました。見かけ上、依存するパッケージが多くなりましたが、全部自分で作ったものという…。
I: 車輪の再発明も含まれていそうですね…。
Y: そうですね。でも、車輪の再発明も大好きなので。こうして、2008年ごろから更新が滞り始め2012年には完全に止まってしまったサイトの更新を2021年に再開することができました21。サイトの更新が目的となってしまっているので中身はないですし、CSSやHTMLの知識は昔のままですが。気付いたら、HTMLはフロントエンドで生成することが主流になっているようで(?)、完全に時代に取り残されていますね22。本当はMarkdownのパーサもSwiftで書きたかったのですが、一旦はフロントエンド側で処理することにしました。ただ、その時には**「JavaScript怖い」**という思いは強かったです。
I: 何が怖かったのでしょう?
Y: 動的型付けだからです。Swiftを長く触ったあとに久しぶりにJavaScriptを触ったので、コード上に型表記できないことやnull safetyがないことに強烈な恐怖心を覚えました。そこからTypeScriptを導入するまではすぐでした。Swiftの素晴らしさの再発見でもありますね。
おわりに: SwiftはCGIに相応しい言語か
I: Swift(というかFoundation)への想いは強いようですが、SwiftはCGIプログラムに相応しい言語と言えるでしょうか?
Y: 現在のところ最も相応しいですね、私にとっては。当たり前ですが、言語の選択は「誰が何のためにどのような環境で動かすプログラムなのか」によって変わってくるものですから。CGIプログラムに限らない話ですね。最初のほうの話にもあったように、CGIプログラムは標準出力と標準入力を扱うことができればそれで良いのです。bashが一番自分にとって書きやすいのであればbashで書けば良いと思いますし、Cが良ければCで書けば良いという話です。少なくともフレームワークがないと何もできないということはあり得ません。まずはprint
文から始めれば良いと思います。SwiftならSwiftCGIResponderを使っても良いですよ(宣伝)。まぁ、あくまで個人サイト1の開発に限った話ですが。
-
実際には2003年ぐらいの知識で止まっているような感覚。そして、もともと知識は乏しい。 ↩
-
他の例→Perlから始めないCGI入門 ↩
-
Swiftは2014年6月登場。 ↩
-
実例→ https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q147029824 ↩
-
HTTP/2だとreason phraseが空文字列に…🥺 ↩
-
2016年3月末でサービス終了 ↩
-
現エイチエムディティ株式会社社長。 ↩
-
2000年に設計が始まったPerl 6の正式リリースは2015年12月25日のこと。結局Perl 5との互換性はなく、のちに言語名がRakuに変わった。 ↩
-
専サバに比べれば狭い無人島なのでしょうが…。 ↩
-
もちろんRailsは使わない ↩
-
参考記事『AUTOLOADについて』 ↩
-
最後に作ったソフトは"DKT"というディベート甲子園専用のタイマー。2005年に愛・地球博で開催された第10回大会で実際に使用した。 ↩
-
Unix系OSだけでなくWindowsでも動作する。 ↩
-
結局開発してませんが。 ↩
-
多くはCore FoundationをSwiftでラップしたもの。 ↩
-
いつまた止まるか分かりませんが。 ↩
-
実は2012年に作り直した(作り直そうとした)サイトはJavaScriptでページを生成する仕組みでした。しかし、当時のGoogleクローラは動的ページに対応しておらず(たぶん)、JavaScript非対応のクライアントにはPerlでページを生成して返していました。つまり、PerlとJavaScriptで同じ結果を生み出すための同じコードを書かなくてはならず、2倍の作業量になっていたのです。時代を先取りしすぎたのかもしれません…。 ↩