LoginSignup
0
1

More than 1 year has passed since last update.

"localhost"をやめて...というお話...

Last updated at Posted at 2022-01-29

今すぐ、"localhost"をやめよう!

localhostは時にとって、開発時の妨げになる。
これはサンプルコードで、例を投稿したり、マニュアルなどで使われたりする付属する厄介者のお話だ。

例えば、ここにはあるサンプルコードを使って簡易的なサーバーを立てて欲しい。
参考例: WebSockets - by.FastAPI
実行時は、uvicorn main:app --host 0.0.0.0 --reloadとしてサーバーを起動してみよう。

from typing import List

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <h2>Your ID: <span id="ws-id"></span></h2>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var client_id = Date.now()
            document.querySelector("#ws-id").textContent = client_id;
            var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""


class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)


manager = ConnectionManager()


@app.get("/")
async def get():
    return HTMLResponse(html)


@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.send_personal_message(f"You wrote: {data}", websocket)
            await manager.broadcast(f"Client #{client_id} says: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"Client #{client_id} left the chat")

そして、localhost:8000へ接続した際、
もちろん開発用のPCで起動すれば、特に問題はない。
だがしかし、開発PC&検証用PCが別々になっている場合、ローカルPCではないPCは同一ネットワーク上でも接続ができなくなってしまうのだ。

これは、開発あるあるだと思うのだが、「macOS or Linux」で開発する場合、「Windows」の存在でも確認が必要となることがある。現代において、マルチプラットフォームに対応して当たり前のような時代だ。

開発を通して、これは未確認でしたなどあってはならないだろう。
ただ、問題はこの先にあるのだ。

...検証用PCで接続してもお繋がらない。では、外部公開して、検証するのもおかしな話だ。
それにわざわざ開発初動で、インフラを構築し、NginxやApacheなどを建てて、リバースプロキシする必要はあるのだろうか?
そこまでする必要はないのでは?

そんな時、localhostは不便である。
では、どうすればいいのか?

基本的に、localhostは、あくまでもローカル内での動作それにサーバーを建てても初期値は大抵、localhostで設定されている。その場合、-host 0.0.0.0などして、ホストを外部公開状態にする必要がある。

次のコードで、今度はuvicorn main:app --host 0.0.0.0 --reloadで実行してみよう。

from typing import List

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <h2>Your ID: <span id="ws-id"></span></h2>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var client_id = Date.now()
            document.querySelector("#ws-id").textContent = client_id;
            var ws = new WebSocket(`ws://${location.host}/ws/${client_id}`);
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.get("/")
async def get():
    return HTMLResponse(html)

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.send_personal_message(f"You wrote: {data}", websocket)
            await manager.broadcast(f"Client #{client_id} says: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"Client #{client_id} left the chat")

これで、幸せになれましたw
フロントで、localhostを使う場合、location.hostとすることで、ポートも合わせて、アクセスが可能となる。
そしてもし今後、サンプル公開する際は、localhostではなく、location.hostを使って公開しよう!!という話でした。

以上。
(つまらないお話で申し訳ない)

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1