개발
gRPC 초미니 개발 (feat.python) - Gateway, Board, Auth
journalctl
2025. 1. 12. 15:59
개요

gRPC-Python 개발
gRPC-Gateway
Gateway 서버로 예전에 사용해봤던 Flask로 개발 당첨! 많은 블로그를 참고해서 만들어보자.. + chatGPT
# gateway.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import grpc
import auth_pb2
import auth_pb2_grpc
import board_pb2
import board_pb2_grpc
app = Flask(__name__)
CORS(app)
def get_auth_grpc_server():
channel = grpc.insecure_channel('localhost:8082')
auth_grpc_server = auth_pb2_grpc.AuthServiceStub(channel)
return auth_grpc_server
def get_board_grpc_server():
channel = grpc.insecure_channel('localhost:8081')
board_grpc_server = board_pb2_grpc.BoardServiceStub(channel)
return board_grpc_server
@app.route('/v1/register', methods=['POST'])
def register():
data = request.get_json()
auth_grpc_server = get_auth_grpc_server()
grpc_request = auth_pb2.RegisterRequest(username=data['username'], password=data['password'])
response = auth_grpc_server.Register(grpc_request)
return jsonify(success=response.success, message=response.message)
@app.route('/v1/login', methods=['POST'])
def login():
data = request.get_json()
auth_grpc_server = get_auth_grpc_server()
grpc_request = auth_pb2.LoginRequest(username=data['username'], password=data['password'])
response = auth_grpc_server.Login(grpc_request)
return jsonify(success=response.success, token=response.token, message=response.message)
@app.route('/v1/posts', methods=['POST'])
def create_post():
data = request.get_json()
board_grpc_server = get_board_grpc_server()
grpc_request = board_pb2.PostRequest(title=data['title'], content=data['content'], token=data['token'])
response = board_grpc_server.CreatePost(grpc_request)
return jsonify(success=response.success, message=response.message)
@app.route('/v1/posts', methods=['GET'])
def get_posts():
board_grpc_server = get_board_grpc_server()
grpc_request = board_pb2.Empty()
response = board_grpc_server.GetPosts(grpc_request)
posts = [{"title": post.title, "content": post.content, "username": post.username} for post in response.posts]
return jsonify(posts=posts)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080, debug=True)
- insecure_channel 함수를 사용하면 인증을 무시하고 사용할 수 있다고 한다. 물론 개발 환경에서만 사용하도록 하자!
- 분명 Chrome에서 cross-origin 에러를 표출할게 다분하므로 CORS를 무시할 수 있도록 설정하자.
- gRPC에서 request, response 메시지를 사용할때는 auth_pb2, board_pb2를 사용해서 주고받으면 된다.
- gateway는 auth-server, board-server의 client이므로 AuthServiceStub, BoardServiceStub 함수를 사용하기.
gRPC-BoardService
# board_service.py
import grpc
from concurrent import futures
import board_pb2
import board_pb2_grpc
import auth_pb2
import auth_pb2_grpc
# 들어오는 Post 담는 배열
posts = []
class BoardService(board_pb2_grpc.BoardServiceServicer):
def __init__(self):
self.auth_channel = grpc.insecure_channel('localhost:8082')
self.auth_grpc_server = auth_pb2_grpc.AuthServiceStub(self.auth_channel)
def CreatePost(self, request, context):
verify_request = auth_pb2.VerifyTokenRequest(token=request.token)
verify_response = self.auth_grpc_server.VerifyToken(verify_request)
if not verify_response.valid:
return board_pb2.PostResponse(success=False, message=verify_response.message)
post = {"title": request.title, "content": request.content, "username": verify_response.username}
posts.append(post)
return board_pb2.PostResponse(success=True, message="게시글 생성 완료!")
def GetPosts(self, request, context):
response = board_pb2.PostsResponse()
for post in posts:
response.posts.add(title=post['title'], content=post['content'], username=post['username'])
return response
def serve_board():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
board_pb2_grpc.add_BoardServiceServicer_to_server(BoardService(), server)
server.add_insecure_port('[::]:8081')
server.start()
print("gRPC-BoardService 실행")
server.wait_for_termination()
if __name__ == "__main__":
serve_board()
- 게시글을 생성할때 사용자 인증 단계를 추가했다. gRPC 채널을 생성하고, AuthServiceSutb 함수를 사용해서 AuthService 함수를 호출할 수 있도록 만들기.
- BoardService에서 CreatePost, GetPosts 함수를 구현하기. CreatePost는 사용자 인증 로직을 추가하기.
- add_BoardServiceServicer_to_server 함수 사용해서 gRPC 서버 열기.
- 8081 포트로 열기.
gRPC-AuthService
# auth_service.py
import grpc
from concurrent import futures
import auth_pb2
import auth_pb2_grpc
from auth import encode_auth_token, decode_auth_token
# 유저 담기
users = {}
class AuthService(auth_pb2_grpc.AuthServiceServicer):
def Register(self, request, context):
if request.username in users:
return auth_pb2.AuthResponse(success=False, message="중복된 사용자입니다.")
users[request.username] = request.password
return auth_pb2.AuthResponse(success=True, message="사용자 생성 성공!")
def Login(self, request, context):
if users.get(request.username) == request.password:
token = encode_auth_token(request.username)
return auth_pb2.AuthResponse(success=True, token=token, message="로그인 성공")
else:
return auth_pb2.AuthResponse(success=False, message="아이디 혹은 비밀번호를 확인하세용")
def VerifyToken(self, request, context):
username = decode_auth_token(request.token)
if isinstance(username, str) and "Invalid" in username:
return auth_pb2.VerifyTokenResponse(valid=False, message="만료된 토큰이거나 잘못된 토큰입니다.")
return auth_pb2.VerifyTokenResponse(valid=True, username=username, message="토큰 확인")
def serve_auth():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
auth_pb2_grpc.add_AuthServiceServicer_to_server(AuthService(), server)
server.add_insecure_port('[::]:8082')
server.start()
print("gRPC-AuthService 실행")
server.wait_for_termination()
if __name__ == "__main__":
serve_auth()
- AuthService는 BoardService의 기능을 사용하지 않으므로 BoardServiceStub은 필요없다.
- BoardService와 비슷하게 개발하면 될 것 같다.
- 8082 포트로 열기.
결과
gateway, board, auth 서버 3개를 실행시키고 정상적으로 동작하는지 확인하기!
# 각 터미널에서 실행하기
$ python3 gateway.py
$ python3 auth_service.py
$ python3 board_service.py
- 게시글 확인하기
$ curl localhost:8080/v1/posts -XGET

- 사용자 생성하기
$ curl localhost:8080/v1/register -XPOST -H 'Content-Type: application/json' -d "{\"username\":\"journalctl\",\"password\":\"aaaabbbb\"}"

- 사용자 로그인하기
$ curl localhost:8080/v1/login -XPOST -H 'Content-Type: application/json' -d "{\"username\":\"journalctl\",\"password\":\"aaaabbbb\"}"

후기

간단한 로직이라서 금방 개발할 줄 알았는데 gRPC를 잘 모르니까 꽤 어려웠다… 머리속에 고정이 안되니까 며칠동안 헤맨 느낌이다 ㅠㅠ
원하는대로 REST API ↔ gRPC가 되서 뿌듯하다.