Docker-ComposeでGodot用のサーバーを立てようとした話（接続編）

これの続きでございます

https://qiita.com/typegarnet/items/5025ed18e42ad1d0d587

# Godotの資材をアップロードすれば行けるっしょ？
とりあえずPythonでWebSocketのバックエンドをコーディングをして、GodotのWebSocketPeerとの接続が成功すればあとはHTTPSのHTML置き場にアップロードすればチョチョイのパーっしょ。
そう思っていた時期が私にもありました。

# とりあえずバックエンド
#### Python3でWebSocketのサーバーサイドのエコーバックを作る。
ポート番号に深い意味はない。
```python
# coding: utf-8

import asyncio
import websockets
class WebSocketServer:
    def __init__(self, host, port):
        self.host = host
        self.port = port

    async def server(self, ws, path):
        try:
            async for msg in ws:
                print(f"msg from client: {msg}")
                print(f"Got your messaget: {msg}")
                await ws.send(msg)
        except Exception as e:
            print(e)

    async def start(self):
        async with websockets.serve(self.server, self.host, self.port):
            print(f"Server started : {self.host}:{self.port}")
            await asyncio.Future()

    
if __name__ == "__main__":
    print('WebSocketServer start')
    server = WebSocketServer("0.0.0.0", 4545)
    asyncio.run(server.start())
```
ローカル上で実行
繋がりますね！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3255598/e4ef4482-d0cf-2df8-e0a2-f488adbc7247.png)

Godotのプロジェクトをブラウザで実行
繋がりますね！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3255598/cc57da90-a330-10ec-b94f-a596ca5cabf5.png)


#### いざサーバーへ
ここまでは順調に事が運んだ。
だがしかし、Godotの資材をHTTPSに配置し実行してもうんともすんとも言わないのである。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3255598/710d59c8-6ccb-9655-ffe6-154665033c0f.png)

というわけで、Godotの有識者が集まるDiscordサーバへお邪魔しスレ立てを行いました。
するとこのような回答が。

▼ お返事
> 「nginx上にシンプルなwebsocket実装のjsファイルを置いて、そこからpython側に通信できるかは試してみましたか?」

そうじゃん！
バケモンにはバケモンをぶつけるのは映画だけ、まずはシンプルな状態でアプローチをかけて問題の切り分けを行うのが鉄則でございます。
Godotもふたを開ければjsファイル吐き出しているっぽいし、簡単なNodeJSで作ってぶつけちゃおう。

#### 使用したテストコード
```javascript
const WebSocket = require('ws');

const ws = new WebSocket('ws://192.168.3.251:4545');

ws.on('open', function open() {
  console.log('Connected to server');
  ws.send('Hello, server!');
});

ws.on('message', function incoming(message) {
  console.log('Received: %s', message);
});

```
結果：NG

ってことはサーバー側があかんのとちゃいます？
サーバ側をPythonからNodeJSでやってみる
```javascript
const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');

const options = {
  key: fs.readFileSync('/etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/privkey.pem'),
  cert: fs.readFileSync('/etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/cert.pem')
};

const server = https.createServer(options);
const wss = new WebSocket.Server({ server });

wss.on('connection', function connection(ws) {
  console.log('Client connected');
  
  ws.on('message', function incoming(message) {
    console.log('Received: %s', message);
  });
  
  ws.send('Hello, client!');
});

server.listen(4545, '0.0.0.0', function() {
  console.log('Server is listening on port 4545');
});
```
なんと結果はOKだったんですね。
スクリーンショットとっておけばよかったと後悔

<font size="6">その時梨に電流走る
(あれ？フォントタグ使ってもフォントのサイズ変わらないの！？)</font>

NodeJSでバックエンド作ってしまえば通信が確立するのでは？

とりあえずGodotのプロジェクトをHTML5形式でエクスポートしアップロード
何やら繋がっている模様
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3255598/e3bc1aae-6503-5c22-a680-33526ce14b34.png)
動くやん・・・
そういえばPythonでSSL証明書読み込むやつ書いたっけ？
```python
import ssl
import pathlib
import asyncio
import websockets

# SSL証明書と秘密鍵のパス
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain('/etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/cert.pem', '/etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/privkey.pem')

# 接続時の処理
async def handle_connection(websocket, path):
    print('Client connected')

    try:
        # メッセージ受信待機
        async for message in websocket:
            print('Received:', message)

            # クライアントにメッセージを返信
            await websocket.send('Server received: ' + message)

    except websockets.exceptions.ConnectionClosedError:
        pass

    finally:
        print('Client disconnected')

# WebSocketサーバーを作成し、SSLで接続を待機
start_server = websockets.serve(
    handle_connection, '0.0.0.0', 4545, ssl=ssl_context
)

# サーバーを起動
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
```
結果NG（スクリーンショット撮ってなかったなぁ。。。）

とりあえず現状動いているNodeJSをバックエンドの言語に採用することにしました。
Pythonファンの方ごめんなさい！！（Pythonでやりたかったなぁ・・・誰か有識者の方教えてください！）

# 設定ファイルの整理
NodeJSで作るサーバもどうせならDocker-Composeにまとめる。
Pythonでサーバを作ることはあきらめてないけど、調査は打ち切りで未来の君たちに託す！
とりあえず自分の環境ではこれで動いたというものを置いておく

docker-compose.yml
```dockerfile
version: '3'
services:
  nginx:
    image: nginx:latest
    ports:
      - "1010:1010"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl.conf:/etc/nginx/conf.d/ssl.conf
      - ./html:/usr/share/nginx/html
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - nodeapp  # nginxがnodeappに依存していることを指定
    restart: always 
  nodeapp:
    image: node:latest
    working_dir: /usr/src/app
    ports:
      - "4545:4545"
    volumes:
      - /etc/letsencrypt:/etc/letsencrypt:ro
      - ./server:/usr/src/app
    command: node server.js  # アプリケーションのエントリーポイントを指定するコマンド
    restart: always 
networks:
  default:
    driver: bridge
```

nginx.conf
```conf
events {}
http {
    include /etc/nginx/conf.d/*.conf;
}
```

ssl.conf
```conf
server {
    listen 1010 ssl;
    server_name [NoIPあたりで取得したドメイン];

    ssl_certificate /etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/cert.pem;
    ssl_certificate_key /etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    # Cross-Origin-Embedder-Policyヘッダーを設定
    add_header Cross-Origin-Embedder-Policy "require-corp";

    # Cross-Origin-Opener-Policyヘッダーを設定
    add_header Cross-Origin-Opener-Policy "same-origin";
    
    include       mime.types;
    default_type  application/octet-stream;
    
    types {
        text/javascript js;
        application/javascript js;
    }
    
    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}
```

server.js
```javascript
const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');

const options = {
  key: fs.readFileSync('/etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/privkey.pem'),
  cert: fs.readFileSync('/etc/letsencrypt/live/[NoIPあたりで取得したドメイン]/cert.pem')
};

const server = https.createServer(options);
const wss = new WebSocket.Server({ server });

wss.on('connection', function connection(ws) {
  console.log('Client connected');
  
  ws.on('message', function incoming(message) {
    console.log('Received: %s', message);
  });
  
  ws.send('Hello, client!');
});

server.listen(4545, '0.0.0.0', function() {
  console.log('Server is listening on port 4545');
});
```

ディレクトリ構成
```
root/
┣docker-compose.yml
┣nginx.conf
┣ssl.conf
┣server/
┃┗server.js
┗html/
 ┗Godotがエクスポートした資材を丸ごと配置
```

# 謎はまだまだ！調べないといけないこと
じつはほかにも気になっていることがあって、これは仕様なのかどうなのかがわからないんでござ。
今後GodotのDiscordサーバーにスレ立てするかもしれません。
1. ブラウザ上に表示されたGodotの画面から別のタブを新しく開き、新しいタブをアクティブ状態にするとWebSocket通信を停止してしまう
1. そもそもこのエラーや警告は何？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3255598/76e5cb67-751e-e647-9669-b772691fdab9.png)