Spring Boot+Thymeleaf+DockerでMiRmの基幹システムを開発した (PWA対応)

普通科高校の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で書かれています。

全体構成

FlowChart_Qiita.png

Spring Boot

文献が豊富にあり、kotlinで扱うにはちょうどいいと思ったので採用しました。

csrfを有効にした時の403エラー

これはかなりハマった点なのですが、Spring security標準のcsrfを有効化した状態でREST Controllerで組んだAPIにpostすると403 Forbiddenで弾かれる問題が発生しました。(getだと問題はありませんでした。)

これに対する解決策としては

  1. csrf認証自体を無効化する
  2. 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の方が発展している)ですが、実用には十分耐えられるレベルです。

iOSでは以下のように動作します。
スプラッシュ
905F8EF9-C33E-4B90-9192-E102AFA7B96A.jpeg

動作
1EC78B27-3186-4482-9503-7B45EACAB3B1.png

タスクビュー
BB46FFEA-D5BF-4E6C-AAB7-1FC0F2B58750.png

SimpleMDE

超簡単にMarkdownエディタをWebページに実装可能な"SimpleMDE"を参考にさせていただきました。ありがとうございます。

MiRmでサーバーリストを表示するのに、各サーバーに紹介文を書いてもらいたかったのですが、普通の文章だとみんな同じで目立たなくなってしまう。とはいえhtmlを敷居が高い...
と思った矢先、SimpleMDEというjsライブラリを見つけたのでこれを使用して簡単なMarkdownに対応させることにしました。

右下のLobiのアイコンはフォントを作成して表示しています。(参考:https://nelog.jp/feedly-web-iconic-font)

編集画面
a.PNG

実際の表示
b.PNG

編集は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>

今後の展望

コントロールパネルや設定画面等、コンテンツの中身が多いがゆえに表示速度が若干遅いので、その辺をもっと改善していきたいと思っています。
また将来的にはより簡単に操作できるバージョンのリリースも考えています。

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした