diff --git a/app.go b/app.go index da6ffe8..7399c8f 100644 --- a/app.go +++ b/app.go @@ -10,6 +10,7 @@ import ( "git.diulo.com/mogfee/kit/transport" "github.com/google/uuid" "golang.org/x/sync/errgroup" + "os" "os/signal" "sync" diff --git a/cmd/kit/main.go b/cmd/kit/main.go index ad16310..1ca8f77 100644 --- a/cmd/kit/main.go +++ b/cmd/kit/main.go @@ -25,8 +25,13 @@ func (u *Kit) Generate(plugin *protogen.Plugin) error { } u.addImports("context") u.addImports("git.diulo.com/mogfee/kit/middleware") +<<<<<<< HEAD u.addImports("git.diulo.com/mogfee/kit/response") u.addImports("git.diulo.com/mogfee/kit/errors") +======= + u.addImports("git.diulo.com/mogfee/kit/pkg/response") + u.addImports("git.diulo.com/mogfee/kit/pkg/errors") +>>>>>>> 434d858718d87c9c434ae68dc9095f66fb161008 u.addImports("github.com/gin-gonic/gin") for _, f := range plugin.Files { if len(f.Services) == 0 { diff --git a/errors/errors.go b/errors/errors.go index 9d5fe1a..8ae26d0 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -2,8 +2,9 @@ package errors import ( "errors" + "fmt" + httpstatus "git.diulo.com/mogfee/kit/transport/http/status" "google.golang.org/genproto/googleapis/rpc/errdetails" - "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -15,36 +16,69 @@ const ( ) type Error struct { - Status int - Message string - Reason string - Metadata map[string]string + Status + cause error } -func (e *Error) WithMetadata(data map[string]string) *Error { - err := Clone(e) - err.Metadata = data - return err +func (e *Error) Error() string { + return fmt.Sprintf("error: code = %d reason = %s message = %s metadata = %v cause = %v", e.Code, e.Reason, e.Message, e.Metadata, e.cause) } -func (s *Error) Error() string { - return s.Message +func (e *Error) Unwarp() error { + return e.cause } -func New(code int, reason, message string) *Error { - return &Error{ - Status: code, - Message: message, - Reason: reason, - Metadata: map[string]string{}, +func (e *Error) Is(err error) bool { + if se := new(Error); errors.As(err, &se) { + return se.Code == e.Code && se.Reason == e.Reason } + return false } +func (e *Error) WithCause(cause error) *Error { + err := Clone(e) + err.cause = cause + return err +} +func (e *Error) WithMetadata(md map[string]string) *Error { + err := Clone(e) + err.Metadata = md + return err +} + func (e *Error) GRPCStatus() *status.Status { - s, _ := status.New(codes.Code(e.Status), e.Message). + s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message). WithDetails(&errdetails.ErrorInfo{ Reason: e.Reason, Metadata: e.Metadata, }) return s } +func New(code int, reason, message string) *Error { + return &Error{ + Status: Status{ + Code: int32(code), + Reason: reason, + Message: message, + }, + } +} +func Newf(code int, reason string, format string, a ...any) *Error { + return New(code, reason, fmt.Sprintf(format, a...)) +} +func Errorf(code int, reason string, format string, a ...any) *Error { + return New(code, reason, fmt.Sprintf(format, a...)) +} +func Code(err error) int { + if err == nil { + return 200 + } + return int(FromError(err).Code) +} +func Reason(err error) string { + if err == nil { + return UnknownReason + } + return FromError(err).Reason +} + func Clone(err *Error) *Error { if err == nil { return nil @@ -54,17 +88,14 @@ func Clone(err *Error) *Error { metadata[k] = v } return &Error{ - Status: err.Status, - Reason: err.Reason, - Message: err.Message, - Metadata: metadata, - } -} -func Code(err error) int { - if err == nil { - return 200 + cause: err.cause, + Status: Status{ + Code: err.Code, + Reason: err.Reason, + Message: err.Message, + Metadata: metadata, + }, } - return FromError(err).Status } func FromError(err error) *Error { @@ -74,5 +105,17 @@ func FromError(err error) *Error { if se := new(Error); errors.As(err, &se) { return se } - return New(UnknownCode, UnknownReason, err.Error()) + gs, ok := status.FromError(err) + if !ok { + return New(UnknownCode, UnknownReason, err.Error()) + } + ret := New(httpstatus.FromGRPCCode(gs.Code()), UnknownReason, gs.Message()) + for _, detail := range gs.Details() { + switch d := detail.(type) { + case *errdetails.ErrorInfo: + ret.Reason = d.Reason + return ret.WithMetadata(d.Metadata) + } + } + return ret } diff --git a/errors/errors.pb.go b/errors/errors.pb.go new file mode 100644 index 0000000..541600d --- /dev/null +++ b/errors/errors.pb.go @@ -0,0 +1,226 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.22.0 +// source: errors.proto + +package errors + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Status struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Status) Reset() { + *x = Status{} + if protoimpl.UnsafeEnabled { + mi := &file_errors_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_errors_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Status.ProtoReflect.Descriptor instead. +func (*Status) Descriptor() ([]byte, []int) { + return file_errors_proto_rawDescGZIP(), []int{0} +} + +func (x *Status) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *Status) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *Status) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *Status) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +var file_errors_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.EnumOptions)(nil), + ExtensionType: (*int32)(nil), + Field: 1108, + Name: "errors.default_code", + Tag: "varint,1108,opt,name=default_code", + Filename: "errors.proto", + }, + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*int32)(nil), + Field: 1109, + Name: "errors.code", + Tag: "varint,1109,opt,name=code", + Filename: "errors.proto", + }, +} + +// Extension fields to descriptorpb.EnumOptions. +var ( + // optional int32 default_code = 1108; + E_DefaultCode = &file_errors_proto_extTypes[0] +) + +// Extension fields to descriptorpb.EnumValueOptions. +var ( + // optional int32 code = 1109; + E_Code = &file_errors_proto_extTypes[1] +) + +var File_errors_proto protoreflect.FileDescriptor + +var file_errors_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc5, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x3a, 0x40, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd4, + 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, + 0x64, 0x65, 0x3a, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, + 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x08, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, + 0x74, 0x2e, 0x64, 0x69, 0x75, 0x6c, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x6f, 0x67, 0x66, + 0x65, 0x65, 0x2f, 0x6b, 0x69, 0x74, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_errors_proto_rawDescOnce sync.Once + file_errors_proto_rawDescData = file_errors_proto_rawDesc +) + +func file_errors_proto_rawDescGZIP() []byte { + file_errors_proto_rawDescOnce.Do(func() { + file_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_proto_rawDescData) + }) + return file_errors_proto_rawDescData +} + +var file_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_errors_proto_goTypes = []interface{}{ + (*Status)(nil), // 0: errors.Status + nil, // 1: errors.Status.MetadataEntry + (*descriptorpb.EnumOptions)(nil), // 2: google.protobuf.EnumOptions + (*descriptorpb.EnumValueOptions)(nil), // 3: google.protobuf.EnumValueOptions +} +var file_errors_proto_depIdxs = []int32{ + 1, // 0: errors.Status.metadata:type_name -> errors.Status.MetadataEntry + 2, // 1: errors.default_code:extendee -> google.protobuf.EnumOptions + 3, // 2: errors.code:extendee -> google.protobuf.EnumValueOptions + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 1, // [1:3] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_errors_proto_init() } +func file_errors_proto_init() { + if File_errors_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Status); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_errors_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 2, + NumServices: 0, + }, + GoTypes: file_errors_proto_goTypes, + DependencyIndexes: file_errors_proto_depIdxs, + MessageInfos: file_errors_proto_msgTypes, + ExtensionInfos: file_errors_proto_extTypes, + }.Build() + File_errors_proto = out.File + file_errors_proto_rawDesc = nil + file_errors_proto_goTypes = nil + file_errors_proto_depIdxs = nil +} diff --git a/errors/errors.proto b/errors/errors.proto new file mode 100644 index 0000000..0572677 --- /dev/null +++ b/errors/errors.proto @@ -0,0 +1,16 @@ +syntax="proto3"; +package errors; +option go_package="git.diulo.com/mogfee/kit/errors;errors"; +import "google/protobuf/descriptor.proto"; +message Status{ + int32 code=1; + string reason=2; + string message=3; + mapmetadata=4; +} +extend google.protobuf.EnumOptions{ + int32 default_code=1108; +} +extend google.protobuf.EnumValueOptions{ + int32 code=1109; +} \ No newline at end of file diff --git a/errors/warp.go b/errors/warp.go new file mode 100644 index 0000000..24f56f0 --- /dev/null +++ b/errors/warp.go @@ -0,0 +1,16 @@ +package errors + +import ( + stderrors "errors" +) + +func Is(err, target error) bool { + return stderrors.Is(err, target) +} + +func As(err error, target any) bool { + return stderrors.As(err, target) +} +func Unwarp(err error) error { + return stderrors.Unwrap(err) +} diff --git a/middleware/logger.go b/middleware/logger.go index 09b1e8f..0d497d9 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -9,7 +9,6 @@ func Logger(logger log.Logger) Middleware { //helper:=log.NewHelper(logger) return func(handler Handler) Handler { return func(ctx context.Context, a any) (any, error) { - //helper.Infof("") return handler(ctx, a) } } diff --git a/test/main.go b/test/main.go index 21bb7ed..b06aecf 100644 --- a/test/main.go +++ b/test/main.go @@ -1,16 +1,36 @@ package main import ( + "errors" "fmt" "git.diulo.com/mogfee/kit" + "git.diulo.com/mogfee/kit/transport/http" ) func main() { app := kit.New( kit.Name("user-server"), - kit.Server()) + kit.Server(appServer())) fmt.Println("run start") app.Run() fmt.Println("run end") app.Stop() } +func appServer() *http.Server { + srv := http.NewServer(http.Address("localhost:9019")) + group := srv.Route("/") + group.Handle("GET", "/", func(ctx http.Context) error { + return errors.New("abc ") + }) + //group.GET("/", func(ctx http.Context) error { + // return ctx.String(200, "index page") + //}) + //group.GET("/hello", func(ctx http.Context) error { + // return ctx.String(200, "hello page") + //}) + //group.GET("/error", func(ctx http.Context) error { + // fmt.Println("err 1") + // return errors.New(400, "BAD_REQUEST", "") + //}) + return srv +} diff --git a/third_party/errors/errors.proto b/third_party/errors/errors.proto new file mode 100644 index 0000000..0572677 --- /dev/null +++ b/third_party/errors/errors.proto @@ -0,0 +1,16 @@ +syntax="proto3"; +package errors; +option go_package="git.diulo.com/mogfee/kit/errors;errors"; +import "google/protobuf/descriptor.proto"; +message Status{ + int32 code=1; + string reason=2; + string message=3; + mapmetadata=4; +} +extend google.protobuf.EnumOptions{ + int32 default_code=1108; +} +extend google.protobuf.EnumValueOptions{ + int32 code=1109; +} \ No newline at end of file diff --git a/proto/google/api/annotations.proto b/third_party/google/api/annotations.proto similarity index 100% rename from proto/google/api/annotations.proto rename to third_party/google/api/annotations.proto diff --git a/proto/google/api/client.proto b/third_party/google/api/client.proto similarity index 100% rename from proto/google/api/client.proto rename to third_party/google/api/client.proto diff --git a/proto/google/api/field_behavior.proto b/third_party/google/api/field_behavior.proto similarity index 100% rename from proto/google/api/field_behavior.proto rename to third_party/google/api/field_behavior.proto diff --git a/proto/google/api/http.proto b/third_party/google/api/http.proto similarity index 100% rename from proto/google/api/http.proto rename to third_party/google/api/http.proto diff --git a/proto/google/api/httpbody.proto b/third_party/google/api/httpbody.proto similarity index 100% rename from proto/google/api/httpbody.proto rename to third_party/google/api/httpbody.proto diff --git a/proto/google/protobuf/any.proto b/third_party/google/protobuf/any.proto similarity index 100% rename from proto/google/protobuf/any.proto rename to third_party/google/protobuf/any.proto diff --git a/proto/google/protobuf/api.proto b/third_party/google/protobuf/api.proto similarity index 100% rename from proto/google/protobuf/api.proto rename to third_party/google/protobuf/api.proto diff --git a/proto/google/protobuf/compiler/plugin.proto b/third_party/google/protobuf/compiler/plugin.proto similarity index 100% rename from proto/google/protobuf/compiler/plugin.proto rename to third_party/google/protobuf/compiler/plugin.proto diff --git a/proto/google/protobuf/descriptor.proto b/third_party/google/protobuf/descriptor.proto similarity index 100% rename from proto/google/protobuf/descriptor.proto rename to third_party/google/protobuf/descriptor.proto diff --git a/proto/google/protobuf/duration.proto b/third_party/google/protobuf/duration.proto similarity index 100% rename from proto/google/protobuf/duration.proto rename to third_party/google/protobuf/duration.proto diff --git a/proto/google/protobuf/empty.proto b/third_party/google/protobuf/empty.proto similarity index 100% rename from proto/google/protobuf/empty.proto rename to third_party/google/protobuf/empty.proto diff --git a/proto/google/protobuf/field_mask.proto b/third_party/google/protobuf/field_mask.proto similarity index 100% rename from proto/google/protobuf/field_mask.proto rename to third_party/google/protobuf/field_mask.proto diff --git a/proto/google/protobuf/source_context.proto b/third_party/google/protobuf/source_context.proto similarity index 100% rename from proto/google/protobuf/source_context.proto rename to third_party/google/protobuf/source_context.proto diff --git a/proto/google/protobuf/struct.proto b/third_party/google/protobuf/struct.proto similarity index 100% rename from proto/google/protobuf/struct.proto rename to third_party/google/protobuf/struct.proto diff --git a/proto/google/protobuf/timestamp.proto b/third_party/google/protobuf/timestamp.proto similarity index 100% rename from proto/google/protobuf/timestamp.proto rename to third_party/google/protobuf/timestamp.proto diff --git a/proto/google/protobuf/type.proto b/third_party/google/protobuf/type.proto similarity index 100% rename from proto/google/protobuf/type.proto rename to third_party/google/protobuf/type.proto diff --git a/proto/google/protobuf/wrappers.proto b/third_party/google/protobuf/wrappers.proto similarity index 100% rename from proto/google/protobuf/wrappers.proto rename to third_party/google/protobuf/wrappers.proto diff --git a/proto/validate/validate.proto b/third_party/validate/validate.proto similarity index 100% rename from proto/validate/validate.proto rename to third_party/validate/validate.proto diff --git a/transport/http/calloption.go b/transport/http/calloption.go new file mode 100644 index 0000000..7b0ed82 --- /dev/null +++ b/transport/http/calloption.go @@ -0,0 +1,109 @@ +package http + +import ( + "net/http" +) + +// CallOption configures a Call before it starts or extracts information from +// a Call after it completes. +type CallOption interface { + // before is called before the call is sent to any server. If before + // returns a non-nil error, the RPC fails with that error. + before(*callInfo) error + + // after is called after the call has completed. after cannot return an + // error, so any failures should be reported via output parameters. + after(*callInfo, *csAttempt) +} + +type callInfo struct { + contentType string + operation string + pathTemplate string +} + +// EmptyCallOption does not alter the Call configuration. +// It can be embedded in another structure to carry satellite data for use +// by interceptors. +type EmptyCallOption struct{} + +func (EmptyCallOption) before(*callInfo) error { return nil } +func (EmptyCallOption) after(*callInfo, *csAttempt) {} + +type csAttempt struct { + res *http.Response +} + +// ContentType with request content type. +func ContentType(contentType string) CallOption { + return ContentTypeCallOption{ContentType: contentType} +} + +// ContentTypeCallOption is BodyCallOption +type ContentTypeCallOption struct { + EmptyCallOption + ContentType string +} + +func (o ContentTypeCallOption) before(c *callInfo) error { + c.contentType = o.ContentType + return nil +} + +func defaultCallInfo(path string) callInfo { + return callInfo{ + contentType: "application/json", + operation: path, + pathTemplate: path, + } +} + +// Operation is serviceMethod call option +func Operation(operation string) CallOption { + return OperationCallOption{Operation: operation} +} + +// OperationCallOption is set ServiceMethod for client call +type OperationCallOption struct { + EmptyCallOption + Operation string +} + +func (o OperationCallOption) before(c *callInfo) error { + c.operation = o.Operation + return nil +} + +// PathTemplate is http path template +func PathTemplate(pattern string) CallOption { + return PathTemplateCallOption{Pattern: pattern} +} + +// PathTemplateCallOption is set path template for client call +type PathTemplateCallOption struct { + EmptyCallOption + Pattern string +} + +func (o PathTemplateCallOption) before(c *callInfo) error { + c.pathTemplate = o.Pattern + return nil +} + +// Header returns a CallOptions that retrieves the http response header +// from server reply. +func Header(header *http.Header) CallOption { + return HeaderCallOption{header: header} +} + +// HeaderCallOption is retrieve response header for client call +type HeaderCallOption struct { + EmptyCallOption + header *http.Header +} + +func (o HeaderCallOption) after(c *callInfo, cs *csAttempt) { + if cs.res != nil && cs.res.Header != nil { + *o.header = cs.res.Header + } +} diff --git a/transport/http/codec.go b/transport/http/codec.go index f89a29b..869e7b1 100644 --- a/transport/http/codec.go +++ b/transport/http/codec.go @@ -81,7 +81,7 @@ func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) { return } w.Header().Set("Content-Type", httputil.ContentType(codec.Name())) - w.WriteHeader(int(se.Status)) + w.WriteHeader(int(se.Status.Code)) _, _ = w.Write(body) } func CodeForRequest(r *http.Request, name string) (encoding.Codec, bool) { diff --git a/transport/http/server.go b/transport/http/server.go index 4a89bda..57b1276 100644 --- a/transport/http/server.go +++ b/transport/http/server.go @@ -213,6 +213,9 @@ func (s *Server) WalkRoute(fn WalkRouteFunc) error { } // Route registers an HTTP router. +func (s *Server) Kind() transport.Kind { + return transport.KindHTTP +} func (s *Server) Route(prefix string, filters ...FilterFunc) *Router { return newRouter(prefix, s, filters...) } @@ -242,6 +245,37 @@ func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { s.Handler.ServeHTTP(res, req) } +func (s *Server) Endpoint() (*url.URL, error) { + if err := s.listenAndEndpoint(); err != nil { + return nil, err + } + return s.endpoint, nil +} + +func (s *Server) Start(ctx context.Context) error { + if err := s.listenAndEndpoint(); err != nil { + return err + } + s.BaseContext = func(listener net.Listener) context.Context { + return ctx + } + log.Infof("[HTTP] server listening on: %s", s.endpoint) + var err error + if s.tlsConf != nil { + err = s.ServeTLS(s.lis, "", "") + } else { + err = s.Serve(s.lis) + } + if !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil +} + +func (s *Server) Stop(ctx context.Context) error { + log.Info("[HTTP] server stopping") + return s.Shutdown(ctx) +} func (s *Server) filter() mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { @@ -266,7 +300,7 @@ func (s *Server) filter() mux.MiddlewareFunc { operation: pathTemplate, pathTemplate: pathTemplate, reqHeader: headerCarrier(req.Header), - replyHeader: headerCarrier(w.Header()), + replyHeader: headerCarrier(req.Header), request: req, } if s.endpoint != nil { @@ -277,46 +311,6 @@ func (s *Server) filter() mux.MiddlewareFunc { }) } } - -// Endpoint return a real address to registry endpoint. -// examples: -// -// https://127.0.0.1:8000 -// Legacy: http://127.0.0.1:8000?isSecure=false -func (s *Server) Endpoint() (*url.URL, error) { - if err := s.listenAndEndpoint(); err != nil { - return nil, err - } - return s.endpoint, nil -} - -// Start start the HTTP server. -func (s *Server) Start(ctx context.Context) error { - if err := s.listenAndEndpoint(); err != nil { - return err - } - s.BaseContext = func(net.Listener) context.Context { - return ctx - } - log.Infof("[HTTP] server listening on: %s", s.lis.Addr().String()) - var err error - if s.tlsConf != nil { - err = s.ServeTLS(s.lis, "", "") - } else { - err = s.Serve(s.lis) - } - if !errors.Is(err, http.ErrServerClosed) { - return err - } - return nil -} - -// Stop stop the HTTP server. -func (s *Server) Stop(ctx context.Context) error { - log.Info("[HTTP] server stopping") - return s.Shutdown(ctx) -} - func (s *Server) listenAndEndpoint() error { if s.lis == nil { lis, err := net.Listen(s.network, s.address) diff --git a/transport/http/status/status.go b/transport/http/status/status.go new file mode 100644 index 0000000..a9510cf --- /dev/null +++ b/transport/http/status/status.go @@ -0,0 +1,96 @@ +package status + +import ( + "google.golang.org/grpc/codes" + "net/http" +) + +const ( + ClientClosed = 499 +) + +type Converter interface { + ToGRPCCode(code int) codes.Code + FromGRPCCode(code codes.Code) int +} + +var DefaultConverter Converter = statusConverter{} + +type statusConverter struct { +} + +func (s statusConverter) ToGRPCCode(code int) codes.Code { + switch code { + case http.StatusOK: + return codes.OK + case http.StatusBadRequest: + return codes.InvalidArgument + case http.StatusUnauthorized: + return codes.Unauthenticated + case http.StatusForbidden: + return codes.PermissionDenied + case http.StatusNotFound: + return codes.NotFound + case http.StatusConflict: + return codes.Aborted + case http.StatusTooManyRequests: + return codes.ResourceExhausted + case http.StatusInternalServerError: + return codes.Internal + case http.StatusNotImplemented: + return codes.Unimplemented + case http.StatusServiceUnavailable: + return codes.Unavailable + case http.StatusGatewayTimeout: + return codes.DeadlineExceeded + case ClientClosed: + return codes.Canceled + } + return codes.Unknown +} + +func (s statusConverter) FromGRPCCode(code codes.Code) int { + switch code { + case codes.OK: + return http.StatusOK + case codes.Canceled: + return ClientClosed + case codes.Unknown: + return http.StatusInternalServerError + case codes.InvalidArgument: + return http.StatusBadRequest + case codes.DeadlineExceeded: + return http.StatusGatewayTimeout + case codes.NotFound: + return http.StatusNotFound + case codes.AlreadyExists: + return http.StatusConflict + case codes.PermissionDenied: + return http.StatusForbidden + case codes.Unauthenticated: + return http.StatusUnauthorized + case codes.ResourceExhausted: + return http.StatusTooManyRequests + case codes.FailedPrecondition: + return http.StatusBadRequest + case codes.Aborted: + return http.StatusConflict + case codes.OutOfRange: + return http.StatusBadRequest + case codes.Unimplemented: + return http.StatusNotImplemented + case codes.Internal: + return http.StatusInternalServerError + case codes.Unavailable: + return http.StatusServiceUnavailable + case codes.DataLoss: + return http.StatusInternalServerError + } + return http.StatusInternalServerError +} +func ToGRPCCode(code int) codes.Code { + return DefaultConverter.ToGRPCCode(code) +} +func FromGRPCCode(code codes.Code) int { + return DefaultConverter.FromGRPCCode(code) +}