Services sicher und zuverlässig kommunizieren lassen mit gRPC

Seite 3: Vom Client zum Server

Inhaltsverzeichnis

Ohne einen Client bringt aber der beste Server nichts. Auch hierfür erstellt der Generator den Code. Der Typ weather.WeatherServiceClient befindet sich im gleichen Package wie der Code für den Server – er wird für die von beiden Seiten genutzten Messages benötigt.

Den Beginn macht der Verbindungsaufbau des gRPC-Stacks mit dem Server. Steht die Verbindung, wird ein spezieller Client für den individuellen Service erzeugt.

conn, err := grpc.Dial("localhost:8080")
if err != nil {
    log.Fatalf("failed to connect the server: %v", err)
    return
}
defer conn.Close()

weatherServiceClient := weather.NewWeatherServiceClient(conn)

Listing 8: Verbindung des Clients

Zum Verwenden des Clients lassen sich Hilfsfunktionen entwickeln, die zusätzlichen Komfort bieten, um beispielsweise leichter mit einem Timeout umzugehen, individuelle Informationen wie die Temperatur zurückzuliefern oder einen eventuellen Fehler zu beheben.

func ReadTemperature(
    client weather.WeatherServiceClient,
    woeID string,
    timeout time.Duration,
) (float64, error) {
    ctx := context.WithTimeout(context.Background(), timeout)
    defer ctx.Cancel()

    resp, err := client.Read(
        ctx,
        &weather.WeatherResponse{
            WoeID: woeID,
        })

    if err != nil {
        return 0.0, fmt.Errorf("transport error: %v", err)
    }
    if resp.GetError() != nil {
        return 0.0, fmt.Errorf("reading error %s: %s",
            resp.GetError().GetCode(),
            resp.GetError().GetReason())
    }

    return resp.GetWeather().GetTheTemp(), nil
}

Listing 9: Auslesen der Temperatur

Ähnlich schaut es beim Verarbeiten von Abonnements aus. Sie lassen sich mit einer übersichtlichen und leichten Funktion realisieren. Neben dem Client ist erneut die Where-On-Earth-ID sowie eine Callback-Funktion für das Verarbeiten der Wetterdaten nötig. Zum Beenden des Abonnements von außen wird die Cancel-Funktion des Contexts zurückgeliefert.

func Subscribe(
    client weather.WeatherServiceClient,
    woeID string,
    callback func(w *weather.Weather, err error),
) (func(), error) {
    ctx, cancel := context.WithCancel(context.Background())
    subscription := &weather.WeatherRequest{
        WoeID: woeID,
    }
    stream, err := client.Subscribe(
        ctx,
        &weather.WeatherRequest{
            WoeID: woeID,
        })
    if err != nil {
        return null, fmt.Errorf("transport error: %v", err)
    }

    // Process received values in the background.
    go func(sc weather.WeatherService_SubscribeClient) {
        for {
            resp, err := sc.Recv()
            if err == io.EOF {
                break
            }
            if err != nil {
                process(nil, fmt.Errorf("transport error: %v", err))
            }
            if resp.GetError() != nil {
                process(nil, fmt.Errorf("reading error %s: %s",
                    resp.GetError().GetCode(),
                    resp.GetError().GetReason()))
            }
            process(resp.GetWeather(), nil)
        }
    }(stream)

    return cancel, nil
}

Listing 10: Abonnement mit dem Client

Bis zu diesem Zeitpunkt kommunizieren Client und Server über das Netz im Beispiel per Klartext. Für den Einsatz in der Praxis ist aus Sicherheitsgründen jedoch eine Verschlüsselung dringend empfohlen. Der gRPC-Stack hält alle dazu notwendigen Komponenten bereit. Ein Teil der gRPC-Bibliothek ist das Paket credentials, das mit wenigen Zeilen Code das einfache Nutzen eines generierten Schlüsselpaars durch den Server erlaubt. Mit der Option grpc.Creds() lassen sich die erzeugten Schlüssel unmittelbar dem Server übergeben.

creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
    // Handle the error.
    ...
}
grpcServer := grpc.NewServer(grpc.Creds(creds))

Listing 11: Server-Start mit SSL

Die Vorgehensweise auf Client-Seite ist ähnlich. Wie für SSL üblich wird hier einzig die Datei mit den Zertifikaten für die Credentials benötigt.

creds, err := credentials.NewClientTLSFromFile(certFile, "")
if err != nil {
    // Handle the error.
    ...
}
conn, err := grpc.Dial(
    "localhost:8080",
    grpc.WithTransportCredentials(creds))

Listing 12: Client-Start mit SSL