100 lines
2.9 KiB
Go
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
|
|
}
|