This article is participating in Python Theme Month. See the link to the event for more details

GRPC

What is the GRPC?

With the popularity of microservices, GRPC is gaining momentum. Our back-end project may have many microservices, and these services may be written in many languages. For example, the authentication service is Java, the notification service is Python, the other business uses C, and so on. Since these are microservices, we also need to call each other between these services to cooperate to complete the business, so how to call between different languages? GRPC is one such communication tool that helps you to let programs in different languages talk to each other. It doesn’t require you to deal with language barriers, making it as easy as calling a function you wrote yourself.

GRPC, as the name implies, is a remote call. Why? GRPC Remote Procedure Calls.

GRPC and REST

  • REST is based on HTTP /1.1, while GRPC is based on HTTP /2. GRPC is much faster than REST.
  • For message transmission, REST is JSON/XML, while GRPC is Protobuf, which is transmitted in binary form, so it is much smaller than JSON/XML.
  • The GRPC API is very strict and must be explicitly defined in the proto file, whereas REST does not.
  • GRPC code generation can be automatically generated within GRPC projects using the protocol buffer compiler, whereas REST requires tripartite tools (Swagger, OpenAPI)
  • GRPC can be two-way, while REST is one-way.
  • REST supports browsers while GRPC does not, so the most common RPC scenarios are in hardware areas such as IOT.

Prerequisites for using GRPC

You must know the following concepts

  • Protocol Buffers
  • Simple Flow (Unary RPC)
  • Server Streaming RPC
  • Client Streaming RPC
  • Bidirectional Streaming RPC

GRPC in Python

The following is an example of a simple stream. Other streams can be found in the official code, in a variety of languages found in this repository. The RouteGuide example includes all types of GRPC services. Note: You must install grPcio-Tools and GRpCIO before using Python.

python -m pip install grpcio
python -m pip install grpcio-tools
Copy the code

1. Write proto files

First, we define the service that constrains us according to the Protocol Buffers file. Let’s take our most common helloWorld example. Create the folder protos in the project root directory and create the helloWorld. proto file in the folder as follows.

syntax = "proto3";
// To be compatible with Java configuration, it does not work in Python
// If false, only a single .java file will be generated for this .proto file.
// If true, separate .java files will be generated for each of the Java classes/enums/etc.
option java_multiple_files = true;
// The package you want to use for your generated Java/Kotlin classes.
option java_package = "com.wangscaler.examples.helloworld";
// The class name (and hence the file name) for the wrapper Java class you want to generate.
option java_outer_classname = "HelloWorldProto";
​
​
package helloworld;
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayAuthor (AuthorRequest) returns (AuthorReply) {}}// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}
​
// The request message containing the user's name.
message AuthorRequest {
  string name = 1;
  int32 age = 2;
}
​
// The response message containing the greetings
message HelloReply {
  string message= 1; } / /The response message containing the greetings
message AuthorReply {
  string message = 1;
  int32 code = 2;
}
Copy the code

We define a Greeter service that provides two interfaces, SayHello and SayAuthor. The request parameters and response parameters of the two interfaces are restricted.

The difference is that the input parameter of SayHello is a string, and the response parameter is a string; The AuthorReply input has an int age and the response parameter has an int code.

2. Automatically generate code

Execute in the project root directory

python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=. ./protos/helloworld.proto
Copy the code

After execution, two Python files helloWorld_pb2. py and helloWorld_pb2_grPC. py are generated in the project root path.

3. Develop the server

Define Greeter to inherit from helloWorld_PB2_grPC. GreeterServicer and override SayHello and SayAuthor to implement our business.

from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
​
​
class Greeter(helloworld_pb2_grpc.GreeterServicer) :
​
    def SayHello(self, request, context) :
        print("Get a message from %s client" % request.name)
        return helloworld_pb2.HelloReply(message='Hello world, %s client ! ' % request.name)
​
    def SayAuthor(self, request, context) :
        print("Hi author(%(name)s), your age is %(age)d" % {"name": request.name, "age": request.age})
        return helloworld_pb2.AuthorReply(
            message='Hello, %s ! ' % request.name, code=0)
​
​
def serve() :
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[: :] : 50051')
    server.start()
    server.wait_for_termination()
​
​
if __name__ == '__main__':
    logging.basicConfig()
    serve()
​
​
Copy the code

4. Developing the client (Python)

import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
​
​
def run() :
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        hello_response = stub.SayHello(helloworld_pb2.HelloRequest(name='python'))
        author_response = stub.SayAuthor(helloworld_pb2.AuthorRequest(name='scaler', age=18))
    print("Greeter client received: " + hello_response.message)
    print("Greeter client received message: %(message)s and received code: %(code)d !" % {
        "message": author_response.message,
        "code": author_response.code})
