画像アップローダーっていうと、あれだ、ユーザーがアップロードした画像を保存しておいて掲示板かなんかから参照できるようになってるやつだ。あれ、なんでアップローダーっていうんだろうね。
ま、とにかく Go で WEB アプリケーションを書く練習に作ってみた。Go ってワンバイナリでサーバーが作れるからいいよね。
名前は Sulaimān (スライマーン)にした。
cf. github.com/takatoh/sulaiman
シングルページアプリケーション
複雑なシステムではないので、html を読み込むのはルートにアクセスしたときだけで、あとはすべて ajax で非同期に更新するシングルページアプリケーションにした。
おかげでサーバーサイドの Go だけじゃなくて、クライアントサイドの JavaScript も結構書いた。
サーバーサイド
サーバーサイドの WEB フレームワークには Echo というのを使った。ググってみた範囲では、結構簡単そうだったし、速度も速いらしい。それからデータベースの OR マッパーには gorm というのを採用。Ruby や Python の OR マッパーとちょっと勝手が異なるけど、データベースといってもテーブルひとつの簡単なものだし、Go の中では割とメジャーなものみたい。
cf. Echo
cf. gorm
ほかにも JSON を扱うライブラリとか、画像のサムネイルを作るのにはこないだちょっと書いた resize を使った。
コードのすべては上でリンクした GitHub を見てもらいたいけど、メインのファイルだけ載せておこう。
package main
import (
"encoding/json"
"io/ioutil"
"strconv"
"github.com/labstack/echo"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/takatoh/sulaiman/handler"
"github.com/takatoh/sulaiman/data"
)
func main() {
var config = new(data.Config)
jsonString, err := ioutil.ReadFile("config.json")
if err != nil {
panic(err)
}
json.Unmarshal(jsonString, config)
db, err := gorm.Open("sqlite3", "sulaiman.sqlite")
if err != nil {
panic(err)
}
defer db.Close()
db.AutoMigrate(&data.Photo{})
e := echo.New()
h := handler.New(db, config)
e.GET("/", h.Index)
e.GET("/title", h.Title)
e.Static("/css", "static/css")
e.Static("/js", "static/js")
e.Static("/img", config.PhotoDir + "/img")
e.Static("/thumb", config.PhotoDir + "/thumb")
e.GET("/list/:page", h.List)
e.POST("/upload", h.Upload)
port := ":" + strconv.Itoa(config.Port)
e.Logger.Fatal(e.Start(port))
}
設定ファイルやデータベースの読み込みをしたあと、Echo とハンドラーのインスタンスを作って、ルーティングの設定をしている。GET
とか POST
とかのメソッドでルーティングの設定をするのは Ruby の Sinatra なんかと雰囲気が似ている。
クライアントサイド
上に書いた通り、シングルページアプリケーションなので、いったんページを読み込んだ後はすべて ajax で非同期に処理をする。ページ遷移はない。
で、JavaScript のライブラリには定番の jQuery と、ページ描画のフレームワークには Vue.js というを使ってみた。jQuery は以前使ったことがあるけど Vue.js は初めてだ。
cf. Vue.js
でも、Vue.js は学習コストが低いのも売りらしく、実際特に苦労することもなかった。もっとも大したことはしてないんだけども。
ちょっとひっかかった点
それでもうまくいかなかったところはあって、ひとつにはページのタイトルがあげられる。ページタイトルは設定ファイルから読み込んで表示するようにした。まあ、当たり前の発想だよね。で、どうやって表示しようかというときに、サーバー側でテンプレートを使うことを最初に考えた。Go には html/template というライブラリが標準であって、それを使おうとしたんだけど、テンプレートの構文が Vue.js のものと同じ(データを埋め込みたいところに {{ foo }}
みたいに書く)で、共存ができなかった。仕方がないので、タイトルも ajax でとってきてクライアントサイドで更新するようにした。
もうひとつもクライアントサイド。次のページを読み込む Next リンクをクリックしたときの動作を、ajax で次のようにやろうとしたところ、うまくいかない。
$("#next_link").on("click", function(event) {
...
});
これ、結構悩んでいろいろ調べて、結局こうなった。
$("body").on("click", "#next_link", function(event) {
...
});
なんでこうじゃなきゃ動かないのかわからない。あとで調べよう。
ともかくもこれで一通りはできた。
やり残したこと
たいていのアップローダーにはついている、ファイルの削除機能がない。一応、削除のためのキーをアップロードの際に入力するようにはしてあるので、今後実装するかもしれない。でもどんな UI にしようか悩んでいる。
それと、ページの下までスクロールしたら自動で次のページを読み込む、いわゆる無限スクロールも実装してみたい。
いつになるかわからないけど。