java version of gRPC practice 3: server stream

Posted by smordue on Tue, 07 Dec 2021 04:56:05 +0100

Links to the full series of "java version gRPC actual combat"

  1. Generate code with proto
  2. Service publishing and invocation
  3. Server stream
  4. Client stream
  5. Bidirectional flow
  6. The client dynamically obtains the server address
  7. Registration discovery based on eureka

Four types of gRPC definitions

This article is the third in the series of gRPC actual combat in java. Earlier, we experienced simple RPC request and response. In fact, that simple request response method is only one of the four types defined by gRPC. Here is the description of these four gRPC types in gRPC official document Chinese version:

  1. Simple RPC: clients use stubs to send requests to the server and wait for the response to return, just like normal function calls;
  2. Server side streaming RPC: the client sends a request to the server and gets a stream to read the returned message sequence. The client reads the returned stream until there is no message in it; (i.e. the content of this article)
  3. Client streaming RPC: the client writes a message sequence and sends it to the server. It also uses streaming. Once the client completes writing the message, it waits for the server to complete reading and return its response;
  4. Bidirectional streaming RPC: both parties use read-write streams to send a message sequence. The two streams operate independently, so the client and server can read and write in any desired order: for example, the server can wait to receive all client messages before writing a response, or can read and write messages alternately, or other combinations of reading and writing. The message sequence in each flow is reserved;

Overview of this article

This chapter is the actual combat of gRPC service of service end stream type, including the following contents:

  1. Develop a gRPC service with the type of service end stream;
  2. Develop a client and call the gRPC service published earlier;
  3. verification;
  • Not much to say, start the code;

Source download

  • The complete source code in this actual combat can be downloaded from GitHub. The address and link information are shown in the table below( https://github.com/zq2599/blog_demos):

name

link

remarks

Project Home

https://github.com/zq2599/blog_demos

The project is on the GitHub home page

git warehouse address (https)

https://github.com/zq2599/blog_demos.git

The warehouse address of the source code of the project, https protocol

git warehouse address (ssh)

git@github.com:zq2599/blog_demos.git

The project source code warehouse address, ssh protocol

  • There are multiple folders in the git project. The source code of the gRPC practical combat series for java is in the gRPC tutorials folder, as shown in the red box below:
  • There are multiple directories in the grpc tutorials folder. The server code corresponding to this article is in the server stream server side directory, and the client code is in the server stream client side directory, as shown in the following figure:

Develop a gRPC service with the type of service end stream

  • The first thing to develop is the gRPC server, which needs to do seven things as shown in the figure below:
  • Open gRPC lib module and add a new file mall.proto in src/main/proto directory, which defines a gRPC method ListOrders and its input and return objects. The contents are as follows. Note that the return value should be modified with the keyword stream, indicating that the interface type is a service-side stream:
syntax = "proto3";

option java_multiple_files = true;
// package for generating java code
option java_package = "com.bolingcavalry.grpctutorials.lib";
// Class name
option java_outer_classname = "MallProto";

// gRPC service, which is an order query service of online mall
service OrderQuery {
    // Service side streaming: order list interface. The input parameter is buyer information and returns the order list (modify the return value with stream)
    rpc ListOrders (Buyer) returns (stream Order) {}
}

// Buyer ID
message Buyer {
    int32 buyerId = 1;
}

// Return the data structure of the result
message Order {
    // Order ID
    int32 orderId = 1;
    // Item ID
    int32 productId = 2;
    // Trading time
    int64 orderTime = 3;
    // Buyer remarks
    string buyerRemark = 4;
}
  • Double click generateProto in the red box below to generate java code according to proto:
  • The newly generated java code is shown in the red box below:
  • Create a new module named server stream server side under the parent project grpc tutorials. The content of build.gradle is as follows:
// Using the springboot plug-in
plugins {
    id 'org.springframework.boot'
}

dependencies {
    implementation 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter'
    // As a gRPC service provider, you need to use this library
    implementation 'net.devh:grpc-server-spring-boot-starter'
    // Projects that rely on automatic source code generation
    implementation project(':grpc-lib')
}
  • Create a new configuration file application.yml:
spring:
  application:
    name: server-stream-server-side
# For the configuration related to gRPC, only the service port number needs to be configured here
grpc:
  server:
    port: 9899
  • Startup class:
package com.bolingcavalry.grpctutorials;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServerStreamServerSideApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerStreamServerSideApplication.class, args);
    }
}
  • Next is the most critical gRPC service. The code is as follows. It can be seen that the responseObserver.onNext method is called many times to continuously output data to the client. Finally, the output is ended through responseObserver.onCompleted:
package com.bolingcavalry.grpctutorials;

import com.bolingcavalry.grpctutorials.lib.Buyer;
import com.bolingcavalry.grpctutorials.lib.Order;
import com.bolingcavalry.grpctutorials.lib.OrderQueryGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.ArrayList;
import java.util.List;

@GrpcService
public class GrpcServerService extends OrderQueryGrpc.OrderQueryImplBase {

