hz client code generation

Introduction

Based on IDL, it generates RPC-like http requests code with a single click, which can block the tedious operation of creating and initializing hertz client and interoperate directly with the server code generated by hz.

Generated code examples can be found at code .

Usage

$ hz client -h
NAME:
   hz client - Generate hertz client based on IDL

USAGE:
   hz client [command options] [arguments...]

OPTIONS:
   --idl value [ --idl value ]                                        Specify the IDL file path. (.thrift or .proto)
   --module value, --mod value                                        Specify the Go module name to generate go.mod.
   --base_domain value                                                Specify the request domain.
   --model_dir value                                                  Specify the model path.
   --client_dir value                                                 Specify the client path. If not specified, IDL generated path is used for 'client' command; no client code is generated for 'new' command
   --proto_path value, -I value [ --proto_path value, -I value ]      Add an IDL search path for includes. (Valid only if idl is protobuf)
   --thriftgo value, -t value [ --thriftgo value, -t value ]          Specify arguments for the thriftgo. ({flag}={value})
   --protoc value, -p value [ --protoc value, -p value ]              Specify arguments for the protoc. ({flag}={value})
   --no_recurse                                                       Generate master model only. (default: false)
   --json_enumstr                                                     Use string instead of num for json enums when idl is thrift. (default: false)
   --unset_omitempty                                                  Remove 'omitempty' tag for generated struct. (default: false)
   --pb_camel_json_tag                                                Convert Name style for json tag to camel(Only works protobuf). (default: false)
   --snake_tag                                                        Use snake_case style naming for tags. (Only works for 'form', 'query', 'json') (default: false)
   --exclude_file value, -E value [ --exclude_file value, -E value ]  Specify the files that do not need to be updated.
   --protoc-plugins value [ --protoc-plugins value ]                  Specify plugins for the protoc. ({plugin_name}:{options}:{out_dir})
   --thrift-plugins value [ --thrift-plugins value ]                  Specify plugins for the thriftgo. ({plugin_name}:{options})
   --help, -h                                                         show help (default: false)

When generating code, simply use the following five options:

  • idl: Specify the idl path
  • module: Specify the go module of the project, if not specified, it defaults to the path relative to the “go path”.
  • model_dir: Specify the path to the model generated by the project, default is “biz/model”
  • client_dir: Specify the path to generate the client stub code, default is “biz/model/{Namespace}”
  • base_domain: Specify the domain to be accessed, it can be domain name, IP:PORT, service name (with service discovery), or can be declared in IDL by annotation
hz client --idl=../idl/psm.thrift --model_dir=hertz_gen -t=template=slim --client_dir=hz_client

Example

Generate client based on thrift IDL

Define IDL

The definition and semantics of the IDL are exactly the same as the current definition, so it is basically possible to generate client code without modifying the original IDL But for the client scenario, two annotations have been added api.file_name: Specify the file api.base_domain: specifies the default request domain to access

namespace go toutiao.middleware.hertz_client

struct FormReq {
    1: string FormValue (api.form="form1"); // form annotation is used to declare the form parameter ("multipart/form-data")
    2: string FileValue (api.file_name="file1"); // file_name is used to declare the key of the file to be uploaded, its actual value is the file name
}

struct QueryReq {
    1: string QueryValue (api.query="query1"); // query annotation is used to declare the query parameters of the request
}

struct PathReq {
    1: string PathValue (api.path="path1"); // path annotation is used to declare the routing parameters in the url
}

struct BodyReq {
    1: string BodyValue (api.body="body"); //  body annotation sets the entire structure to the body as a json, regardless of whether it is declared or not.
    2: string QueryValue (api.query="query2");
}

struct Resp {
    1: string Resp;
}

service HelloService {
    // api.post is used to declare the route of the request
    Resp FormMethod(1: FormReq request) (api.post="/form", api.handler_path="post");
    Resp QueryMethod(1: QueryReq request) (api.get="/query", api.handler_path="get");
    Resp PathMethod(1: PathReq request) (api.post="/path:path1", api.handler_path="post");
    Resp BodyMethod(1: BodyReq request) (api.post="/body", api.handler_path="post");
}(
    // api.base_domain is used to specify the default domain for client requests
    api.base_domain="http://127.0.0.1:8888";
)

Generate client code

hz client --mod=a/b/c --idl=../idl/psm.thrift --model_dir=model --client_dir=hertz_client -t=template=slim

Advanced Settings

Request-level configuration

Take the code generated by thrift IDL as an example

func main() {
	generatedClient, err := hello_service.NewHelloServiceClient(
		"http://toutiao.hertz.testa",
		)
    // The request level configuration can be specified when the call is initiated
    resp, rawResp, err := generatedClient.QueryMethod(
        context.Background(),
        QueryReq,
        config.WithSD(true), // Specify the request level setting to enable service discovery
        config.WithReadTimeout(), // Specify the request read timeout
        )
    if err != nil {
       fmt.Println(err)
       return
    }
}

Set client middleware

Take the code generated by thrift IDL as an example

func main() {
	generatedClient, err := hello_service.NewHelloServiceClient(
		"http://toutiao.hertz.testa", 
		hello_service.WithHertzClientMiddleware(), // Specify the client's middleware
		)
}

Set global header

Take the code generated by thrift IDL as an example

There are some generic header that may need to be carried in every request, or some header that cannot be defined in IDL, then we can inject these header with “WithHeader” so that every request sent will carry these header.

func main() {
	generatedClient, err := hello_service.NewHelloServiceClient(
		"http://toutiao.hertz.testa",
		hello_service.WithHeader(), // Specify the header that needs to be carried for each request sent 
	)
}

Configure TLS

Take the code generated by thrift IDL as an example

Hertz client’s TLS goes through the standard network library, so you need to configure it for the standard network library when using the generated one-click calls

func main() {
	generatedClient, err := hello_service.NewHelloServiceClient("https://www.example.com"), 
	hello_service.WithHertzClientOption(
		client.WithDialer(standard.NewDialer()), // Use of standard libraries
		client.WithTLSConfig(clientCfg), // TLS Configuration
		), 
	)
}

Last modified April 4, 2023 : optimzie: custom_tpl docs (#597) (b3e25f4)