Skip to content

Instantly share code, notes, and snippets.

@rl4debug
Last active April 10, 2020 12:36
Show Gist options
  • Save rl4debug/22ede7eb725629e52c786d6a5f75b5c8 to your computer and use it in GitHub Desktop.
Save rl4debug/22ede7eb725629e52c786d6a5f75b5c8 to your computer and use it in GitHub Desktop.
[design grpc gateway error] #grpc

Việc viết document cho API rất quan trọng.

Ta cần viết ra các trường hợp có thể xảy của http response:

  • Trường hợp không thể kết nối tới server, việc xử lý nằm ở client. Nếu đã kết nối được tới server, mọi response trả về phải có định nghĩa schema rõ ràng

Vấn đề phát sinh khi dùng grpc-gateway đó là làm sao để thông báo error message tới user rõ ràng nhất.

Thông thường khi làm việc với GRPC, ta sẽ dùng grpc-gateway làm công cụ để sinh ra client, và server stuff.

Việc xử lý logic của API sẽ nằm trong GRPC handlers, nó có dạng như sau:

func (service *ExampleService) Get(ctx context.Context, r *p.ExampleRequest) (*p.ExampleResponse, error) {
  // logic is wrote here
  // ...
}

Ở đầu end-user (bên yêu cầu http request), ta cần định nghĩa error message đồng nhất.

http request -> parse http request to GRPC request -> GRPC handler
client receive response <- grpc gateway map this code to http status code <- GPRC handler return GRPC status code

How grpc-gateway map GRPC code to HTTP code

How codes are mapped: https://github.com/grpc-ecosystem/grpc-gateway/blob/384f7b89a3985def35a82ac7079e3ba665a4380b/runtime/errors.go#L15

Flows

Client call 1 http request tới grpc-gateway, gọi là GW. GW sẽ parse http request đó thành proto message. Trong quá trình parse có lỗi, GW sẽ return http response với code là BadRequest.

Nếu quá trình parse thành công. Kết quả cho ra 1 proto message. GW sẽ tiền hành Invoke proto request đó tới GRPC server và nhận về proto response. GW tiến hành xử lý proto response để trả về http response.

Việc xử lý được tiến hành dựa vào error trả về của GRPC server. GW sẽ map status code của GRPC thành status code của http.

Cuối cùng tiền hành trả về http response.

Trong quá trình trên, ta có thể custom các hành động của nó.

Một request tới gateway sẽ được xử lý ở phần http handler. Handler cần 2 tham số http.ResponseWriterhttp.Request, các tham số này được xử lý ở phần http Server.

Http server sẽ tự động call hanlder của nó, mỗi khi có bất kỳ request nào tới, ta gọi handler đó là root handler. Root handler có nhiệm vụ gửi request đó cho các handler con xử lý. Như vậy root handler có thể là http.ServeMux. Cuối cùng bằng cách nào đó mà request đi tới phần chuyển đổi http request thành pb Message value. Lưu ý ta gọi Message được định nghĩa trong file proto là proto message definition (proto message) và struct sinh ra bởi protoc-gen-go tương ứng với proto message đó là protobuf Message (pb message).

Http request được chuyển thành pb message như thế nào?

func request_ExampleService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client ExampleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
	var protoReq GetRequest
	var metadata runtime.ServerMetadata

	if err := req.ParseForm(); err != nil {
		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
	}
	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Theme_Get_0); err != nil {
		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
	}

	msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
	return msg, metadata, err
}

Đoạn code trên được sinh ra tự động bởi protoc-gen-grpc-gateway. Nó có 2 nhiệm vụ, 1 là parses query parameters và req.Form vào protoReq, có type GetRequest chính là pb message, 2 là thực hiện rpc đến GRPC server.

Trong quá trình đó có req.ParseForm() là 1 helper function yêu cầu http request tự sinh ra giá trị req.Form dựa vào thông tin của req. Ngoài ra có runtime.PopulateQueryParameters để parses req.Form vào protoReq. Như vậy có thể hiểu query paramters đã được chuyển vào req.Form. Làm cách nào mà req.Form có thể được parse thành protoReq? Đi sâu vào ta sẽ thấy có nó dùng 1 interface tên là runtime.QueryParameter. Interface đó được implement bởi defaultQueryParser{}, nó chính là phần xử lý. Tới đây ta nên dừng lại.

Cho tới giờ, ta đã hiểu được Get request được chuyển thành pb message như thế nào. Tiếp theo ta xem xét tới đoạn client.Get(ctx....

Nhìn sơ ta có thể thấy gw đang thực hiện 1 rpc đến GRPC server, cho method tên là Get, kèm theo param cần cho GRPC là protoReq. Thế còn các arguments grpc.Header(&metadata.HeaderMD)grpc.Trailer(&metadata.TrailerMD) để làm gì?

Ở đây ta nhìn thấy client là 1 interface tên là ExampelServiceClient. Vì nó là 1 interface nên cần phải có 1 type nào đó implement nó.

runtime.QueryParameterParser

// QueryParameterParser defines interface for all query parameter parsers
type QueryParameterParser interface {
	Parse(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error
}

Điều này nghĩa là query parameters của http requests sẽ được parses bởi interface này. Nếu ta muốn parse query parameters, ta nên implement interface này.

Kết quả của việc parse sẽ đưa vào msg có type là interface proto.Message nằm ở package golang/protobuf/proto, các pb messages được generate bởi protoc-gen-go sẽ mặc định implement interface này.

Input của việc parse sẽ nằm ở values, thông thường chính là req.Form của http request. Giả sử ta có file proto như sau:

message Echo {
	string value = 1;
}

http request có dạng GET /api/echo?value=value1, khi đó req.Form sẽ là:

{"value": ["value1"]}

Nếu giả sử proto:

message Echo {
	string value = 1;
	repeated string arr = 2;
}

http request có dạng GET /api/echo?value=value1?arr=item1&arr=item2, khi đó req.Form sẽ là:

{
	"value": ["value1"],
	"arr": ["item1", "item2"]
}

Nếu proto:

message Echo {
	string value = 1;
	Nested nested = 2;
}

message Nested {
	string value = 1;
}

Khi đó http request có dạng GET /api/echo?value=value1&nested.value=nested_value, khi đó req.Form có dạng:

{
	"value": ["value1"],
	"nested.value": ["nested_value"]
}

Vây còn filter?, nó là 1 DoubleArray. Thông thường nó được truyền vào như sau:

filter_ExampleService_Get_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}

Mục đích để làm gì?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment