Produced by OpenMix: https://openmix.org
Assistant for gRPC and Gateway
go mod install
go get github.com/mix-go/xrpc@latest
Install the proto compiler
Download proto: https://github.com/protocolbuffers/protobuf/releases
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Install the grpc-gateway compilation tool
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
Install google proto
mkdir `go env GOPATH`/src/google
wget https://github.com/googleapis/googleapis/archive/refs/heads/master.zip -O googleapis.zip
unzip googleapis.zip
cp -R googleapis-master/google/api `go env GOPATH`/src/google
wget https://github.com/protocolbuffers/protobuf/archive/refs/heads/main.zip -O protobuf.zip
unzip protobuf.zip
cp -R protobuf-main/src/google/protobuf `go env GOPATH`/src/google
Goland settings: Settings > Languages & Frameworks > Protocol Buffers
Add Import Paths: $GOPATH
/src
- .proto style
- service name, rpc name, message name:
AppMessages
PascalCase - message field name:
string parse_mode = 1;
snake_case
- service name, rpc name, message name:
- urls:
- website url:
/send-message
kebab-case - grpc gateway url: inner api:
/internal/send_message
snake_case - grpc gateway url: open api:
/v1/send_message
snake_case
- website url:
- aws secrets manager:
- name:
Service-Test
Pascal-Case - key:
googleapis_credentials
snake_case
- name:
- .yaml
- file name:
config_test.yaml
snake_case - field name:
clientId
camelCase
- file name:
- mysql:
- table name:
app_messages
snake_case - field name:
client_id
snake_case
- table name:
- mongodb:
- table name:
app_messages
snake_case - field name: Unrestricted, as it depends on the 3rd party, storing raw data
- table name:
- docker:
- container name:
express-gateway
kebab-case
- container name:
service AppMessages {
rpc Send(SendRequest) returns (SendResponse) {
option (google.api.http) = {
post: "/v1/send_message"
body: "*"
};
}
}
message SendRequest {
string text = 1;
string parse_mode = 2;
}
message SendResponse {
int64 message_id = 1;
}
generate-pb.sh
The necessary functions are encapsulated internally for unified management
- Start
s := &xrpc.RpcServer{
GrpcServer: &xrpc.GrpcServer{
Addr: "0.0.0.0:50000",
Registrar: func(s *grpc.Server) {
pb.RegisterOrderServer(s, &service{})
},
},
GatewayServer: &xrpc.GatewayServer{ // Optional
Addr: "0.0.0.0:50001",
Registrar: func(mux *runtime.ServeMux, conn *grpc.ClientConn) {
pb.RegisterOrderHandler(context.Background(), mux, conn)
},
},
Logger: &RpcLogger{SugaredLogger: zapLogger},
}
s.Serve()
- Shutdown
s.Shutdown()
We need to use it when we write core financial services
tlsConf, err := xrpc.LoadServerTLSConfig("/certificates/ca.pem", "/certificates/server.pem", "/certificates/server.key")
if err != nil {
log.Fatal(err)
}
tlsClientConf, err := xrpc.LoadClientTLSConfig("/certificates/ca.pem", "/certificates/client.pem", "/certificates/client.key")
if err != nil {
log.Fatal(err)
}
s := &xrpc.RpcServer{
GrpcServer: &xrpc.GrpcServer{
Addr: "0.0.0.0:50000",
Registrar: func(s *grpc.Server) {
pb.RegisterOrderServer(s, &service{})
},
},
GatewayServer: &xrpc.GatewayServer{ // Optional
Addr: "0.0.0.0:50001",
Registrar: func(mux *runtime.ServeMux, conn *grpc.ClientConn) {
pb.RegisterOrderHandler(context.Background(), mux, conn)
},
},
Logger: &RpcLogger{SugaredLogger: zapLogger},
TLSConfig: tlsConf,
TLSClientConfig: tlsClientConf,
}
s.Serve()
Implement the following interfaces
type Logger interface {
Log(ctx context.Context, level Level, msg string, fields ...any)
}
Loggable Events
- No content: logging.StartCall, logging.FinishCall
- With content: logging.PayloadReceived, logging.PayloadSent
s := &xrpc.RpcServer{
GrpcServer: &xrpc.GrpcServer{
LoggableEvents: []logging.LoggableEvent{logging.StartCall, logging.FinishCall},
}
}
Zap Logger
type RpcLogger struct {
*zap.SugaredLogger
}
func (t *RpcLogger) Log(ctx context.Context, level logging.Level, msg string, fields ...any) {
f := make([]zap.Field, 0, len(fields)/2)
for i := 0; i < len(fields); i += 2 {
key := fields[i]
value := fields[i+1]
switch v := value.(type) {
case string:
f = append(f, zap.String(key.(string), v))
case int:
f = append(f, zap.Int(key.(string), v))
case bool:
f = append(f, zap.Bool(key.(string), v))
default:
f = append(f, zap.Any(key.(string), v))
}
}
logger := t.Desugar().WithOptions(zap.AddCallerSkip(1)).With(f...)
switch level {
case logging.LevelDebug:
logger.Debug(msg)
case logging.LevelInfo:
logger.Info(msg)
case logging.LevelWarn:
logger.Warn(msg)
case logging.LevelError:
logger.Error(msg)
default:
panic(fmt.Sprintf("unknown level %v", level))
}
}
logger := &RpcLogger{SugaredLogger: zapLogger}
The necessary functions are encapsulated internally for unified management
Please reuse this connection, you don't need to handle disconnect and reconnect, but you do need to handle request retry after errors.
conn, err := xrpc.NewGrpcClient("127.0.0.1:50000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewAppMessagesClient(conn)
ctx, _ := context.WithTimeout(context.Background(), xrpc.CallTimeout)
resp, err := client.Send(ctx, &pb.SendRequest{
Text: "foo",
})
We need to use it when we write core financial services
tlsConf, err := xrpc.LoadClientTLSConfig("/certificates/ca.pem", "/certificates/client.pem", "/certificates/client.key")
if err != nil {
log.Fatal(err)
}
conn, err := xrpc.NewGrpcClient("127.0.0.1:50000", grpc.WithTransportCredentials(credentials.NewTLS(tlsConf)))
Examples of other languages
<?php
require __DIR__ . '/vendor/autoload.php';
$opts = [
'credentials' => Grpc\ChannelCredentials::createSsl(file_get_contents('/certificates/server.pem'), file_get_contents('/certificates/client.key'), file_get_contents('/certificates/client.pem')),
// 'grpc.ssl_target_name_override' => '127.0.0.1:50000',
// 'grpc.default_authority' => '127.0.0.1:50000'
];
$client = new \Example\AppMessagesClient('127.0.0.1:50000', $opts);
$request = new \Example\SendRequest();
$request->setText('foo');
list($reply, $status) = $client->Send($request)->wait();
var_dump($reply, $status);
tlsConf, err := xrpc.LoadClientTLSConfig("/certificates/ca.pem", "/certificates/client.pem", "/certificates/client.key")
if err != nil {
log.Fatal(err)
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConf,
},
}
defer client.CloseIdleConnections()
resp, err := client.Post("https://127.0.0.1:50001/v1/send_message", "application/json", strings.NewReader(`{"text":"foo"}`))
fmt.Println(resp.Body)
Examples of other languages
<?php
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'cert' => '/certificates/client.pem',
'ssl_key' => '/certificates/client.key',
'verify' => '/certificates/ca.pem'
]);
$response = $client->request('POST', 'https://127.0.0.1:50001/v1/send_message', ['body' => '{"text":"foo"}']);
var_dump($response->getBody()->getContents());
Modify subjectAltName in generate-rsa.cnf
, the generated files are in the certificates
directory.
generate-rsa.sh
Code new TLS Config
- Server
Load from file
tlsConf, err := xrpc.LoadServerTLSConfig("/certificates/ca.pem", "/certificates/server.pem", "/certificates/server.key")
New by bytes
tlsConf, err := xrpc.NewServerTLSConfig([]byte{}, []byte{}, []byte{})
- Client
Load from file
tlsConf, err := xrpc.LoadClientTLSConfig("/certificates/ca.pem", "/certificates/client.pem", "/certificates/client.key")
New by bytes
tlsConf, err := xrpc.NewClientTLSConfig([]byte{}, []byte{}, []byte{})
Apache License Version 2.0, http://www.apache.org/licenses/