GitHub上のcommitとかtreeをrawgitみたいにキャッシュして提供したい
GitHub上のリポジトリを直接実行できるゲームエンジンが欲しいので構成を検討する。
- 1分間1回程度のリクエストは匿名でもできるので、リポジトリの
HEAD
をSHA1に変換する操作は単純にproxyする (たぶんCORS的に直接はできないんじゃないか) - リポジトリ上のファイルはgithack等のGitHub CDN https://qiita.com/okuoku/items/9c72a88662831d774742 系のサービスを使う
- treeオブジェクトやcommitオブジェクトは内容が変化することがないので、CloudFlare Workersか何かでキャッシュに突っ込みつつ処理することにすれば、1日1000回とかプレイされるようになっても余裕があると考えられる
3つ目を考える。
- TODO: Gistのサポート、sizeをtreeのレスポンスに含めて良いか?、GitHub以外のリポジトリプロバイダ?commitにはユーザのアイコンとかユーザ名とかリポジトリのリンクとかを含めた方が良いか?
GitHub Rest APIで手動で取ってくる
GitHubのURLから直接treeの値を知ることはできないので、あるファイルを取得するには branch名 → commit → tree → blob と辿る必要がある。
(注意: ブラウザでもクリックで実行できるが、APIにかなり保守的なrate limitがあるので注意。また本来は Accept: application/vnd.github.v3+json
が必要。)
refの取得
リポジトリのURL https://github.com/okuoku/yuni が与えられたとして、 デフォルトブランチの最新のコミット を取得することを考える。このためには、デフォルトブランチ名と、それが指すrefを取得する必要がある。
- ユーザ名(owner)、リポジトリ名を取得する (
okuoku
、yuni
) - デフォルトブランチを取得する: https://api.github.com/repos/okuoku/yuni の
default_branch
=>master
- refを参照する: https://api.github.com/repos/okuoku/yuni/git/ref/heads/master の
object.ref
=>22a55b9c403fc3f929cc39727a06e44a7db19715
デフォルトブランチとそれが指すcommitは日々刻々と変化するのでブラウザからfetchするべきだろう。GitHubのAPIは1時間に60回までは匿名でリクエストできる。
commitの取得
commitからコミットメッセージとtreeを取得する。
-
https://api.github.com/repos/okuoku/yuni/git/commits/22a55b9c403fc3f929cc39727a06e44a7db19715 の
tree.sha
=>063a4d0e2446a25b1728fb7236d8b87b4489c795
これはキャッシュすべき内容だろう。一度生成されたら絶対に変更されない。
treeの取得
treeの実際の内容を取得する。これにはファイル名やディレクトリ(他のtree)が含まれる。
重大な注意事項として、treeは可変長であるという点がある。treeが大きすぎて返却できない場合は truncated
が真になることで通知される。
本来のtree objectにはサイズの情報は入っていないが、GitHubは返却するようだ。
また、Gitの仕様として、treeが何というディレクトリ名だったかの情報は含まれていない。このため、GitHub CDNにリクエストを行うには クライアントサイドで参照元のcommitとパスを覚えておいてURLを組み立てる 必要がある。また、逆に、ファイルのフルパスが事前に判明している場合はcommitやtreeを取得する必要がない。
ファイルの取得
まだ生きているジェネリックなGitHub CDNはgithackくらいのようだ。
ここでのURLにはtreeではなくcommitとフルパスが使用される。
レスポンスの仕様を考える
基本的には名前付きのjsonオブジェクトを返却する。どこかのタイミングでPublic GitHub以外にも対応したいので、他のシステムの仕様に合わせた変更が必要になる可能性はそれなりにある。
基本構造
レスポンスは以下のようなオブジェクトにwrapする。
{"time": unix_time,
"paged": false,
"result": {OBJECT}}
今のところ、ページングはサポートする必要が無いと思っている。というわけで paged
は false 固定。
repoinfo
: /:plt/:owner/:repo/repoinfo
デフォルトブランチや、最近更新されたrefの情報は専用のrepoinfoに格納させる。これは完全に独自仕様。
{"private": false,
"provider": "github_public",
"web_url": "https://...",
"default_branch": "master"}
例えば clone url とかを後で追加すると良さそう。
plt
はプラットフォームで、public GitHubには gh
をアサインする。
refs
{"refs": [...]}
refs
はオブジェクトで、リポジトリに存在するブランチを列挙するために使う。GitHubで言うと https://api.github.com/repos/okuoku/yuni/git/refs の内容となる。... これはマジで全てのrefを含むため何らかの方法でフィルタリングが必要になる。例えば https://api.github.com/repos/facebook/react/git/refs は508ページもある。
これは後回しでも良いかな。。列挙はそれぞれのプロバイダのWebUIでやってもらうって事で。。
ref
: /:plt/:owner/:repo/ref/:refname
refの指すオブジェクトを表わす。
{"commit": git_hash}
commit
: /:plt/:owner/:repo/commit/:sha
{"web_url": "https://...", "tree": git_hash, parents: [git_hash, ...]}
★ コミットメッセージはJSONに返却しない。独立したテキストファイルとしてコミットメッセージの中身だけを提供する。
FIXME: コミッタは複数になる可能性があるけど、authorはどうなのか?
tree
: /:plt/:owner/:repo/tree/:sha
{"entries": [...]}
{"path": "name", "mode": "10666", "type": "blob", "size": 0, "sha": "...."}
size
はちょっと悩みどころだが、いわゆる stat
コマンドに相当する操作で必要なデータは埋めておいた方が都合が良いので含めておくことにした。
GitHub等のWebUIでは最終更新となるcommitの情報も入っているが、それは演算負荷無しでは取れないので省略。