개발

gRPC 초미니 개발 (feat.python) - Gateway, Board, Auth

journalctl 2025. 1. 12. 15:59

개요

오늘은 친숙한 Python 이라서 신난다.

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
  1. 게시글 확인하기
$ curl localhost:8080/v1/posts -XGET

문제 없이 성공했다?

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

사용자 생성 성공! 근데 한글이라서 유니코드 형태로 응답받는다.

 

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

왜 토큰이 반환이 안되지... 그래도 성공 메시지는 전달받았다!


후기

코드 짜는데 너무 오래걸려서 GPT에게도 물어봤지만 실패했을때 모습..

 

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

원하는대로 REST API ↔ gRPC가 되서 뿌듯하다.

댓글수2