diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4641e321f..1099f2a06 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,9 +21,9 @@ The description of the title will be attached in Release Notes, so please describe it from user-oriented, what this PR does / why we need it. Please check your PR title with the below requirements: --> -- [ ] This PR title match the format: \(optional scope): \ +- [ ] This PR title match the format: `(optional scope): `. - [ ] The description of this PR title is user-oriented and clear enough for others to understand. - +- [ ] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io). #### (Optional) Translate the PR title into Chinese. @@ -35,8 +35,13 @@ Provide more detailed info for review. If it is a perf type PR, perf data is sug en: zh(optional): -#### Which issue(s) this PR fixes: +#### (Optional) Which issue(s) this PR fixes: + +#### (Optional) The PR that updates user documentation: + diff --git a/cmd/hz/app/app.go b/cmd/hz/app/app.go index 835ee03e5..3f40e91e9 100644 --- a/cmd/hz/app/app.go +++ b/cmd/hz/app/app.go @@ -140,17 +140,16 @@ func Client(c *cli.Context) error { } func PluginMode() { - pluginName := filepath.Base(os.Args[0]) - if util.IsWindows() { - pluginName = strings.TrimSuffix(pluginName, ".exe") - } - switch pluginName { - case meta.ThriftPluginName: - plugin := new(thrift.Plugin) - os.Exit(plugin.Run()) - case meta.ProtocPluginName: - plugin := new(protobuf.Plugin) - os.Exit(plugin.Run()) + mode := os.Getenv(meta.EnvPluginMode) + if len(os.Args) <= 1 && mode != "" { + switch mode { + case meta.ThriftPluginName: + plugin := new(thrift.Plugin) + os.Exit(plugin.Run()) + case meta.ProtocPluginName: + plugin := new(protobuf.Plugin) + os.Exit(plugin.Run()) + } } } diff --git a/cmd/hz/config/cmd.go b/cmd/hz/config/cmd.go index b825992b7..983946c22 100644 --- a/cmd/hz/config/cmd.go +++ b/cmd/hz/config/cmd.go @@ -71,54 +71,6 @@ func lookupTool(idlType string) (string, error) { } } - exe, err := os.Executable() - if err != nil { - return "", fmt.Errorf("failed to get executable path: %s", err) - } - gopath, err := util.GetGOPATH() - if err != nil { - return "", err - } - // softlink the plugin to "$GOPATH/bin" - dir := gopath + string(os.PathSeparator) + "bin" - if tool == meta.TpCompilerProto { - pgh, err := exec.LookPath(meta.ProtocPluginName) - linkName := filepath.Join(dir, meta.ProtocPluginName) - if util.IsWindows() { - linkName = linkName + ".exe" - } - if err != nil { - err = link(exe, linkName) - if err != nil { - return "", err - } - } else { - err = link(exe, pgh) - if err != nil { - return "", err - } - } - } - - if tool == meta.TpCompilerThrift { - tgh, err := exec.LookPath(meta.ThriftPluginName) - linkName := filepath.Join(dir, meta.ThriftPluginName) - if util.IsWindows() { - linkName = linkName + ".exe" - } - if err != nil { - err = link(exe, linkName) - if err != nil { - return "", err - } - } else { - err = link(exe, tgh) - if err != nil { - return "", err - } - } - } - return path, nil } @@ -136,6 +88,11 @@ func link(src, dst string) error { } func BuildPluginCmd(args *Argument) (*exec.Cmd, error) { + exe, err := os.Executable() + if err != nil { + return nil, fmt.Errorf("failed to detect current executable, err: %v", err) + } + argPacks, err := args.Pack() if err != nil { return nil, err @@ -152,6 +109,7 @@ func BuildPluginCmd(args *Argument) (*exec.Cmd, error) { if args.IdlType == meta.IdlThrift { // thriftgo + os.Setenv(meta.EnvPluginMode, meta.ThriftPluginName) cmd.Args = append(cmd.Args, meta.TpCompilerThrift) for _, inc := range args.Includes { cmd.Args = append(cmd.Args, "-i", inc) @@ -167,7 +125,7 @@ func BuildPluginCmd(args *Argument) (*exec.Cmd, error) { cmd.Args = append(cmd.Args, "-o", args.ModelOutDir(), "-g", thriftOpt, - "-p", "hertz:"+kas, + "-p", "hertz="+exe+":"+kas, ) for _, p := range args.ThriftPlugins { cmd.Args = append(cmd.Args, "-p", p) @@ -177,6 +135,7 @@ func BuildPluginCmd(args *Argument) (*exec.Cmd, error) { } } else { // protoc + os.Setenv(meta.EnvPluginMode, meta.ProtocPluginName) cmd.Args = append(cmd.Args, meta.TpCompilerProto) for _, inc := range args.Includes { cmd.Args = append(cmd.Args, "-I", inc) @@ -185,6 +144,7 @@ func BuildPluginCmd(args *Argument) (*exec.Cmd, error) { cmd.Args = append(cmd.Args, "-I", filepath.Dir(inc)) } cmd.Args = append(cmd.Args, + "--plugin=protoc-gen-hertz="+exe, "--hertz_out="+args.OutDir, "--hertz_opt="+kas, ) diff --git a/cmd/hz/generator/handler.go b/cmd/hz/generator/handler.go index a39e0febd..dca3282e9 100644 --- a/cmd/hz/generator/handler.go +++ b/cmd/hz/generator/handler.go @@ -152,6 +152,9 @@ func (pkgGen *HttpPackageGenerator) updateHandler(handler interface{}, handlerTp if !isExist { return pkgGen.TemplateGenerator.Generate(handler, handlerTpl, filePath, noRepeat) } + if pkgGen.HandlerByMethod { // method by handler, do not need to insert new content + return nil + } file, err := ioutil.ReadFile(filePath) if err != nil { diff --git a/cmd/hz/meta/const.go b/cmd/hz/meta/const.go index 7e5e5db7c..0836c79cb 100644 --- a/cmd/hz/meta/const.go +++ b/cmd/hz/meta/const.go @@ -19,7 +19,7 @@ package meta import "runtime" // Version hz version -const Version = "v0.6.2" +const Version = "v0.6.3" // Mode hz run modes type Mode int @@ -29,6 +29,8 @@ const SysType = runtime.GOOS const WindowsOS = "windows" +const EnvPluginMode = "HERTZ_PLUGIN_MODE" + // hz Commands const ( CmdUpdate = "update" diff --git a/cmd/hz/protobuf/api/api.pb.go b/cmd/hz/protobuf/api/api.pb.go index 6a38b1b2c..9707626a4 100644 --- a/cmd/hz/protobuf/api/api.pb.go +++ b/cmd/hz/protobuf/api/api.pb.go @@ -1,17 +1,16 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v3.19.4 +// protoc v3.21.12 // source: api.proto package api import ( - reflect "reflect" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" ) const ( @@ -86,14 +85,6 @@ var file_api_proto_extTypes = []protoimpl.ExtensionInfo{ Tag: "bytes,50108,opt,name=form", Filename: "api.proto", }, - { - ExtendedType: (*descriptorpb.FieldOptions)(nil), - ExtensionType: (*string)(nil), - Field: 51001, - Name: "api.go_tag", - Tag: "bytes,51001,opt,name=go_tag", - Filename: "api.proto", - }, { ExtendedType: (*descriptorpb.FieldOptions)(nil), ExtensionType: (*string)(nil), @@ -110,6 +101,54 @@ var file_api_proto_extTypes = []protoimpl.ExtensionInfo{ Tag: "bytes,50110,opt,name=file_name", Filename: "api.proto", }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50111, + Name: "api.none", + Tag: "bytes,50111,opt,name=none", + Filename: "api.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50131, + Name: "api.form_compatible", + Tag: "bytes,50131,opt,name=form_compatible", + Filename: "api.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50132, + Name: "api.js_conv_compatible", + Tag: "bytes,50132,opt,name=js_conv_compatible", + Filename: "api.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50133, + Name: "api.file_name_compatible", + Tag: "bytes,50133,opt,name=file_name_compatible", + Filename: "api.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50134, + Name: "api.none_compatible", + Tag: "bytes,50134,opt,name=none_compatible", + Filename: "api.proto", + }, + { + ExtendedType: (*descriptorpb.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 51001, + Name: "api.go_tag", + Tag: "bytes,51001,opt,name=go_tag", + Filename: "api.proto", + }, { ExtendedType: (*descriptorpb.MethodOptions)(nil), ExtensionType: (*string)(nil), @@ -246,6 +285,14 @@ var file_api_proto_extTypes = []protoimpl.ExtensionInfo{ Tag: "bytes,50309,opt,name=handler_path", Filename: "api.proto", }, + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50331, + Name: "api.handler_path_compatible", + Tag: "bytes,50331,opt,name=handler_path_compatible", + Filename: "api.proto", + }, { ExtendedType: (*descriptorpb.EnumValueOptions)(nil), ExtensionType: (*int32)(nil), @@ -262,6 +309,14 @@ var file_api_proto_extTypes = []protoimpl.ExtensionInfo{ Tag: "bytes,50402,opt,name=base_domain", Filename: "api.proto", }, + { + ExtendedType: (*descriptorpb.ServiceOptions)(nil), + ExtensionType: (*string)(nil), + Field: 50731, + Name: "api.base_domain_compatible", + Tag: "bytes,50731,opt,name=base_domain_compatible", + Filename: "api.proto", + }, } // Extension fields to descriptorpb.FieldOptions. @@ -282,62 +337,82 @@ var ( E_Vd = &file_api_proto_extTypes[6] // optional string form = 50108; E_Form = &file_api_proto_extTypes[7] - // optional string go_tag = 51001; - E_GoTag = &file_api_proto_extTypes[8] // optional string js_conv = 50109; - E_JsConv = &file_api_proto_extTypes[9] + E_JsConv = &file_api_proto_extTypes[8] // optional string file_name = 50110; - E_FileName = &file_api_proto_extTypes[10] + E_FileName = &file_api_proto_extTypes[9] + // optional string none = 50111; + E_None = &file_api_proto_extTypes[10] + // 50131~50160 used to extend field option by hz + // + // optional string form_compatible = 50131; + E_FormCompatible = &file_api_proto_extTypes[11] + // optional string js_conv_compatible = 50132; + E_JsConvCompatible = &file_api_proto_extTypes[12] + // optional string file_name_compatible = 50133; + E_FileNameCompatible = &file_api_proto_extTypes[13] + // optional string none_compatible = 50134; + E_NoneCompatible = &file_api_proto_extTypes[14] + // optional string go_tag = 51001; + E_GoTag = &file_api_proto_extTypes[15] ) // Extension fields to descriptorpb.MethodOptions. var ( // optional string get = 50201; - E_Get = &file_api_proto_extTypes[11] + E_Get = &file_api_proto_extTypes[16] // optional string post = 50202; - E_Post = &file_api_proto_extTypes[12] + E_Post = &file_api_proto_extTypes[17] // optional string put = 50203; - E_Put = &file_api_proto_extTypes[13] + E_Put = &file_api_proto_extTypes[18] // optional string delete = 50204; - E_Delete = &file_api_proto_extTypes[14] + E_Delete = &file_api_proto_extTypes[19] // optional string patch = 50205; - E_Patch = &file_api_proto_extTypes[15] + E_Patch = &file_api_proto_extTypes[20] // optional string options = 50206; - E_Options = &file_api_proto_extTypes[16] + E_Options = &file_api_proto_extTypes[21] // optional string head = 50207; - E_Head = &file_api_proto_extTypes[17] + E_Head = &file_api_proto_extTypes[22] // optional string any = 50208; - E_Any = &file_api_proto_extTypes[18] + E_Any = &file_api_proto_extTypes[23] // optional string gen_path = 50301; - E_GenPath = &file_api_proto_extTypes[19] // The path specified by the user when the client code is generated, with a higher priority than api_version + E_GenPath = &file_api_proto_extTypes[24] // The path specified by the user when the client code is generated, with a higher priority than api_version // optional string api_version = 50302; - E_ApiVersion = &file_api_proto_extTypes[20] // Specify the value of the :version variable in path when the client code is generated + E_ApiVersion = &file_api_proto_extTypes[25] // Specify the value of the :version variable in path when the client code is generated // optional string tag = 50303; - E_Tag = &file_api_proto_extTypes[21] // rpc tag, can be multiple, separated by commas + E_Tag = &file_api_proto_extTypes[26] // rpc tag, can be multiple, separated by commas // optional string name = 50304; - E_Name = &file_api_proto_extTypes[22] // Name of rpc + E_Name = &file_api_proto_extTypes[27] // Name of rpc // optional string api_level = 50305; - E_ApiLevel = &file_api_proto_extTypes[23] // Interface Level + E_ApiLevel = &file_api_proto_extTypes[28] // Interface Level // optional string serializer = 50306; - E_Serializer = &file_api_proto_extTypes[24] // Serialization method + E_Serializer = &file_api_proto_extTypes[29] // Serialization method // optional string param = 50307; - E_Param = &file_api_proto_extTypes[25] // Whether client requests take public parameters + E_Param = &file_api_proto_extTypes[30] // Whether client requests take public parameters // optional string baseurl = 50308; - E_Baseurl = &file_api_proto_extTypes[26] // Baseurl used in ttnet routing + E_Baseurl = &file_api_proto_extTypes[31] // Baseurl used in ttnet routing // optional string handler_path = 50309; - E_HandlerPath = &file_api_proto_extTypes[27] // handler_path specifies the path to generate the method + E_HandlerPath = &file_api_proto_extTypes[32] // handler_path specifies the path to generate the method + // 50331~50360 used to extend method option by hz + // + // optional string handler_path_compatible = 50331; + E_HandlerPathCompatible = &file_api_proto_extTypes[33] // handler_path specifies the path to generate the method ) // Extension fields to descriptorpb.EnumValueOptions. var ( // optional int32 http_code = 50401; - E_HttpCode = &file_api_proto_extTypes[28] + E_HttpCode = &file_api_proto_extTypes[34] ) // Extension fields to descriptorpb.ServiceOptions. var ( // optional string base_domain = 50402; - E_BaseDomain = &file_api_proto_extTypes[29] + E_BaseDomain = &file_api_proto_extTypes[35] + // 50731~50760 used to extend service option by hz + // + // optional string base_domain_compatible = 50731; + E_BaseDomainCompatible = &file_api_proto_extTypes[36] ) var File_api_proto protoreflect.FileDescriptor @@ -373,88 +448,122 @@ var file_api_proto_rawDesc = []byte{ 0x76, 0x64, 0x3a, 0x33, 0x0a, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbc, 0x87, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x36, 0x0a, 0x06, 0x67, 0x6f, 0x5f, 0x74, 0x61, - 0x67, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0xb9, 0x8e, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x6f, 0x54, 0x61, 0x67, 0x3a, - 0x38, 0x0a, 0x07, 0x6a, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x76, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbd, 0x87, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6a, 0x73, 0x43, 0x6f, 0x6e, 0x76, 0x3a, 0x3c, 0x0a, 0x09, 0x66, 0x69, 0x6c, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbe, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, - 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x32, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x12, 0x1e, + 0x09, 0x52, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x38, 0x0a, 0x07, 0x6a, 0x73, 0x5f, 0x63, 0x6f, + 0x6e, 0x76, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0xbd, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x73, 0x43, 0x6f, 0x6e, + 0x76, 0x3a, 0x3c, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbe, 0x87, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x3a, + 0x33, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbf, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x6f, 0x6e, 0x65, 0x3a, 0x48, 0x0a, 0x0f, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd3, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x66, 0x6f, 0x72, 0x6d, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x4d, + 0x0a, 0x12, 0x6a, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x76, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, + 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6a, 0x73, 0x43, + 0x6f, 0x6e, 0x76, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x51, 0x0a, + 0x14, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69, + 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, + 0x3a, 0x48, 0x0a, 0x0f, 0x6e, 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, + 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0xd6, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x6e, 0x65, + 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x36, 0x0a, 0x06, 0x67, 0x6f, + 0x5f, 0x74, 0x61, 0x67, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0xb9, 0x8e, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x6f, 0x54, + 0x61, 0x67, 0x3a, 0x32, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x99, 0x88, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x67, 0x65, 0x74, 0x3a, 0x34, 0x0a, 0x04, 0x70, 0x6f, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x99, - 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x67, 0x65, 0x74, 0x3a, 0x34, 0x0a, 0x04, 0x70, - 0x6f, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9a, + 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x73, 0x74, 0x3a, 0x32, 0x0a, 0x03, + 0x70, 0x75, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x9a, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x73, - 0x74, 0x3a, 0x32, 0x0a, 0x03, 0x70, 0x75, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9b, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x70, 0x75, 0x74, 0x3a, 0x38, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, - 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x9c, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x3a, - 0x36, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9d, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x3a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x9e, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x3a, 0x34, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x2e, 0x67, 0x6f, + 0x6f, 0x6e, 0x73, 0x18, 0x9b, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x75, 0x74, + 0x3a, 0x38, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9c, 0x88, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61, + 0x74, 0x63, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x9d, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x3a, 0x3a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9e, 0x88, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x34, + 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9f, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x65, 0x61, 0x64, 0x3a, 0x32, 0x0a, 0x03, 0x61, 0x6e, 0x79, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9f, 0x88, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x3a, 0x32, 0x0a, 0x03, 0x61, 0x6e, 0x79, - 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0xa0, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6e, 0x79, 0x3a, 0x3b, 0x0a, - 0x08, 0x67, 0x65, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfd, 0x88, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x41, 0x0a, 0x0b, 0x61, 0x70, - 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfe, 0x88, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x32, 0x0a, - 0x03, 0x74, 0x61, 0x67, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa0, 0x88, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6e, 0x79, 0x3a, 0x3b, 0x0a, 0x08, 0x67, 0x65, 0x6e, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xff, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, - 0x67, 0x3a, 0x34, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x80, 0x89, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x3d, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x5f, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfd, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x65, + 0x6e, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x41, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x81, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x70, - 0x69, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x40, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfe, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, + 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x32, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x12, + 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0xff, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x3a, 0x34, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x82, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x18, 0x83, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, - 0x3a, 0x3a, 0x0a, 0x07, 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x12, 0x1e, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x84, 0x89, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x3a, 0x43, 0x0a, 0x0c, - 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x2e, 0x67, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x80, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x3a, 0x3d, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x81, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x70, 0x69, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x3a, 0x40, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, + 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x82, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x72, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x85, 0x89, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50, 0x61, 0x74, - 0x68, 0x3a, 0x40, 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x5f, 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, 0xe1, 0x89, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x68, 0x74, 0x74, 0x70, 0x43, - 0x6f, 0x64, 0x65, 0x3a, 0x42, 0x0a, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0xe2, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x62, 0x61, 0x73, - 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x83, 0x89, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x3a, 0x3a, 0x0a, 0x07, 0x62, + 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x84, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x3a, 0x43, 0x0a, 0x0c, 0x68, 0x61, 0x6e, 0x64, 0x6c, + 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x85, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x58, 0x0a, 0x17, + 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9b, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x15, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6d, 0x70, + 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x40, 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x5f, 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, 0xe1, 0x89, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x68, 0x74, 0x74, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x3a, 0x42, 0x0a, 0x0b, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe2, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x62, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x3a, 0x57, 0x0a, 0x16, + 0x62, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xab, 0x8c, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x14, 0x62, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61, + 0x74, 0x69, 0x62, 0x6c, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69, } var file_api_proto_goTypes = []interface{}{ @@ -472,32 +581,39 @@ var file_api_proto_depIdxs = []int32{ 0, // 5: api.path:extendee -> google.protobuf.FieldOptions 0, // 6: api.vd:extendee -> google.protobuf.FieldOptions 0, // 7: api.form:extendee -> google.protobuf.FieldOptions - 0, // 8: api.go_tag:extendee -> google.protobuf.FieldOptions - 0, // 9: api.js_conv:extendee -> google.protobuf.FieldOptions - 0, // 10: api.file_name:extendee -> google.protobuf.FieldOptions - 1, // 11: api.get:extendee -> google.protobuf.MethodOptions - 1, // 12: api.post:extendee -> google.protobuf.MethodOptions - 1, // 13: api.put:extendee -> google.protobuf.MethodOptions - 1, // 14: api.delete:extendee -> google.protobuf.MethodOptions - 1, // 15: api.patch:extendee -> google.protobuf.MethodOptions - 1, // 16: api.options:extendee -> google.protobuf.MethodOptions - 1, // 17: api.head:extendee -> google.protobuf.MethodOptions - 1, // 18: api.any:extendee -> google.protobuf.MethodOptions - 1, // 19: api.gen_path:extendee -> google.protobuf.MethodOptions - 1, // 20: api.api_version:extendee -> google.protobuf.MethodOptions - 1, // 21: api.tag:extendee -> google.protobuf.MethodOptions - 1, // 22: api.name:extendee -> google.protobuf.MethodOptions - 1, // 23: api.api_level:extendee -> google.protobuf.MethodOptions - 1, // 24: api.serializer:extendee -> google.protobuf.MethodOptions - 1, // 25: api.param:extendee -> google.protobuf.MethodOptions - 1, // 26: api.baseurl:extendee -> google.protobuf.MethodOptions - 1, // 27: api.handler_path:extendee -> google.protobuf.MethodOptions - 2, // 28: api.http_code:extendee -> google.protobuf.EnumValueOptions - 3, // 29: api.base_domain:extendee -> google.protobuf.ServiceOptions - 30, // [30:30] is the sub-list for method output_type - 30, // [30:30] is the sub-list for method input_type - 30, // [30:30] is the sub-list for extension type_name - 0, // [0:30] is the sub-list for extension extendee + 0, // 8: api.js_conv:extendee -> google.protobuf.FieldOptions + 0, // 9: api.file_name:extendee -> google.protobuf.FieldOptions + 0, // 10: api.none:extendee -> google.protobuf.FieldOptions + 0, // 11: api.form_compatible:extendee -> google.protobuf.FieldOptions + 0, // 12: api.js_conv_compatible:extendee -> google.protobuf.FieldOptions + 0, // 13: api.file_name_compatible:extendee -> google.protobuf.FieldOptions + 0, // 14: api.none_compatible:extendee -> google.protobuf.FieldOptions + 0, // 15: api.go_tag:extendee -> google.protobuf.FieldOptions + 1, // 16: api.get:extendee -> google.protobuf.MethodOptions + 1, // 17: api.post:extendee -> google.protobuf.MethodOptions + 1, // 18: api.put:extendee -> google.protobuf.MethodOptions + 1, // 19: api.delete:extendee -> google.protobuf.MethodOptions + 1, // 20: api.patch:extendee -> google.protobuf.MethodOptions + 1, // 21: api.options:extendee -> google.protobuf.MethodOptions + 1, // 22: api.head:extendee -> google.protobuf.MethodOptions + 1, // 23: api.any:extendee -> google.protobuf.MethodOptions + 1, // 24: api.gen_path:extendee -> google.protobuf.MethodOptions + 1, // 25: api.api_version:extendee -> google.protobuf.MethodOptions + 1, // 26: api.tag:extendee -> google.protobuf.MethodOptions + 1, // 27: api.name:extendee -> google.protobuf.MethodOptions + 1, // 28: api.api_level:extendee -> google.protobuf.MethodOptions + 1, // 29: api.serializer:extendee -> google.protobuf.MethodOptions + 1, // 30: api.param:extendee -> google.protobuf.MethodOptions + 1, // 31: api.baseurl:extendee -> google.protobuf.MethodOptions + 1, // 32: api.handler_path:extendee -> google.protobuf.MethodOptions + 1, // 33: api.handler_path_compatible:extendee -> google.protobuf.MethodOptions + 2, // 34: api.http_code:extendee -> google.protobuf.EnumValueOptions + 3, // 35: api.base_domain:extendee -> google.protobuf.ServiceOptions + 3, // 36: api.base_domain_compatible:extendee -> google.protobuf.ServiceOptions + 37, // [37:37] is the sub-list for method output_type + 37, // [37:37] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 0, // [0:37] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } @@ -513,7 +629,7 @@ func file_api_proto_init() { RawDescriptor: file_api_proto_rawDesc, NumEnums: 0, NumMessages: 0, - NumExtensions: 30, + NumExtensions: 37, NumServices: 0, }, GoTypes: file_api_proto_goTypes, diff --git a/cmd/hz/protobuf/api/api.proto b/cmd/hz/protobuf/api/api.proto index 5a5cd081d..a41e2f8d6 100644 --- a/cmd/hz/protobuf/api/api.proto +++ b/cmd/hz/protobuf/api/api.proto @@ -15,9 +15,17 @@ extend google.protobuf.FieldOptions { optional string path = 50106; optional string vd = 50107; optional string form = 50108; - optional string go_tag = 51001; optional string js_conv = 50109; optional string file_name = 50110; + optional string none = 50111; + + // 50131~50160 used to extend field option by hz + optional string form_compatible = 50131; + optional string js_conv_compatible = 50132; + optional string file_name_compatible = 50133; + optional string none_compatible = 50134; + + optional string go_tag = 51001; } extend google.protobuf.MethodOptions { @@ -38,12 +46,20 @@ extend google.protobuf.MethodOptions { optional string param = 50307; // Whether client requests take public parameters optional string baseurl = 50308; // Baseurl used in ttnet routing optional string handler_path = 50309; // handler_path specifies the path to generate the method + + // 50331~50360 used to extend method option by hz + optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method } extend google.protobuf.EnumValueOptions { optional int32 http_code = 50401; + + // 50431~50460 used to extend enum option by hz } extend google.protobuf.ServiceOptions { optional string base_domain = 50402; + + // 50731~50760 used to extend service option by hz + optional string base_domain_compatible = 50731; } \ No newline at end of file diff --git a/cmd/hz/protobuf/ast.go b/cmd/hz/protobuf/ast.go index 0e2803ef9..c86d21c92 100644 --- a/cmd/hz/protobuf/ast.go +++ b/cmd/hz/protobuf/ast.go @@ -31,6 +31,7 @@ import ( "google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/runtime/protoimpl" "google.golang.org/protobuf/types/descriptorpb" ) @@ -117,7 +118,7 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmd } service.BaseDomain = "" - domainAnno := checkFirstOption(api.E_BaseDomain, s.GetOptions()) + domainAnno := getCompatibleAnnotation(s.GetOptions(), api.E_BaseDomain, api.E_BaseDomainCompatible) if cmdType == meta.CmdClient { val, ok := domainAnno.(string) if ok && len(val) != 0 { @@ -136,7 +137,7 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmd path := vpath.(string) var handlerOutDir string - genPath := checkFirstOption(api.E_HandlerPath, m.GetOptions()) + genPath := getCompatibleAnnotation(m.GetOptions(), api.E_HandlerPath, api.E_HandlerPathCompatible) handlerOutDir, ok := genPath.(string) if !ok || len(handlerOutDir) == 0 { handlerOutDir = "" @@ -234,6 +235,16 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmd return out, nil } +func getCompatibleAnnotation(options proto.Message, anno *protoimpl.ExtensionInfo, compatibleAnno *protoimpl.ExtensionInfo) interface{} { + if proto.HasExtension(options, anno) { + return checkFirstOption(anno, options) + } else if proto.HasExtension(options, compatibleAnno) { + return checkFirstOption(compatibleAnno, options) + } + + return nil +} + func parseAnnotationToClient(clientMethod *generator.ClientMethod, gen *protogen.Plugin, ast *descriptorpb.FileDescriptorProto, m *descriptorpb.MethodDescriptorProto) error { file, exist := gen.FilesByPath[ast.GetName()] if !exist { @@ -284,9 +295,8 @@ func parseAnnotationToClient(clientMethod *generator.ClientMethod, gen *protogen } } - if proto.HasExtension(f.Desc.Options(), api.E_Form) { + if formAnnos := getCompatibleAnnotation(f.Desc.Options(), api.E_Form, api.E_FormCompatible); formAnnos != nil { hasAnnotation = true - formAnnos := proto.GetExtension(f.Desc.Options(), api.E_Form) hasFormAnnotation = true val := formAnnos.(string) if isStringFieldType { @@ -301,9 +311,8 @@ func parseAnnotationToClient(clientMethod *generator.ClientMethod, gen *protogen hasBodyAnnotation = true } - if proto.HasExtension(f.Desc.Options(), api.E_FileName) { + if fileAnnos := getCompatibleAnnotation(f.Desc.Options(), api.E_FileName, api.E_FileNameCompatible); fileAnnos != nil { hasAnnotation = true - fileAnnos := proto.GetExtension(f.Desc.Options(), api.E_FileName) hasFormAnnotation = true val := fileAnnos.(string) clientMethod.FormFileCode += fmt.Sprintf("%q: req.Get%s(),\n", val, f.GoName) diff --git a/cmd/hz/protobuf/tags.go b/cmd/hz/protobuf/tags.go index 30ba1b867..23c046490 100644 --- a/cmd/hz/protobuf/tags.go +++ b/cmd/hz/protobuf/tags.go @@ -84,8 +84,9 @@ var ( api.E_Cookie: "cookie", api.E_Body: "json", // Do not change the relative order of "api.E_Form" and "api.E_Body", so that "api.E_Form" can overwrite the form tag generated by "api.E_Body" - api.E_Form: "form", - api.E_RawBody: "raw_body", + api.E_Form: "form", + api.E_FormCompatible: "form", + api.E_RawBody: "raw_body", } ValidatorTags = map[*protoimpl.ExtensionInfo]string{api.E_Vd: "vd"} @@ -165,6 +166,8 @@ func jsonTag(f *descriptorpb.FieldDescriptorProto) (ret model.Tag) { ret.Value = checkSnakeName(f.GetJsonName()) if v := checkFirstOption(api.E_JsConv, f.GetOptions()); v != nil { ret.Value += ",string" + } else if v := checkFirstOption(api.E_JsConvCompatible, f.GetOptions()); v != nil { + ret.Value += ",string" } if !unsetOmitempty && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL { ret.Value += ",omitempty" @@ -234,6 +237,8 @@ func injectTagsToModel(f *descriptorpb.FieldDescriptorProto, gf *model.Field, ne func getJsonValue(f *descriptorpb.FieldDescriptorProto, val string) string { if v := checkFirstOption(api.E_JsConv, f.GetOptions()); v != nil { val += ",string" + } else if v := checkFirstOption(api.E_JsConvCompatible, f.GetOptions()); v != nil { + val += ",string" } if !unsetOmitempty && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL { val += ",omitempty" @@ -285,11 +290,13 @@ func defaultBindingStructTags(f protoreflect.FieldDescriptor) []model.Tag { api.E_Path, api.E_Query, api.E_Form, + api.E_FormCompatible, api.E_Header, api.E_Cookie, api.E_Body, api.E_RawBody, } + // If the user provides an annotation, return json tag directly for _, tag := range bindingTags { if vv := checkFirstOption(tag, opts); vv != nil { out[0] = reflectJsonTag(f) @@ -314,8 +321,13 @@ func defaultBindingStructTags(f protoreflect.FieldDescriptor) []model.Tag { val := checkStructRequire(f, v.(string)) out[2] = tag(BindingTags[api.E_Form], val) } else { - val := checkStructRequire(f, checkSnakeName(string(f.Name()))) - out[2] = tag(BindingTags[api.E_Form], val) + if v := checkFirstOption(api.E_FormCompatible, opts); v != nil { // compatible form_compatible + val := checkStructRequire(f, v.(string)) + out[2] = tag(BindingTags[api.E_Form], val) + } else { + val := checkStructRequire(f, checkSnakeName(string(f.Name()))) + out[2] = tag(BindingTags[api.E_Form], val) + } } return out } @@ -370,11 +382,24 @@ func injectTagsToStructTags(f protoreflect.FieldDescriptor, out *structTags, nee }) } } - + disableTag := false + if vv := checkFirstOption(api.E_None, as); vv != nil { + if strings.EqualFold(vv.(string), "true") { + disableTag = true + } + } else if vv := checkFirstOption(api.E_NoneCompatible, as); vv != nil { + if strings.EqualFold(vv.(string), "true") { + disableTag = true + } + } // protobuf tag as first sort.Sort(tags[1:]) for _, t := range tags { - *out = append(*out, m2s(t)) + if disableTag { + *out = append(*out, [2]string{t.Key, "-"}) + } else { + *out = append(*out, m2s(t)) + } } return nil } @@ -382,6 +407,8 @@ func injectTagsToStructTags(f protoreflect.FieldDescriptor, out *structTags, nee func getStructJsonValue(f protoreflect.FieldDescriptor, val string) string { if v := checkFirstOption(api.E_JsConv, f.Options()); v != nil { val += ",string" + } else if v := checkFirstOption(api.E_JsConvCompatible, f.Options()); v != nil { + val += ",string" } if descriptorpb.FieldDescriptorProto_Label(f.Cardinality()) == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED { diff --git a/cmd/hz/test_hz_windows.sh b/cmd/hz/test_hz_windows.sh index b421babf6..2f7aad08d 100644 --- a/cmd/hz/test_hz_windows.sh +++ b/cmd/hz/test_hz_windows.sh @@ -18,7 +18,7 @@ judge_exit() { } compile_hz() { - go build -o hz + go install . judge_exit "$?" } @@ -31,15 +31,15 @@ test_thrift() { # test thrift mkdir -p test cd test - ../hz new --idl=$thriftIDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router + hz new --idl=$thriftIDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router judge_exit "$?" go mod tidy && go build . judge_exit "$?" - ../hz update --idl=$thriftIDL + hz update --idl=$thriftIDL judge_exit "$?" - ../hz model --idl=$thriftIDL --model_dir=hertz_model + hz model --idl=$thriftIDL --model_dir=hertz_model judge_exit "$?" - ../hz client --idl=$thriftIDL --client_dir=hertz_client + hz client --idl=$thriftIDL --client_dir=hertz_client judge_exit "$?" cd .. rm -rf test @@ -49,15 +49,15 @@ test_protobuf2() { # test protobuf2 mkdir -p test cd test - ../hz new -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router + hz new -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router judge_exit "$?" go mod tidy && go build . judge_exit "$?" - ../hz update -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL + hz update -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL judge_exit "$?" - ../hz model -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --model_dir=hertz_model + hz model -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --model_dir=hertz_model judge_exit "$?" - ../hz client -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --client_dir=hertz_client + hz client -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --client_dir=hertz_client judge_exit "$?" cd .. rm -rf test @@ -67,15 +67,15 @@ test_protobuf3() { # test protobuf2 mkdir -p test cd test - ../hz new -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router + hz new -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router judge_exit "$?" go mod tidy && go build . judge_exit "$?" - ../hz update -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL + hz update -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL judge_exit "$?" - ../hz model -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --model_dir=hertz_model + hz model -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --model_dir=hertz_model judge_exit "$?" - ../hz client -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --client_dir=hertz_client + hz client -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --client_dir=hertz_client judge_exit "$?" cd .. rm -rf test diff --git a/cmd/hz/testdata/protobuf2/api.proto b/cmd/hz/testdata/protobuf2/api.proto index 15330da60..9081737ff 100644 --- a/cmd/hz/testdata/protobuf2/api.proto +++ b/cmd/hz/testdata/protobuf2/api.proto @@ -15,9 +15,17 @@ extend google.protobuf.FieldOptions { optional string path = 50106; optional string vd = 50107; optional string form = 50108; - optional string go_tag = 51001; optional string js_conv = 50109; optional string file_name = 50110; + optional string none = 50111; + + // 50131~50160 used to extend field option by hz + optional string form_compatible = 50131; + optional string js_conv_compatible = 50132; + optional string file_name_compatible = 50133; + optional string none_compatible = 50134; + + optional string go_tag = 51001; } extend google.protobuf.MethodOptions { @@ -29,29 +37,29 @@ extend google.protobuf.MethodOptions { optional string options = 50206; optional string head = 50207; optional string any = 50208; - optional string re_get = 50211; - optional string re_post = 50212; - optional string re_put = 50213; - optional string re_delete = 50214; - optional string re_patch = 50215; - optional string re_options = 50216; - optional string re_head = 50217; - optional string re_any = 50218; - optional string gen_path = 50301; - optional string api_version = 50302; - optional string tag = 50303; - optional string name = 50304; - optional string api_level = 50305; - optional string serializer = 50306; - optional string param = 50307; - optional string baseurl = 50308; - optional string handler_path = 50309; + optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version + optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated + optional string tag = 50303; // rpc tag, can be multiple, separated by commas + optional string name = 50304; // Name of rpc + optional string api_level = 50305; // Interface Level + optional string serializer = 50306; // Serialization method + optional string param = 50307; // Whether client requests take public parameters + optional string baseurl = 50308; // Baseurl used in ttnet routing + optional string handler_path = 50309; // handler_path specifies the path to generate the method + + // 50331~50360 used to extend method option by hz + optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method } extend google.protobuf.EnumValueOptions { optional int32 http_code = 50401; + + // 50431~50460 used to extend enum option by hz } extend google.protobuf.ServiceOptions { optional string base_domain = 50402; + + // 50731~50760 used to extend service option by hz + optional string base_domain_compatible = 50731; } \ No newline at end of file diff --git a/cmd/hz/testdata/protobuf2/psm/psm.proto b/cmd/hz/testdata/protobuf2/psm/psm.proto index 0561d601e..b88536639 100644 --- a/cmd/hz/testdata/protobuf2/psm/psm.proto +++ b/cmd/hz/testdata/protobuf2/psm/psm.proto @@ -9,8 +9,8 @@ import "base.proto"; import "other/other.proto"; enum EnumType { - TWEET = 0; - RETWEET = 1; + TWEET = 0; + RETWEET = 1; } message UnusedMessageType { optional string IsUnusedMessageType = 1; @@ -74,7 +74,7 @@ message MultiTypeReq { // nested oneof oneof NestedMsgOneof { string IsNestedMsgOneofString = 4; - EnumType IsNestedMsgOneofEnumType =5; + EnumType IsNestedMsgOneofEnumType = 5; } } // nested message @@ -92,14 +92,21 @@ message MultiTypeReq { } message MultiTagReq { - optional string QueryTag = 1 [(api.query)="query"]; - optional string RawBodyTag = 2 [(api.raw_body)="raw_body"]; - optional string CookieTag = 3 [(api.cookie)="cookie"]; - optional string BodyTag = 4 [(api.body)="body"]; - optional string PathTag = 5 [(api.path)="path"]; - optional string VdTag = 6 [(api.vd)="$!='?'"]; - optional string FormTag = 7 [(api.form)="form"]; - optional string DefaultTag = 8 [(api.go_tag)="FFF:\"fff\" json:\"json\""]; + optional string QueryTag = 1 [(api.query) = "query", (api.none) = "true"]; + optional string RawBodyTag = 2 [(api.raw_body) = "raw_body"]; + optional string CookieTag = 3 [(api.cookie) = "cookie"]; + optional string BodyTag = 4 [(api.body) = "body"]; + optional string PathTag = 5 [(api.path) = "path"]; + optional string VdTag = 6 [(api.vd) = "$!='?'"]; + optional string FormTag = 7 [(api.form) = "form"]; + optional string DefaultTag = 8 [(api.go_tag) = "FFF:\"fff\" json:\"json\""]; +} + +message CompatibleAnnoReq { + optional string FormCompatibleTag = 1 [(api.form_compatible) = "form"]; + optional string FilenameCompatibleTag = 2 [(api.file_name_compatible) = "file_name"]; + optional string NoneCompatibleTag = 3 [(api.none_compatible) = "true"]; + optional string JsConvCompatibleTag = 4 [(api.js_conv_compatible) = "true"]; } message Resp { @@ -120,29 +127,29 @@ message MultiNameStyleMessage { service Hertz { rpc Method1(MultiTypeReq) returns(Resp) { - option (api.get)="/company/department/group/user:id/name"; + option (api.get) = "/company/department/group/user:id/name"; } rpc Method2(MultiTypeReq) returns(Resp) { - option (api.post)="/company/department/group/user:id/sex"; + option (api.post) = "/company/department/group/user:id/sex"; } rpc Method3(MultiTypeReq) returns(Resp) { - option (api.put)="/company/department/group/user:id/number"; + option (api.put) = "/company/department/group/user:id/number"; } rpc Method4(MultiTypeReq) returns(Resp) { - option (api.delete)="/company/department/group/user:id/age"; + option (api.delete) = "/company/department/group/user:id/age"; } rpc Method5(MultiTagReq) returns(Resp) { - option (api.options)="/school/class/student/name"; + option (api.options) = "/school/class/student/name"; } rpc Method6(MultiTagReq) returns(Resp) { - option (api.head)="/school/class/student/number"; + option (api.head) = "/school/class/student/number"; } rpc Method7(MultiTagReq) returns(Resp) { - option (api.patch)="/school/class/student/sex"; + option (api.patch) = "/school/class/student/sex"; } rpc Method8(MultiTagReq) returns(Resp) { - option (api.any)="/school/class/student/grade/*subjects"; + option (api.any) = "/school/class/student/grade/*subjects"; } } \ No newline at end of file diff --git a/cmd/hz/testdata/protobuf3/api.proto b/cmd/hz/testdata/protobuf3/api.proto index 15330da60..9081737ff 100644 --- a/cmd/hz/testdata/protobuf3/api.proto +++ b/cmd/hz/testdata/protobuf3/api.proto @@ -15,9 +15,17 @@ extend google.protobuf.FieldOptions { optional string path = 50106; optional string vd = 50107; optional string form = 50108; - optional string go_tag = 51001; optional string js_conv = 50109; optional string file_name = 50110; + optional string none = 50111; + + // 50131~50160 used to extend field option by hz + optional string form_compatible = 50131; + optional string js_conv_compatible = 50132; + optional string file_name_compatible = 50133; + optional string none_compatible = 50134; + + optional string go_tag = 51001; } extend google.protobuf.MethodOptions { @@ -29,29 +37,29 @@ extend google.protobuf.MethodOptions { optional string options = 50206; optional string head = 50207; optional string any = 50208; - optional string re_get = 50211; - optional string re_post = 50212; - optional string re_put = 50213; - optional string re_delete = 50214; - optional string re_patch = 50215; - optional string re_options = 50216; - optional string re_head = 50217; - optional string re_any = 50218; - optional string gen_path = 50301; - optional string api_version = 50302; - optional string tag = 50303; - optional string name = 50304; - optional string api_level = 50305; - optional string serializer = 50306; - optional string param = 50307; - optional string baseurl = 50308; - optional string handler_path = 50309; + optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version + optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated + optional string tag = 50303; // rpc tag, can be multiple, separated by commas + optional string name = 50304; // Name of rpc + optional string api_level = 50305; // Interface Level + optional string serializer = 50306; // Serialization method + optional string param = 50307; // Whether client requests take public parameters + optional string baseurl = 50308; // Baseurl used in ttnet routing + optional string handler_path = 50309; // handler_path specifies the path to generate the method + + // 50331~50360 used to extend method option by hz + optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method } extend google.protobuf.EnumValueOptions { optional int32 http_code = 50401; + + // 50431~50460 used to extend enum option by hz } extend google.protobuf.ServiceOptions { optional string base_domain = 50402; + + // 50731~50760 used to extend service option by hz + optional string base_domain_compatible = 50731; } \ No newline at end of file diff --git a/cmd/hz/testdata/protobuf3/psm/psm.proto b/cmd/hz/testdata/protobuf3/psm/psm.proto index 972a85c85..3dfeaf597 100644 --- a/cmd/hz/testdata/protobuf3/psm/psm.proto +++ b/cmd/hz/testdata/protobuf3/psm/psm.proto @@ -79,7 +79,7 @@ message MultiTypeReq { } message MultiTagReq { - optional string QueryTag = 1 [(api.query)="query"]; + optional string QueryTag = 1 [(api.query) = "query", (api.none) = "true"]; optional string RawBodyTag = 2 [(api.raw_body)="raw_body"]; optional string CookieTag = 3 [(api.cookie)="cookie"]; optional string BodyTag = 4 [(api.body)="body"]; @@ -92,6 +92,13 @@ message MultiTagReq { } } +message CompatibleAnnoReq { + optional string FormCompatibleTag = 1 [(api.form_compatible) = "form"]; + optional string FilenameCompatibleTag = 2 [(api.file_name_compatible) = "file_name"]; + optional string NoneCompatibleTag = 3 [(api.none_compatible) = "true"]; + optional string JsConvCompatibleTag = 4 [(api.js_conv_compatible) = "true"]; +} + message Resp { optional string Resp = 1; } diff --git a/cmd/hz/testdata/thrift/psm.thrift b/cmd/hz/testdata/thrift/psm.thrift index 5baedc81c..816e77ac6 100644 --- a/cmd/hz/testdata/thrift/psm.thrift +++ b/cmd/hz/testdata/thrift/psm.thrift @@ -104,6 +104,8 @@ struct MultiDefaultReq { 24: required common.CommonType IsDepCommonTypeReq = {"IsCommonString":"fffffff", "TTT":"ttt", "HHH":true, "GGG": {"AAA":"test","BBB":32}}; } +typedef map IsTypedefContainer + service Hertz { Resp Method1(1: MultiTypeReq request) (api.get="/company/department/group/user:id/name", api.handler_path="v1"); Resp Method2(1: MultiTagReq request) (api.post="/company/department/group/user:id/sex", api.handler_path="v1"); @@ -114,4 +116,7 @@ service Hertz { Resp Method6(1: MultiTagReq request) (api.head="/school/class/student/number", api.handler_path="v2"); Resp Method7(1: MultiTagReq request) (api.patch="/school/class/student/sex", api.handler_path="v2"); Resp Method8(1: BaseType request) (api.any="/school/class/student/grade/*subjects", api.handler_path="v2"); + + Resp Method9(1: IsTypedefContainer request) (api.get="/typedef/container", api.handler_path="v2"); + Resp Method10(1: map request) (api.get="/container", api.handler_path="v2"); } \ No newline at end of file diff --git a/cmd/hz/thrift/plugin.go b/cmd/hz/thrift/plugin.go index b2903988e..99d80b4ca 100644 --- a/cmd/hz/thrift/plugin.go +++ b/cmd/hz/thrift/plugin.go @@ -33,6 +33,7 @@ import ( "github.com/cloudwego/thriftgo/generator/backend" "github.com/cloudwego/thriftgo/generator/golang" "github.com/cloudwego/thriftgo/generator/golang/styles" + "github.com/cloudwego/thriftgo/parser" thriftgo_plugin "github.com/cloudwego/thriftgo/plugin" ) @@ -333,22 +334,10 @@ func (plugin *Plugin) InsertTag() ([]*thriftgo_plugin.Generated, error) { stName := st.GetName() for _, f := range st.Fields { fieldName := f.GetName() - field := model.Field{} - err := injectTags(f, &field, true, false) + tagString, err := getTagString(f) if err != nil { return nil, err } - tags := field.Tags - var tagString string - for idx, tag := range tags { - if idx == 0 { - tagString += " " + tag.Key + ":\"" + tag.Value + ":\"" + " " - } else if idx == len(tags)-1 { - tagString += tag.Key + ":\"" + tag.Value + ":\"" - } else { - tagString += tag.Key + ":\"" + tag.Value + ":\"" + " " - } - } insertPointer := "struct." + stName + "." + fieldName + "." + "tag" gen := &thriftgo_plugin.Generated{ Content: tagString, @@ -371,22 +360,10 @@ func (plugin *Plugin) InsertTag() ([]*thriftgo_plugin.Generated, error) { stName := st.GetName() for _, f := range st.Fields { fieldName := f.GetName() - field := model.Field{} - err := injectTags(f, &field, true, false) + tagString, err := getTagString(f) if err != nil { return nil, err } - tags := field.Tags - var tagString string - for idx, tag := range tags { - if idx == 0 { - tagString += " " + tag.Key + ":\"" + tag.Value + "\"" + " " - } else if idx == len(tags)-1 { - tagString += tag.Key + ":\"" + tag.Value + "\"" - } else { - tagString += tag.Key + ":\"" + tag.Value + "\"" + " " - } - } insertPointer := "struct." + stName + "." + fieldName + "." + "tag" gen := &thriftgo_plugin.Generated{ Content: tagString, @@ -422,3 +399,34 @@ func (plugin *Plugin) GetResponse(files []generator.File, outputDir string) (*th Contents: contents, }, nil } + +func getTagString(f *parser.Field) (string, error) { + field := model.Field{} + err := injectTags(f, &field, true, false) + if err != nil { + return "", err + } + disableTag := false + if v := getAnnotation(f.Annotations, AnnotationNone); len(v) > 0 { + if strings.EqualFold(v[0], "true") { + disableTag = true + } + } + var tagString string + tags := field.Tags + for idx, tag := range tags { + value := tag.Value + if disableTag { + value = "-" + } + if idx == 0 { + tagString += " " + tag.Key + ":\"" + value + "\"" + " " + } else if idx == len(tags)-1 { + tagString += tag.Key + ":\"" + value + "\"" + } else { + tagString += tag.Key + ":\"" + value + "\"" + " " + } + } + + return tagString, nil +} diff --git a/cmd/hz/thrift/resolver.go b/cmd/hz/thrift/resolver.go index 0bcb4e3eb..118d0fa1e 100644 --- a/cmd/hz/thrift/resolver.go +++ b/cmd/hz/thrift/resolver.go @@ -283,6 +283,14 @@ func (resolver *Resolver) ResolveIdentifier(id string) (ret ResolvedSymbol, err } func (resolver *Resolver) ResolveTypeName(typ *parser.Type) (string, error) { + if typ.GetIsTypedef() { + rt, err := resolver.ResolveIdentifier(typ.GetName()) + if err != nil { + return "", err + } + + return rt.Expression(), nil + } switch typ.GetCategory() { case parser.Category_Map: keyType, err := resolver.ResolveTypeName(typ.GetKeyType()) diff --git a/go.mod b/go.mod index 79b5fabe2..78039a7ec 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 github.com/bytedance/mockey v1.2.1 github.com/bytedance/sonic v1.8.1 - github.com/cloudwego/netpoll v0.3.1 + github.com/cloudwego/netpoll v0.3.2 github.com/fsnotify/fsnotify v1.5.4 github.com/tidwall/gjson v1.13.0 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c diff --git a/go.sum b/go.sum index f9ea5da26..fe32d2b48 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZX github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/cloudwego/netpoll v0.3.1 h1:xByoORmCLIyKZ8gS+da06WDo3j+jvmhaqS2KeKejtBk= -github.com/cloudwego/netpoll v0.3.1/go.mod h1:1T2WVuQ+MQw6h6DpE45MohSvDTKdy2DlzCx2KsnPI4E= +github.com/cloudwego/netpoll v0.3.2 h1:/998ICrNMVBo4mlul4j7qcIeY7QnEfuCCPPwck9S3X4= +github.com/cloudwego/netpoll v0.3.2/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/pkg/app/server/hertz_test.go b/pkg/app/server/hertz_test.go index aef7824ab..b9a07fbce 100644 --- a/pkg/app/server/hertz_test.go +++ b/pkg/app/server/hertz_test.go @@ -37,6 +37,7 @@ import ( "github.com/cloudwego/hertz/pkg/app/server/registry" "github.com/cloudwego/hertz/pkg/common/config" errs "github.com/cloudwego/hertz/pkg/common/errors" + "github.com/cloudwego/hertz/pkg/common/hlog" "github.com/cloudwego/hertz/pkg/common/test/assert" "github.com/cloudwego/hertz/pkg/common/test/mock" "github.com/cloudwego/hertz/pkg/common/utils" @@ -740,3 +741,43 @@ func TestOnprepare(t *testing.T) { time.Sleep(time.Second) c.Get(context.Background(), nil, "http://127.0.0.1:9231/ping") } + +type lockBuffer struct { + sync.Mutex + b bytes.Buffer +} + +func (l *lockBuffer) Write(p []byte) (int, error) { + l.Lock() + defer l.Unlock() + return l.b.Write(p) +} + +func (l *lockBuffer) String() string { + l.Lock() + defer l.Unlock() + return l.b.String() +} + +func TestSilentMode(t *testing.T) { + hlog.SetSilentMode(true) + b := &lockBuffer{b: bytes.Buffer{}} + + hlog.SetOutput(b) + + h := New(WithHostPorts("localhost:9232"), WithTransport(standard.NewTransporter)) + h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { + ctx.Write([]byte("hello, world")) + }) + go h.Spin() + time.Sleep(time.Second) + + d := standard.NewDialer() + conn, _ := d.DialConnection("tcp", "127.0.0.1:9232", 0, nil) + conn.Write([]byte("aaa")) + conn.Close() + + if strings.Contains(b.String(), "Error") { + t.Fatalf("unexpected error in log: %s", b.String()) + } +} diff --git a/pkg/app/server/render/html_test.go b/pkg/app/server/render/html_test.go index fb9445877..eb8f36e96 100644 --- a/pkg/app/server/render/html_test.go +++ b/pkg/app/server/render/html_test.go @@ -32,8 +32,8 @@ func TestHTMLDebug_StartChecker_timer(t *testing.T) { } render.startChecker() select { - case <-time.After(render.RefreshInterval + 100*time.Millisecond): - t.Fatalf("should be triggered in 1 second") + case <-time.After(render.RefreshInterval + 500*time.Millisecond): + t.Fatalf("should be triggered in 1.5 second") case <-render.reloadCh: } } diff --git a/pkg/common/hlog/consts.go b/pkg/common/hlog/consts.go new file mode 100644 index 000000000..e0d31bb8d --- /dev/null +++ b/pkg/common/hlog/consts.go @@ -0,0 +1,23 @@ +/* + * Copyright 2023 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hlog + +const ( + systemLogPrefix = "HERTZ: " + + EngineErrorFormat = "Error=%s, remoteAddr=%s" +) diff --git a/pkg/common/hlog/hlog.go b/pkg/common/hlog/hlog.go index 11a81615e..7f73aaa0d 100644 --- a/pkg/common/hlog/hlog.go +++ b/pkg/common/hlog/hlog.go @@ -22,10 +22,6 @@ import ( "os" ) -const ( - systemLogPrefix = "HERTZ: " -) - var ( // Provide default logger for users to use logger FullLogger = &defaultLogger{ diff --git a/pkg/common/hlog/system.go b/pkg/common/hlog/system.go index cb8a1453c..6d2b86bd4 100644 --- a/pkg/common/hlog/system.go +++ b/pkg/common/hlog/system.go @@ -23,6 +23,15 @@ import ( "sync" ) +var silentMode = false + +// SetSilentMode is used to mute engine error log, +// for example: error when reading request headers. +// If true, hertz engine will mute it. +func SetSilentMode(s bool) { + silentMode = s +} + var builderPool = sync.Pool{New: func() interface{} { return &strings.Builder{} // nolint:SA6002 }} @@ -80,6 +89,9 @@ func (ll *systemLogger) Fatalf(format string, v ...interface{}) { } func (ll *systemLogger) Errorf(format string, v ...interface{}) { + if silentMode && format == EngineErrorFormat { + return + } ll.logger.Errorf(ll.addPrefix(format), v...) } diff --git a/pkg/network/netpoll/transport.go b/pkg/network/netpoll/transport.go index 12e54a914..17829cb83 100644 --- a/pkg/network/netpoll/transport.go +++ b/pkg/network/netpoll/transport.go @@ -20,6 +20,7 @@ package netpoll import ( "context" + "io" "net" "sync" "time" @@ -30,6 +31,11 @@ import ( "github.com/cloudwego/netpoll" ) +func init() { + // disable netpoll's log + netpoll.SetLoggerOutput(io.Discard) +} + type transporter struct { sync.RWMutex network string diff --git a/pkg/network/standard/dial_test.go b/pkg/network/standard/dial_test.go index 7d9f3a03b..c7d9fa200 100644 --- a/pkg/network/standard/dial_test.go +++ b/pkg/network/standard/dial_test.go @@ -75,20 +75,25 @@ func TestDialTLS(t *testing.T) { t.Fatalf("timeout") } + buf := make([]byte, len(data)) + dial := NewDialer() - _, err := dial.DialConnection(nw, addr, time.Second, &tls.Config{ + conn, err := dial.DialConnection(nw, addr, time.Second, &tls.Config{ InsecureSkipVerify: true, }) assert.Nil(t, err) - conn, err := dial.DialConnection(nw, addr, time.Second, nil) + _, err = conn.Read(buf) + assert.Nil(t, err) + assert.DeepEqual(t, string(data), string(buf)) + + conn, err = dial.DialConnection(nw, addr, time.Second, nil) assert.Nil(t, err) nConn, err := dial.AddTLS(conn, &tls.Config{ InsecureSkipVerify: true, }) assert.Nil(t, err) - buf := make([]byte, len(data)) _, err = nConn.Read(buf) assert.Nil(t, err) assert.DeepEqual(t, string(data), string(buf)) diff --git a/pkg/protocol/header.go b/pkg/protocol/header.go index 0e1d48ab2..b91405751 100644 --- a/pkg/protocol/header.go +++ b/pkg/protocol/header.go @@ -66,8 +66,6 @@ type RequestHeader struct { noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used disableNormalizing bool - noHTTP11 bool - protocol string connectionClose bool noDefaultContentType bool @@ -82,8 +80,10 @@ type RequestHeader struct { requestURI []byte host []byte contentType []byte - userAgent []byte - mulHeader [][]byte + + userAgent []byte + mulHeader [][]byte + protocol string h []argsKV bufKV argsKV @@ -111,7 +111,6 @@ type ResponseHeader struct { noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used disableNormalizing bool - noHTTP11 bool connectionClose bool noDefaultContentType bool noDefaultDate bool @@ -124,6 +123,7 @@ type ResponseHeader struct { contentType []byte server []byte mulHeader [][]byte + protocol string h []argsKV bufKV argsKV @@ -173,8 +173,17 @@ func (h *ResponseHeader) PeekArgBytes(key []byte) []byte { return peekArgBytes(h.h, key) } +// Deprecated: Use ResponseHeader.SetProtocol(consts.HTTP11) instead +// +// Now SetNoHTTP11(true) equal to SetProtocol(consts.HTTP10) +// SetNoHTTP11(false) equal to SetProtocol(consts.HTTP11) func (h *ResponseHeader) SetNoHTTP11(b bool) { - h.noHTTP11 = b + if b { + h.protocol = consts.HTTP10 + return + } + + h.protocol = consts.HTTP11 } // Cookie fills cookie for the given cookie.Key. @@ -196,7 +205,7 @@ func (h *ResponseHeader) FullCookie() []byte { // IsHTTP11 returns true if the response is HTTP/1.1. func (h *ResponseHeader) IsHTTP11() bool { - return !h.noHTTP11 + return h.protocol == consts.HTTP11 } // SetContentType sets Content-Type header value. @@ -222,7 +231,6 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) { dst.Reset() dst.disableNormalizing = h.disableNormalizing - dst.noHTTP11 = h.noHTTP11 dst.connectionClose = h.connectionClose dst.noDefaultContentType = h.noDefaultContentType dst.noDefaultDate = h.noDefaultDate @@ -289,7 +297,7 @@ func (h *ResponseHeader) VisitAll(f func(key, value []byte)) { // IsHTTP11 returns true if the request is HTTP/1.1. func (h *RequestHeader) IsHTTP11() bool { - return !h.noHTTP11 + return h.protocol == consts.HTTP11 } func (h *RequestHeader) SetProtocol(p string) { @@ -300,8 +308,17 @@ func (h *RequestHeader) GetProtocol() string { return h.protocol } +// Deprecated: Use RequestHeader.SetProtocol(consts.HTTP11) instead +// +// Now SetNoHTTP11(true) equal to SetProtocol(consts.HTTP10) +// SetNoHTTP11(false) equal to SetProtocol(consts.HTTP11) func (h *RequestHeader) SetNoHTTP11(b bool) { - h.noHTTP11 = b + if b { + h.protocol = consts.HTTP10 + return + } + + h.protocol = consts.HTTP11 } func (h *RequestHeader) InitBufValue(size int) { @@ -486,7 +503,7 @@ func checkWriteHeaderCode(code int) { } func (h *ResponseHeader) ResetSkipNormalize() { - h.noHTTP11 = false + h.protocol = "" h.connectionClose = false h.statusCode = 0 @@ -1075,7 +1092,6 @@ func (h *RequestHeader) CopyTo(dst *RequestHeader) { dst.Reset() dst.disableNormalizing = h.disableNormalizing - dst.noHTTP11 = h.noHTTP11 dst.connectionClose = h.connectionClose dst.noDefaultContentType = h.noDefaultContentType @@ -1395,7 +1411,6 @@ func (h *RequestHeader) SetCanonical(key, value []byte) { } func (h *RequestHeader) ResetSkipNormalize() { - h.noHTTP11 = false h.connectionClose = false h.protocol = "" h.noDefaultContentType = false @@ -1803,3 +1818,11 @@ func (h *RequestHeader) Trailer() *Trailer { } return h.trailer } + +func (h *ResponseHeader) SetProtocol(p string) { + h.protocol = p +} + +func (h *ResponseHeader) GetProtocol() string { + return h.protocol +} diff --git a/pkg/protocol/header_test.go b/pkg/protocol/header_test.go index a48e66332..f7c85578b 100644 --- a/pkg/protocol/header_test.go +++ b/pkg/protocol/header_test.go @@ -70,18 +70,18 @@ func TestResponseHeaderSetHeaderLength(t *testing.T) { func TestSetNoHTTP11(t *testing.T) { rh := ResponseHeader{} rh.SetNoHTTP11(true) - assert.True(t, rh.noHTTP11) + assert.DeepEqual(t, consts.HTTP10, rh.protocol) rh.SetNoHTTP11(false) - assert.False(t, rh.noHTTP11) + assert.DeepEqual(t, consts.HTTP11, rh.protocol) assert.True(t, rh.IsHTTP11()) h := RequestHeader{} h.SetNoHTTP11(true) - assert.True(t, h.noHTTP11) + assert.DeepEqual(t, consts.HTTP10, h.protocol) h.SetNoHTTP11(false) - assert.False(t, h.noHTTP11) + assert.DeepEqual(t, consts.HTTP11, h.protocol) assert.True(t, h.IsHTTP11()) } diff --git a/pkg/protocol/http1/req/header.go b/pkg/protocol/http1/req/header.go index da7e72956..4d571a304 100644 --- a/pkg/protocol/http1/req/header.go +++ b/pkg/protocol/http1/req/header.go @@ -153,13 +153,11 @@ func parseFirstLine(h *protocol.RequestHeader, buf []byte) (int, error) { // parse requestURI n = bytes.LastIndexByte(b, ' ') if n < 0 { - h.SetNoHTTP11(true) h.SetProtocol(consts.HTTP10) n = len(b) } else if n == 0 { return 0, fmt.Errorf("requestURI cannot be empty in %q", buf) } else if !bytes.Equal(b[n+1:], bytestr.StrHTTP11) { - h.SetNoHTTP11(true) h.SetProtocol(consts.HTTP10) } h.SetRequestURIBytes(b[:n]) diff --git a/pkg/protocol/http1/resp/header.go b/pkg/protocol/http1/resp/header.go index 622c72bdd..18a0f8a08 100644 --- a/pkg/protocol/http1/resp/header.go +++ b/pkg/protocol/http1/resp/header.go @@ -239,7 +239,14 @@ func parseFirstLine(h *protocol.ResponseHeader, buf []byte) (int, error) { if n < 0 { return 0, fmt.Errorf("cannot find whitespace in the first line of response %q", buf) } - h.SetNoHTTP11(!bytes.Equal(b[:n], bytestr.StrHTTP11)) + + isHTTP11 := bytes.Equal(b[:n], bytestr.StrHTTP11) + if !isHTTP11 { + h.SetProtocol(consts.HTTP10) + } else { + h.SetProtocol(consts.HTTP11) + } + b = b[n+1:] // parse status code diff --git a/pkg/protocol/http1/resp/response_test.go b/pkg/protocol/http1/resp/response_test.go index af146f0b7..01a38c1ea 100644 --- a/pkg/protocol/http1/resp/response_test.go +++ b/pkg/protocol/http1/resp/response_test.go @@ -157,11 +157,11 @@ func testResponseReadError(t *testing.T, resp *protocol.Response, response strin } testResponseReadSuccess(t, resp, "HTTP/1.1 303 Redisred sedfs sdf\r\nContent-Type: aaa\r\nContent-Length: 5\r\n\r\nHELLOaaa", - consts.StatusSeeOther, 5, "aaa", "HELLO", nil) + consts.StatusSeeOther, 5, "aaa", "HELLO", nil, consts.HTTP11) } func testResponseReadSuccess(t *testing.T, resp *protocol.Response, response string, expectedStatusCode, expectedContentLength int, - expectedContentType, expectedBody string, expectedTrailer map[string]string, + expectedContentType, expectedBody string, expectedTrailer map[string]string, expectedProtocol string, ) { zr := mock.NewZeroCopyReader(response) err := Read(resp, zr) @@ -169,7 +169,7 @@ func testResponseReadSuccess(t *testing.T, resp *protocol.Response, response str t.Fatalf("Unexpected error: %s", err) } - verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "") + verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "", expectedProtocol) if !bytes.Equal(resp.Body(), []byte(expectedBody)) { t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody)) } @@ -183,46 +183,46 @@ func TestResponseReadSuccess(t *testing.T) { // usual response testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789", - consts.StatusOK, 10, "foo/bar", "0123456789", nil) + consts.StatusOK, 10, "foo/bar", "0123456789", nil, consts.HTTP11) // zero response testResponseReadSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n", - consts.StatusInternalServerError, 0, "foo/bar", "", nil) + consts.StatusInternalServerError, 0, "foo/bar", "", nil, consts.HTTP11) // response with trailer testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", - consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"Foo": "bar"}) + consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"Foo": "bar"}, consts.HTTP11) // response with trailer disableNormalizing resp.Header.DisableNormalizing() resp.Header.Trailer().DisableNormalizing() testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", - consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"foo": "bar"}) + consts.StatusMultipleChoices, 5, "bar", "56789", map[string]string{"foo": "bar"}, consts.HTTP11) // no content-length ('identity' transfer-encoding) testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxxx", - consts.StatusOK, 5, "foobar", "zxxxx", nil) + consts.StatusOK, 5, "foobar", "zxxxx", nil, consts.HTTP11) // explicitly stated 'Transfer-Encoding: identity' testResponseReadSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag", - 234, 3, "xxx", "xag", nil) + 234, 3, "xxx", "xag", nil, consts.HTTP11) // big 'identity' response body := string(mock.CreateFixedBody(100500)) testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body, - consts.StatusOK, 100500, "aa", body, nil) + consts.StatusOK, 100500, "aa", body, nil, consts.HTTP11) // chunked response testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo2\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nFoo2: bar2\r\n\r\n", - 200, 6, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}) + 200, 6, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}, consts.HTTP11) // chunked response with non-chunked Transfer-Encoding. testResponseReadSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTrailer: Foo3\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\nFoo3: bar3\r\n\r\n", - 230, 4, "text", "erty", map[string]string{"Foo3": "bar3"}) + 230, 4, "text", "erty", map[string]string{"Foo3": "bar3"}, consts.HTTP11) // chunked response with empty body testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo5\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo5: bar5\r\n\r\n", - consts.StatusOK, 0, "text/html", "", map[string]string{"Foo5": "bar5"}) + consts.StatusOK, 0, "text/html", "", map[string]string{"Foo5": "bar5"}, consts.HTTP11) } func TestResponseReadError(t *testing.T) { @@ -416,23 +416,23 @@ func TestResponseReadWithoutBody(t *testing.T) { var resp protocol.Response testResponseReadWithoutBody(t, &resp, "HTTP/1.1 304 Not Modified\r\nContent-Type: aa\r\nContent-Encoding: gzip\r\nContent-Length: 1235\r\n\r\n", false, - consts.StatusNotModified, 1235, "aa", nil, "gzip") + consts.StatusNotModified, 1235, "aa", nil, "gzip", consts.HTTP11) testResponseReadWithoutBody(t, &resp, "HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTrailer: Foo\r\nContent-Encoding: deflate\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo: bar\r\n\r\n", false, - consts.StatusNoContent, -1, "aab", map[string]string{"Foo": "bar"}, "deflate") + consts.StatusNoContent, -1, "aab", map[string]string{"Foo": "bar"}, "deflate", consts.HTTP11) testResponseReadWithoutBody(t, &resp, "HTTP/1.1 123 AAA\r\nContent-Type: xxx\r\nContent-Encoding: gzip\r\nContent-Length: 3434\r\n\r\n", false, - 123, 3434, "xxx", nil, "gzip") + 123, 3434, "xxx", nil, "gzip", consts.HTTP11) testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Encoding: deflate\r\nContent-Length: 123\r\n\r\nfoobar\r\n", true, - consts.StatusOK, 123, "text/xml", nil, "deflate") + consts.StatusOK, 123, "text/xml", nil, "deflate", consts.HTTP10) // '100 Continue' must be skipped. testResponseReadWithoutBody(t, &resp, "HTTP/1.1 100 Continue\r\nFoo-bar: baz\r\n\r\nHTTP/1.1 329 aaa\r\nContent-Type: qwe\r\nContent-Encoding: gzip\r\nContent-Length: 894\r\n\r\n", true, - 329, 894, "qwe", nil, "gzip") + 329, 894, "qwe", nil, "gzip", consts.HTTP11) } -func verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding string) { +func verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding, expectedProtocol string) { if h.StatusCode() != expectedStatusCode { t.Fatalf("Unexpected status code %d. Expected %d", h.StatusCode(), expectedStatusCode) } @@ -445,6 +445,10 @@ func verifyResponseHeader(t *testing.T, h *protocol.ResponseHeader, expectedStat if string(h.ContentEncoding()) != expectedContentEncoding { t.Fatalf("Unexpected content encoding %q. Expected %q", h.ContentEncoding(), expectedContentEncoding) } + + if h.GetProtocol() != expectedProtocol { + t.Fatalf("Unexpected protocol %q. Expected %q", h.GetProtocol(), expectedProtocol) + } } func testResponseSuccess(t *testing.T, statusCode int, contentType, serverName, body string, @@ -492,7 +496,7 @@ func testResponseSuccess(t *testing.T, statusCode int, contentType, serverName, } func testResponseReadWithoutBody(t *testing.T, resp *protocol.Response, s string, skipBody bool, - expectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string, expectedContentEncoding string, + expectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string, expectedContentEncoding, expectedProtocol string, ) { zr := mock.NewZeroCopyReader(s) resp.SkipBody = skipBody @@ -504,13 +508,13 @@ func testResponseReadWithoutBody(t *testing.T, resp *protocol.Response, s string t.Fatalf("Unexpected response body %q. Expected %q. response=%q", resp.Body(), "", s) } - verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, expectedContentEncoding) + verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, expectedContentEncoding, expectedProtocol) verifyResponseTrailer(t, &resp.Header, expectedTrailer) // verify that ordinal response is read after null-body response resp.SkipBody = false testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789", - consts.StatusMultipleChoices, 5, "bar", "56789", nil) + consts.StatusMultipleChoices, 5, "bar", "56789", nil, consts.HTTP11) } func verifyResponseTrailer(t *testing.T, h *protocol.ResponseHeader, expectedTrailers map[string]string) { @@ -696,7 +700,7 @@ func testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[str } func testResponseReadBodyStreamSuccess(t *testing.T, resp *protocol.Response, response string, expectedStatusCode, expectedContentLength int, - expectedContentType, expectedBody string, expectedTrailer map[string]string, + expectedContentType, expectedBody string, expectedTrailer map[string]string, expectedProtocol string, ) { zr := mock.NewZeroCopyReader(response) err := ReadBodyStream(resp, zr, 0, nil) @@ -709,7 +713,7 @@ func testResponseReadBodyStreamSuccess(t *testing.T, resp *protocol.Response, re if err != nil && err != io.EOF { t.Fatalf("Unexpected error: %s", err) } - verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "") + verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, "", expectedProtocol) if !bytes.Equal(body, []byte(expectedBody)) { t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody)) } @@ -737,46 +741,46 @@ func TestResponseReadBodyStream(t *testing.T) { // usual response testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789", - consts.StatusOK, 10, "foo/bar", "0123456789", nil) + consts.StatusOK, 10, "foo/bar", "0123456789", nil, consts.HTTP11) // zero response testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n", - consts.StatusInternalServerError, 0, "foo/bar", "", nil) + consts.StatusInternalServerError, 0, "foo/bar", "", nil, consts.HTTP11) // response with trailer testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: Foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", - consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"Foo": "bar"}) + consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"Foo": "bar"}, consts.HTTP11) // response with trailer disableNormalizing resp.Header.DisableNormalizing() resp.Header.Trailer().DisableNormalizing() testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nTrailer: foo\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", - consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"foo": "bar"}) + consts.StatusMultipleChoices, -1, "bar", "56789", map[string]string{"foo": "bar"}, consts.HTTP11) // no content-length ('identity' transfer-encoding) testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxxx", - consts.StatusOK, -2, "foobar", "zxxxx", nil) + consts.StatusOK, -2, "foobar", "zxxxx", nil, consts.HTTP11) // explicitly stated 'Transfer-Encoding: identity' testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag", - 234, -2, "xxx", "xag", nil) + 234, -2, "xxx", "xag", nil, consts.HTTP11) // big 'identity' response body := string(mock.CreateFixedBody(100500)) testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body, - consts.StatusOK, -2, "aa", body, nil) + consts.StatusOK, -2, "aa", body, nil, consts.HTTP11) // chunked response testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo2\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nFoo2: bar2\r\n\r\n", - 200, -1, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}) + 200, -1, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}, consts.HTTP11) // chunked response with non-chunked Transfer-Encoding. testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTrailer: Foo3\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\nFoo3: bar3\r\n\r\n", - 230, -1, "text", "erty", map[string]string{"Foo3": "bar3"}) + 230, -1, "text", "erty", map[string]string{"Foo3": "bar3"}, consts.HTTP11) // chunked response with empty body testResponseReadBodyStreamSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTrailer: Foo5\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo5: bar5\r\n\r\n", - consts.StatusOK, -1, "text/html", "", map[string]string{"Foo5": "bar5"}) + consts.StatusOK, -1, "text/html", "", map[string]string{"Foo5": "bar5"}, consts.HTTP11) } func TestResponseReadBodyStreamBadTrailer(t *testing.T) { diff --git a/pkg/protocol/multipart.go b/pkg/protocol/multipart.go index 429049111..e926c947a 100644 --- a/pkg/protocol/multipart.go +++ b/pkg/protocol/multipart.go @@ -137,11 +137,11 @@ func WriteMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r i // Auto detect actual multipart content type cbuf := make([]byte, 512) size, err := r.Read(cbuf) - if err != nil { + if err != nil && err != io.EOF { return err } - partWriter, err := w.CreatePart(CreateMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf))) + partWriter, err := w.CreatePart(CreateMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf[:size]))) if err != nil { return err } diff --git a/pkg/protocol/multipart_test.go b/pkg/protocol/multipart_test.go index be93b8953..86995d28e 100644 --- a/pkg/protocol/multipart_test.go +++ b/pkg/protocol/multipart_test.go @@ -208,6 +208,9 @@ func TestWriteMultipartFormFile(t *testing.T) { t.Fatalf("write multipart error: %s", err) } assert.False(t, strings.Contains(bodyBuffer.String(), f3.Name())) + + // test empty file + assert.Nil(t, WriteMultipartFormFile(w, "empty_test", "test.data", bytes.NewBuffer(nil))) } func TestMarshalMultipartForm(t *testing.T) { diff --git a/pkg/route/engine.go b/pkg/route/engine.go index 8a9a00b0c..10cffac7a 100644 --- a/pkg/route/engine.go +++ b/pkg/route/engine.go @@ -459,7 +459,7 @@ func errProcess(conn io.Closer, err error) { } } // other errors - hlog.SystemLogger().Errorf("Error=%s, remoteAddr=%s", err.Error(), rip) + hlog.SystemLogger().Errorf(hlog.EngineErrorFormat, err.Error(), rip) } func getRemoteAddrFromCloser(conn io.Closer) string { diff --git a/version.go b/version.go index 208f61c48..5428b83b5 100644 --- a/version.go +++ b/version.go @@ -19,5 +19,5 @@ package hertz // Name and Version info of this framework, used for statistics and debug const ( Name = "Hertz" - Version = "v0.6.2" + Version = "v0.6.3" )