​
​
if __name__ == '__main__':
    logging.basicConfig()
    run()
​
Copy the code

After execution, the console prints the following message:

Greeter client received: Hello world, python client !
Greeter client received message: Hello, scaler !  and received code: 0 !
Copy the code

Our server print message looks like this:

Get a message from python client
Hi author(scaler), your age is 18
Copy the code

GRPC Java client

As we said at the beginning, GRPC is compatible with calls from multiple languages, so our Java client can also call the above Python server-side SayHello and SayAuthor interfaces.

Create Maven project

Use IDEA to create, but more introduction.

2. Copy the proto file above

Create the proto folder under the SRC /main folder of the Maven project and copy the proro file created in Python above into this folder.

3. Automatically generate code

Use the MVN compile command to generate the required files in target/generated-sources/protobuf/grpc-java and target/generated-sources/protobuf/ Java.

The package name of the file is the java_package specified in our proto file.

4. Develop client (Java)

Create a package in SRC /main/ Java that is consistent with java_package, that is, with the package name of the generated code.

package com.wangscaler.examples;
​
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
​
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
​
/ * * *@author WangScaler
 * @date2021/7/22 blessed * /
​
​
public class Client {
    private static final Logger logger = Logger.getLogger(Client.class.getName());
    private final GreeterGrpc.GreeterBlockingStub blockingStub;
​
    public Client(Channel channel) {
        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }
​
    public void greet(String name) {
        logger.info("Will try to greet " + name + "...");
        HelloRequest helloRequest = HelloRequest.newBuilder().setName(name).build();
        AuthorRequest authorRequest = AuthorRequest.newBuilder().setName("wangscaler").setAge(18).build();
        HelloReply helloResponse;
        AuthorReply authorResponse;
        try {
            helloResponse = blockingStub.sayHello(helloRequest);
            authorResponse = blockingStub.sayAuthor(authorRequest);
        } catch (StatusRuntimeException e) {
            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
            return;
        }
        logger.info("Greeter client received: " + helloResponse.getMessage());
        logger.info(MessageFormat.format("Greeter client received message: {0} and received code: {1} ! ", authorResponse.getMessage(), authorResponse.getCode()));
    }
​
    public static void main(String[] args) throws Exception {
        String user = "java";
        String target = "localhost:50010";
        if (args.length > 0) {
            if ("--help".equals(args[0])) {
                System.err.println("Usage: [name [target]]");
                System.err.println("");
                System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
                System.err.println(" target The server to connect to. Defaults to " + target);
                System.exit(1);
            }
            user = args[0];
        }
        if (args.length > 1) {
            target = args[1];
        }
​
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
                .usePlaintext()
                .build();
        try {
            Client client = new Client(channel);
            client.greet(user);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); }}}Copy the code

After execution, the console prints the following message:

Will try to greet Java... July 22, 2021 5:00:17 afternoon com. Wangscaler. Examples. The Client greet information: Greeter Client received: Hello world, Java Client! July 22, 2021 5:00:17 afternoon com. Wangscaler. Examples. The Client greet information: Greeter Client received message: Hello, wangscaler! and received code: 0 !Copy the code

Our Python server print message looks like this:

Get a message from java client
Hi author(wangscaler), your age is 18
Copy the code

Cool! Our Java client calls methods on the remote Python server as if it were calling its own internal functions. This is the power of GRPC. Invocations between microservices are as easy and simple as this.

Use SSL authentication

1. Generate the root certificate and private key

Use OpenSSL to generate AN SSL certificate.

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
Copy the code

Note: You need to enter your information after executing this command. If you are testing on SSL local, remember that CN is localhost so that your client can access your server through localhost.

2. Modify the server

from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
​
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
​
​
class Greeter(helloworld_pb2_grpc.GreeterServicer) :
​
    def SayHello(self, request, context) :
        print("Get a message from %s client" % request.name)
        return helloworld_pb2.HelloReply(message='Hello world, %s client ! ' % request.name)
​
    def SayAuthor(self, request, context) :
        print("Hi author(%(name)s), your age is %(age)d" % {"name": request.name, "age": request.age})
        return helloworld_pb2.AuthorReply(
            message='Hello, %s ! ' % request.name, code=0)
​
​
def serve() :
    with open('server.key'.'rb') as f:
        private_key = f.read()
    with open('server.crt'.'rb') as f:
        certificate_chain = f.read()
    server_credentials = grpc.ssl_server_credentials(
        ((private_key, certificate_chain,),))
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_secure_port('[: :] : 50051', server_credentials)
    server.start()
    server.wait_for_termination()
