Use gRPC and Python for Remote Procedural Calls
Traducciones al EspañolEstamos traduciendo nuestros guías y tutoriales al Español. Es posible que usted esté viendo una traducción generada automáticamente. Estamos trabajando con traductores profesionales para verificar las traducciones de nuestro sitio web. Este proyecto es un trabajo en curso.
gRPC is an open-source remote procedure call (RPC) framework that enables cross-platform and cross-language communication between clients and a central server. gRPC allows for the specification of a common interface or API to define shared functions, constants, and message types. The server implements the full interface, while clients use stub functions to call the methods in the API. Client and server applications can be written in one of several supported programming languages, which do not have to necessarily match up. This guide introduces and explains gRPC, and describes how to implement an application with remote function calls using gRPC and Python.
What is a Remote Procedural Call?
A remote procedure call (RPC) is a central concept in distributed computing. It allows one system to call a function in a different address space, or even on a completely different system. If the client and server are running on the same system, they use distinct virtual addressing spaces. Client systems only require a description of the shared interface to interact with the remote system.
RPCs are usually implemented in terms of requests and responses. RPC requests can be either synchronous and blocking, or asynchronous. The client sends a request message to the server and receives a response message in return. From the client’s perspective, the service interface it is using is part of the same program or package.
RPCs are obviously slower than calls to the same address space. They can also fail for a variety of reasons, including network problems or a mismatch between interface implementations. These failures can sometimes be difficult to understand and debug, and client applications must be robust enough to handle them.
Many common programming languages provide some level of RPC functionality. In addition to gRPC, other generic RPC utilities include NFS, Etch, Apache Thrift, and several program variants from Microsoft. Most RPC programs encourage the client and server to follow the same sequence of events.
- The client calls a local stub function that is responsible for communicating with the server.
- The stub function invokes a system call to transmit the message, including the necessary function parameters as arguments.
- The client sends the message to the server, which is either on the same system or a remote component.
- The server receives the message and passes it to a server stub.
- The server stub processes the message and extracts the parameters.
- The server stub calls a function to handle the request and the task runs to completion.
- When processing is complete, the reply is transmitted. The previous six steps reoccur in the opposite direction. The server calls a stub function to transmit the message. The client receives the message and finally passes the information to the stub that originally made the request.
What is gRPC?
gRPC is a specific implementation of RPC. It allows different machines, running applications that might be written in different languages, to communicate. The simplicity and efficiency of its design are useful for implementing microservices, where the server provides specific and finely-tuned services to multiple independent clients.
gRPC accomplishes this task through the use of protocol buffers, also known as protobufs. Protocol buffers were originally developed by Google. They are small, efficient, and can be transmitted and processed quickly. These buffers provide gRPC with an Interface Definition Language (IDL) and a message-exchange format. More information about protocol buffers can be found on the Google Developers Page for Protocol Buffers. The Google site also includes a Protocol Buffers Language Specification.
Application developers can use gRPC’s protocol buffers to design a service interface. This interface consists of all the available methods, along with a definition of each message type. This interface definition allows a client application to call a method that is running remotely on the server. These components are defined in a .proto
file, which is written in plain text. Every protocol buffer entry is structured as a message. Each message defines a list of fields, and each field consists of a name-value pair. The protocol buffer compiler protoc is used to translate the .proto
file into two source files in the target programming language. These files serve as an infrastructure library containing classes and data access methods, along with handlers for the protocol buffers. The client and server designers independently use these files to develop their applications.
In addition to running on different hosts, the client and the server can use different languages for the implementation. Some of these supported languages include C++, Go, Java, Kotlin, Node, PHP, and Python. A full list of all languages and their documentation can be found on the gRPC Supported Languages Page.
gRPC uses HTTP/2 to leverage the availability of longer-lasting parallel connections. In most applications, the bulk of the implementation happens on the server-side. The gRPC server application instantiates a server to accept incoming requests, implements the methods defined in the interface, and transmits responses. The server continues to poll for requests as long as it is running, and can serve many clients simultaneously. The client-side implementation is usually more straightforward. It consists of one or more stub functions that construct and transmit messages to the server and block while waiting for a response. There is usually one stub for each method in the interface.
Several types of gRPC services are supported:
Simple RPC: This is the most straightforward type of interface. A client uses a stub function to synchronously transmit a message to the server. It then waits for a response.
Response-streaming RPC: In this interface, the client transmits a request to the server in the same way as a simple RPC. However, it receives a stream in return, which it can process as a list of messages.
Request-streaming RPC: In this case, the client transmits a sequence of messages to the server in the form of a stream. It then waits for a response from the server.
Bidirectionally-streaming RPC: This architecture combines aspects of the response-streaming and request-streaming RPCs. Both the client and server transmit a stream of messages asynchronously and independently and process the messages independently.
## Advantages of gRPC
gRPC has many advantages over traditional client-server architectures.
- It permits the development of highly-scaled and distributed systems incrementally.
- It facilitates rapid development and prototyping. Developers can begin with a simple API and receive direct feedback. As the system develops, more functionality can be added to the same interface without affecting the original methods.
- It offers language-independent communication, so client developers can write their programs in any language they are comfortable and familiar with. They do not have to know what language the client application uses, or how to program in it.
- Clients can develop their own implementation independent of each other. Because each method is called independently, clients are only required to use stubs for the functions they care about. They can ignore the remainder of the interface.
- It yields good performance and low latency, partly due to its use of HTTP/2. The protocol buffers are efficient and lightweight and can be transmitted and processed quickly.
- gRPC provides advanced features such as authentication and load balancing to enhance security and performance.
gRPC vs REST
gRPC is often compared and contrasted with Representational State Transfer (REST). REST also uses a client-server architecture. Both systems are useful for implementing microservices. However, there are major differences between the two technologies.
- REST does not use RPCs. In a RESTful system, a client sends a request to a remote Uniform Resource Identifier (URI). It later receives a response encoded in XML, HTML, JSON, or another similar format. The request and reply typically use HTTP/HTTPS methods such as
GET
andPOST
. - All browsers support REST, and it is widely used throughout the internet. gRPC has more limited web capabilities. gRPC Web does provide some measure of web support. (gRPC Web is explained in the next section). In general, REST is more commonly used for web applications, while gRPC is used inside internal networks. gRPC is also frequently used in embedded systems, facilitating communication between the different components of the device.
- gRPC uses HTTP/2 and can take advantage of its inherent client-response communication model. This allows it to transmit requests or responses via a stream. REST uses HTTP version 1.1 and has to handle one request at a time using inefficient short-lived connections.
- Performance is better with gRPC compared to REST. It also more efficiently uses bandwidth due to its lightweight design.
- gRPC can be quickly translated into the target programming language for the application using the protoc compiler. REST does not have an equivalent feature, although third-party tools can streamline the process somewhat.
- REST can be implemented in HTTP without additional tools. gRPC programs require the protoc compiler, and the
.proto
files must be compiled into the final programming language before they can be used. The client and server applications cannot be fully written or tested until the interface is defined and the auto-generated files are available.
gRPC Web
gRPC Web is an attempt to address the lack of web support for gRPC. It is a JavaScript version of gRPC that is designed for web clients. gRPC Web also requires a complex web proxy to connect to RPC services. It currently uses Envoy as the default proxy, but in-house work is in progress on a Java implementation. gRPC Web is currently a work in progress, and more language-specific frameworks are expected soon. For more information about how to develop gRPC Web applications, see the gRPC Web documentation.
Create a gRPC Project in Python for Remote Procedural Calls**
This tutorial demonstrates the steps required to build a working gRPC application using Python. Some basic knowledge of Python is required to understand and contextualize the material. The simple Teams application accepts an incoming request from a client that contains the name of a city. It uses the city name to retrieve the name of the fictitious sports team that is located there from its internal dictionary. Finally, it transmits a multi-field message back to the client containing the name of the city and the nickname of the team.
This simple RPC application accepts a single request and returns a single response. No streams are involved in this application, but the guide explains how a stream-based method could work in each step. Both the server and client are implemented in Python, but any one of the supported languages could potentially be used on either side. Also, both the server and client here run on the same server in different code spaces. A more realistic server application would probably pull its data from an external database rather than a hard-coded dictionary.
Extensive information about using gRPC with Python can be found in gRPC’s Python Documentation. This page contains links to a series of resources, including the gRPC Python API and a basic tutorial.
A Summary of the gRPC Application Creation Process
This procedure is geared towards Ubuntu users but is generally applicable to all Linux distributions. The application development process follows these steps:
- Install Python: If Python is already installed on the system, skip to the second step.
- Install the Python gRPC Tools: These tools install the protoc compiler and other necessary components.
- Create the Proto File and Generate the Python Stubs: All RPC projects start with a
.proto
file containing definitions for the service interface, including the message types and methods. Theprotoc
compiler is used to generate two infrastructure files for the client and server. - Implement the gRPC Server: The gRPC server code launches the server and implements the methods from the service interface. A function is required for each method specified in the interface.
- Implement the gRPC Client: The client code includes Python stub functions to initiate a connection to the server, generate requests, and receive the responses.
- Run and Test the gRPC Application: Launch the server and start the client. Ensure the application generates the expected results.
### Before You Begin
If you have not already done so, create a Linode account and Compute Instance. See our Getting Started with Linode and Creating a Compute Instance guides.
Follow our Setting Up and Securing a Compute Instance guide to update your system. You may also wish to set the timezone, configure your hostname, create a limited user account, and harden SSH access.
sudo
. If you are not familiar with the sudo
command, see the
Linux Users and Groups guide.Install Python**
In this example, Python is used to write the code for both the client and server. Instructions on how to create a gRPC application using C, C++, Go, Java, PHP, and other languages can be found on the gRPC Languages page.
Verify whether Python is installed on the Linode using the command below. If Python version 3.7 or later is installed, skip to the next section.
python3 ––version
Refresh the Ubuntu repository.
sudo apt update
Install the Python application along with its pip component.
sudo apt install python3-dev python3-pip
Confirm the version of both Python and pip. Pip version 9.0.1 or higher is required to run gRPC.
python3 --version pip3 --version
Python 3.8.5 pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)
Install the Python gRPC Tools
To use gRPC, install the grpc
components using pip. If the command is prefaced with sudo
, these packages are installed system-wide. Otherwise, they are only installed inside your local Python instance.
Install gRPC.
sudo python3 -m pip install grpcio
Installing collected packages: grpcio Successfully installed grpcio-1.38.0
Install the gRPC tools. This also installs the protocol buffer compiler protoc.
sudo python3 -m pip install grpcio-tools
Installing collected packages: protobuf, grpcio-tools Successfully installed grpcio-tools-1.38.0 protobuf-3.17.3
Create the Proto File and Generate the Python Stubs
The next step is to complete the .proto
file, which serves as an API between the client and server. This file contains declarations for the interface methods, and indicates the parameters and return values. It also defines the message types used for client-server communication, and possibly some shared enumerations and definitions.
Create the main directory for the project named
teams
. Inside this directory, create another directory namedprotobufs
.Change the working directory to
teams/protobufs
and create a file namedteams.proto
.cd teams/protobufs touch teams.proto
The
teams.proto
file contains a shared API that the client and server can use to communicate with one another. At the top of the file, declare thesyntax
attribute asproto3
.Note For simplicity, this example does not use packages. If there are multiple projects in the same Python workspace, add this code to a package to avoid naming conflicts.- File: ~/teams/protobufs/teams.proto
1 2
syntax = "proto3";
Define the
TeamRequest
message, which consists of a single field of typestring
. This message type is used for the client request. TheTeamResponse
message contains two fields, one for thecity
and the second for thenickname
of the team. It defines the message that the server sends back to the client in response.- File: ~/teams/protobufs/teams.proto
1 2 3 4 5 6 7 8 9 10 11
// The request message is a string containing the city name. message TeamRequest { string city = 1; } // The response message contains the name of the city and the name of the team that plays there. message TeamResponse { string city = 1; string nickname = 2; }
The final section of the file defines the
Teams
service and any sharedrpc
methods. In this case,Teams
only contains theGetTeam
method. This method accepts a message parameter of typeTeamRequest
and returns a message of typeTeamResponse
. The client can call this function on the server using an auto-generated stub method.- File: ~/teams/protobufs/teams.proto
1 2 3 4 5 6
// The teams service definition. service Teams { // Returns the city and nickname of a team based on a string from the client rpc GetTeam (TeamRequest) returns (TeamResponse) {} }
Following is an overview of the entire
teams.proto
file.- File: ~/teams/protobufs/teams.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
syntax = "proto3"; // The request message is a string containing the type name. message TeamRequest { string city = 1; } // The response message contains the name of the city and the name of the team that plays there. message TeamResponse { string city = 1; string nickname = 2; } // The teams service definition. service Teams { // Returns the city and nickname of a team based on a string from the client. rpc GetTeam (TeamRequest) returns (TeamResponse) {} }
Before this interface can be used by other Python files, it must be compiled into Python stub files. Run the following command from the main
teams
directory to do so. The-I
parameter indicates the location of theprotobufs
directory. The final variable is the location of the mainteams.proto
file. The destination for the auto-generated files is specified bypython_out
andgrpc_python_out
and can be set to the current directory.python3 -m grpc_tools.protoc -I ./protobufs --python_out=. --grpc_python_out=. ./protobufs/teams.proto
After compilation of the
.proto
file, theteams
directory should contain Python files namedteams_pb2.py
andteams_pb2_grpc.py
. These files contain auto-generated classes for the messages and the service. Make a note of these file names to reference them again later.Important Do not edit either of the auto-generated files. This could render them unusable.
To create a response-streaming, request-streaming, or bidirectionally-streaming RPC, declare the message to be streamed as a stream
. For instance, to allow GetTeam
to return a stream of team messages, declare it using the following format:
rpc GetTeam (TeamRequest) returns (stream TeamResponse) {}.
Implement the gRPC Server
The server application is responsible for implementing every function defined inside the .protos
file. To accomplish this, it must import the auto-generated files and the grpc
package. The server file must also define a serve
function. This is called upon startup and runs for the duration of the program.
Create a file named
teams_server.py
inside the mainteams
directory.At the top of the file,
import
the required packages. To import the auto-generated classes, add animport
statement corresponding to the filename of each auto-generated file, minus the.py
extension.- File: ~/teams/teams_server.py
1 2 3 4 5 6 7 8
from concurrent import futures import logging import grpc import teams_pb2 import teams_pb2_grpc
Create the main class, following these guidelines:
- The name of the
class
is the name of theservice
from the.protos
file. In this case, the service name isTeams
. - As a parameter, it accepts a
servicer
for the class. The name of theservicer
follows the format<name-of-the-grpc-class>.<name-of-the-class>Servicer
, for instance,teams_pb2_grpc.TeamsServicer
. - The
class
must implement all RPC methods defined within the API. In this case, the only method to implement isGetTeam
. - In addition to the mandatory
self
object, theGetTeam
method takes two additional parameters,request
andcontext
. Therequest
parameter contains theTeamRequest
message sent by the client. The fields inside this message follow the definition from the.proto
file. Thecontext
parameter contains RPC-specific metadata about the request. - The function itself is simple. The information from the client is referenced through the
request.city
field. Thecity
field was defined within theTeamRequest
message declaration insideteams.proto
. - At the end of the function, the relevant information is returned using the
teams_pb2.TeamResponse
structure. The format of theTeamResponse
message was declared insideteams.proto
. Set a value for each message field using the format<fieldname>=<value>
, for example,nickname=team_name
. - For the purposes of this demo application,
GetTeam
prints the incoming metadata when a message is received. This is useful for debugging purposes as well as documenting the timing and nature of the client requests.
- File: ~/teams/teams_server.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
class Teams(teams_pb2_grpc.TeamsServicer): def GetTeam(self, request, context): metadata = dict(context.invocation_metadata()) print(metadata) teamList = { 'Chicago' : 'Jackals', 'Detroit' : 'Wheels', 'Minneapolis' : 'Nordics', 'New York' : 'Metros', 'Arizona' : 'Roadrunners', 'San Francisco' : 'Stingers', 'Seattle' : 'Orcas', 'Los Angeles' : 'Pumas', 'Philadelphia' : 'Foxes', 'Boston' : 'Revolutionaries' } if request.city in teamList: team_name = teamList[request.city] else: team_name = 'not a member' return teams_pb2.TeamResponse(city=request.city, nickname=team_name)
- The name of the
The server must handle all client calls. This is the responsibility of the
serve
function. It creates the server, attaches aservicer
to it, and tells it to listen for incoming messages on port50051
. Theserve
function follows the same basic format in every gRPC application. The second line of theserve
function is complicated. It uses theadd_TeamsServicer_to_server
method from theteams_pb2_grpc
class. This method takes aTeams
object and theserver
itself as parameters.- File: ~/teams/teams_server.py
1 2 3 4 5 6 7
def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) teams_pb2_grpc.add_TeamsServicer_to_server(Teams(), server) server.add_insecure_port('[::]:50051') server.start() server.wait_for_termination()
The file ends with a simple
main
function that only exists to callserve
. Althoughserver.start
is non-blocking and runs inside its own thread,wait_for_termination
blocks the caller as long as the server is running.- File: ~/teams/teams_server.py
1 2 3 4
if **__name__** == '**__main__**': logging.basicConfig() serve()
The entire
teams_server.py
file is shown here for reference.- File: ~/teams/teams_server.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
from concurrent import futures import logging import grpc import teams_pb2 import teams_pb2_grpc class Teams(teams_pb2_grpc.TeamsServicer): def GetTeam(self, request, context): metadata = dict(context.invocation_metadata()) print(metadata) teamList = { 'Chicago' : 'Jackals', 'Detroit' : 'Wheels', 'Minneapolis' : 'Nordics', 'New York' : 'Metros', 'Arizona' : 'Roadrunners', 'San Francisco' : 'Stingers', 'Seattle' : 'Orcas', 'Los Angeles' : 'Pumas', 'Philadelphia' : 'Foxes', 'Boston' : 'Revolutionaries' } if request.city in teamList: team_name = teamList[request.city] else: team_name = 'not a member' return teams_pb2.TeamResponse(city=request.city, nickname=team_name) def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) teams_pb2_grpc.add_TeamsServicer_to_server(Teams(), server) server.add_insecure_port('[::]:50051') server.start() server.wait_for_termination() if **__name__** == '**__main__**': logging.basicConfig() serve()
GetTeam
would yield
each response message rather than returning it. Typically, the routine would iterate over the entire database or dictionary using a for ... in
control structure and would yield
each relevant entry in turn. This line would then become yield teams_pb2.TeamResponse(city=tmp_city_name, nickname=tmp_team_name)
.Implement the gRPC Client
In most cases, the client is much simpler than the server. The main function invokes run
, which executes the calls to the server. In a real-life, the input information would come from the user via the command line. To simplify the program, the server calls use hard-coded input values.
Create a file named
teams_client.py
inside the mainteams
directory.At the top of the file,
import
the required packages. This section is similar to the corresponding section of the server file.- File: ~/teams/teams_client.py
1 2 3 4 5 6 7 8
from **__future__** import print_function import logging import grpc import teams_pb2 import teams_pb2_grpc
Create the
run
function. This function is not part of any class, although it uses classes from the auto-generated files.- Create a channel using
grpc.insecure_channel
. This example assumes the client and server are running on the same system. To connect with a gRPC server on a remote system, replacelocalhost
with the gRPC server’s IP address in this example. - Create a local stub object using
teams_pb2_grpc.TeamStub
, passing it to the channel. This stub is used to call the methods defined in the.proto
file. - The
stub
possesses a method for each function in the service interface. Use thestub.GetTeam
method to invoke theGetTeam
function on the server. TheGetTeam
method accepts ateams_pb2.TeamRequest
message as a parameter. Populate theTeamRequest
message by passing it a field-value pair, such ascity='Chicago'
, for each field. - The results can be extracted from the
response
object returned by the server. For example, useresponse.nickname
to access the value of thenickname
field.
- File: ~/teams/teams_client.py
1 2 3 4 5 6 7 8 9 10
def run(): with grpc.insecure_channel('localhost:50051') as channel: stub = teams_pb2_grpc.TeamsStub(channel) response = stub.GetTeam(teams_pb2.TeamRequest(city='Chicago')) print("Teams client received: " + response.city + response.nickname) response = stub.GetTeam(teams_pb2.TeamRequest(city='Philadelphia')) print("Teams client received: " + response.city + response.nickname) response = stub.GetTeam(teams_pb2.TeamRequest(city='Miami')) print("Teams client received: " + response.city + response.nickname)
- Create a channel using
The main function logs some information and then calls
run
. Therun
function is non-blocking, so when it is finished, the program ends.- File: ~/teams/teams_client.py
1 2 3 4
if **__name__** == '**__main__**': logging.basicConfig() run()
The entire
teams_client.py
file is shown here.- File: ~/teams/teams_client.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
from **__future__** import print_function import logging import grpc import teams_pb2 import teams_pb2_grpc def run(): with grpc.insecure_channel('localhost:50051') as channel: stub = teams_pb2_grpc.TeamsStub(channel) response = stub.GetTeam(teams_pb2.TeamRequest(city='Chicago')) print("Teams client received: " + response.city + " " + response.nickname) response = stub.GetTeam(teams_pb2.TeamRequest(city='Philadelphia')) print("Teams client received: " + response.city + " " + response.nickname) response = stub.GetTeam(teams_pb2.TeamRequest(city='Miami')) print("Teams client received: " + response.city + " " + response.nickname) if **__name__** == '**__main__**': logging.basicConfig() run()
GetTeam
returned a stream, the stub.GetTeam
function call would have received a list of messages in response. The client would then process these messages, possibly with a Python list comprehension or a for ... in
control structure.Run and Test the gRPC Application
The server and the client are launched like any other Python program. Ensure the server program is started first. The server blocks run indefinitely until it is terminated, so it does not appear to be doing anything until it receives a client request.
From the
teams
directory, run theteams_server.py
Python application. The application should start running, but it should not display any output yet.cd teams python3 teams_server.py
Open a new console on the Linode. Change to the
teams
directory, and run theteams_client.py
program.cd teams python3 teams_client.py
The
teams_client.py
application displays the results of the remote procedural calls it made to the server. The program terminates when it has printed all of the results.Teams client received: Chicago Jackals Teams client received: Philadelphia Foxes Teams client received: Miami not a member
Return to the window where you launched the
teams_server
application. The program should still be running. The contents of the window should display the metadata from the client requests.{'user-agent': 'grpc-python/1.38.0 grpc-c/16.0.0 (linux; chttp2)'} {'user-agent': 'grpc-python/1.38.0 grpc-c/16.0.0 (linux; chttp2)'} {'user-agent': 'grpc-python/1.38.0 grpc-c/16.0.0 (linux; chttp2)'}
When you are finished using the gRPC client, enter
Ctrl-C
in the same console to terminate the application. The server can be restarted at any time.
More Information
You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.
This page was originally published on