    /**
     * mock Batch data
     * @return
     */
    private static List<Order> mockOrders(){
        List<Order> list = new ArrayList<>();
        Order.Builder builder = Order.newBuilder();

        for (int i = 0; i < 10; i++) {
            list.add(builder
                    .setOrderId(i)
                    .setProductId(1000+i)
                    .setOrderTime(System.currentTimeMillis()/1000)
                    .setBuyerRemark(("remark-" + i))
                    .build());
        }

        return list;
    }

    @Override
    public void listOrders(Buyer request, StreamObserver<Order> responseObserver) {
        // Continuous output to client
        for (Order order : mockOrders()) {
            responseObserver.onNext(order);
        }
        // End output
        responseObserver.onCompleted();
    }
}
  • So far, the development of the server is completed. Let's develop a springboot application as the client to see how to remotely call the listOrders interface to get the data output by the responseObserver.onNext method;

Develop a client and call the gRPC service published earlier

  • The basic function of the client module is to provide a web interface, which will call the listOrders interface of the server and return the obtained data to the front end, as shown in the following figure:
  • Create a new module named server stream client side under the parent project grpc tutorials. The content of build.gradle is as follows:
plugins {
    id 'org.springframework.boot'
}

dependencies {
    implementation 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'net.devh:grpc-client-spring-boot-starter'
    implementation project(':grpc-lib')
}
  • The application configuration information application.yml is as follows. It can be seen that it is the configuration of port and gRPC server address:
server:
  port: 8081
spring:
  application:
    name: server-stream-client-side

grpc:
  client:
    # The name of gRPC configuration. GrpcClient annotation will be used
    server-stream-server-side:
      # gRPC server address
      address: 'static://127.0.0.1:9899'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext
  • The Order object returned by the listOrders interface on the server side contains many contents related to gRPC, which is not suitable to be used as the return value of the web interface. Therefore, define a DispOrder class as the return value of the web interface:
package com.bolingcavalry.grpctutorials;

import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;

@Data
@AllArgsConstructor
public class DispOrder {
    private int orderId;
    private int productId;
    private String orderTime;
    private String buyerRemark;
}
  • Plain startup class:
package com.bolingcavalry.grpctutorials;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServerStreamClientSideApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerStreamClientSideApplication.class, args);
    }
}
  • Let's focus on GrpcClientService.java, which shows how to remotely call the listOrders interface of gRPC service. It can be seen that for the service side stream type interface, the client side will get the return value of Iterator type through stub call. The next thing to do is to traverse the Iterator:
package com.bolingcavalry.grpctutorials;

import com.bolingcavalry.grpctutorials.lib.Buyer;
import com.bolingcavalry.grpctutorials.lib.Order;
import com.bolingcavalry.grpctutorials.lib.OrderQueryGrpc;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Service
@Slf4j
public class GrpcClientService {

    @GrpcClient("server-stream-server-side")
    private OrderQueryGrpc.OrderQueryBlockingStub orderQueryBlockingStub;

    public List<DispOrder> listOrders(final String name) {
        // gRPC request parameters
        Buyer buyer = Buyer.newBuilder().setBuyerId(101).build();

        // gRPC response
        Iterator<Order> orderIterator;

        // The return value of the current method
        List<DispOrder> orders = new ArrayList<>();

        // Initiate remote gRPC request through stub
        try {
            orderIterator = orderQueryBlockingStub.listOrders(buyer);
        } catch (final StatusRuntimeException e) {
            log.error("error grpc invoke", e);
            return new ArrayList<>();
        }

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        log.info("start put order to list");
        while (orderIterator.hasNext()) {
            Order order = orderIterator.next();

            orders.add(new DispOrder(order.getOrderId(),
                                    order.getProductId(),
                                    // Convert timestamp to string using DateTimeFormatter
                                    dtf.format(LocalDateTime.ofEpochSecond(order.getOrderTime(), 0, ZoneOffset.of("+8"))),
                                    order.getBuyerRemark()));
            log.info("");
        }

        log.info("end put order to list");

        return orders;
    }
}
  • Finally, make a controller class to provide a web interface, which will call the method of GrpcClientService:
package com.bolingcavalry.grpctutorials;

import com.bolingcavalry.grpctutorials.lib.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
public class GrpcClientController {

    @Autowired
    private GrpcClientService grpcClientService;

    @RequestMapping("/")
    public List<DispOrder> printMessage(@RequestParam(defaultValue = "will") String name) {
        return grpcClientService.listOrders(name);
    }
}
  • At this point, the coding is completed and verification begins

verification

  1. Start server stream server side. After successful startup, it will listen to port 9989:
  1. Start server stream client side, and then access in the browser: http://localhost:8081/?name=Tom , the results are as follows (firefox automatically formats json data). It can be seen that the remote data of gRPC is successfully obtained:

So far, the development and use of gRPC interface of service end stream type have been completed. The next chapters will continue to learn the other two types;