
- gRPC Tutorials
- gRPC - Home
- gRPC - Introduction
- gRPC - Setup
- gRPC - Helloworld App with Java
- gRPC - Helloworld App with Python
- gRPC - Unary
- gRPC - Server Streaming RPC
- gRPC - Client Streaming RPC
- gRPC - Bidirectional RPC
- gRPC - Client Calls
- gRPC - Timeouts & Cancellation
- gRPC - Send/Receive Metadata
- gRPC Useful Resources
- gRPC - Quick Guide
- gRPC - Useful Resources
- gRPC - Discussion
gRPC - Send/Receive Metadata
gRPC supports sending metadata. The metadata is basically a set of data we want to send that is not part of the business logic, while making gRPC calls.
Let us look at the following two cases −
- The Client sends Metadata and the Server reads it.
- The Server sends Metadata and Client reads it.
We will go through both these cases one by one.
Client Sends Metadata
As mentioned, gRPC supports the client sending the metadata which the server can read. gRPC supports extending the client and server interceptor which can be used to write and read metadata, respectively. Let us take an example to understand it better. Here is our client code which sends the hostname as metadata −
Let us take an example to understand it better. Here is our client code which sends the hostname as metadata −
Example
package com.tp.bookstore; import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; import io.grpc.ForwardingClientCall; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.StatusRuntimeException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; import com.tp.bookstore.BookStoreOuterClass.Book; import com.tp.bookstore.BookStoreOuterClass.BookSearch; public class BookStoreClientUnaryBlockingMetadata { private static final Logger logger = Logger.getLogger(BookStoreClientUnaryBlockingMetadata.class.getName()); private final BookStoreGrpc.BookStoreBlockingStub blockingStub; public BookStoreClientUnaryBlockingMetadata(Channel channel) { blockingStub = BookStoreGrpc.newBlockingStub(channel); } static class BookClientInterceptor implements ClientInterceptor { @Override public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next ) { return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) { @Override public void start(Listener<RespT> responseListener, Metadata headers) { logger.info("Added metadata"); headers.put(Metadata.Key.of("HOSTNAME", ASCII_STRING_MARSHALLER), "MY_HOST"); super.start(responseListener, headers); } }; } } public void getBook(String bookName) { logger.info("Querying for book with title: " + bookName); BookSearch request = BookSearch.newBuilder().setName(bookName).build(); Book response; CallOptions.Key<String> metaDataKey = CallOptions.Key.create("my_key"); try { response = blockingStub.withOption(metaDataKey, "bar").first(request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; } logger.info("Got following book from server: " + response); } public static void main(String[] args) throws Exception { String bookName = args[0]; String serverAddress = "localhost:50051"; ManagedChannel channel = ManagedChannelBuilder.forTarget(serverAddress).usePlaintext().intercept(new BookClientInterceptor()).build(); try { BookStoreClientUnaryBlockingMetadata client = new BookStoreClientUnaryBlockingMetadata(channel); client.getBook(bookName); } finally { channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); } } }
The interesting bit here is the interceptor.
static class BookClientInterceptor implements ClientInterceptor{ @Override public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) { @Override public void start(Listener<RespT>responseListener, Metadata headers) { logger.info("Added metadata"); headers.put(Metadata.Key.of("HOSTNAME", ASCII_STRING_MARSHALLER), "MY_HOST"); super.start(responseListener, headers); } }; } }
We intercept any call which is being made by the client and then add hostname metadata to it before it is called further.
Server Reads Metadata
Now, let us look at the server code which reads this metadata −
package com.tp.bookstore; import io.grpc.CallOptions; import io.grpc.Context; import io.grpc.Metadata; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerCall; import io.grpc.ServerCall.Listener; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.stub.StreamObserver; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import java.util.stream.Collectors; import com.tp.bookstore.BookStoreOuterClass.Book; import com.tp.bookstore.BookStoreOuterClass.BookSearch; public class BookeStoreServerMetadata { private static final Logger logger = Logger.getLogger(BookeStoreServerMetadata.class.getName()); static Map<String, Book> bookMap = new HashMap<>(); static { bookMap.put("Great Gatsby", Book.newBuilder().setName("Great Gatsby").setAuthor("Scott Fitzgerald").setPrice(300).build()); } private Server server; class BookServerInterceptor implements ServerInterceptor{ @Override public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { logger.info("Recieved following metadata: " + headers); return next.startCall(call, headers); } } private void start() throws IOException { int port = 50051; server = ServerBuilder.forPort(port).addService(new BookStoreImpl()).intercept(new BookServerInterceptor()).build().start(); logger.info("Server started, listening on " + port); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { System.err.println("Shutting down gRPC server"); try { server.shutdown().awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(System.err); } } }); } public static void main(String[] args) throws IOException, InterruptedException { final BookeStoreServerMetadata greetServer = new BookeStoreServerMetadata(); greetServer.start(); greetServer.server.awaitTermination(); } static class BookStoreImpl extends BookStoreGrpc.BookStoreImplBase { @Override public void first(BookSearch searchQuery, StreamObserver<Book> responseObserver) { logger.info("Searching for book with title: " + searchQuery.getName()); List<String> matchingBookTitles = bookMap.keySet().stream().filter(title ->title.startsWith(searchQuery.getName().trim())).collect(Collectors.toList()); Book foundBook = null; if(matchingBookTitles.size() > 0) { foundBook = bookMap.get(matchingBookTitles.get(0)); } responseObserver.onNext(foundBook); responseObserver.onCompleted(); } } }
Again, the interesting bit here is the interceptor.
class BookServerInterceptor implements ServerInterceptor{ @Override public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers,ServerCallHandler<ReqT, RespT> next) { logger.info("Recieved following metadata: " + headers); return next.startCall(call, headers); } }
We intercept any call which is incoming to the server and then log the metadata before the call can be handled by the actual method.
Client-Server Call
Let us now see this in action. For running the code, fire up two shells. Start the server on the first shell by executing the following command −
java -cp .\target\grpc-point-1.0.jar com.tp.bookstore.BookeStoreServerMetadata
We would see the following output −
Output
Jul 31, 2021 5:29:14 PM com.tp.bookstore.BookeStoreServerMetadata start INFO: Server started, listening on 50051
The above output implies that the server has started.
Now, let us start the client.
java -cp .\target\grpc-point-1.0.jar com.tp.bookstore.BookStoreClientUnaryBlockingMetadata Great
We would see the following output −
Output
Jul 31, 2021 5:29:39 PM com.tp.bookstore.BookStoreClientUnaryBlockingMetadata getBook INFO: Querying for book with title: Great Jul 31, 2021 5:29:39 PM com.tp.bookstore.BookStoreClientUnaryBlockingMetadata$BookCli entInterceptor$1 start INFO: Added metadata Jul 31, 2021 5:29:40 PM com.tp.bookstore.BookStoreClientUnaryBlockingMetadata getBook INFO: Got following book from server: name: "Great Gatsby" author: "Scott Fitzgerald" price: 300
And we will have the following data in the server logs −
Jul 31, 2021 5:29:40 PM com.tp.bookstore.BookeStoreServerMetadata$BookServerIntercept or interceptCall INFO: Recieved following metadata: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.38.0,hostname=MY_HOST,grpc-accept-encoding=gzip) Jul 31, 2021 5:29:40 PM com.tp.bookstore.BookeStoreServerMetadata$BookStoreImpl first INFO: Searching for book with title: Great
As we can see, the server is able to read the metadata: hostname=MY_HOST which was added by the client.