ゲーム開発
Unity
UnrealEngine
C++
ゲーム数学
ゲームAI
サウンド
アニメーション
GBDK
制作日記
3DCG
Houdini
Blender
USD
グラフィックス
テクノロジ
ツール開発
フロントエンド関連
サーバサイド関連
ソフトウェア設計
ハードウェア関連
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
その他
都会のエレキベア
ラーメン日記
四コマ漫画
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • ゲーム数学
    • ゲームAI
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • 3DCG
    • Houdini
    • Blender
    • USD
    • グラフィックス
  • テクノロジ
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • ソフトウェア設計
    • ハードウェア関連
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • その他
    • 都会のエレキベア
    • ラーメン日記
    • 四コマ漫画
    • おすすめアイテム
    • おもしろコラム
  1. ホーム
  2. 20240415_01_unity_ranking_api

【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】

Unityサーバサイド関連GoNCMBVPS
2024-04-14

マイケル
マイケル
みなさんこんにちは! マイケルです!
エレキベア
エレキベア
こんにちクマ〜〜〜
マイケル
マイケル
これまで制作したいくつかのアプリについて、ニフクラ mobile backend (NCMB) というサービスを使用してランキング処理を実装していたのですが、2024/4よりサービスが終了してしまいました。

ニフクラ mobile backend - サービス提供終了のご案内

20240415_01_unity_ranking_api_02
▲NCMBを使用したランキング処理 (ゴーゴーゴロヤン)

20240415_01_unity_ranking_api_03
▲NCMBを使用したランキング処理 (エレキシューティング)

エレキベア
エレキベア
長らくお世話になったクマ・・・ おつかれさまクマ・・・
マイケル
マイケル
そういうわけで移行先をどうしようかとしばらく悩んでいたのですが、 ここらでサーバも一台持っておくかと踏み切り、簡易的な自作のランキングAPIを作成することにしました。 実際に作成したAPIと実行サンプルは下記になります。
20240415_01_unity_ranking_api_01
▲ランキング処理のサンプル

GitHub - unity-go-ranking-sample

エレキベア
エレキベア
APIも自分で作るとはまた思い切ったクマね
マイケル
マイケル
サーバ台はかかるけどドメインを設定しておけばアプリ側の対応が不要になるし このブログのコメント機能など他にもサーバを使用したい用途があったのが決め手ですね・・・
エレキベア
エレキベア
まあ確かに一台持っておくと便利そうクマ
マイケル
マイケル
サーバに関しては今回は VPS を使用することにして、XServer VPSの2GBプランを契約しました! API実装の他、こちらのデプロイ方法についても軽く記載しておきますので、同様にサーバを立てたい方は参考にしてください!

XServer VPS

エレキベア
エレキベア
値段もそこまで高くないクマし、 固定料金な分、AWSとかと比べて個人開発で使いやすそうクマね

GoでのランキングAPI実装

マイケル
マイケル
まずはGoで実装したランキングAPIの内容について紹介します。 こちらはリポジトリの下記フォルダに格納してあります。

GitHub - unity-go-ranking-sample / go-ranking-api

エレキベア
エレキベア
Goは前の記事でも軽く触ったクマね
【Go言語】Gin、GORMでシンプルなREST APIを作成する【CRUD】
2024-03-26
マイケル
マイケル
今回のAPIも上記のコードをベースに作成しています。 GoでのAPI作成の基礎知識について知りたい方は上記記事もご参照ください!

API設計

マイケル
マイケル
まずテーブル定義、API定義に関しては下記のように設定しました。 立ち上げるサーバを最低限で済ませられるよう、アプリIDを渡すことで複数アプリで使用できるようにしています。
テーブル定義
mst_apps(アプリ情報マスタ)
Id
Name
ClientKey
アプリID
アプリ名
クライアントキー
integer
string
string
scores(ランキングスコアテーブル)
Id
AppId
Type
Name
Score
スコアID
アプリID
スコア種類
プレイヤー名
スコア
integer
integer
integer
string
integer
API定義
URL
Method
Description
Requests
ranking/scores
GET
ランキング情報取得
app_id: アプリID
type: スコア種別
limit_count: 取得最大数
order_desc: 値降順で取得するか?
ranking/scores
POST
ランキング情報登録
※mst_appsのテーブル定義と同様
エレキベア
エレキベア
最低限の構成って感じクマね scoresテーブルにゲーム内のスコアを登録するクマね

実装内容

マイケル
マイケル
次に実装内容について見ていきます。
ルーティング処理
マイケル
マイケル
先ほど載せたルーティングについては、controllerに定義してあります。 リクエストパラメータを受け取った後にDBアクセスする流れで、そちらはrepositoryの方に記述してあります。
package controller

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
	"vps-general-api/repository"
)

type ScoreController struct{}

func (ctrl ScoreController) Routes() []Route {
	return Routes{
		Route{
			GroupType:   Ranking,
			MethodType:  GET,
			Path:        "/scores",
			HandlerFunc: ctrl.Index,
		},
		Route{
			GroupType:   Ranking,
			MethodType:  POST,
			Path:        "/scores",
			HandlerFunc: ctrl.Create,
		},
	}
}

func (ctrl ScoreController) Index(c *gin.Context) {
	// リクエストパラメータ取得
	appId, err := strconv.Atoi(c.Query("app_id"))
	if err != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		fmt.Println(err)
		return
	}
	scoreType, err := strconv.Atoi(c.Query("type"))
	if err != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		fmt.Println(err)
		return
	}
	limitCount, err := strconv.Atoi(c.Query("limit_count"))
	if err != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		fmt.Println(err)
		return
	}

・・・略・・・

	// パラメータからレコードを取得
	var r repository.ScoreRepository
	scores, err := r.Get(appId, scoreType, limitCount)
	if err != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		fmt.Println(err)
	} else {
		c.JSON(http.StatusOK, gin.H{"scores": scores})
	}
}

func (ctrl ScoreController) Create(c *gin.Context) {
	var r repository.ScoreRepository
	book, err := r.Create(c)
	if err != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		fmt.Println(err)
	} else {
		c.JSON(http.StatusOK, book)
	}
}

▲ルーティング定義
package repository

import (
	"github.com/gin-gonic/gin"
	"vps-general-api/model"
)

type ScoreRepository struct{}
type Score model.Score

func (r ScoreRepository) GetAll() ([]Score, error) {
	var scores []Score
	err := Db.Find(&scores).Error
	if err != nil {
		return nil, err
	}
	return scores, nil
}

func (r ScoreRepository) Get(appId int, scoreType int, limitCount int) ([]Score, error) {
	var scores []Score
	err := Db.Order("score").Limit(limitCount).Find(&scores, "app_id = ? AND type = ? ", appId, scoreType).Error
	if err != nil {
		return nil, err
	}
	return scores, nil
}

func (r ScoreRepository) Create(c *gin.Context) (Score, error) {
	var score Score
	err := c.ShouldBindJSON(&score)
	if err != nil {
		return score, err
	}
	err = Db.Create(&score).Error
	if err != nil {
		return score, err
	}
	return score, nil
}

▲DB操作処理
エレキベア
エレキベア
SQL実行くらいで難しいことは特にしていないクマね
Basic認証処理
マイケル
マイケル
そして認証方式については、今回はBasic認証を使用しています。 簡易的なAPIなのでこの方式を採用しましたが、重大なデータを扱う場合にはよりセキュリティ強度の高い方式を選択したほうがいいかもしれません・・・ また、Basic認証は通信盗聴の恐れがあるので、最低限HTTPSでの通信は必須にしましょう。
エレキベア
エレキベア
Basic認証はユーザID、パスワードの組み合わせを リクエスト時に付与することで認証する方式だったクマね

Basic認証 - Wikipedia

マイケル
マイケル
今回はアプリ側にユーザ作成は行わないため、代わりにアプリIDとそれに対するトークンをセットにして送信しています。 Ginの機能でBasic認証を設定し、ルーティングに紐づけたRouteGroup単位で設定するようにしました。

・・・略・・・

type RouteGroupType int

const (
	None RouteGroupType = iota
	Ranking
)

type RouteGroup struct {
	Path         string
	AuthAccounts map[string]string
}
type RouteGroupMap map[RouteGroupType]RouteGroup

var routeGroupMap RouteGroupMap

// GetRouteGroupMap all controller route groups.
func GetRouteGroupMap() RouteGroupMap {
	var r repository.MstAppRepository

	// routeグループと認証情報を定義
	routeGroupMap = RouteGroupMap{}
	routeGroupMap[Ranking] = RouteGroup{
		Path:         "/ranking",
		AuthAccounts: r.GetRankingAuthAccounts(),
	}
	return routeGroupMap
}

▲ランキング処理のRouteGroup返却
package server

import (
	"github.com/gin-gonic/gin"
	"vps-general-api/controller"
)

var router *gin.Engine

func Init() {
	router = CreateRouter(controller.GetAllRoutes(), controller.GetRouteGroupMap())
}

func CreateRouter(routes controller.Routes, routeGroupMap controller.RouteGroupMap) *gin.Engine {
	r := gin.Default()

	routerGroup := map[controller.RouteGroupType]*gin.RouterGroup{}
	for key, value := range routeGroupMap {
		routerGroup[key] = r.Group(value.Path, gin.BasicAuth(value.AuthAccounts))
	}

	for _, route := range routes {
		if group, ok := routerGroup[route.GroupType]; ok {
			switch route.MethodType {
			case controller.GET:
				group.GET(route.Path, route.HandlerFunc)
			case controller.POST:
				group.POST(route.Path, route.HandlerFunc)
			case controller.PUT:
				group.PUT(route.Path, route.HandlerFunc)
			case controller.DELETE:
				group.DELETE(route.Path, route.HandlerFunc)
			}
		} else {
			switch route.MethodType {
			case controller.GET:
				r.GET(route.Path, route.HandlerFunc)
			case controller.POST:
				r.POST(route.Path, route.HandlerFunc)
			case controller.PUT:
				r.PUT(route.Path, route.HandlerFunc)
			case controller.DELETE:
				r.DELETE(route.Path, route.HandlerFunc)
			}
		}

	}
	return r
}

func Listen() {
	err := router.Run(":8080")
	if err != nil {
		panic(err)
	}
}

▲GinでのBasic認証登録
エレキベア
エレキベア
gin.BasicAuthで設定しているクマね
マイケル
マイケル
アプリに紐づくトークン情報はmst_appsに設定しているため、repository内で取得して返却しています。 今回はIDの番号によってランキング対象のアプリかどうかを判断するようにしました。
package repository

import (
	"strconv"
	"vps-general-api/model"
)

type MstAppRepository struct{}
type MstApp model.MstApp

func (r MstAppRepository) GetRankingAuthAccounts() map[string]string {
	var apps []MstApp

	// 1000番台がランキング対象アプリ
	err := Db.Find(&apps, "id BETWEEN ? AND ?", 1000, 1999).Error
	if err != nil {
		panic(err)
	}

	// mapにして返却
	authAccounts := map[string]string{}
	for _, app := range apps {
		key := strconv.FormatUint(uint64(app.Id), 10)
		authAccounts[key] = app.ClientKey
	}
	return authAccounts
}

▲mst_appからのクライアントキー取得

・・・略・・・

func (ctrl ScoreController) Index(c *gin.Context) {

・・・略・・・

	// AppIdが認証キーと一致しているか?
	authUserId := c.MustGet(gin.AuthUserKey).(string)
	if authUserId != strconv.Itoa(appId) {
		c.AbortWithStatus(http.StatusUnauthorized)
		return
	}

・・・略・・・

}

・・・略・・・

▲アプリ情報との整合性チェック
エレキベア
エレキベア
これで簡易的な認証の実装も完了クマね

Unity側での実装

マイケル
マイケル
ランキングAPIをUnityで実行するサンプルについても作成しました。 こちらは以前作成したUnityWebRequestのサンプルをベースに修正したものになります。
20240415_01_unity_ranking_api_01
▲ランキング処理のサンプル

GitHub - unity-go-ranking-sample / unity-ranking-project
▲リポジトリ

【Unity】UnityWebRequestを使ってCRUD機能を実装する【Ruby on Rails】
2021-12-12
マイケル
マイケル
ランキングAPI実行処理は主にRankingApiSettings、RankingApiServiceになります。 特に特殊なことはしておらず、UnityWebRequestで作成したAPIを叩いているだけです。
using UnityEngine;

namespace Molegoro.Ranking
{
    [CreateAssetMenu(fileName = "RankingSettings", menuName = "Molegoro/RankingSettings")]
    public class RankingApiSettings : ScriptableObject
    {
        /// <summary>
        /// ランキングAPI URL
        /// </summary>
        [SerializeField]
        private string _apiUrl = "http://localhost:8080";
        public string ApiUrl => _apiUrl;

        /// <summary>
        /// アプリケーションID
        /// </summary>
        [SerializeField]
        private int _appId;
        public int AppId => _appId;

        /// <summary>
        /// クライアントキー
        /// </summary>
        [SerializeField]
        private string _appClientKey;
        public string AppClientKey => _appClientKey;
    }
}

▲ランキング処理設定クラス
using System;

namespace Ranking
{
    /// <summary>
    /// ランキングサービス interface
    /// </summary>
    public interface IRankingApiService
    {
        [Serializable]
        public class ScoreSchema
        {
            /// <summary>
            /// ID
            /// </summary>
            public int Id;

            /// <summary>
            /// アプリケーションID
            /// </summary>
            public int AppId;

            /// <summary>
            /// スコア種別
            /// </summary>
            public int Type;

            /// <summary>
            /// プレイヤー名
            /// </summary>
            public string Name;

            /// <summary>
            /// スコア
            /// </summary>
            public int Score;
        }

        [Serializable]
        public class ScoreSchemaArray
        {
            public ScoreSchema[] scores;
        }

        /// <summary>
        /// スコアレコードの取得
        /// </summary>
        /// <param name="type">スコア種別</param>
        /// <param name="limitCount">最大取得数</param>
        /// <param name="isOrderDesc">値降順で取得するか?</param>
        /// <param name="callback">コールバック</param>
        public void GetScores(int type, int limitCount, bool isOrderDesc, Action<bool, ScoreSchema[]> callback);

        /// <summary>
        /// スコアレコードの追加
        /// </summary>
        /// <param name="type">スコア種別</param>
        /// <param name="name">プレイヤー名</param>
        /// <param name="score">スコア</param>
        /// <param name="callback">コールバック</param>
        public void AddScore(int type, string name, int score, Action<bool, string> callback = null);
    }
}
▲ランキング処理サービスインターフェイス
using System;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

namespace Molegoro.Ranking
{
    /// <summary>
    /// ランキングサービスクラス
    /// </summary>
    public class RankingApiApiService : IRankingApiService
    {
        /// <summary>
        /// ランキング設定
        /// </summary>
        private readonly RankingApiSettings _rankingApiSettings;

        /// <summary>
        /// MonoBehaviour(コルーチン実行用)
        /// </summary>
        private readonly MonoBehaviour _monoBehaviour;

        /// <summary>
        /// ランキングAPI URL
        /// </summary>
        private string ApiUrl => $"{_rankingApiSettings.ApiUrl}/ranking";

        /// <summary>
        /// アプリケーションID
        /// </summary>
        private int AppId => _rankingApiSettings.AppId;

        /// <summary>
        /// アプリケーションクライアントキー
        /// </summary>
        private string AppClientKey => _rankingApiSettings.AppClientKey;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="rankingApiSettings">ランキング設定</param>
        public RankingApiApiService(RankingApiSettings rankingApiSettings)
        {
            _rankingApiSettings = rankingApiSettings;

            // MonoBehaviourハンドラの生成
            var monoBehaviourHandlerObject = new GameObject(nameof(RankingApiMonoBehaviour));
            var monoBehaviourHandler = monoBehaviourHandlerObject.AddComponent<RankingApiMonoBehaviour>();
            UnityEngine.Object.DontDestroyOnLoad(monoBehaviourHandlerObject);
            _monoBehaviour = monoBehaviourHandler;
        }

