Implementing a Server
สามขั้นตอน เหมือนเดิมทุกครั้ง
หัวข้อที่มีชื่อว่า “สามขั้นตอน เหมือนเดิมทุกครั้ง”codegen ให้ server interface ที่มี method หนึ่งตัวต่อ rpc มาแล้ว การ implement server มี 3 ขั้นตอนเหมือนเดิมเสมอ:
- Implement handler — เขียน body ของ
GetUser: รับ request คืน response (หรือ error) - Register implementation ของคุณเข้ากับ gRPC server object
- Serve — bind กับ port แล้วเริ่มรับ HTTP/2 connection
flowchart LR client["client stub"] -->|GetUserRequest| server["gRPC server (listening on :50051)"] server -->|dispatch by method| handler["your GetUser handler"] handler -->|User| server server -->|response| client
gRPC server จัดการเรื่อง HTTP/2, การ decode protobuf และการ dispatch method ให้ทั้งหมด handler ของคุณเห็นแค่ typed request แล้วคืน typed response
unary server ครบชุด
หัวข้อที่มีชื่อว่า “unary server ครบชุด”นี่คือทั้งหมด — handler, registration และ serve — สำหรับ UserService.GetUser:
type server struct { userv1.UnimplementedUserServiceServer // forward-compat embedding}
// GetUser is the handler: request in, response out.func (s *server) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.User, error) { // (normally you'd look this up in a database) return &userv1.User{ Id: req.Id, Name: "Ada Lovelace", }, nil}
func main() { lis, _ := net.Listen("tcp", ":50051") s := grpc.NewServer() userv1.RegisterUserServiceServer(s, &server{}) // register log.Println("listening on :50051") s.Serve(lis) // serve}class UserService(user_pb2_grpc.UserServiceServicer): # GetUser is the handler: request in, response out. def GetUser(self, request, context): return user_pb2.User( id=request.id, name="Ada Lovelace", )
def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server) # register server.add_insecure_port("[::]:50051") server.start() # serve print("listening on :50051") server.wait_for_termination()// GetUser is the handler: request in, callback out.const userService = { getUser(call, callback) { callback(null, { id: call.request.id, name: 'Ada Lovelace', }); },};
const server = new Server();server.addService(UserServiceService, userService); // registerserver.bindAsync('0.0.0.0:50051', ServerCredentials.createInsecure(), () => { console.log('listening on :50051'); // serve});คืน error ไม่ใช่ throw exception
หัวข้อที่มีชื่อว่า “คืน error ไม่ใช่ throw exception”เมื่อมีอะไรผิดพลาด คุณจะไม่ throw exception มั่ว ๆ — แต่คืน gRPC status user ที่หาไม่เจอคือ NOT_FOUND, input ที่ผิดคือ INVALID_ARGUMENT error model มีบทเรียนของตัวเองในโมดูล Service Definition นิสัยสำคัญที่ควรเริ่มตอนนี้คือ map ความล้มเหลวไปเป็น status code อย่างตั้งใจ เพราะ status นั้นคือสิ่งที่ err ฝั่ง client จะได้รับ
handler เดียว หลาย call พร้อมกัน
หัวข้อที่มีชื่อว่า “handler เดียว หลาย call พร้อมกัน”เพราะ server ทำงานบน HTTP/2 multiplexing หลาย call จึงวิ่งเข้า handler ของคุณพร้อมกันบน connection เดียวกัน ใน Go แต่ละ call รันใน goroutine ของตัวเอง; ใน Python มี thread pool คอย serve; ใน Node event loop สลับกันจัดการ นั่นแปลว่า handler ของคุณต้องรันพร้อมกันได้อย่างปลอดภัย — ห้ามมี shared mutable state ที่ไม่ได้ป้องกันด้วย synchronization