Spaces:
Sleeping
Sleeping
| 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` 関数を実行し、結果を返します。 | |
| """) |