Files
grpccanary/lib/grpc/http3/client.go
T

100 lines
2.9 KiB
Go

package http3
import (
"context"
"crypto/tls"
"fmt"
"io"
"net"
"time"
"grpccanary/lib/grpc/http3/protoapi"
"github.com/quic-go/quic-go"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func quicDialer(tlsConf *tls.Config) func(context.Context, string) (net.Conn, error) {
return func(ctx context.Context, addr string) (net.Conn, error) {
qconn, err := quic.DialAddr(ctx, addr, tlsConf, &quic.Config{
KeepAlivePeriod: 10 * time.Second,
})
if err != nil {
return nil, fmt.Errorf("quic dial failed: %w", err)
}
stream, err := qconn.OpenStreamSync(ctx)
if err != nil {
_ = qconn.CloseWithError(0, "failed to open stream")
return nil, fmt.Errorf("failed to open stream: %w", err)
}
return &quicNetConn{Stream: stream, conn: qconn}, nil
}
}
func ClientRun(addr string) error {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"grpc-http3-canary"},
}
conn, err := grpc.NewClient(addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(quicDialer(tlsConf)),
)
if err != nil {
return fmt.Errorf("failed to create grpc client: %w", err)
}
defer conn.Close()
client := protoapi.NewHttp3ServiceClient(conn)
// Call 1: First Unary Ping
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
res1, err := client.Ping(ctx, &protoapi.PingRequest{Message: "First Message"})
if err != nil {
return fmt.Errorf("unary ping 1 failed: %w", err)
}
fmt.Printf("[HTTP3 Client] Received Ping 1 Response: Message='%s', Transport='%s'\n", res1.GetMessage(), res1.GetTransport())
// Call 2: Second Unary Ping (to verify stream multiplexing / reuse over same QUIC connection)
ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel2()
res2, err := client.Ping(ctx2, &protoapi.PingRequest{Message: "Second Message"})
if err != nil {
return fmt.Errorf("unary ping 2 failed: %w", err)
}
fmt.Printf("[HTTP3 Client] Received Ping 2 Response: Message='%s', Transport='%s'\n", res2.GetMessage(), res2.GetTransport())
// Call 3: Bidirectional Streaming Ping
streamCtx, streamCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer streamCancel()
stream, err := client.StreamPing(streamCtx)
if err != nil {
return fmt.Errorf("failed to open bi-directional stream: %w", err)
}
for i := 1; i <= 3; i++ {
msg := fmt.Sprintf("Stream Message %d", i)
err := stream.Send(&protoapi.PingRequest{Message: msg})
if err != nil {
return fmt.Errorf("failed to send stream message %d: %w", i, err)
}
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("failed to receive stream response %d: %w", i, err)
}
fmt.Printf("[HTTP3 Client] Received Stream Response %d: Message='%s', Transport='%s'\n", i, res.GetMessage(), res.GetTransport())
}
_ = stream.CloseSend()
return nil
}