Getdown入門

2018年6月23日

※Getdownについては/tag/getdownに投稿一覧がある。

Javaにはウェブからアプリケーションを配布するためのJava Web Start(JWS)というものがあり、これを何年も使っているのだが、非常に使いづらい。トラブルが良く起こるし、もともと配布jarすべてに署名しなければならなかったのだが、近年は正式な署名でない場合(自己署名証明書の場合)にはユーザに事前設定の負担がかかる。

もはやJWSを使いたく無いのだが、一から仕組みを作るのも面倒なので、JWSの代替を目指して作られたGwtdownを試してみる。https://github.com/threerings/getdownにあるオープンソースのプロジェクトであり、もちろんMavenリポジトリもある。

以下では、ゆっくりとこのプロジェクトの目的と仕組み等を調べていく。

作成した理由

以下はRationaleの概要。

主なデザインゴール

  • ユーザの負担を減らしたい。すべてのファイルにチェックサムをつけ、ダウンロード失敗は自動的に再ダウンロードする。定期的にあるいは、要求されたときに、インストール済ファイルのチェックを行い、ユーザマシン上のファイルを適切に保つ。

JWSは、これによく失敗することが経験上あった。

  • プロキシキャッシュやら、ウェブブラウザキャッシュやらの問題を避け、可能な限り強固にする。バージョンのあるアプリの配布では、いったんファイルが特定のパスに配布されたら、その中は変わらない。

JWSではよくこれに失敗する、アグレッシブなキャッシングプロキシのせいだ。

  • 巨大な数のユーザが同時にアップデートできるようにスケールする。我々のゲームアプリではこれが多る。Getdownでは最もスケーラブルなHTTPサーバが単純なファイルを扱うだけである。

この意味ではJWSは疑問だらけのデザインである。スケーラブルにするのは難しく、いやになるほど壊れやすい。

  • 単一アプリにダウンロード・アップデート・起動に必要なこと以外は行わない。

JWSには過剰な機能がある。これは巨大な企業には役に立つかもしれないが、我々には不要。

マイナーなデザインゴール

メジャーでなければやらないのだが、しかしJWSでもたらされたウンザリへの処方箋がいくつかある。

  • Getdownは、アプリ全体を一つのディレクトリ(とそのサブディレクトリ)の中に格納する。これはファイルシステムのどこでもよい、そのディレクトリはデザイン上アプリにとって既知である。

JWSはウンザリすることに、ファイルをあちこちにおいており、アプリからはわからない。

  • Getdownはいかなる引数もJVMに渡す

  • GetdownはプライベートなJVMインストールでも動作する。

JWSはこれを行うと主張するが、しかし実際には使えない。

  • アプリが動作し始めたら、Getdownはいかなるウインドウもオープンしない。

デザイン

以下は、Designの要約。

Getdownは単一のjarファイルからなり、ここには、ファイルをダウンロードし検証するためのコードが含まれる。

コントロールファイル

getdown.txt
digest.txt
version.txt [バージョニングするアプリの場合のみ]
[アプリに関連するファイル]

これらのファイルがアプリの配布ディレクトリにあるものとし、Gwtdownのパラメータとして指定される。

java -jar getdown-client.jar /your/app/dir

アプリを起動する前にGwtdownはアプリ配布ディレクトリにchdirする。アプリへの引数としてディレクトリを指定することも可能。

注意:すべてのコントロールファイルはUTF-8エンコーディング。

getdown.txtは以下の構成である。

# アプリのバージョンを示す。単一の整数であること、無い場合は
# バージョン無しアプリとみなされる。
version = 15

# すべてがダウンロードされる元のURL。バージョン付アプリの場合には、
# バージョン番号として%VERSION%が必要
appbase = http://www.yoursite.com/path/to/your/app/v%VERSION%

# コードはすべてappbaseからの相対で記述すること。
# アプリ配布ディレクトリからの同じ相対位置にインストールされる。
# これらのjarファイルはアプリ起動時にすべてclasspathに指定される。
# アプリ自体のクラスファイルは、どこにあってもよい。最初でなくてもよい。
code = application.jar
code = happy/funcode.jar

# リソースはappbaseからの相対で記述され、アプリ配布ディレクトリからの
# 同じ相対位置にインストールされる。
resource = media/kibbles.jar
resource = media/bits.jar
resource = media/gravy_bits.jar

# これらのオプションはアプリのスペックの前にコマンドラインにて
# JVMに渡される。ここで最大ヒープサイズや-Xdisable_annoying_bugs
# を指定することができる。
# 加えて、プロパティもここで定義できる。
# %APPDIR%はアプリ配布ディレクトリに置換されることに注意
jvmarg = -Xmx128M
jvmarg = -Dmonkeys=true
jvmarg = -Dappdir=%APPDIR%
jvmarg = %ENV.foo%
# mainクラスを示す。ここにはmain()メソッドが必要
class = com.yoursite.crazy.Application

# 以下の引数は起動時にアプリに渡される。
# %APPDIR%は現在のアプリ配布ディレクトリに置換されることに注意。
apparg = peanuts
apparg = %APPDIR%

digest.txtファイルは以下のようになる。

getdown.txt = 06ddb6c55541fa9ef179a4c5029412df
application.jar = 20261a3bd402f339b6a3cb213bb821c7
happy/funcode.jar = 0326044be41f575d060dd43f76b8e888
media/kibbles.jar = 0b2ea3d3e006b4fa42956d450849d1dd
media/bits.jar = cb6560616a38c7520be7e85bbfd2355d
media/gravy_bits.jar = d40d9ffec9d8309b149f692fe760c7bf
meta = 943bd46399a77df39a02c33d428471c4