        /// <summary>
        /// スコアレコードの取得
        /// </summary>
        /// <param name="type">スコア種別</param>
        /// <param name="limitCount">最大取得数</param>
        /// <param name="callback">コールバック</param>
        public void GetScores(int type, int limitCount, Action<bool, IRankingApiService.ScoreSchema[]> callback)
        {
            var queryString = System.Web.HttpUtility.ParseQueryString("");
            queryString.Add("app_id", AppId.ToString());
            queryString.Add("type", type.ToString());
            queryString.Add("limit_count", limitCount.ToString());

            var requestUrl = $"{ApiUrl}/scores";
            var uriBuilder = new System.UriBuilder(requestUrl)
            {
                Query = queryString.ToString()
            };

            var request = UnityWebRequest.Get(uriBuilder.Uri);
            _monoBehaviour.StartCoroutine(SendRequestCoroutine(request, (isSuccess, response) =>
            {
                if (isSuccess)
                {
                    var schema = JsonUtility.FromJson<IRankingApiService.ScoreSchemaArray>(response);
                    callback.Invoke(true, schema.scores);
                }
                else
                {
                    callback?.Invoke(false, null);
                }
            }));
        }

        /// <summary>
        /// スコアレコードの追加
        /// </summary>
        /// <param name="type">スコア種別</param>
        /// <param name="name">プレイヤー名</param>
        /// <param name="score">スコア</param>
        /// <param name="callback">コールバック</param>
        public void AddScore(int type, string name, int score, Action<bool, string> callback = null)
        {
            var schema = new IRankingApiService.ScoreSchema()
            {
                AppId = AppId,
                Type = type,
                Name = name,
                Score = score,
            };
            var json = JsonUtility.ToJson(schema);
            var bodyRaw = Encoding.UTF8.GetBytes(json);

            var requestUrl = $"{ApiUrl}/scores";
            var request = new UnityWebRequest(requestUrl, "POST");
            request.uploadHandler = new UploadHandlerRaw(bodyRaw);
            request.downloadHandler = new DownloadHandlerBuffer();
            request.SetRequestHeader("Content-Type", "application/json");

            _monoBehaviour.StartCoroutine(SendRequestCoroutine(request, (isSuccess, response) =>
            {
                callback?.Invoke(isSuccess, response);
            }));
        }

        /// <summary>
        /// リクエスト送信
        /// </summary>
        /// <param name="request">リクエスト</param>
        /// <param name="callback">コールバック</param>
        private IEnumerator SendRequestCoroutine(UnityWebRequest request, Action<bool, string> callback = null)
        {
            request.SetRequestHeader("Authorization", GetBasicAuthToken());
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.ConnectionError
                || request.result == UnityWebRequest.Result.ProtocolError)
            {
                callback?.Invoke(false, request.error);
                yield break;
            }

            callback?.Invoke(true, request.downloadHandler?.text);
        }

        /// <summary>
        /// Basic認証トークン取得
        /// </summary>
        /// <returns></returns>
        private string GetBasicAuthToken() {
            var auth = AppId + ":" + AppClientKey;
            auth = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(auth));
            auth = "Basic " + auth;
            return auth;
        }
    }

    /// <summary>
    /// MonoBehaviour実行用
    /// </summary>
    public class RankingApiMonoBehaviour : MonoBehaviour
    {
    }
}

▲ランキング処理サービスクラス
エレキベア
エレキベア
アプリID、トークンを設定した設定ファイルをサービスに渡して実行するクマね 一般的なAPIリクエストクマ〜〜
マイケル
マイケル
これでAPIとの繋ぎ込みまで確認することができました。

XServerVPSへのデプロイ

マイケル
マイケル
最後におまけにはなりますが、作成したAPIをVPSで起動するまでの手順についても紹介します。

VPS構成

マイケル
マイケル
今回、VPSは下記構成のものを契約しました。 XServerはスケールアップ・ダウンが可能なため、もしスペックが足りなくなったら拡充する戦法です。
種類
概要
VPS
XServer VPS
CPU
3Core
メモリ
2GB
SSD
50GB
イメージタイプ
Ubuntu 22.04
エレキベア
エレキベア
費用も最低限に抑えたいクマからね〜〜

Docker環境構築

