Skip to content

Instantly share code, notes, and snippets.

@salrashid123
Last active June 6, 2022 14:45
Show Gist options
  • Save salrashid123/e7ac53fd289f20c6e2a7d5ea40ed38b7 to your computer and use it in GitHub Desktop.
Save salrashid123/e7ac53fd289f20c6e2a7d5ea40ed38b7 to your computer and use it in GitHub Desktop.
gRPC healthcheck using curl
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