飴屋

Go/なんか動くものを作る

Perlでやってたようなことをやりたい

Perl・・・マイナーバージョンがジワジワ上がってきていますが、利用者が少なくなってきているのはさすがに肌で感じています。使えない人が減るとどうしてもプロジェクトに採用しにくいというのがありますよね。Perl・・・後方互換性が高くて、どんなサーバー(レンタルサーバーとか)でも動かしやすくて便利なんですが、人を選ぶ面があるかもなとは思っていました。Python、Ruby、サーバーサイド用のJavaやJavaScriptといろいろ選択肢が増えてますが、国内のWeb系だとPHPを選ぶ人が多いですかね?WordPress需要がありますし、互換性を殺しながらもどんどんスピードアップが図られてるらしいじゃないですか。PHPにもっと真剣に取り組むのもいいかもしれないと思いつつ、一回スクリプト言語ではないものへのチャレンジをやっておくのもいいかと思いまして、話題によく上がるGo言語の開発環境をセットアップしてみました。コンパイルした実行ファイルはやはりインタープリタプロセスがない分、速いはずですよね!Perlで作り上げたオレオレモジュールと同程度に動いてくれれば、生産性も上がりそうです。

Goの特徴

最近Rustをいじることも多いのですが、あれは普通に使っている分にはメモリの管理に失敗することがなくてとてもラクチンです。それとは違ってGo言語は基本的にガベージコレクションを持っていて、使い終わったメモリはどこかでまとめて処理してくれます。この辺に独自の工夫が施されてそうだとチラッとみかけたのでそれも楽しみですが、ガベージコレクションはこれはこれでまた気が楽なんですよね。

Goの開発環境

私はVS CodeにGo Team at Google純正の拡張を入れて開発してみることにしました。Goは公式な書き方のお作法があるようですが、意識しなくてもソースコードを成形してくれるので楽です。ファイル先頭のimport宣言部分も勝手に更新してくれるし、面倒なことはやってくれるスタンスがいいですね。

デプロイ先

これエックスサーバーみたいなレンタルサーバーでも試せるのかなと、ちょっと調べてみたらやってる人がいたので、AWSとかGCPとかの環境を用意するのは後でいいかと思ってます。レンタルサーバーのいわゆる共有プランなので、root権限は持てないながらもFast CGIの枠組みが用意されているので、それを使ってHTTPリクエストにレスポンスを返すのはできそうです。

RewriteEngine On
RewriteRule ^test/(.*)$ /test.fcgi?$1 [L]

予め、.htaccessファイルに↑こんな風に書いておいて、パスが /test/ で始まるアクセスはtest.fcgiを実行するようになってます。パスの一部をクエリーにつけてみたけど、今のところこの情報は使ってないかも。このfcgiの中身はgoがコンパイルしたバイナリの実行ファイルです。ファイルの転送時にバイナリとして送信することと、実行権限をファイルに付与するのに気を付けましょう。(エックスサーバーなら705かな)Fast CGIは実行後しばらく(エックスサーバーなら数分くらい?)メモリに残って次のアクセスに備えてくれるので、メモリへの読み込み時間が省力的でいいですね。大勢のアクセスを受け付ける時は威力を発揮するかな?そのせいか、実行ファイルの更新ができないタイミングができるっぽいです。FCGIでシステム組んでる人は、どうデプロイしてるんですかね?

最初のソース

とりあえず、最初の一歩は「Hello, world」

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, world!")
}

この短いプログラムからもわかることがいろいろあります。まず、packageを宣言するんですね。名前空間をわけてモジュール化(パッケージ化?)することが念頭に考えられています。今回、便利なオレオレモジュールを作るところまで進むかどうか未定なので、packageはずっとmain宣言のままかもしれません。Perlなんかだとpackage宣言を省略すると、mainパッケージであると暗黙のまま決定されますが、Goで宣言が省略されているのをまだ見たことがないので、できないんでしょうね。複数のファイルにソースが分かれても、同じpackage名で宣言されていたら、ちゃんと両者は一緒に解釈されるとのこと。

次は、import群のブロックですね。モジュールの依存関係がここにまとまるわけですか。

import "fmt"

一つのモジュールしか使わないなら括弧で囲う必要ないみたいです。まぁ、一つで済むことはそんなにないかもしれませんけど。
このimport群は、VS CodeのGo拡張がうまいこと自動的に更新してくれるようなので、あんまりちゃんと見てません。以降出てくるサンプルソースでも省略します、多分。

最後がmain関数のブロックです。他のプログラム同様、実行される処理の本体ですね。fmtなるモジュールを使って文字列を出力して、終了するだけの内容になっています。基本的に標準出力に文字列が出力されるので、HTTPヘッダとHTMLを出力すれば、もうCGI的なものが作れそうです。

ページの描画

Goにはよく使われるモジュールとしてnet/httpというものが用意されていて、Webサイトを作る時なんかにお世話になる機能がまとまってそうでした。そのサブモジュール的なもの(?)にnet/http/fcgiがあって、Fast CGIに関するあれこれを一手に引き受けてくれるようでした。普通のCGI向けにnet/http/cgiというのもあるのかな。net/httpにはHTTPサーバーの実装もついているので、Webサーバーを用意する必要がそもそもないのですが、今回はレンタルサーバーで動かす前提で作るので、Apacheやnginxなど元からあるものに乗っかっていきます。見た感じ各ページを描画するハンドラなるものを一つの単位として考えて、FCGIにしろ自前のサーバーにしろ似た感じで書けるようになっているみたいです。すなわち、サーバーの引っ越しとかでもちょっとした手直しで移管とかできちゃうようにみえますね。

func main() {
	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/test", testHandler)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	templates = template.Must(template.ParseFiles(
		filepath.Join("templates", "layouts", "base.html"),
		filepath.Join("templates", "home.html"),
	))
	data := map[string]interface{}{
		"Title": "Home Page",
	}
	if err := templates.ExecuteTemplate(w, "base", data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

↑こんな感じで、どのURLパスにアクセスがあったら、どのハンドラに処理をさせるかをmain関数で登録しています。ハンドラにはResponseWriterとRequestという引数が渡されています。それぞれページの出力先と、ページの要求データを意味します。PerlでCGIを書いていたときなんかはCGIモジュールなんかを使って、CookieとかFormのパラメータみたいなリクエストデータを元に、printで標準出力にHTTPヘッダと本文部分を書いてましたが、やることはそんなに変わらないはずですね。サンプルではトップディレクトリ("/")へのアクセスにhomeHandlerというハンドラをあてがって、HTTPヘッダとテンプレートを使った本文のHTML文書をResponseWriterに渡しています。(テンプレートについてはここでは紹介しません。)ハンドラをいっぱい書いて、URLに対応させていくという感覚だけ掴めば、頭の整理も楽そうです。

コンパイル

go build '-ldflags=-s -w' -trimpath -o test.fcgi

コンパイルの指示について、まだちゃんとわかってないのですが、とりあえず↑上記のような感じでやってます。バイナリに余計な情報がつかないようなフラグをつけたり、出力ファイルの拡張子をfcgiにしたりしています。そうそうエックスサーバーで動かすために、Linux向けにコンパイルする必要があります。

$env:GOOS="linux"
$env:GOARCH="amd64"

PowerShellではこんな感じでOSとアーキテクチャを環境変数に事前に設定しておきます。Windowsローカル端末で確認環境があれば、この設定をそれように書き換えてあげる必要がありますが、今は特に不要かな?WSLとかで今どきはみんなテスト環境を構築しちゃうんでしょうか?