使用Flask+gRPC+betterproto遇socket.gaierror错误的解决方法
问题描述
我正在用gRPC构建微服务,采用betterproto替代grpcio-tools从.proto文件生成代码。当用Flask作为客户端调用gRPC服务时,触发了socket.gaierror: [Errno 8] nodename nor servname provided, or not known错误。环境为MacOS、Python 3.9.12、预发布版protobuf,相关代码文件如下:
db.proto
syntax = "proto3"; package db; import "google/protobuf/timestamp.proto"; message Box { string name = 1; int32 id = 2; int32 price = 3; string description = 4; string category = 5; int32 quantity = 6; google.protobuf.Timestamp created_at = 7; } enum RequestStatus { OK = 0; ERROR = 1; } message GetAllBoxesRequest { } message GetBoxesResponse { repeated Box box = 1; RequestStatus status = 2; } service DatabaseService { rpc GetBoxes(GetAllBoxesRequest) returns (GetBoxesResponse) {} }
db/init.py
# Generated by the protocol buffer compiler. DO NOT EDIT! # sources: db.proto # plugin: python-betterproto from dataclasses import dataclass from datetime import datetime from typing import Dict, List import betterproto from betterproto.grpc.grpclib_server import ServiceBase import grpclib class RequestStatus(betterproto.Enum): OK = 0 ERROR = 1 @dataclass(eq=False, repr=False) class Box(betterproto.Message): name: str = betterproto.string_field(1) id: int = betterproto.int32_field(2) price: int = betterproto.int32_field(3) description: str = betterproto.string_field(4) category: str = betterproto.string_field(5) quantity: int = betterproto.int32_field(6) created_at: datetime = betterproto.message_field(7) @dataclass(eq=False, repr=False) class GetAllBoxesRequest(betterproto.Message): pass @dataclass(eq=False, repr=False) class GetBoxesResponse(betterproto.Message): box: List["Box"] = betterproto.message_field(1) status: "RequestStatus" = betterproto.enum_field(2) class DatabaseServiceStub(betterproto.ServiceStub): async def get_boxes(self) -> "GetBoxesResponse": request = GetAllBoxesRequest() return await self._unary_unary( "/db.DatabaseService/GetBoxes", request, GetBoxesResponse ) class DatabaseServiceBase(ServiceBase): async def get_boxes(self) -> "GetBoxesResponse": raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) async def __rpc_get_boxes(self, stream: grpclib.server.Stream) -> None: request = await stream.recv_message() request_kwargs = {} response = await self.get_boxes(**request_kwargs) await stream.send_message(response) def __mapping__(self) -> Dict[str, grpclib.const.Handler]: return { "/db.DatabaseService/GetBoxes": grpclib.const.Handler( self.__rpc_get_boxes, grpclib.const.Cardinality.UNARY_UNARY, GetAllBoxesRequest, GetBoxesResponse, ), }
server.py
import asyncio import logging from db import ( DatabaseServiceBase, Box, RequestStatus, GetBoxesResponse, ) from grpclib.server import Server from pymongo import MongoClient log = logging.getLogger(__name__) def get_database(): CONNECTION_STRING = f"mongodb://{DB_USERNAME}:{DB_USER_PASSWORD}@{DB_HOST}:{DB_PORT}" # Create a connection using MongoClient client = MongoClient(CONNECTION_STRING) # Create the database and return it return client.boxes boxes_db = get_database() def dict_to_box(data): if "_id" in data: data["id"] = data.pop("_id") return Box(**data) class DatabaseService(DatabaseServiceBase): async def get_boxes(self) -> "GetBoxesResponse": boxes = boxes_db.boxes.find() list_of_boxes = [dict_to_box(box) for box in boxes] return GetBoxesResponse(box=list_of_boxes, status=RequestStatus.OK) async def main(): server = Server([DatabaseService()]) await server.start("127.0.0.1", 50051) await server.wait_closed() if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main())
app.py(Flask客户端)
import os import asyncio from flask import Flask, render_template from grpclib.client import Channel import db app = Flask(__name__) SERVICE_HOST = os.getenv("SERVICE_HOST", "127.0.0.1") SERVICE_PORT = os.getenv("SERVICE_PORT", 50051) @app.route("/") async def render_homepage(): service_channel = Channel(f"{SERVICE_HOST}:{SERVICE_PORT}") service = db.DatabaseServiceStub(service_channel) get_boxes_response = await service.get_boxes() # <-- Error is throwing here service_channel.close() return render_template( "index.html", boxes=get_boxes_response.box, ) if __name__ == '__main__': app.run(debug=True)
解决方案
核心问题修复
这个错误是因为grpclib的Channel构造参数使用错误导致的。你当前把host:port拼接成单个字符串传入Channel,但grpclib要求分别传入host和port两个独立参数,而非拼接字符串。
修改app.py中的Channel初始化代码:
# 错误写法 service_channel = Channel(f"{SERVICE_HOST}:{SERVICE_PORT}") # 正确写法 service_channel = Channel(SERVICE_HOST, SERVICE_PORT)
额外注意事项
- Flask异步路由兼容性:Flask 2.0+支持异步路由,但默认运行环境是同步的,建议安装
flask-async扩展或确保运行环境配置正确,避免异步代码执行异常。 - 服务端环境变量缺失:
server.py中使用的DB_USERNAME、DB_USER_PASSWORD、DB_HOST、DB_PORT未定义,需要提前通过环境变量设置,或直接硬编码测试值,否则服务启动会报错。
内容的提问来源于stack exchange,提问作者Kholdarbekov
相关产品推荐
相关产品推荐

