patanopanda commited on
Commit
ec73f44
·
verified ·
1 Parent(s): d85ec18

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -0
app.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import grpc
3
+ from concurrent import futures
4
+ import time
5
+ import subprocess
6
+ import sys
7
+ import os
8
+
9
+ # ==========================================
10
+ # 1. 準備フェーズ: Protoファイルの自動コンパイル
11
+ # ==========================================
12
+ # 通常はビルド時に行いますが、講義用としてコード内で明示的に実行しています。
13
+ def compile_proto():
14
+ # protocコマンドをPythonから呼び出し
15
+ cmd = [
16
+ sys.executable, "-m", "grpc_tools.protoc",
17
+ "-I.", "--python_out=.", "--grpc_python_out=.",
18
+ "greet.proto"
19
+ ]
20
+ subprocess.run(cmd, check=True)
21
+
22
+ # 初回実行時やファイルがない場合にコンパイル
23
+ if not os.path.exists("greet_pb2.py") or not os.path.exists("greet_pb2_grpc.py"):
24
+ compile_proto()
25
+
26
+ # コンパイル生成物をインポート
27
+ import greet_pb2
28
+ import greet_pb2_grpc
29
+
30
+
31
+ # ==========================================
32
+ # 2. サーバーサイド: gRPC Serverの実装
33
+ # ==========================================
34
+ class Greeter(greet_pb2_grpc.GreeterServicer):
35
+ def SayHello(self, request, context):
36
+ """
37
+ クライアントから呼ばれる関数。
38
+ request.name に送信された名前が入っています。
39
+ """
40
+ # 処理時間をシミュレーション(少し待たせる)
41
+ time.sleep(0.5)
42
+
43
+ # レスポンスを作成して返す
44
+ msg = f"こんにちは、{request.name}さん! (from gRPC Server)"
45
+ return greet_pb2.HelloReply(message=msg)
46
+
47
+ def start_server():
48
+ """gRPCサーバーをバックグラウンドで起動"""
49
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
50
+ greet_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
51
+
52
+ # コンテナ内部のポート 50051 で待ち受け
53
+ server.add_insecure_port('[::]:50051')
54
+ server.start()
55
+ return server
56
+
57
+
58
+ # ==========================================
59
+ # 3. クライアントサイド: Streamlit UI
60
+ # ==========================================
61
+ st.set_page_config(page_title="gRPC Demo", page_icon="🔗")
62
+
63
+ st.title("gRPC Hands-on Lab 🔗")
64
+ st.markdown("Dockerコンテナの中で、**Client (UI)** と **Server (Python)** がgRPCで会話しています。")
65
+
66
+ # --- サーバーの起動管理 ---
67
+ # Streamlitは再描画のたびにコードが走るため、サーバーが二重起動しないように管理
68
+ if 'server_active' not in st.session_state:
69
+ server = start_server()
70
+ st.session_state['server_active'] = True
71
+ st.toast("gRPC Server started on port 50051", icon="🚀")
72
+
73
+ # --- UI部分 ---
74
+ col1, col2 = st.columns(2)
75
+
76
+ with col1:
77
+ st.subheader("Client (Request)")
78
+ name_input = st.text_input("名前を入力してください", "Student")
79
+ send_btn = st.button("gRPCリクエスト送信", type="primary")
80
+
81
+ # --- 通信処理 ---
82
+ if send_btn:
83
+ with st.spinner("gRPCサーバーと通信中..."):
84
+ try:
85
+ # 1. チャンネル開設 (localhost:50051 へ接続)
86
+ # ※Docker内なのでlocalhostで繋がります
87
+ with grpc.insecure_channel('localhost:50051') as channel:
88
+ stub = greet_pb2_grpc.GreeterStub(channel)
89
+
90
+ # 2. リクエスト作成 (Protoで定義した型を使う)
91
+ req = greet_pb2.HelloRequest(name=name_input)
92
+
93
+ # 3. リモート呼び出し (RPC)
94
+ start_ts = time.time()
95
+ response = stub.SayHello(req)
96
+ end_ts = time.time()
97
+
98
+ # 4. 結果表示
99
+ with col2:
100
+ st.subheader("Server (Response)")
101
+ st.success(response.message)
102
+
103
+ # 通信の詳細データ(学習用)
104
+ st.info(f"""
105
+ **Technical Metrics:**
106
+ - Protocol: HTTP/2 (gRPC)
107
+ - Payload Type: Protocol Buffers (Binary)
108
+ - Latency: {(end_ts - start_ts)*1000:.2f} ms
109
+ - Req Size: {req.ByteSize()} bytes
110
+ - Res Size: {response.ByteSize()} bytes
111
+ """)
112
+
113
+ except grpc.RpcError as e:
114
+ st.error(f"通信エラーが発生しました: {e}")
115
+
116
+ st.divider()
117
+
118
+ # --- 解説エリア ---
119
+ with st.expander("📝 アーキテクチャ解説(ここをクリック)"):
120
+ st.markdown("""
121
+ このデモアプリの内部構造です。
122
+
123
+ 1. **Frontend (今見ている画面)**
124
+ - Streamlitが動いています。
125
+ - ユーザーの入力を受け取り、`HelloRequest`というデータ型に変換します。
126
+
127
+ 2. **Network (内部通信)**
128
+ - `localhost:50051` に対してリクエストを送ります。
129
+ - データはJSONではなく、バイナリ形式で飛んでいます。
130
+
131
+ 3. **Backend (gRPC Server)**
132
+ - 同じコンテナの裏側でPythonサーバーが動いています。
133
+ - `SayHello` 関数を実行し、結果を返します。
134
+ """)