マイケル
マイケル
Docker環境の構築についてですが、イメージタイプはUbuntuを使用しているため 一般的なUbuntuのDocker環境構築手順と同様の手順になります。

Docker - Install using the apt repository

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
▲Docker環境のインストール
# Git Install
sudo apt-get install git-all

# Git Settings
git config --global user.name [name]
git config --global user.email [email]

# リポジトリのクローン
cd [workspace dir]
git clone [repository]

# Dockerの立ち上げ
cd [repository dir]
docker compose up -d
▲Gitのインストール
エレキベア
エレキベア
Ubuntu上にDockerを導入してリポジトリをクローンする流れクマね

ドメイン設定

マイケル
マイケル
次にドメインの設定についてです。 こちらはVPSのサーバ名を直接指定してもよいですが、今後の移行のしやすさも考えて独自ドメインを取得・設定しておくことにしました。
エレキベア
エレキベア
アプリから呼び出すとなると再提出も面倒クマからね・・・
ドメインサービス側の設定
マイケル
マイケル
まずはドメインの取得とVPSとの紐付けについてです。 今回は お名前com で取得したので、下記のようにドメイン側のネームサーバ設定にVPS指定のものを設定します。

お名前.com - 公式ページ

XServer VPS - ネームサーバについて

20240415_01_unity_ranking_api_04
▲VPSのネームサーバを設定

エレキベア
エレキベア
ドメインサーバ側で認識するための設定クマね
VPS側の設定
マイケル
マイケル
次にVPS側で独自ドメインの追加を行います。 デフォルトで設定されるNSレコードの他、Aレコード、CNAMEレコードを設定します。
ホスト名
種別
内容
[独自ドメイン]
A
[VPSのIPアドレス]
www.[独自ドメイン]
CNAME
[独自ドメイン]
20240415_01_unity_ranking_api_05
▲VPS側でNSレコード、Aレコードを設定する

マイケル
マイケル
この状態でVPS側のポートを解放してURLにアクセスすると Ubuntuのトップ画面が表示されるようになっているはずです。
20240415_01_unity_ranking_api_07
▲VSP側で必要なポートを空けておく

20240415_01_unity_ranking_api_08
▲この状態で独自ドメインにアクセスするとApacheの画面が表示される

エレキベア
エレキベア
これでドメインとの紐付けが完了したクマね
apacheの独自設定
マイケル
マイケル
基本的にはこれで使用できるようになったのですが、今回は費用を抑えるために一つのサーバ内でドメインやポートを振り分けれるようにします。 そのためApache設定でVirutualHostを使用して設定します。
エレキベア
エレキベア
メモリ容量は心配クマが、複数アプリ立ち上げることも可能にするクマね
マイケル
マイケル
UbuntuのApacheフォルダは/etc/apache2になります。 その中のapache2.confがルートになっていて、ここから各設定を読み込んでいます。
# Include list of ports to listen on
Include ports.conf

・・・中略・・・

# Include generic snippets of statements
IncludeOptional conf-enabled/*.conf

# Include the virtual host configurations:
IncludeOptional sites-enabled/*.conf
▲apache.conf内で各設定を読み込んでいる
マイケル
マイケル
デフォルトで用意されているsites-enabled/000-default.confをコピーして独自設定ファイル(今回はvhosts.conf)を作成し、下記のようにリバースプロキシでアクセスするようにします。
cd /etc/apache2/
sudo cp sites-enabled/000-default.conf sites-enabled/vhosts.conf
sudo vim sites-enabled/vhosts.conf
▲000-default.confをコピーしてvhosts.confを作成
<VirtualHost [独自ドメイン]:80>
	ServerName [独自ドメイン]
	DocumentRoot /var/www/[独自ドメイン]/public
	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

	ProxyPass        /api http://localhost:8080
	ProxyPassReverse /api http://localhost:8080
</VirtualHost>

▲8080ポートでの接続設定
エレキベア
エレキベア
独自ドメイン/apiでアクセスされた時に自身の8080ポートに接続しているクマね
マイケル
マイケル
あとはproxyモジュールをインストールしてApacheを再起動すれば完了です!
# proxyモジュールインストール
sudo a2enmod proxy_http

# サービス再起動
sudo systemctl restart apache2.service

# サービスの状態確認
sudo systemctl status apache2.service
▲proxyモジュールの導入
マイケル
マイケル
http:[独自ドメイン]/apiのURLでアクセスすると、localhostの時と同様にAPIアクセスできるようになっていると思います。
エレキベア
エレキベア
HTTP経由でのアクセスはこれでクリアクマね

SSL設定

マイケル
マイケル
最後に、HTTPS経由でアクセスできるよう証明書とApacheの設定を行います。
証明書のインストール
マイケル
マイケル
VPS上での証明書インストール手順については公式がまとめてくれています。 今回はpython3-certbot-apacheを使用して証明書を作成しcronで更新処理を設定しました。

XServer VPS - 証明書のインストール手順

# certbotのインストール
apt update -y
apt install certbot python3-certbot-apache

# ドメインの設定
certbot --apache -d [独自ドメイン]

# 作成された証明書の確認
 ls /etc/letsencrypt/live
▲certbotのインストール
# nanoエディタでcron処理を開く
nano /etc/cron.d/letsencrypt-renew

# 下記内容を記述(毎月1日に更新するcron処理)
0 0 1 * * root certbot renew --pre-hook "service apache2 stop" --post-hook "service apache2 start"
▲証明書更新のcron設定
エレキベア
エレキベア
証明書は一ヶ月で切れるクマから定期的な更新処理が必要クマね
apacheの設定
マイケル
マイケル
次にApache側の設定も変更します。 下記サイトを参考にさせていただきながら設定しました。

Apacheのリバースプロキシをhttps化する手順について - Rainbow Engine

# rewrite, ssl の有効化
a2enmod rewrite
a2enmod ssl
▲有効にした時点でvhosts.confを元にvhosts-le-ssl.confが生成される
<IfModule mod_ssl.c>
<VirtualHost [独自ドメイン]:443>
	ServerName [独自ドメイン]
	DocumentRoot /var/www/[独自ドメイン]/public
	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

	ProxyPass        /api http://localhost:8080
	ProxyPassReverse /api http://localhost:8080

	# SSL settings
	SSLEngine On
	SSLProxyEngine On
	SSLCertificateFile     /etc/letsencrypt/live/[独自ドメイン]/fullchain.pem
	SSLCertificateKeyFile  /etc/letsencrypt/live/[独自ドメイン]/privkey.pem

	Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

▲HTTPS接続設定
エレキベア
エレキベア
作成した証明書を指定するクマね
マイケル
マイケル
今回はBasic認証を使用してるので、HTTP経由でのアクセスはできないように対応します。 最初に作成したvhosts.confの内容はコメントアウトしましょう。
マイケル
マイケル
最後に確認として、HTTPS経由でのみアクセスできることを確認できれば、設定は完了です!!
エレキベア
エレキベア
長かったクマ〜〜〜

おわりに

マイケル
マイケル
というわけで今回はランキングAPIの作成とVPSへのデプロイについてでした! どうだったかな??
エレキベア
エレキベア
サーバ構築は難しそうなイメージだったクマが、 これくらいなら個人でも全然立てれるクマね
マイケル
マイケル
自分だけのサーバ環境が出来たからこれからいろいろ遊べそうだね! 今回は用途が限られていたので簡易的な仕組みで構築しましたが、がっつりサーバエンジニアを目指したい方は認証周りももう少し踏み込んで挑戦してみてください!
マイケル
マイケル
それでは今日はこの辺で! アデューー!!
エレキベア
エレキベア
クマ〜〜〜〜〜

【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】〜完〜


Unityサーバサイド関連GoNCMBVPS
2024-04-14

関連記事
【Unity】Timeline × Excelでスライドショーを効率よく制作する
2024-10-31
【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
2024-05-28
【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
2024-03-30
【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
2024-03-30
【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
2024-03-28
【Unity】第一回 CRI ADXを使用したサウンド制御 〜基本動作、周波数解析編〜
2024-03-28
【Unity】サウンドミドルウェアに依存しない設計を考える【CRI ADX・Wwise】
2024-03-27
【Go言語】Gin、GORMでシンプルなREST APIを作成する【CRUD】
2024-03-26