proto / app.py
patanopanda's picture
Create app.py
ec73f44 verified
import streamlit as st
import grpc
from concurrent import futures
import time
import subprocess
import sys
import os
# ==========================================
# 1. 準備フェーズ: Protoファイルの自動コンパイル
# ==========================================
# 通常はビルド時に行いますが、講義用としてコード内で明示的に実行しています。
def compile_proto():
# protocコマンドをPythonから呼び出し
cmd = [
sys.executable, "-m", "grpc_tools.protoc",
"-I.", "--python_out=.", "--grpc_python_out=.",
"greet.proto"
]
subprocess.run(cmd, check=True)
# 初回実行時やファイルがない場合にコンパイル
if not os.path.exists("greet_pb2.py") or not os.path.exists("greet_pb2_grpc.py"):
compile_proto()
# コンパイル生成物をインポート
import greet_pb2
import greet_pb2_grpc
# ==========================================
# 2. サーバーサイド: gRPC Serverの実装
# ==========================================
class Greeter(greet_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
"""
クライアントから呼ばれる関数。
request.name に送信された名前が入っています。
"""
# 処理時間をシミュレーション(少し待たせる)
time.sleep(0.5)
# レスポンスを作成して返す
msg = f"こんにちは、{request.name}さん! (from gRPC Server)"
return greet_pb2.HelloReply(message=msg)
def start_server():
"""gRPCサーバーをバックグラウンドで起動"""
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
greet_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
# コンテナ内部のポート 50051 で待ち受け
server.add_insecure_port('[::]:50051')
server.start()
return server
# ==========================================
# 3. クライアントサイド: Streamlit UI
# ==========================================
st.set_page_config(page_title="gRPC Demo", page_icon="🔗")
st.title("gRPC Hands-on Lab 🔗")
st.markdown("Dockerコンテナの中で、**Client (UI)** と **Server (Python)** がgRPCで会話しています。")
# --- サーバーの起動管理 ---
# Streamlitは再描画のたびにコードが走るため、サーバーが二重起動しないように管理
if 'server_active' not in st.session_state:
server = start_server()
st.session_state['server_active'] = True
st.toast("gRPC Server started on port 50051", icon="🚀")
# --- UI部分 ---
col1, col2 = st.columns(2)
with col1:
st.subheader("Client (Request)")
name_input = st.text_input("名前を入力してください", "Student")
send_btn = st.button("gRPCリクエスト送信", type="primary")
# --- 通信処理 ---
if send_btn:
with st.spinner("gRPCサーバーと通信中..."):
try:
# 1. チャンネル開設 (localhost:50051 へ接続)
# ※Docker内なのでlocalhostで繋がります
with grpc.insecure_channel('localhost:50051') as channel:
stub = greet_pb2_grpc.GreeterStub(channel)
# 2. リクエスト作成 (Protoで定義した型を使う)
req = greet_pb2.HelloRequest(name=name_input)
# 3. リモート呼び出し (RPC)
start_ts = time.time()
response = stub.SayHello(req)
end_ts = time.time()
# 4. 結果表示
with col2:
st.subheader("Server (Response)")
st.success(response.message)
# 通信の詳細データ(学習用)
st.info(f"""
**Technical Metrics:**
- Protocol: HTTP/2 (gRPC)
- Payload Type: Protocol Buffers (Binary)
- Latency: {(end_ts - start_ts)*1000:.2f} ms
- Req Size: {req.ByteSize()} bytes
- Res Size: {response.ByteSize()} bytes
""")
except grpc.RpcError as e:
st.error(f"通信エラーが発生しました: {e}")
st.divider()
# --- 解説エリア ---
with st.expander("📝 アーキテクチャ解説(ここをクリック)"):
st.markdown("""
このデモアプリの内部構造です。
1. **Frontend (今見ている画面)**
- Streamlitが動いています。
- ユーザーの入力を受け取り、`HelloRequest`というデータ型に変換します。
2. **Network (内部通信)**
- `localhost:50051` に対してリクエストを送ります。
- データはJSONではなく、バイナリ形式で飛んでいます。
3. **Backend (gRPC Server)**
- 同じコンテナの裏側でPythonサーバーが動いています。
- `SayHello` 関数を実行し、結果を返します。
""")