Last active
June 6, 2022 14:45
-
-
Save salrashid123/e7ac53fd289f20c6e2a7d5ea40ed38b7 to your computer and use it in GitHub Desktop.
gRPC healthcheck using curl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
/* | |
grpc Healthcheck from scratch | |
https://blog.salrashid.dev/articles/2022/grpc_healthcheck_curl/ | |
*/ | |
import ( | |
"bytes" | |
"encoding/hex" | |
"flag" | |
"fmt" | |
"io/ioutil" | |
"github.com/psanford/lencode" | |
"google.golang.org/protobuf/proto" | |
"google.golang.org/protobuf/reflect/protodesc" | |
"google.golang.org/protobuf/reflect/protoreflect" | |
"google.golang.org/protobuf/reflect/protoregistry" | |
"google.golang.org/protobuf/types/descriptorpb" | |
"google.golang.org/protobuf/types/dynamicpb" | |
) | |
const () | |
var ( | |
// cacert = flag.String("cacert", "", "CACert for server") | |
serviceName = flag.String("serviceName", "echo.EchoServer", "service name to healthcheck") | |
// url = flag.String("url", "https://grpc-server-6w42z6vi3q-uc.a.run.app/grpc.health.v1.Health/Check", "gRPC server fully qualified") | |
// serverName = flag.String("servername", "grpc-server-6w42z6vi3q-uc.a.run.app:", "SNI for server") | |
) | |
func main() { | |
flag.Parse() | |
pbFiles := []string{ | |
"health.pb", | |
} | |
for _, fileName := range pbFiles { | |
protoFile, err := ioutil.ReadFile(fileName) | |
if err != nil { | |
panic(err) | |
} | |
fileDescriptors := &descriptorpb.FileDescriptorSet{} | |
err = proto.Unmarshal(protoFile, fileDescriptors) | |
if err != nil { | |
panic(err) | |
} | |
for _, pb := range fileDescriptors.GetFile() { | |
var fdr protoreflect.FileDescriptor | |
fdr, err = protodesc.NewFile(pb, protoregistry.GlobalFiles) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("Loading package %s\n", fdr.Package().Name()) | |
err = protoregistry.GlobalFiles.RegisterFile(fdr) | |
if err != nil { | |
panic(err) | |
} | |
for _, m := range pb.MessageType { | |
fmt.Printf(" Registering MessageType: %s\n", *m.Name) | |
md := fdr.Messages().ByName(protoreflect.Name(*m.Name)) | |
mdType := dynamicpb.NewMessageType(md) | |
err = protoregistry.GlobalTypes.RegisterMessage(mdType) | |
if err != nil { | |
panic(err) | |
} | |
} | |
} | |
} | |
echoRequestMessageType, err := protoregistry.GlobalTypes.FindMessageByName("grpc.health.v1.HealthCheckRequest") | |
if err != nil { | |
panic(err) | |
} | |
echoRequestMessageDescriptor := echoRequestMessageType.Descriptor() | |
fname := echoRequestMessageDescriptor.Fields().ByName("service") | |
reflectEchoRequest := echoRequestMessageType.New() | |
reflectEchoRequest.Set(fname, protoreflect.ValueOfString(*serviceName)) | |
fmt.Printf("HealthCheckRequest: %v\n", reflectEchoRequest) | |
in, err := proto.Marshal(reflectEchoRequest.Interface()) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("Encoded HealthCheckRequest using protoreflect %s\n", hex.EncodeToString(in)) | |
var out bytes.Buffer | |
enc := lencode.NewEncoder(&out, lencode.SeparatorOpt([]byte{0})) | |
err = enc.Encode(in) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("wire encoded HealthCheckRequest: %s\n", hex.EncodeToString(out.Bytes())) | |
// uncomment if you want to actually send this...but the point of this code is to construct the raw message by hand anyway | |
// tlsConfig := tls.Config{} | |
// if *cacert != "" { | |
// caCert, err := ioutil.ReadFile(*cacert) | |
// if err != nil { | |
// log.Fatalf("did not load ca: %v", err) | |
// } | |
// caCertPool := x509.NewCertPool() | |
// caCertPool.AppendCertsFromPEM(caCert) | |
// tlsConfig = tls.Config{ | |
// ServerName: *serverName, | |
// RootCAs: caCertPool, | |
// } | |
// } | |
// client := http.Client{ | |
// Transport: &http2.Transport{ | |
// TLSClientConfig: &tlsConfig, | |
// }, | |
// } | |
// reader := bytes.NewReader(out.Bytes()) | |
// resp, err := client.Post(*url, "application/grpc", reader) | |
// if err != nil { | |
// log.Fatal(err) | |
// } | |
// if resp.StatusCode != http.StatusOK { | |
// log.Fatal(err) | |
// } | |
// bodyBytes, err := io.ReadAll(resp.Body) | |
// if err != nil { | |
// log.Fatal(err) | |
// } | |
// bytesReader := bytes.NewReader(bodyBytes) | |
// // now unpack the wiremessage to get to the unary response | |
// respMessage := lencode.NewDecoder(bytesReader, lencode.SeparatorOpt([]byte{0})) | |
// respMessageBytes, err := respMessage.Decode() | |
// if err != nil { | |
// log.Fatal(err) | |
// } | |
// echoReplyMessageType, err := protoregistry.GlobalTypes.FindMessageByName("grpc.health.v1.HealthCheckResponse") | |
// if err != nil { | |
// panic(err) | |
// } | |
// echoReplyMessageDescriptor := echoReplyMessageType.Descriptor() | |
// pmr := echoReplyMessageType.New() | |
// err = proto.Unmarshal(respMessageBytes, pmr.Interface()) | |
// if err != nil { | |
// panic(err) | |
// } | |
// msg := echoReplyMessageDescriptor.Fields().ByName("status") | |
// fmt.Printf("HealthCheckResponse.status using protoreflect: %s\n", pmr.Get(msg).String()) | |
// s, err := protojson.Marshal(pmr.Interface()) | |
// if err != nil { | |
// panic(err) | |
// } | |
// fmt.Printf("HealthCheckResponse as string JSON: %s\n", string(s)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment