본문 바로가기

개발일지/go

gRPC - Context and Error Handling

Context

context의 주요 기능중 하나는 deadline을 통한 timeout 설정이 가능하다는 것입니다. MSA 환경에서는 client의 요청이 다수의 gRPC 요청이 필요한 상황이 많기 때문에 gRPC에서는 context 기능을 지원하고 있습니다. deadline은 요청이 생성할 때, 설정되고 서비스들 전반적으로 사용될 수 있습니다. 

다음은 gRPC에서 deadline을 설정하고 사용하는 예제입니다. 

 

func (cl *Client) login()  {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    // 1 second deadline
	defer cancel()  // (1)
	r, err := cl.ChatTaskClient.Login(ctx, &pb.UserInfo{Username: cl.Username, 
    Password: cl.Password})
    // gRPC request
	if err != nil {
    	errStatus := status.Code(err) // error status check
		log.Printf("%v occured", errStatus)
	} else {
      if r.Response == pb.ResponseType_NOMATCH {
          log.Printf("There is no match username %s and password\n", cl.username)
      } else if r.Response == pb.ResponseType_SUCCESS {
          log.Printf("Welcome! %s\n", username)
          cl.username = username
          cl.password = password
          cl.chatSession(ctx)
      }
   }
}

 

위 코드는 gRPC를 요청하는 client코드로 context를 1초로 설정하고 gRPC server에 로그인을 요청하는 코드입니다.

그리고 서버에서는 ctx.Err() == context.DeadlineExceeded로 해당 컨텍스트가 타임아웃되었는지 확인할 수 있으며, select문을 통해 처리하는 방법이 있습니다. 또한 context로 클라이언트에서 위 코드에서 (1)부분의 cancel을 수행할 수 있습니다. 그리고 서버에서는 stream.Context().Err() == context.Canceled로 해당 컨텍스트가 cancel되었는지 확인하고 처리를 할 수 있습니다. 

 

 

Error handling

gRPC를 통해 요청을 보낼 때, client에서는 gRPC에서 error condition을 응답과 함께 얻을 수 있습니다. 응답코드를 protobuf에서 정의해서 쓸 수도 있지만 gRPC에서 이미 다음과 같은 에러를 잘 정의하고 있기 때문에 사용하는 것이 좋은 방식인 것 같습니다.

 

https://github.com/grpc/grpc/blob/master/doc/statuscodes.md

 

grpc/grpc

The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) - grpc/grpc

github.com

아래는 서버에서 에러를 정의하는 예제와 클라이언트에서 에러를 처리하는 예제입니다.

 

func (ar *UserRepository) SignUp(user *model.User) error {
	_, ok := ar.userMap.info.Load(user.Username); if ok {
		errorStatus := status.New(codes.AlreadyExists, "Already Exists")
		return errorStatus.Err()
	}
	ar.userMap.info.Store(user.Username, &model.User{ Username:user.Username, Password:user.Password})
	return nil
}


func (s *AuthApplication) SignUp(in *pb.UserInfo) (*pb.SignupResponse, error) {
	if err := s.repository.SignUp(&model.User{Username:in.Username, Password:in.Password}); err != nil {
		return &pb.SignupResponse{Response:pb.ResponseType_FAIL}, err
	}
	return &pb.SignupResponse{Response:pb.ResponseType_SUCCESS}, nil
}

 

func (cl *Client) signup() {
	var username, password string
	fmt.Printf("username: ")
	fmt.Scanln(&username)
	fmt.Printf("password: ")
	fmt.Scanln(&password)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := cl.ChatTaskClient.SignUp(ctx, &pb.UserInfo{Username: username, Password: password})
	if err != nil {
		errorStatus := status.Code(err)
		if errorStatus == codes.AlreadyExists {
			log.Printf("Already Exists Error : %s", errorStatus)
		}
	}
	log.Printf("Thank you for signup! %s\n", username)
}

'개발일지 > go' 카테고리의 다른 글

GORM (2) - CRUD  (0) 2020.04.24
GORM (1) - Getting Started  (0) 2020.04.23
gRPC - Multiplexing, Metadata, Load Balancing and Compression  (0) 2020.04.13
gRPC - Interceptor  (0) 2020.03.31
GRPC로 이미지 파일 보내기  (0) 2020.03.30