ゼロからJavaのWebアプリを作ってRenderで公開する


# ゼロからJavaのWebアプリを作ってRenderで公開する

## はじめに

JavaでWebアプリを作って，それを公開するまでの手順をまとめてみました．

とりあえず，動かせるものを作る！という方向けです．実際に動いたのを確認した後に色々考えたりできることもあると思うので，とりあえず作る方法をまとめてみました．

## 作成手順

### 1. プロジェクトの概要を決める

まずは，どんなWebアプリを作るのかを決めます．

今回は単純なゲームを作成したかったので，とりあえず数あてゲームを作成することにしました．

### 2. プロジェクトの作成

必要なファイルを作成します．
render.comではdockerに対応しているので，Dockerfileを作成します．

```Dockerfile
FROM openjdk:11-slim

# 作業ディレクトリを設定
WORKDIR /app

# Javaファイルをコピー
COPY Main.java /app

# Javaファイルをコンパイル
RUN javac Main.java

# ポート8080を公開
EXPOSE 8080

# アプリケーションを実行
CMD ["java", "Main"]
```


単純ですね．
続けて，Main.javaを作成します．

一度，Hello worldを表示するだけのプログラムを作成します．

```java
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class Main {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/", new MyHandler());
        server.setExecutor(null); // デフォルトのエグゼキュータを使用
        server.start();
    }

    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "Hallo world";
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}
```

render.comでデプロイするようの`render.yaml`を作成します．

```yaml
services:
  - type: web
    name: java-app
    runtime: docker
    plan: free
    dockerfilePath: ./Dockerfile
    buildCommand: ""
    startCommand: ""
```

:::note info
`render.yaml`は必須ではありませんが，以下のようなメリットがあります．
### Blueprintの活用
render.yamlを使用すると、RenderのBlueprint機能を利用できます。これにより、インフラ構成をコードとして管理できます（Infrastructure as Code）。

### 自動デプロイの設定
render.yamlファイルがリポジトリのルートに配置されていると、Renderは自動的にその内容に従ってデプロイを行います。

### 環境の一貫性
データベースやWebサービスなど、プロジェクトに必要な全てのリソースを一つのファイルで定義できるため、環境の一貫性を保ちやすくなります。

### Preview Environments
render.yamlを使用することで、Pull Requestごとに独立した環境を自動生成するPreview Environments機能を利用できます。

### 設定の簡素化
デプロイに必要な様々な設定（ビルドコマンド、起動コマンド、環境変数など）をファイル内で一元管理できます。
:::

### 3. プロジェクトのデプロイ

render.comにアクセスし，GitHubアカウントでログインします．

https://render.com/

![sign in with github](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670528/9282931e-0ed1-a5c1-3574-1c88bb3b7e2e.png)


右上の「New」ボタンをクリックし，「Web Service」を選択します．

![New Web Service](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670528/a1d15cbe-d5d7-121a-09f3-597b0d7436a3.png)

GitHubリポジトリを選択し，デプロイするリポジトリを選択します．

設定は基本的にはデフォルトのままで問題ありません．

dockerfileも自動で検知してくれるので，問題なければそのままデプロイします．

### 4. 動作確認

ダッシュボードに戻ると，自分のアプリが下の方に表示されるのでアクセスしてみます．

![dashboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670528/2c877bbb-4971-cbc2-1f35-77f05da8fd02.png)

:::note info
デプロイが遅いかもしれませんが，`STATUS`が`Deployed`になるまで待ちましょう．
:::

上の方にあるURLをクリックすると，アプリが表示されます．

![url](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670528/d139743d-910a-cac9-ae8a-8210ebb89e41.png)


Hallo worldと表示されれば成功です．

### 5. アプリの変更

`Main.java`を変更して，再度デプロイしてみましょう．

```java
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    private static AtomicInteger targetNumber = new AtomicInteger(new Random().nextInt(100) + 1);

    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/", new MyHandler());
        server.setExecutor(null); // デフォルトのエグゼキュータを使用
        server.start();
    }

    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String query = t.getRequestURI().getQuery();
            String response;
            if (query != null && query.startsWith("guess=")) {
                try {
                    int guess = Integer.parseInt(query.split("=")[1]);
                    int target = targetNumber.get();
                    if (guess < target) {
                        response = "Too low!";
                    } else if (guess > target) {
                        response = "Too high!";
                    } else {
                        response = "Correct! Generating new number...";
                        targetNumber.set(new Random().nextInt(100) + 1);
                    }
                } catch (NumberFormatException e) {
                    response = "Invalid number!";
                }
            } else {
                response = "Please provide a guess parameter, e.g., /?guess=50";
            }

            String htmlResponse = "<html><body>" +
                    "<h1>Guess the Number Game</h1>" +
                    "<p>" + response + "</p>" +
                    "<form method='get' action='/'>" +
                    "  <label for='guess'>Enter your guess:</label>" +
                    "  <input type='number' id='guess' name='guess'>" +
                    "  <input type='submit' value='Submit'>" +
                    "</form>" +
                    "</body></html>";

            t.sendResponseHeaders(200, htmlResponse.length());
            OutputStream os = t.getResponseBody();
            os.write(htmlResponse.getBytes());
            os.close();
        }
    }
}
```

このプログラムは，1から100までのランダムな数を当てるゲームです．

デザインは考えていませんが，とりあえず動くものを作成しました．

### 6. 再度デプロイ

render.comは自動でデプロイしてくれるので，先ほどのmainブランチに`git push`するだけでデプロイされます．

### 7. 動作確認

URLにアクセスして，ゲームが表示されることを確認しましょう．

![game screen](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670528/7bd831bc-1e01-c96c-9faf-3f27e4584514.png)


これで，JavaのWebアプリを作成して，Renderで公開するまでの手順が完了しました．

## まとめ

JavaでWebアプリを作成して，それを公開するまでの手順をまとめてみました．

とりあえず，動かせるものを作れたのでこれをベースに色々考えてみます．