リストされた配布ファイルは、すべてMD5ダイジェストを持ち、さらにdigestファイルそのものもである。metaプロパティがそれを示している。これは、すべてのファイルのすべてと、ダイジェストファイル中のダイジェストを接続したもの。

version.txtには単一の整数が含まれ、これはアプリの最新バージョンを示す。このバージョンがインストールされていなければ、Getdownシステムは新たなバージョンがあるとしてインストールしようとする。

アプリは新たなバージョンがあるかどうかを自身で決定しなければならない。なぜなら、HTTPを信用しないからである。

通常の動作

省略

失敗シナリオとリカバリ

省略

アプリの配布とパッチの作成

配布アプリは、配布サーバ上の単一のディレクトリ(及びそのサブディレクトリ)に格納されねばならない、正確にユーザマシンの側と同じようにである。アプリのそれぞれのバージョンは、配布サーバ上の異なるディレクトリでなければならない。これが、配布を単純化する、以下のように。

  1. 配布サーバの適切なディレクトリにすべてのアプリファイルをインストールする。
  2. getdown.txtファイルを、作成し(あるいは、以前のバージョンをコピーして変更し)、そのバージョン用に変更する。適切なディレクトリに置く。
  3. 提供しているツールを使用してdigest.txtファイルを作成する。このツールはまた、以前のバージョンの配布ディレクトリを通知することによって、patch.datファイルを作成することができる。これにより、Getdownのクライアントは、以前のバージョンからのアップグレードができる。ただし、このパッチメカニズムについてはここでは定義しない。
  4. もし、アプリが複数のレプリカサーバに配布される場合は、これらすべてのファイルがサーバにコピーされねばならない。

これらの単純なステップの後でアップデートが可能になる。

クイックスタート

Quick Startの要約。

メタファイル

二つのメタファイルが必要、getdown.txtとdigest.txtである。getdown.txtは自身で作る(後述する)が、digest.txtはツールを使って作る。

getdown.txt

getdown.txtには、Getdownがアプリ配布とアップデートに必要な情報である。基本的なgetdown.txtを以下に示す。

# クライアントがダウンロードされるURL
appbase = http://myapplication.com/myapp/

# UIコンフィギュレーション
ui.name = My Application

# アプリのjarファイル
code = application.jar

# アプリのメインエントリ
class = myapplication.MyApplication

appbaseはクライアントをダウンロードするURLである。getdown.txt中のすべてのファイル名は、このURL相対となる。例えば、上の例では、以下のファイルがダウンロードされる。

  • http://myapplication.com/myapp/getdown.txt
  • http://myapplication.com/myapp/digest.txt
  • http://myapplication.com/myapp/application.jar

digest.txt

digest.txtは、com.threerings.getdown.tools.Digesterを起動することによって作成される。最初にgetdown-X.Y.jarをダウンロードしておくこと。

例えば、以下のようなディレクトリ構造があるとする。

myapp/getdown.txt
myapp/application.jar

以下のコマンドでdigest.txtファイルを作成できる。

java -classpath getdown-X.Y.jar com.threerings.getdown.tools.Digester myapp

myappは、クライアントファイルを含むmyappディレクトリのパスである。以下が表示される。

Generating digest file 'myapp/digest.txt'...

すると、クライアントファイルに加えてdigest.txtファイルが現れる。

ホスティング

myappにはGetdownでサービスするためのすべてのファイルが含まれることになった。これをウェブサーバに格納すれば、getdown.txtに記述されたhttp://myapplication.com/myapp/というURLからダウンロードすることができる。

このサブサーバには特別の機能は必要無い、単にmyappディレクトリの中身を普通のHTTPでサービスすればよい。

テスティング

アプリの動作をテストするには、最初にgetdown-X.Y.jarをMavenセントラルからダウンロードする。そして、以下の「スタブ」インストールディレクトリを作成する(getdown-X.Y.jarをgetdown.jarに名称変更している)

myapp/getdown.jar
myapp/getdown.txt

ただし、getdown.txtは以下の単一行であること。

appbase = http://myapplication.com/myapp/

最終的には、このスタブディレクトリを作成するプラットフォームごとのインストーラを作成することになる。つまり、それがそのプラットフォーム用の適切なアプリランチャーとなる。(あるいは、Getdownをアプレットとして起動する)。しかし、今の時点では、Getdownをマニュアルでコマンドラインから起動する。

java -jar myapp/getdown-X.Y.jar myapp

これでGetdownが起動し、アプリをダウンロードし、検証し、実行することになる。

インストーラとアプレット

プラットフォームごとのインストーラを作成するのは、より複雑なプロセスで、このクイックスタートの範囲を超える。

アプリのアップデート

アプリをアップデートするには、単純に新たなステージングディレクトリを作成し、更新jarファイルを格納する。(そして、更新されたgetdown.txtである)。Digesterを起動し、digest.txtを作成し、Webサーバにアップロードし、古いmyappディレクトリの内容を上書きする。

Getdownは、ウェブサーバ上のgetdown.txtの最終更新時間をチェックし、もしローカルよりあたらしければ、変更されたすべてのファイルをダウンロードする。MD5ハッシュによりファイル変更がなければ再ダウンロードはされない。

これはバージョンレスモードの動作であることに注意する。これとは別に明示的にバージョン番号を提供することもできる。