普通科高校の2年生をやっている@itsu_devです。
今回はMiRm(Minecraft 無料マルチプレイサーバーホスティングサービス)の基幹システムの入れ替えに伴い、@haniokasai氏と新しくシステムを開発しなおしたのでそれを紹介します。
はじめに
そもそもMiRmとは?
Minecraftを友達としようにも、
- サーバーが必要
- ソフトの導入・設定が面倒
- 環境構築が難しい
- 維持費がかかる
といった欠点があり、なかなか敷居の高いものでした。
そこでこれらすべてを解決すべく始まったのがMiRm Projectです。
MiRmにはどんな機能があるの?
- サーバーソフトの標準出力のリアルタイム表示
- サーバーソフトへの標準入力
- FTP経由でのファイル操作
- ワンタッチでのサーバー起動・停止
- Markdown記法によるサーバーリストへの紹介文掲載
こういった機能をメインに、ほかにも様々な機能をユーザーに提供しています。
技術的な内容
使用した技術
タイトルにもあるように、Spring BootベースのWebアプリケーションとしてすべてを完結させています。「スマホだけでできる」がウリなので、もちろんPWAにも対応しています。
各ユーザーが所有するサーバーは一つのDockerコンテナで完結しており、ファイル操作やサーバーソフトの動作もすべてこの中で行っています。
他の主なものとしては
- Thymeleaf(htmlテンプレートエンジン)
- MySQL
- Material Design Bootstrap(Bootstrapのマテリアルデザインライブラリ)
- jQuery
- SimpleMDE(埋め込みMarkdownエディタ)
- darkmode.js(ダークモードの実装)
- kotlin coroutine
といったところです。
全体を管理する基幹システムはkotlinで書かれています。
全体構成
Spring Boot
文献が豊富にあり、kotlinで扱うにはちょうどいいと思ったので採用しました。
csrfを有効にした時の403エラー
これはかなりハマった点なのですが、Spring security標準のcsrfを有効化した状態でREST Controllerで組んだAPIにpostすると403 Forbiddenで弾かれる問題が発生しました。(getだと問題はありませんでした。)
これに対する解決策としては
- csrf認証自体を無効化する
- postを受けるページに対するcsrf認証を無効化する
の二つが挙げられます。
(参考: https://qiita.com/xx2xyyy/items/385492431a95caa60d7a)
Thymeleaf
Spring Bootとよく使われるhtmlテンプレートエンジンです。一度別件で使ったことがある程度でしたが、改めて使ってみることにしました。
<span th:text=“${text}”></span>
@GetMapping(value = “example”)
public ModelAndView display(ModelAndView mv) {
// 中略
mv.addObject(“text”, “Hello, World!”);
return mv;
}
上のようにhtmlにJava(kotlin)から変数を埋め込めるのですが、これがWebアプリを作る時に案外便利です。
Docker
基幹システムとDockerコンテナとの接続
基本的にはDocker-JavaとJava標準のProcess経由でのコマンド実行の両方を使っています。
Minecraftのコマンドを標準入力に書き込むのに、普通にdocker attach CONTAINER
をProcess経由で実行して得たそれに対して行ってもよいのですが、それだとある問題が発生します。
それは、コンテナ内部のサーバーソフトがPID 1で動作していないことです。
この問題によってProcessで得られる標準入力に書き込んでもサーバーソフトに書きこまれない現象が発生しました。それを解決するために、コマンドを/proc/PID/fd/0
に書き込むことにしました。
以下のコードでは、上記のPIDを得るためにdocker topコマンドの内容をパースしています。
※MiRmDockerClient#getCommandExceptList サーバーソフトのプロセスを取得するために除外する文字列のリスト
※TopElementクラス 出力内容のオブジェクトクラス
public static List<TopElement> parse(String serverId) {
ArrayList<TopElement> elements = new ArrayList<>();
LinkedList<String> outputs = new LinkedList<>();
try {
StringBuffer buf = new StringBuffer();
buf.append("docker -H "+ DOCKERHOST + " top " + serverId + MiRmDockerClient.getPrefix());
MiRmDockerClient.getCommandExceptList().forEach(str -> buf.append(" | grep -v \"" + str + "\""));
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", buf.toString());
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
String temp;
while ((temp = reader.readLine()) != null) {
outputs.add(temp);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
outputs.forEach(str -> {
str = str.replaceAll("\\s+", " ");
if (!str.startsWith("UID")) {
String[] data = str.split(" ");
StringBuffer buf = new StringBuffer();
for (int i = 7; i < data.length; i++) {
buf.append(" " + data[i]);
}
elements.add(new TopElement(
data[0],
Long.parseLong(data[1]),
Long.parseLong(data[2]),
Integer.parseInt(data[3]),
data[4],
data[5],
data[6],
buf.toString().substring(1)
));
}
});
return elements;
}
PWA (Progressive Web Application)
最近過熱してきている技術で、Webアプリをスマートフォンのネイティブアプリかのように動作させられる技術です。OS(Android/iOS)によって使える機能はまちまち(Androidの方が発展している)ですが、実用には十分耐えられるレベルです。
SimpleMDE
超簡単にMarkdownエディタをWebページに実装可能な"SimpleMDE"を参考にさせていただきました。ありがとうございます。
MiRmでサーバーリストを表示するのに、各サーバーに紹介文を書いてもらいたかったのですが、普通の文章だとみんな同じで目立たなくなってしまう。とはいえhtmlを敷居が高い...
と思った矢先、SimpleMDEというjsライブラリを見つけたのでこれを使用して簡単なMarkdownに対応させることにしました。
右下のLobiのアイコンはフォントを作成して表示しています。(参考:https://nelog.jp/feedly-web-iconic-font)
編集はMarkdownですが、表示はhtmlで行っています。この変換もSimpleMDEに標準でついており、簡単に使用することができます。
以下のコードでtextareaをエディタ化しています。
toolbarの部分でエディタ上部のツールバーをカスタマイズできます。
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
</head>
<body>
<label for="description">マークダウン記法が使えます。(htmlタグは使用不可・ボタンとして載っている記法のみ使用可能)</label>
<textarea class="form-control z-depth-1" id="description" name="description" rows="8" cols="40"
placeholder="xxサーバーへようこそ!" th:text="${description}">
</textarea>
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
<script>
var simplemde = new SimpleMDE({
element: document.getElementById("description"),
forceSync: true,
spellChecker: false,
toolbar: ["bold", "italic", "strikethrough", "heading", "|", "quote", "unordered-list", "ordered-list", "link", "|", "preview", "side-by-side", "guide"]
});
marked.setOptions({
sanitize: true,
sanitizer: escape,
breaks: true
});
</script>
</body>
</html>
今後の展望
コントロールパネルや設定画面等、コンテンツの中身が多いがゆえに表示速度が若干遅いので、その辺をもっと改善していきたいと思っています。
また将来的にはより簡単に操作できるバージョンのリリースも考えています。