​
​
if __name__ == '__main__':
    logging.basicConfig()
    serve()
​
Copy the code

At this point in our GRPC, SSL authentication is added.

3. Modify the client (Python)

We used the previous client to connect, but found that the connection failed, as shown in the figure below.

Next, modify as follows:

First copy server.key and server. CRT generated by OpenSSL to the root path of the project. Finally modify the code as follows:

import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
​
​
def run() :
    with open('server.crt'.'rb') as f:
        trusted_certs = f.read()
    credentials = grpc.ssl_channel_credentials(root_certificates=trusted_certs)
    with grpc.secure_channel('localhost:50051', credentials) as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        hello_response = stub.SayHello(helloworld_pb2.HelloRequest(name='python'))
        author_response = stub.SayAuthor(helloworld_pb2.AuthorRequest(name='scaler', age=18))
    print("Greeter client received: " + hello_response.message)
    print("Greeter client received message: %(message)s and received code: %(code)d !" % {
        "message": author_response.message,
        "code": author_response.code})
​
​
if __name__ == '__main__':
    logging.basicConfig()
    run()
Copy the code

Run the client again and the console prints normally

Greeter client received: Hello world, python client !
Greeter client received message: Hello, scaler !  and received code: 0 !
Copy the code

4. Modify client (Java)

Test the connection before modifying it. Error as follows:

RPC failed: Status{code=UNAVAILABLE, description=Network closed for unknown reason, cause=null}
Copy the code

Modify as follows: copy the server. CRT generated by OpenSSL to the project path. If it can be found, it is ok.

package com.wangscaler.examples;
​
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NegotiationType;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
​
import javax.net.ssl.SSLException;
import java.io.File;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
​
/ * * *@author WangScaler
 * @date2021/7/22 blessed * /
​
​
public class Client {
    private static final Logger logger = Logger.getLogger(Client.class.getName());
    private final GreeterGrpc.GreeterBlockingStub blockingStub;
​
    public Client(Channel channel) {
        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }
​
    public void greet(String name) {
        logger.info("Will try to greet " + name + "...");
        HelloRequest helloRequest = HelloRequest.newBuilder().setName(name).build();
        AuthorRequest authorRequest = AuthorRequest.newBuilder().setName("wangscaler").setAge(18).build();
        HelloReply helloResponse;
        AuthorReply authorResponse;
        try {
            helloResponse = blockingStub.sayHello(helloRequest);
            authorResponse = blockingStub.sayAuthor(authorRequest);
        } catch (StatusRuntimeException e) {
            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
            return;
        }
        logger.info("Greeter client received: " + helloResponse.getMessage());
        logger.info(MessageFormat.format("Greeter client received message: {0} and received code: {1} ! ", authorResponse.getMessage(), authorResponse.getCode()));
    }
​
    private static SslContext buildSslContext(String trustCertCollectionFilePath) throws SSLException {
        SslContextBuilder builder = GrpcSslContexts.forClient();
        if(trustCertCollectionFilePath ! =null) {
            builder.trustManager(new File(trustCertCollectionFilePath));
        }
        return builder.build();
    }
​
    public static void main(String[] args) throws Exception {
        String user = "java";
        String target = "localhost:50010";
        if (args.length > 0) {
            if ("--help".equals(args[0])) {
                System.err.println("Usage: [name [target]]");
                System.err.println("");
                System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
                System.err.println(" target The server to connect to. Defaults to " + target);
                System.exit(1);
            }
            user = args[0];
        }
        if (args.length > 1) {
            target = args[1];
        }
        String[] targets = new String[2];
        targets = target.split(":");
        String host = targets[0];
        int port = Integer.parseInt(targets[1]);
        SslContext sslContext = Client.buildSslContext("D://springboot/test-grpc/src/main/java/com/wangscaler/examples/server.crt");
        ManagedChannel channel = NettyChannelBuilder.forAddress(host, port)
                .negotiationType(NegotiationType.TLS)
                .sslContext(sslContext)
                .build();
        try {
            Client client = new Client(channel);
            client.greet(user);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); }}}Copy the code

Reconnect. The console normally calls functions on the server side.

Will try to greet Java... July 27, 2021 com 9:38:51 morning. Wangscaler. Examples. The Client greet information: the Greeter Client received: Hello world, Java Client! July 27, 2021 com 9:38:51 morning. Wangscaler. Examples. The Client greet information: the Greeter Client received message: Hello, wangscaler! and received code: 0 !Copy the code

The reference information

  • [1] grpc.io
  • [2] grpc example
  • [3] Using SSL with gRPC in Python
  • [4] ssl_grpc_example