diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6f6cfd50d..f7ff358f3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -23,3 +23,21 @@ jobs: - name: Test run: go test -v ./... + + - name: Kratos + run: | + cd cmd/kratos + go build ./... + go test ./... + + - name: HTTP + run: | + cd cmd/protoc-gen-go-http + go build ./... + go test ./... + + - name: Errors + run: | + cd cmd/protoc-gen-go-errors + go build ./... + go test ./... diff --git a/cmd/protoc-gen-go-http/go.mod b/cmd/protoc-gen-go-http/go.mod index 8144ee2a5..89b32274b 100644 --- a/cmd/protoc-gen-go-http/go.mod +++ b/cmd/protoc-gen-go-http/go.mod @@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2 go 1.15 require ( - github.com/go-kratos/kratos/v2 v2.0.0-alpha5 + github.com/go-kratos/kratos/v2 v2.0.0-20210305104106-278210c4755a github.com/golang/protobuf v1.4.3 github.com/gorilla/mux v1.8.0 google.golang.org/genproto v0.0.0-20210202153253-cf70463f6119 diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go index 5820c3787..f8438f709 100644 --- a/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go +++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service.pb.go @@ -491,7 +491,7 @@ var file_echo_service_proto_rawDesc = []byte{ 0x4d, 0x41, 0x47, 0x45, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x45, 0x57, 0x53, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x53, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x56, 0x49, - 0x44, 0x45, 0x4f, 0x10, 0x06, 0x32, 0xbc, 0x04, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x65, + 0x44, 0x45, 0x4f, 0x10, 0x06, 0x32, 0xcc, 0x05, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xf2, 0x01, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, @@ -513,13 +513,22 @@ var file_echo_service_proto_rawDesc = []byte{ 0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, - 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x61, 0x0a, 0x0a, - 0x45, 0x63, 0x68, 0x6f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x18, 0x2e, 0x74, 0x65, 0x73, - 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x1f, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x82, 0x01, 0x0a, + 0x10, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x6f, 0x64, + 0x79, 0x12, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, + 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, + 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x22, 0x1e, 0x2f, 0x76, 0x31, + 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x62, 0x04, 0x62, 0x6f, 0x64, + 0x79, 0x12, 0x6c, 0x0a, 0x0a, 0x45, 0x63, 0x68, 0x6f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, + 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x18, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x2a, 0x22, 0x2f, 0x76, 0x31, + 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x6e, 0x75, 0x6d, 0x7d, 0x12, 0x73, 0x0a, 0x09, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x1f, 0x2e, @@ -569,14 +578,16 @@ var file_echo_service_proto_depIdxs = []int32{ 7, // 6: testproto.DynamicMessageUpdate.update_mask:type_name -> google.protobuf.FieldMask 2, // 7: testproto.EchoService.Echo:input_type -> testproto.SimpleMessage 2, // 8: testproto.EchoService.EchoBody:input_type -> testproto.SimpleMessage - 2, // 9: testproto.EchoService.EchoDelete:input_type -> testproto.SimpleMessage - 4, // 10: testproto.EchoService.EchoPatch:input_type -> testproto.DynamicMessageUpdate - 2, // 11: testproto.EchoService.Echo:output_type -> testproto.SimpleMessage - 2, // 12: testproto.EchoService.EchoBody:output_type -> testproto.SimpleMessage - 2, // 13: testproto.EchoService.EchoDelete:output_type -> testproto.SimpleMessage - 4, // 14: testproto.EchoService.EchoPatch:output_type -> testproto.DynamicMessageUpdate - 11, // [11:15] is the sub-list for method output_type - 7, // [7:11] is the sub-list for method input_type + 4, // 9: testproto.EchoService.EchoResponseBody:input_type -> testproto.DynamicMessageUpdate + 2, // 10: testproto.EchoService.EchoDelete:input_type -> testproto.SimpleMessage + 4, // 11: testproto.EchoService.EchoPatch:input_type -> testproto.DynamicMessageUpdate + 2, // 12: testproto.EchoService.Echo:output_type -> testproto.SimpleMessage + 2, // 13: testproto.EchoService.EchoBody:output_type -> testproto.SimpleMessage + 4, // 14: testproto.EchoService.EchoResponseBody:output_type -> testproto.DynamicMessageUpdate + 2, // 15: testproto.EchoService.EchoDelete:output_type -> testproto.SimpleMessage + 4, // 16: testproto.EchoService.EchoPatch:output_type -> testproto.DynamicMessageUpdate + 12, // [12:17] is the sub-list for method output_type + 7, // [7:12] is the sub-list for method input_type 7, // [7:7] is the sub-list for extension type_name 7, // [7:7] is the sub-list for extension extendee 0, // [0:7] is the sub-list for field type_name diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto b/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto index adee8c6b3..a0a6b743b 100644 --- a/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto +++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service.proto @@ -85,10 +85,17 @@ service EchoService { body: "*" }; } + // EchoResponseBody method receives a simple message and returns it. + rpc EchoResponseBody(DynamicMessageUpdate) returns (DynamicMessageUpdate) { + option (google.api.http) = { + post: "/v1/example/echo_response_body" + response_body: "body" + }; + } // EchoDelete method receives a simple message and returns it. rpc EchoDelete(SimpleMessage) returns (SimpleMessage) { option (google.api.http) = { - delete: "/v1/example/echo_delete" + delete: "/v1/example/echo_delete/{id}/{num}" }; } // EchoPatch method receives a NonStandardUpdateRequest and returns it. diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go index ae03788d4..23374bdcd 100644 --- a/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go +++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service_grpc.pb.go @@ -25,6 +25,8 @@ type EchoServiceClient interface { Echo(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error) // EchoBody method receives a simple message and returns it. EchoBody(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error) + // EchoResponseBody method receives a simple message and returns it. + EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate, opts ...grpc.CallOption) (*DynamicMessageUpdate, error) // EchoDelete method receives a simple message and returns it. EchoDelete(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error) // EchoPatch method receives a NonStandardUpdateRequest and returns it. @@ -57,6 +59,15 @@ func (c *echoServiceClient) EchoBody(ctx context.Context, in *SimpleMessage, opt return out, nil } +func (c *echoServiceClient) EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate, opts ...grpc.CallOption) (*DynamicMessageUpdate, error) { + out := new(DynamicMessageUpdate) + err := c.cc.Invoke(ctx, "/testproto.EchoService/EchoResponseBody", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *echoServiceClient) EchoDelete(ctx context.Context, in *SimpleMessage, opts ...grpc.CallOption) (*SimpleMessage, error) { out := new(SimpleMessage) err := c.cc.Invoke(ctx, "/testproto.EchoService/EchoDelete", in, out, opts...) @@ -86,6 +97,8 @@ type EchoServiceServer interface { Echo(context.Context, *SimpleMessage) (*SimpleMessage, error) // EchoBody method receives a simple message and returns it. EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error) + // EchoResponseBody method receives a simple message and returns it. + EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error) // EchoDelete method receives a simple message and returns it. EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error) // EchoPatch method receives a NonStandardUpdateRequest and returns it. @@ -103,6 +116,9 @@ func (UnimplementedEchoServiceServer) Echo(context.Context, *SimpleMessage) (*Si func (UnimplementedEchoServiceServer) EchoBody(context.Context, *SimpleMessage) (*SimpleMessage, error) { return nil, status.Errorf(codes.Unimplemented, "method EchoBody not implemented") } +func (UnimplementedEchoServiceServer) EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error) { + return nil, status.Errorf(codes.Unimplemented, "method EchoResponseBody not implemented") +} func (UnimplementedEchoServiceServer) EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error) { return nil, status.Errorf(codes.Unimplemented, "method EchoDelete not implemented") } @@ -158,6 +174,24 @@ func _EchoService_EchoBody_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _EchoService_EchoResponseBody_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DynamicMessageUpdate) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EchoServiceServer).EchoResponseBody(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/testproto.EchoService/EchoResponseBody", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EchoServiceServer).EchoResponseBody(ctx, req.(*DynamicMessageUpdate)) + } + return interceptor(ctx, in, info, handler) +} + func _EchoService_EchoDelete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SimpleMessage) if err := dec(in); err != nil { @@ -209,6 +243,10 @@ var EchoService_ServiceDesc = grpc.ServiceDesc{ MethodName: "EchoBody", Handler: _EchoService_EchoBody_Handler, }, + { + MethodName: "EchoResponseBody", + Handler: _EchoService_EchoResponseBody_Handler, + }, { MethodName: "EchoDelete", Handler: _EchoService_EchoDelete_Handler, diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go index 27e3c610e..cba12b921 100644 --- a/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go +++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service_http.pb.go @@ -27,6 +27,8 @@ type EchoServiceHandler interface { EchoDelete(context.Context, *SimpleMessage) (*SimpleMessage, error) EchoPatch(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error) + + EchoResponseBody(context.Context, *DynamicMessageUpdate) (*DynamicMessageUpdate, error) } func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) http.Handler { @@ -38,16 +40,16 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h r.HandleFunc("/v1/example/echo/{id}/{num}", func(w http.ResponseWriter, r *http.Request) { var in SimpleMessage + if err := h.Decode(r, &in); err != nil { + h.Error(w, r, err) + return + } if err := binding.MapProto(&in, mux.Vars(r)); err != nil { h.Error(w, r, err) return } - if err := h.Decode(r, &in); err != nil { - h.Error(w, r, err) - return - } next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.Echo(ctx, req.(*SimpleMessage)) } @@ -59,23 +61,24 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*SimpleMessage) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("GET") r.HandleFunc("/v1/example/echo/{id}/{num}/{lang}", func(w http.ResponseWriter, r *http.Request) { var in SimpleMessage + if err := h.Decode(r, &in); err != nil { + h.Error(w, r, err) + return + } if err := binding.MapProto(&in, mux.Vars(r)); err != nil { h.Error(w, r, err) return } - if err := h.Decode(r, &in); err != nil { - h.Error(w, r, err) - return - } next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.Echo(ctx, req.(*SimpleMessage)) } @@ -87,23 +90,24 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*SimpleMessage) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("GET") r.HandleFunc("/v1/example/echo1/{id}/{line_num}/{status.note}", func(w http.ResponseWriter, r *http.Request) { var in SimpleMessage + if err := h.Decode(r, &in); err != nil { + h.Error(w, r, err) + return + } if err := binding.MapProto(&in, mux.Vars(r)); err != nil { h.Error(w, r, err) return } - if err := h.Decode(r, &in); err != nil { - h.Error(w, r, err) - return - } next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.Echo(ctx, req.(*SimpleMessage)) } @@ -115,23 +119,24 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*SimpleMessage) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("GET") r.HandleFunc("/v1/example/echo2/{no.note}", func(w http.ResponseWriter, r *http.Request) { var in SimpleMessage + if err := h.Decode(r, &in); err != nil { + h.Error(w, r, err) + return + } if err := binding.MapProto(&in, mux.Vars(r)); err != nil { h.Error(w, r, err) return } - if err := h.Decode(r, &in); err != nil { - h.Error(w, r, err) - return - } next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.Echo(ctx, req.(*SimpleMessage)) } @@ -143,23 +148,24 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*SimpleMessage) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("GET") r.HandleFunc("/v1/example/echo/{id}", func(w http.ResponseWriter, r *http.Request) { var in SimpleMessage + if err := h.Decode(r, &in); err != nil { + h.Error(w, r, err) + return + } if err := binding.MapProto(&in, mux.Vars(r)); err != nil { h.Error(w, r, err) return } - if err := h.Decode(r, &in); err != nil { - h.Error(w, r, err) - return - } next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.Echo(ctx, req.(*SimpleMessage)) } @@ -171,18 +177,19 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*SimpleMessage) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("POST") r.HandleFunc("/v1/example/echo_body", func(w http.ResponseWriter, r *http.Request) { var in SimpleMessage - if err := h.Decode(r, &in); err != nil { h.Error(w, r, err) return } + next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.EchoBody(ctx, req.(*SimpleMessage)) } @@ -194,18 +201,48 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*SimpleMessage) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("POST") - r.HandleFunc("/v1/example/echo_delete", func(w http.ResponseWriter, r *http.Request) { - var in SimpleMessage - + r.HandleFunc("/v1/example/echo_response_body", func(w http.ResponseWriter, r *http.Request) { + var in DynamicMessageUpdate if err := h.Decode(r, &in); err != nil { h.Error(w, r, err) return } + + next := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.EchoResponseBody(ctx, req.(*DynamicMessageUpdate)) + } + if h.Middleware != nil { + next = h.Middleware(next) + } + out, err := next(r.Context(), &in) + if err != nil { + h.Error(w, r, err) + return + } + reply := out.(*DynamicMessageUpdate) + if err := h.Encode(w, r, reply.Body); err != nil { + h.Error(w, r, err) + } + }).Methods("POST") + + r.HandleFunc("/v1/example/echo_delete/{id}/{num}", func(w http.ResponseWriter, r *http.Request) { + var in SimpleMessage + if err := h.Decode(r, &in); err != nil { + h.Error(w, r, err) + return + } + + if err := binding.MapProto(&in, mux.Vars(r)); err != nil { + h.Error(w, r, err) + return + } + next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.EchoDelete(ctx, req.(*SimpleMessage)) } @@ -217,18 +254,19 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*SimpleMessage) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("DELETE") r.HandleFunc("/v1/example/echo_patch", func(w http.ResponseWriter, r *http.Request) { var in DynamicMessageUpdate - if err := h.Decode(r, &in.Body); err != nil { h.Error(w, r, err) return } + next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.EchoPatch(ctx, req.(*DynamicMessageUpdate)) } @@ -240,7 +278,8 @@ func NewEchoServiceHandler(srv EchoServiceHandler, opts ...http1.HandleOption) h h.Error(w, r, err) return } - if err := h.Encode(w, r, out); err != nil { + reply := out.(*DynamicMessageUpdate) + if err := h.Encode(w, r, reply); err != nil { h.Error(w, r, err) } }).Methods("PATCH") diff --git a/cmd/protoc-gen-go-http/internal/testproto/echo_service_test.go b/cmd/protoc-gen-go-http/internal/testproto/echo_service_test.go new file mode 100644 index 000000000..8b00e3123 --- /dev/null +++ b/cmd/protoc-gen-go-http/internal/testproto/echo_service_test.go @@ -0,0 +1,212 @@ +package testproto + +import ( + "bytes" + context "context" + "fmt" + "net" + http "net/http" + "testing" + "time" + + "github.com/go-kratos/kratos/v2/encoding" + tr "github.com/go-kratos/kratos/v2/transport/http" + _struct "github.com/golang/protobuf/ptypes/struct" +) + +type echoService struct { +} + +func (s *echoService) Echo(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) { + return m, nil +} + +func (s *echoService) EchoBody(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) { + return m, nil +} + +func (s *echoService) EchoDelete(ctx context.Context, m *SimpleMessage) (*SimpleMessage, error) { + return m, nil +} + +func (s *echoService) EchoPatch(ctx context.Context, m *DynamicMessageUpdate) (*DynamicMessageUpdate, error) { + return m, nil +} + +func (s *echoService) EchoResponseBody(ctx context.Context, m *DynamicMessageUpdate) (*DynamicMessageUpdate, error) { + return m, nil +} + +type echoClient struct { + baseURL string + client *http.Client +} + +// post: /v1/example/echo/{id} +func (c *echoClient) Echo(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) { + codec := encoding.GetCodec("json") + data, err := codec.Marshal(in) + if err != nil { + return + } + req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/example/echo/%s", c.baseURL, in.Id), bytes.NewReader(data)) + if err != nil { + return + } + req.Header.Set("content-type", "application/json") + out = new(SimpleMessage) + if err = tr.Do(c.client, req, out); err != nil { + return + } + return +} + +// post: /v1/example/echo_body +func (c *echoClient) EchoBody(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) { + codec := encoding.GetCodec("json") + data, err := codec.Marshal(in) + if err != nil { + return + } + req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/example/echo_body", c.baseURL), bytes.NewReader(data)) + if err != nil { + return + } + req.Header.Set("content-type", "application/json") + out = new(SimpleMessage) + if err = tr.Do(c.client, req, out); err != nil { + return + } + return +} + +// delete: /v1/example/echo_delete/{id}/{num} +func (c *echoClient) EchoDelete(ctx context.Context, in *SimpleMessage) (out *SimpleMessage, err error) { + req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/example/echo_delete/%s/%d", c.baseURL, in.Id, in.Num), nil) + if err != nil { + return + } + out = new(SimpleMessage) + if err = tr.Do(c.client, req, out); err != nil { + return + } + return +} + +// patch: /v1/example/echo_patch +func (c *echoClient) EchoPatch(ctx context.Context, in *DynamicMessageUpdate) (out *DynamicMessageUpdate, err error) { + codec := encoding.GetCodec("json") + data, err := codec.Marshal(in.Body) + if err != nil { + return + } + req, err := http.NewRequest("PATCH", fmt.Sprintf("%s/v1/example/echo_patch", c.baseURL), bytes.NewReader(data)) + if err != nil { + return + } + req.Header.Set("content-type", "application/json") + out = new(DynamicMessageUpdate) + if err = tr.Do(c.client, req, out); err != nil { + return + } + return +} + +// post: /v1/example/echo_response_body +func (c *echoClient) EchoResponseBody(ctx context.Context, in *DynamicMessageUpdate) (out *DynamicMessageUpdate, err error) { + codec := encoding.GetCodec("json") + data, err := codec.Marshal(in) + if err != nil { + return + } + req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/example/echo_response_body", c.baseURL), bytes.NewReader(data)) + if err != nil { + return + } + req.Header.Set("content-type", "application/json") + out = new(DynamicMessageUpdate) + if err = tr.Do(c.client, req, out.Body); err != nil { + return + } + return +} + +func TestEchoService(t *testing.T) { + s := &echoService{} + h := NewEchoServiceHandler(s) + srv := &http.Server{Addr: ":0", Handler: h} + lis, err := net.Listen("tcp", srv.Addr) + if err != nil { + t.Fatal(err) + } + addr := lis.Addr().(*net.TCPAddr) + time.AfterFunc(time.Second, func() { + defer srv.Shutdown(context.Background()) + testEchoClient(t, fmt.Sprintf("http://127.0.0.1:%d", addr.Port)) + }) + if err := srv.Serve(lis); err != nil && err != http.ErrServerClosed { + t.Fatal(err) + } +} + +func testEchoClient(t *testing.T, baseURL string) { + var ( + err error + in = &SimpleMessage{Id: "test_id", Num: 100} + out = &SimpleMessage{} + ) + check := func(in, out *SimpleMessage) { + if in.Id != out.Id || in.Num != out.Num { + t.Errorf("expected %s got %s", in, out) + } + } + + cli := &echoClient{baseURL: baseURL, client: http.DefaultClient} + + if out, err = cli.Echo(context.Background(), in); err != nil { + t.Fatal(err) + } + check(in, out) + + if out, err = cli.EchoBody(context.Background(), in); err != nil { + t.Fatal(err) + } + check(in, out) + + if out, err = cli.EchoDelete(context.Background(), in); err != nil { + t.Fatal(err) + } + check(in, out) + + var ( + din = &DynamicMessageUpdate{Body: &DynamicMessage{ + ValueField: &_struct.Value{Kind: &_struct.Value_StringValue{StringValue: "test"}}, + }} + dout *DynamicMessageUpdate + ) + if dout, err = cli.EchoPatch(context.Background(), din); err != nil { + t.Fatal(err) + } + if din.Body.ValueField.GetStringValue() != dout.Body.ValueField.GetStringValue() { + t.Errorf("EchoPatch expected %s got %s", din, dout) + } + if dout, err = cli.EchoResponseBody(context.Background(), din); err != nil { + t.Fatal(err) + } + if din.Body.ValueField.GetStringValue() != dout.Body.ValueField.GetStringValue() { + t.Errorf("EchoResponseBody expected %s got %s", din, dout) + } +} + +func TestJSON(t *testing.T) { + in := &SimpleMessage{Id: "test_id", Num: 100} + out := &SimpleMessage{} + codec := encoding.GetCodec("json") + data, err := codec.Marshal(in) + if err != nil { + t.Fatal(err) + } + if err := codec.Unmarshal(data, out); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/protoc-gen-go-http/template.go b/cmd/protoc-gen-go-http/template.go index 6d21337bd..6a5e0849a 100644 --- a/cmd/protoc-gen-go-http/template.go +++ b/cmd/protoc-gen-go-http/template.go @@ -22,16 +22,16 @@ func New{{.ServiceType}}Handler(srv {{.ServiceType}}Handler, opts ...http1.Handl {{range .Methods}} r.HandleFunc("{{.Path}}", func(w http.ResponseWriter, r *http.Request) { var in {{.Request}} + if err := h.Decode(r, &in{{.Body}}); err != nil { + h.Error(w, r, err) + return + } {{if ne (len .Vars) 0}} if err := binding.MapProto(&in, mux.Vars(r)); err != nil { h.Error(w, r, err) return } {{end}} - if err := h.Decode(r, &in{{.Body}}); err != nil { - h.Error(w, r, err) - return - } next := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.{{.Name}}(ctx, req.(*{{.Request}})) } @@ -43,7 +43,8 @@ func New{{.ServiceType}}Handler(srv {{.ServiceType}}Handler, opts ...http1.Handl h.Error(w, r, err) return } - if err := h.Encode(w, r, out{{.ResponseBody}}); err != nil { + reply := out.(*{{.Reply}}) + if err := h.Encode(w, r, reply{{.ResponseBody}}); err != nil { h.Error(w, r, err) } }).Methods("{{.Method}}") diff --git a/encoding/json/json.go b/encoding/json/json.go index 8c88f4ceb..3aa947617 100644 --- a/encoding/json/json.go +++ b/encoding/json/json.go @@ -2,6 +2,7 @@ package json import ( "encoding/json" + "reflect" "github.com/go-kratos/kratos/v2/encoding" @@ -21,6 +22,8 @@ var ( UnmarshalOptions = protojson.UnmarshalOptions{ DiscardUnknown: true, } + + typeProtoMessage = reflect.TypeOf((*proto.Message)(nil)).Elem() ) func init() { @@ -38,6 +41,15 @@ func (codec) Marshal(v interface{}) ([]byte, error) { } func (codec) Unmarshal(data []byte, v interface{}) error { + rv := reflect.ValueOf(v) + for rv.Kind() == reflect.Ptr { + if rv.IsNil() { + rv.Set(reflect.New(rv.Type().Elem())) + v = rv.Interface() + break + } + rv = rv.Elem() + } if m, ok := v.(proto.Message); ok { return UnmarshalOptions.Unmarshal(data, m) } diff --git a/transport/http/client.go b/transport/http/client.go index 35cf11f98..42cae72bb 100644 --- a/transport/http/client.go +++ b/transport/http/client.go @@ -2,10 +2,11 @@ package http import ( "context" - "encoding/json" + "io/ioutil" "net/http" "time" + "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" @@ -116,11 +117,24 @@ func Do(client *http.Client, req *http.Request, target interface{}) error { } defer res.Body.Close() if res.StatusCode < 200 || res.StatusCode > 299 { - se := &errors.StatusError{} - if err := json.NewDecoder(req.Body).Decode(se); err != nil { + se := &errors.StatusError{Code: 2} + if err := decodeResponse(res, se); err != nil { return err } return se } - return json.NewDecoder(req.Body).Decode(target) + return decodeResponse(res, target) +} + +func decodeResponse(res *http.Response, target interface{}) error { + subtype := contentSubtype(res.Header.Get(contentTypeHeader)) + codec := encoding.GetCodec(subtype) + if codec == nil { + codec = encoding.GetCodec("json") + } + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + return codec.Unmarshal(data, target) } diff --git a/transport/http/handle.go b/transport/http/handle.go index a517acfb4..32e61e34f 100644 --- a/transport/http/handle.go +++ b/transport/http/handle.go @@ -1,7 +1,6 @@ package http import ( - "encoding/json" "io/ioutil" "net/http" "strings" @@ -96,18 +95,22 @@ func decodeRequest(req *http.Request, v interface{}) error { // encodeResponse encodes the object to the HTTP response. func encodeResponse(w http.ResponseWriter, r *http.Request, v interface{}) error { + var codec encoding.Codec for _, accept := range r.Header[acceptHeader] { - if codec := encoding.GetCodec(contentSubtype(accept)); codec != nil { - data, err := codec.Marshal(v) - if err != nil { - return err - } - w.Header().Set(contentTypeHeader, contentType(codec.Name())) - w.Write(data) - return nil + if codec = encoding.GetCodec(contentSubtype(accept)); codec != nil { + break } } - return json.NewEncoder(w).Encode(v) + if codec == nil { + codec = encoding.GetCodec("json") + } + data, err := codec.Marshal(v) + if err != nil { + return err + } + w.Header().Set(contentTypeHeader, contentType(codec.Name())) + w.Write(data) + return nil } // encodeError encodes the erorr to the HTTP response. @@ -116,7 +119,6 @@ func encodeError(w http.ResponseWriter, r *http.Request, err error) { if !ok { se = &errors.StatusError{ Code: 2, - Reason: "", Message: err.Error(), } }