Basic Usage
IDL Provider
Although generic calls do not require code generation based on IDL (Interface Definition Language), it is usually necessary to provide an IDL. Then, based on the IDL, data structures like JSON or Map are used to construct the corresponding RPC (Remote Procedure Call) request structures. (Except for binary generic calls)
In Kitex, the interface for IDL Provider is defined as follows:
// DescriptorProvider provide service descriptor
type DescriptorProvider interface {
Closer
// Provide return a channel for provide service descriptors
Provide() <-chan *descriptor.ServiceDescriptor
}
The usage of this interface is introduced in the later section “Basic Usage”. Here, it is only necessary to understand how to create it.
Currently, Kitex offers two implementations of the IDL Provider. Users can choose to specify the IDL path or pass in the IDL content. Additionally, it is possible to extend the generic.DescriptorProvider
interface according to your needs.
Parsing IDL from Local Files
Thrift
There are two methods provided for parsing IDL from local files. Both methods are similar and require passing the IDL path and the paths of other IDLs referenced within it.
p, err := generic.NewThriftFileProvider("./YOUR_IDL_PATH")
if err != nil {
panic(err)
}
p, err := generic.NewThriftFileProviderWithDynamicGo("./YOUR_IDL_PATH")
if err != nil {
panic(err)
}
The generic.NewThriftFileProviderWithDynamicGo
integrates dynamicgo for improved performance when processing RPC data. For more details, see the dynamicgo integration guide.
Protobuf
A method is provided to parse local proto
files, which requires passing the IDL path, context.Context
, and optional option
parameters. For detailed information about the parameters, please refer to dynamicgo proto idl.
p, err := NewPbFileProviderWithDynamicGo("./YOUR_IDL_PATH", context.Background())
if err != nil {
panic(err)
}
Parsing IDL from Memory
You can also directly parse the IDL content. Other IDLs referenced within the IDL need to be constructed into a map and passed in. The key should be the path of the referenced IDL, and the value should be the IDL content.
Thrift
A simple example (to minimize display, the path construction is not a real IDL):
content := `
namespace go kitex.test.server
include "x.thrift"
include "../y.thrift"
service InboxService {}
`
path := "a/b/main.thrift"
includes := map[string]string{
path: content,
"x.thrift": "namespace go kitex.test.server",
"../y.thrift": `
namespace go kitex.test.server
include "z.thrift"
`,
}
p, err := generic.NewThriftContentProvider(content, includes)
if err != nil {
panic(err)
}
p, err := generic.NewThriftContentProviderWithDynamicGo(content, includes)
if err != nil {
panic(err)
}
The generic.NewThriftContentProviderWithDynamicGo
integrates dynamicgo for improved performance when processing RPC data. For more details, see the dynamicgo integration guide.
Protobuf
A simple example (to minimize display, the path construction is not a real IDL):
content := `
syntax = "proto3";
package kitex.test.server;
option go_package = "test";
import "x.proto"
service Echo{}
`
path := "a/b/main.proto"
includes := map[string]string{
path: content,
"x.proto": `syntax = "proto3";
package kitex.test.server;
option go_package = "test";
`,
}
p, err := NewPbContentProvider(content, includes)
if err != nil {
panic(err)
}
Support for Absolute Path in include path Addressing
Currently, only Thrift supports this functionality.
For ease of constructing the IDL Map, you can also use absolute paths as keys with generic.NewThriftContentWithAbsIncludePathProvider
or generic.NewThriftContentWithAbsIncludePathProviderWithDynamicGo
.
content := `
namespace go kitex.test.server
include "x.thrift"
include "../y.thrift"
service InboxService {}
`
path := "a/b/main.thrift"
includes := map[string]string{
path: content,
"a/b/x.thrift": "namespace go kitex.test.server",
"a/y.thrift": `
namespace go kitex.test.server
include "z.thrift"
`,
"a/z.thrift": "namespace go kitex.test.server",
}
p, err := generic.NewThriftContentWithAbsIncludePathProvider(content, includes)
if err != nil {
panic(err)
}
p, err := generic.NewThriftContentWithAbsIncludePathProviderWithDynamicGo(content, includes)
if err != nil {
panic(err)
}
The generic.NewThriftContentWithAbsIncludePathProviderWithDynamicGo
integrates dynamicgo for improved performance when processing RPC data. For more details, see the dynamicgo integration guide.
In Kitex, the generic.Generic
interface represents a generic call, with different implementations for different types of generic calls. A Generic
instance is required when creating both clients and servers.
Client-Side Interface
Create Client
The client-side interfaces are all under the github.com/cloudwego/kitex/client/genericclient
package.
NewClient
Function signature: func NewClient(destService string, g generic.Generic, opts ...client.Option) (Client, error)
Description: This function takes the target service name, a Generic object, and optional Option parameters, returning a generic call client. For details on Option parameters, see Client Option
NewClientWithServiceInfo
Function signature: func NewClientWithServiceInfo(destService string, g generic.Generic, svcInfo *serviceinfo.ServiceInfo, opts ...client.Option) (Client, error)
Description: This function requires the target service name, a Generic object, custom service information, and optional Option parameters to return a generic call client. For details on Option parameters, see Client Option
Server-Side Interface
Generic Call Service Object
In Kitex, the generic.Service
interface represents a generic call service.
// Service generic service interface
type Service interface {
// GenericCall handle the generic call
GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error)
}
As long as the GenericCall
method is implemented, it can be used as a generic call service instance for creating a generic call server.
Create Server
The server-side interfaces are all under the github.com/cloudwego/kitex/client/genericclient
package.
NewServer
Function signature: func NewServer(handler generic.Service, g generic.Generic, opts ...server.Option) server.Server
Description: This function requires a generic call service instance, a Generic object, and optional Option parameters to return a Kitex server. For details on Option parameters, see Server Option
NewServerWithServiceInfo
Function signature: func NewServerWithServiceInfo(handler generic.Service, g generic.Generic, svcInfo *serviceinfo.ServiceInfo, opts ...server.Option) server.Server
Description: This function takes a generic call service instance, a Generic object, custom service information, and optional Option parameters to return a Kitex server. For details on Option parameters, see Server Option
Generic Call Scenarios
Kitex supports generic calls in the following four scenarios:
-
Thrift:
-
Binary generic call
-
HTTP mapping generic call
-
Map mapping generic call
-
JSON mapping generic call
-
-
Protobuf:
- JSON mapping generic call
- Protobuf -> Thrift generic call
Generic
type Generic interface {
Closer
// PayloadCodec return codec implement
PayloadCodec() remote.PayloadCodec
// PayloadCodecType return the type of codec
PayloadCodecType() serviceinfo.PayloadCodec
// RawThriftBinaryGeneric must be framed
Framed() bool
// GetMethod to get method name if need
GetMethod(req interface{}, method string) (*Method, error)
}
The core method of Generic
is the codec implementation. Different Generic
implementations distinguish themselves through various codec implementations. Different codecs are expanded based on the implementation of thriftCodec
.
Binary Generic
Use case: For scenarios like middle-end services, where it is possible to forward the received original Thrift protocol packets as binary streams to the target service.
The following method is provided to create a binary generic call Generic
instance.
BinaryThriftGeneric
Function signature: func BinaryThriftGeneric() Generic
Description: Returns a binary generic call object.
HTTP Generic Call
Use case: For scenarios like API gateways, where HTTP requests can be parsed and then forwarded as RPC requests to backend services.
The following method is provided to create an HTTP generic call Generic
instance.
HTTPThriftGeneric
Function signature: func HTTPThriftGeneric(p DescriptorProvider, opts ...Option) (Generic, error)
Description: Takes an IDL Provider and optional Option parameters to return an HTTP generic call object. Details of Option parameters are provided later in the text.
HTTPPbThriftGeneric
Function signature: func HTTPPbThriftGeneric(p DescriptorProvider, pbp PbDescriptorProvider) (Generic, error)
Description: Takes a Thrift IDL Provider and a Protobuf IDL Provider to return an HTTP generic call object capable of parsing body in Protobuf format.
JSON Generic Call
Use case: For scenarios like interface testing platforms, where users' constructed JSON data is parsed and sent as requests to RPC services to obtain response results.
The following method is provided to create a JSON generic call Generic
instance.
JSONThriftGeneric
Function signature: func JSONThriftGeneric(p DescriptorProvider, opts ...Option) (Generic, error)
Description: Takes an IDL Provider and optional Option parameters to return an Thrift JSON generic call object. Details of Option parameters are provided later in the text.
MapThriftGenericForJSON
Function signature: func MapThriftGenericForJSON(p DescriptorProvider) (Generic, error)
Description: Takes an IDL Provider to return a Thrift JSON generic call object, which internally uses Map generic calls for implementation.
JSONPbGeneric
Function signature: func JSONPbGeneric(p PbDescriptorProviderDynamicGo, opts ...Option) (Generic, error)
Description: Takes an IDL Provider and optional Option parametersand optional Option parameters to return a Protobuf JSON generic call object. Details of Option parameters are provided later in the text.
Map Generic Call
Use case: Scenarios involving dynamic parameter adjustment and verifying certain functionalities during rapid prototyping stages.
The following method is provided to create a Map generic call Generic
instance.
MapThriftGeneric
Function signature: func MapThriftGeneric(p DescriptorProvider) (Generic, error)
Description: Takes an IDL Provider to return a Map generic call object.
Option
Kitex offers Option parameters for customizing configurations when creating a Generic, including the following:
WithCustomDynamicGoConvOpts
Function signature:func WithCustomDynamicGoConvOpts(opts *conv.Options) Option
Description: Customizes conv.Option
configurations when using dynamicgo
. Configuration details can be found at dynamicgo conv. For details on integrating dynamicgo, see the dynamicgo integration guide.
UseRawBodyForHTTPResp
Function signature: func UseRawBodyForHTTPResp(enable bool) Option
Description: In HTTP mapping generic calls, this sets whether to use HTTPResponse.RawBody
as the response result. If this feature is disabled, the response result will only be stored in HTTPResponse.Body
Thrift Usage Example
Binary Generic Call
Client
For client-side binary generic calls, request parameters need to be encoded using the Thrift encoding format.
Note: The binary encoding is not for the original Thrift request parameters, but for the method parameter’s XXXArgs. Refer to test cases for examples.
Kitex provides a Thrift encoding/decoding package github.com/cloudwego/kitex/pkg/utils.NewThriftMessageCodec
。
import (
"github.com/cloudwego/kitex/client/genericclient"
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/pkg/utils.NewThriftMessageCodec"
)
func NewGenericClient(destServiceName string) genericclient.Client {
genericCli := genericclient.NewClient(destServiceName, generic.BinaryThriftGeneric())
return genericCli
}
func main(){
rc := utils.NewThriftMessageCodec()
buf, err := rc.Encode("Test", thrift.CALL, 100, args)
// generic call
genericCli := NewGenericClient("actualServiceName")
resp, err := genericCli.GenericCall(ctx, "actualMethod", buf)
}
Server
The client and server for binary generic calls in Kitex are not necessarily paired. As long as the client provides parameters in the correct Thrift binary encoding format, it can request normal Thrift interface services.
The binary generic server only supports Framed or TTHeader requests, not Buffered Binary. The client needs to specify this through an Option, such as: client.WithTransportProtocol(transport.Framed)
.
package main
import (
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/server/genericserver"
)
func main() {
g := generic.BinaryThriftGeneric()
svr := genericserver.NewServer(&GenericServiceImpl{}, g)
err := svr.Run()
if err != nil {
panic(err)
}
}
type GenericServiceImpl struct {}
// GenericCall ...
func (g *GenericServiceImpl) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
// request is thrift binary
reqBuf := request.([]byte)
// e.g.
fmt.Printf("Method: %s\n", method))
result := xxx.NewMockTestResult()
result.Success = &resp
respBuf, err = rc.Encode(mth, thrift.REPLY, seqID, result)
return respBuf, nil
}
HTTP Mapping Generic Call
HTTP generic calls involve constructing Thrift interface parameters based on an HTTP Request and initiating a generic call. Currently, this is only applicable to the client side. The Thrift IDL must follow the interface mapping specifications, detailed in Thrift-HTTP Mapping’s IDL Standards.
IDL Example
namespace go http
struct ReqItem {
1: optional i64 id(go.tag = "json:\"id\"")
2: optional string text
}
struct BizRequest {
1: optional i64 v_int64(api.query = 'v_int64', api.vd = "$>0&&$<200")
2: optional string text(api.body = 'text')
3: optional i32 token(api.header = 'token')
4: optional map<i64, ReqItem> req_items_map (api.body='req_items_map')
5: optional ReqItem some(api.body = 'some')
6: optional list<string> req_items(api.query = 'req_items')
7: optional i32 api_version(api.path = 'action')
8: optional i64 uid(api.path = 'biz')
9: optional list<i64> cids(api.query = 'cids')
10: optional list<string> vids(api.query = 'vids')
}
struct RspItem {
1: optional i64 item_id
2: optional string text
}
struct BizResponse {
1: optional string T (api.header= 'T')
2: optional map<i64, RspItem> rsp_items (api.body='rsp_items')
3: optional i32 v_enum (api.none = '')
4: optional list<RspItem> rsp_item_list (api.body = 'rsp_item_list')
5: optional i32 http_code (api.http_code = '')
6: optional list<i64> item_count (api.header = 'item_count')
}
service BizService {
BizResponse BizMethod1(1: BizRequest req)(api.get = '/life/client/:action/:biz', api.baseurl = 'ib.snssdk.com', api.param = 'true')
BizResponse BizMethod2(1: BizRequest req)(api.post = '/life/client/:action/:biz', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'form')
BizResponse BizMethod3(1: BizRequest req)(api.post = '/life/client/:action/:biz/other', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'json')
}
Generic Call Example
package main
import (
"github.com/cloudwego/kitex/client/genericclient"
"github.com/cloudwego/kitex/pkg/generic"
)
func main() {
// Local file IDL parsing
// YOUR_IDL_PATH is the path to the Thrift file, for example, ./idl/example.thrift
// includeDirs: specifies include paths, defaulting to the current file's relative path for finding includes
p, err := generic.NewThriftFileProvider("./YOUR_IDL_PATH")
if err != nil {
panic(err)
}
// Construct an HTTP type generic call
g, err := generic.HTTPThriftGeneric(p)
if err != nil {
panic(err)
}
cli, err := genericclient.NewClient("destServiceName", g )
if err != nil {
panic(err)
}
// Construct a request (for testing purposes); in actual applications, you can directly use the original HTTP Request
body := map[string]interface{}{
"text": "text",
"some": map[string]interface{}{
"id": 1,
"text": "text",
},
"req_items_map": map[string]interface{}{
"1": map[string]interface{}{
"id": 1,
"text": "text",
},
},
}
data, err := json.Marshal(body)
if err != nil {
panic(err)
}
url := "http://example.com/life/client/1/1?v_int64=1&req_items=item1,item2,itme3&cids=1,2,3&vids=1,2,3"
req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
if err != nil {
panic(err)
}
// Kitex generic currently directly supports the standard library's http.Request; using hertz requires a request conversion
// httpReq, err := adaptor.GetCompatRequest(hertzReqCtx)
req.Header.Set("token", "1")
customReq, err := generic.FromHTTPRequest(req) // Considering that businesses might use third-party HTTP requests, you can create your own conversion function
// customReq *generic.HttpRequest
// Since the HTTP generic method is obtained from the HTTP request via BAM rules, it's okay to leave it empty
resp, err := cli.GenericCall(ctx, "", customReq)
realResp := resp.(*generic.HTTPResponse)
realResp.Write(w) // Write back to ResponseWriter, used for HTTP gateway
}
Annotation Extension
For example, adding an annotation xxx.source='not_body_struct'
indicates that a certain field itself does not map to any HTTP request field and requires iterating over its subfields to obtain corresponding values from the HTTP request.
Usage is as follows:
struct Request {
1: optional i64 v_int64(api.query = 'v_int64')
2: optional CommonParam common_param (xxx.source='not_body_struct')
}
struct CommonParam {
1: optional i64 api_version (api.query = 'api_version')
2: optional i32 token(api.header = 'token')
}
The extension method is as follows:
func init() {
descriptor.RegisterAnnotation(new(notBodyStruct))
}
// Implementing descriptor.Annotation
type notBodyStruct struct {
}
func (a * notBodyStruct) Equal(key, value string) bool {
return key == "xxx.source" && value == "not_body_struct"
}
// Handle currently supports four types: HttpMapping, FieldMapping, ValueMapping, Router
func (a * notBodyStruct) Handle() interface{} {
return newNotBodyStruct
}
type notBodyStruct struct{}
var newNotBodyStruct descriptor.NewHTTPMapping = func(value string) descriptor.HTTPMapping {
return ¬BodyStruct{}
}
// get value from request
func (m *notBodyStruct) Request(req *descriptor.HttpRequest, field *descriptor.FieldDescriptor) (interface{}, bool) {
return req, true
}
// set value to response
func (m *notBodyStruct) Response(resp *descriptor.HTTPResponse, field *descriptor.FieldDescriptor, val interface{}) {
}
Map Mapping Generic Call
Map mapping generic calls refer to the ability of users to construct Map parameters according to specifications, and Kitex will handle the Thrift encoding/decoding accordingly.
Kitex strictly validates the field names and types constructed by the user based on the given IDL. Field names are only supported as string types corresponding to Map Keys, and the mapping of field Value types can be seen in the type mapping table.
For Responses, the Field ID and type will be validated, and the corresponding Map Key will be generated based on the IDL’s Field Name.
Type Mapping
Golang and Thrift IDL Type Mapping is as follows:
Golang Type | Thrift IDL Type |
---|---|
bool | bool |
int8 | i8 |
int16 | i16 |
int32 | i32 |
int64 | i64 |
float64 | double |
string | string |
[]byte | binary |
[]interface{} | list/set |
map[interface{}]interface{} | map |
map[string]interface{} | struct |
int32 | enum |
Take the following IDL as an example:
enum ErrorCode {
SUCCESS = 0
FAILURE = 1
}
struct Info {
1: map<string,string> Map
2: i64 ID
}
struct EchoRequest {
1: string Msg
2: i8 I8
3: i16 I16
4: i32 I32
5: i64 I64
6: binary Binary
7: map<string,string> Map
8: set<string> Set
9: list<string> List
10: ErrorCode ErrorCode
11: Info Info
255: optional Base Base
}
The request construction is as follows:
req := map[string]interface{}{
"Msg": "hello",
"I8": int8(1),
"I16": int16(1),
"I32": int32(1),
"I64": int64(1),
"Binary": []byte("hello"),
"Map": map[interface{}]interface{}{
"hello": "world",
},
"Set": []interface{}{"hello", "world"},
"List": []interface{}{"hello", "world"},
"ErrorCode": int32(1),
"Info": map[string]interface{}{
"Map": map[interface{}]interface{}{
"hello": "world",
},
"ID": int64(232324),
},
}
Example IDL
base.thrift
:
base.thrift
namespace py base
namespace go base
namespace java com.xxx.thrift.base
struct TrafficEnv {
1: bool Open = false,
2: string Env = "",
}
struct Base {
1: string LogID = "",
2: string Caller = "",
3: string Addr = "",
4: string Client = "",
5: optional TrafficEnv TrafficEnv,
6: optional map<string, string> Extra,
}
struct BaseResp {
1: string StatusMessage = "",
2: i32 StatusCode = 0,
3: optional map<string, string> Extra,
}
example_service.thrift
:
example_service.thrift
include "base.thrift"
namespace go kitex.test.server
struct ExampleReq {
1: required string Msg,
255: base.Base Base,
}
struct ExampleResp {
1: required string Msg,
255: base.BaseResp BaseResp,
}
service ExampleService {
ExampleResp ExampleMethod(1: ExampleReq req),
}
Client
package main
import (
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/client/genericclient"
)
func main() {
// Local file IDL parsing
// YOUR_IDL_PATH is the path to the Thrift file, for example, ./idl/example.thrift
// includeDirs: Specifies the include path, defaults to the current file's relative path for finding includes
p, err := generic.NewThriftFileProvider("./YOUR_IDL_PATH")
if err != nil {
panic(err)
}
// Constructing a map type generic call
g, err := generic.MapThriftGeneric(p)
if err != nil {
panic(err)
}
cli, err := genericclient.NewClient("destServiceName", g)
if err != nil {
panic(err)
}
// 'ExampleMethod' the method name must be included in the IDL definition
// resp type is map[string]interface{}
resp, err := cli.GenericCall(ctx, "ExampleMethod", map[string]interface{}{
"Msg": "hello",
})
}
Server
package main
import (
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/server/genericserver"
)
func main() {
// Local file IDL parsing
// YOUR_IDL_PATH is the path to the Thrift file, e.g., ./idl/example.thrift
p, err := generic.NewThriftFileProvider("./YOUR_IDL_PATH")
if err != nil {
panic(err)
}
// Constructing a generic call with map request and return types
g, err := generic.MapThriftGeneric(p)
if err != nil {
panic(err)
}
svc := genericserver.NewServer(new(GenericServiceImpl), g)
if err != nil {
panic(err)
}
err := svr.Run()
if err != nil {
panic(err)
}
}
type GenericServiceImpl struct {
}
func (g *GenericServiceImpl) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
m := request.(map[string]interface{})
fmt.Printf("Recv: %v\n", m)
return map[string]interface{}{
"Msg": "world",
}, nil
}
JSON Mapping Generic Call
JSON mapping generic calls refer to the ability of users to construct JSON String request parameters or returns directly according to specifications, and Kitex will handle encoding/decoding accordingly.
Unlike the strict validation of field names and types in Map generic calls, JSON generic calls in Kitex transform user request parameters based on the given IDL, eliminating the need for users to specify explicit types, such as int32 or int64.
For Responses, the Field ID and type will be validated, and the corresponding JSON field will be generated based on the IDL’s Field Name.
Type Mapping
The type mapping between Golang and Thrift IDL is as follows:
Golang Type | Thrift IDL Type |
---|---|
bool | bool |
int8 | i8 |
int16 | i16 |
int32 | i32 |
int64 | i64 |
float64 | double |
string | string |
[]interface{} | list/set |
map[interface{}]interface{} | map |
map[string]interface{} | struct |
int32 | enum |
Take the following IDL as an example:
enum ErrorCode {
SUCCESS = 0
FAILURE = 1
}
struct Info {
1: map<string,string> Map
2: i64 ID
}
struct EchoRequest {
1: string Msg
2: i8 I8
3: i16 I16
4: i32 I32
5: i64 I64
6: map<string,string> Map
7: set<string> Set
8: list<string> List
9: ErrorCode ErrorCode
10: Info Info
255: optional Base Base
}
The request construction is as follows:
req := {
"Msg": "hello",
"I8": 1,
"I16": 1,
"I32": 1,
"I64": 1,
"Map": "{\"hello\":\"world\"}",
"Set": ["hello", "world"],
"List": ["hello", "world"],
"ErrorCode": 1,
"Info": "{\"Map\":\"{\"hello\":\"world\"}\", \"ID\":232324}"
}
Example IDL
base.thrift
:
base.thrift
namespace py base
namespace go base
namespace java com.xxx.thrift.base
struct TrafficEnv {
1: bool Open = false,
2: string Env = "",
}
struct Base {
1: string LogID = "",
2: string Caller = "",
3: string Addr = "",
4: string Client = "",
5: optional TrafficEnv TrafficEnv,
6: optional map<string, string> Extra,
}
struct BaseResp {
1: string StatusMessage = "",
2: i32 StatusCode = 0,
3: optional map<string, string> Extra,
}
example_service.thrift
:
example_service.thrift
include "base.thrift"
namespace go kitex.test.server
struct ExampleReq {
1: required string Msg,
255: base.Base Base,
}
struct ExampleResp {
1: required string Msg,
255: base.BaseResp BaseResp,
}
service ExampleService {
ExampleResp ExampleMethod(1: ExampleReq req),
}
Client
package main
import (
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/client/genericclient"
)
func main() {
// Local file IDL parsing
// YOUR_IDL_PATH is the path to the Thrift file, for example, ./idl/example.thrift
// includeDirs: Specifies the include path, defaults to the current file's relative path for finding includes
p, err := generic.NewThriftFileProvider("./YOUR_IDL_PATH")
if err != nil {
panic(err)
}
// Constructing a JSON type generic call
g, err := generic.JSONThriftGeneric(p)
if err != nil {
panic(err)
}
cli, err := genericclient.NewClient("destServiceName", g)
if err != nil {
panic(err)
}
// 'ExampleMethod' the method name must be included in the IDL definition
// resp type is JSON string
resp, err := cli.GenericCall(ctx, "ExampleMethod", "{\"Msg\": \"hello\"}")
}
Server
package main
import (
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/server/genericserver"
)
func main() {
// Local file IDL parsing
// YOUR_IDL_PATH is the path to the Thrift file, e.g., ./idl/example.thrift
p, err := generic.NewThriftFileProvider("./YOUR_IDL_PATH")
if err != nil {
panic(err)
}
// Constructing a generic call with map request and return types
g, err := generic.JSONThriftGeneric(p)
if err != nil {
panic(err)
}
svc := genericserver.NewServer(new(GenericServiceImpl), g)
if err != nil {
panic(err)
}
err := svr.Run()
if err != nil {
panic(err)
}
}
type GenericServiceImpl struct {
}
func (g *GenericServiceImpl) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
// use jsoniter or other json parse sdk to assert request
m := request.(string)
fmt.Printf("Recv: %v\n", m)
return "{\"Msg\": \"world\"}", nil
}
Protobuf Usage Example
JSON Mapping Generic Call
JSON mapping generic calls refer to the ability of users to construct JSON String request parameters or returns directly according to specifications, and Kitex will handle encoding/decoding accordingly.
Type Mapping
The Mapping between Golang and Protocol Buffers:
Protocol Buffers Type | Golang Type |
---|---|
float | float32 |
double | float64 |
int32 | int32 |
int64 | int64 |
uint32 | uint32 |
uint64 | uint64 |
sint32 | int32 |
sint64 | int64 |
fixed32 | uint32 |
fixed64 | uint64 |
sfixed32 | int32 |
sfixed64 | uint64 |
bool | bool |
string | string |
bytes | byte[] |
Also supports lists and dictionaries in json, mapping them to repeated V and map<K,V> in protobufs. Does not support Protobuf special fields, such as Enum, Oneof, etc.;
Example IDl
syntax = "proto3";
package api;
// The greeting service definition.
option go_package = "api";
message Request {
string message = 1;
}
message Response {
string message = 1;
}
service Echo {
rpc EchoPB (Request) returns (Response) {}
}
Client
package main
import (
"context"
dproto "github.com/cloudwego/dynamicgo/proto"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/client/genericclient"
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/pkg/klog"
"github.com/cloudwego/kitex/transport"
)
const serverHostPort = "127.0.0.1:9999"
func main() {
var err error
path := "./YOUR_IDL_PATH"
// initialise DynamicGo proto.ServiceDescriptor
dOpts := dproto.Options{}
p, err := generic.NewPbFileProviderWithDynamicGo(path, context.Background(), dOpts)
if err != nil {
panic(err)
}
// create generic client
g, err := generic.JSONPbGeneric(p)
if err != nil {
panic(err)
}
var opts []client.Option
opts = append(opts, client.WithHostPorts(serverHostPort))
opts = append(opts, client.WithTransportProtocol(transport.TTHeader))
cli, err := genericclient.NewClient("server_name_for_discovery", g, opts...)
if err != nil {
panic(err)
}
jReq := `{"message": "hello"}`
ctx := context.Background()
// jRsp's type is JSON string
jRsp, err := cli.GenericCall(ctx, "EchoPB", jReq)
klog.CtxInfof(ctx, "genericJsonCall: jRsp(%T) = %s, err = %v", jRsp, jRsp, err)
}
Server
package main
import (
"context"
dproto "github.com/cloudwego/dynamicgo/proto"
"github.com/cloudwego/kitex/pkg/generic"
"github.com/cloudwego/kitex/pkg/klog"
"github.com/cloudwego/kitex/server"
"github.com/cloudwego/kitex/server/genericserver"
"net"
)
const serverHostPort = "127.0.0.1:9999"
func WithServiceAddr(hostPort string) server.Option {
addr, _ := net.ResolveTCPAddr("tcp", hostPort)
return server.WithServiceAddr(addr)
}
type GenericEchoImpl struct{}
func (g *GenericEchoImpl) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
buf := request.(string)
return buf, nil
}
func main() {
var opts []server.Option
opts = append(opts, WithServiceAddr(serverHostPort))
path := "./YOUR_IDL_PATH"
dOpts := dproto.Options{}
p, err := generic.NewPbFileProviderWithDynamicGo(path, context.Background(), dOpts)
if err != nil {
panic(err)
}
g, err := generic.JSONPbGeneric(p)
opts = append(opts, WithServiceAddr(serverHostPort))
svr := genericserver.NewServer(new(GenericEchoImpl), g, opts...)
if err := svr.Run(); err != nil {
klog.Infof(err.Error())
}
}