Abhishek Kumar | d259bb1 | 2015-08-07 11:21:31 -0700 | [diff] [blame] | 1 | GRPC Server Reflection Protocol |
| 2 | =============================== |
| 3 | |
| 4 | This document describes server reflection as an optional extension for servers |
| 5 | to assist clients in runtime construction of requests without having stub |
| 6 | information precompiled into the client. |
| 7 | |
| 8 | The primary usecase for server reflection is to write (typically) command line |
| 9 | debugging tools for talking to a grpc server. In particular, such a tool will |
| 10 | take in a method and a payload (in human readable text format) send it to the |
| 11 | server (typically in binary proto wire format), and then take the response and |
| 12 | decode it to text to present to the user. |
| 13 | |
| 14 | This broadly involves two problems: determining what formats (which protobuf |
| 15 | messages) a server’s method uses, and determining how to convert messages |
| 16 | between human readable format and the (likely binary) wire format. |
| 17 | |
Abhishek Kumar | d259bb1 | 2015-08-07 11:21:31 -0700 | [diff] [blame] | 18 | ## Method reflection |
| 19 | |
| 20 | We want to be able to answer the following queries: |
| 21 | 1. What methods does a server export? |
| 22 | 2. For a particular method, how do we call it? |
| 23 | Specifically, what are the names of the methods, are those methods unary or |
| 24 | streaming, and what are the types of the argument and result? |
| 25 | |
| 26 | ``` |
| 27 | #TODO(dklempner): link to an actual .proto later. |
| 28 | package grpc.reflection.v1alpha; |
| 29 | |
| 30 | message ListApisRequest { |
| 31 | } |
| 32 | |
| 33 | message ListApisResponse { |
| 34 | repeated google.protobuf.Api apis = 1; |
| 35 | } |
| 36 | |
| 37 | message GetMethodRequest { |
| 38 | string method = 1; |
| 39 | } |
| 40 | message GetMethodResponse { |
| 41 | google.protobuf.Method method = 1; |
| 42 | } |
| 43 | |
| 44 | service ServerReflection { |
| 45 | rpc ListApis (ListApisRequest) returns (ListApisResponse); |
| 46 | rpc GetMethod (GetMethodRequest) returns (GetMethodResponse); |
| 47 | } |
| 48 | ``` |
| 49 | |
| 50 | Note that a server is under no obligation to return a complete list of all |
| 51 | methods it supports. For example, a reverse proxy may support server reflection |
| 52 | for methods implemented directly on the proxy but not enumerate all methods |
| 53 | supported by its backends. |
| 54 | |
| 55 | |
| 56 | ### Open questions on method reflection |
| 57 | * Consider how to extend this protocol to support non-protobuf methods. |
| 58 | |
| 59 | ## Argument reflection |
| 60 | The second half of the problem is converting between the human readable |
| 61 | input/output of a debugging tool and the binary format understood by the |
| 62 | method. |
| 63 | |
| 64 | This is obviously dependent on protocol type. At one extreme, if both the |
| 65 | server and the debugging tool accept JSON, there may be no need for such a |
| 66 | conversion in the first place. At the opposite extreme, a server using a custom |
| 67 | binary format has no hope of being supported by a generic system. The |
| 68 | intermediate interesting common case is a server which speaks binary-proto and |
| 69 | a debugging client which speaks either ascii-proto or json-proto. |
| 70 | |
| 71 | One approach would be to require servers directly support human readable input. |
| 72 | In the future method reflection may be extended to document such support, |
| 73 | should it become widespread or standardized. |
| 74 | |
| 75 | ## Protobuf descriptors |
| 76 | |
| 77 | A second would be for the server to export its |
| 78 | google::protobuf::DescriptorDatabase over the wire. This is very easy to |
| 79 | implement in C++, and Google implementations of a similar protocol already |
| 80 | exist in C++, Go, and Java. |
| 81 | |
| 82 | This protocol mostly returns FileDescriptorProtos, which are a proto encoding |
| 83 | of a parsed .proto file. It supports four queries: |
| 84 | 1. The FileDescriptorProto for a given file name |
| 85 | 2. The FileDescriptorProto for the file with a given symbol |
| 86 | 3. The FileDescriptorProto for the file with a given extension |
| 87 | 4. The list of known extension tag numbers of a given type |
| 88 | |
| 89 | These directly correspond to the methods of |
| 90 | google::protobuf::DescriptorDatabase. Note that this protocol includes support |
| 91 | for extensions, which have been removed from proto3 but are still in widespread |
| 92 | use in Google’s codebase. |
| 93 | |
| 94 | Because most usecases will require also requesting the transitive dependencies |
| 95 | of requested files, the queries will also return all transitive dependencies of |
| 96 | the returned file. Should interesting usecases for non-transitive queries turn |
| 97 | up later, we can easily extend the protocol to support them. |
| 98 | |
| 99 | ### Reverse proxy traversal |
| 100 | |
| 101 | One potential issue with naive reverse proxies is that, while any individual |
| 102 | server will have a consistent and valid picture of the proto DB which is |
| 103 | sufficient to handle incoming requests, incompatibilities will arise if the |
| 104 | backend servers have a mix of builds. For example, if a given message is moved |
| 105 | from foo.proto to bar.proto, and the client requests foo.proto from an old |
| 106 | server and bar.proto from a new server, the resulting database will have a |
| 107 | double definition. |
| 108 | |
| 109 | To solve this problem, the protocol is structured as a bidirectional stream, |
| 110 | ensuring all related requests go to a single server. This has the additional |
| 111 | benefit that overlapping recursive requests don’t require sending a lot of |
| 112 | redundant information, because there is a single stream to maintain context |
| 113 | between queries. |
| 114 | |
| 115 | ``` |
| 116 | package grpc.reflection.v1alpha; |
| 117 | message DescriptorDatabaseRequest { |
| 118 | string host = 1; |
| 119 | oneof message_request { |
| 120 | string files_for_file_name = 3; |
| 121 | string files_for_symbol_name = 4; |
| 122 | FileContainingExtensionRequest file_containing_extension = 5; |
| 123 | string list_all_extensions_of_type = 6; |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | message FileContainingExtensionRequest { |
| 128 | string base_message = 1; |
| 129 | int64 extension_id = 2; |
| 130 | } |
| 131 | |
| 132 | message DescriptorDatabaseResponse { |
| 133 | string valid_host = 1; |
| 134 | DescriptorDatabaseRequest original_request = 2; |
| 135 | oneof message_response { |
| 136 | // These are proto2 type google.protobuf.FileDescriptorProto, but |
| 137 | // we avoid taking a dependency on descriptor.proto, which uses |
| 138 | // proto2 only features, by making them opaque |
| 139 | // bytes instead |
| 140 | repeated bytes fd_proto = 4; |
| 141 | ListAllExtensionsResponse extensions_response = 5; |
| 142 | // Notably includes error code 5, NOT FOUND |
| 143 | int32 error_code = 6; |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | message ListAllExtensionsResponse { |
| 148 | string base_type_name; |
| 149 | repeated int64 extension_number; |
| 150 | } |
| 151 | |
| 152 | service ProtoDescriptorDatabase { |
| 153 | rpc DescriptorDatabaseInfo(stream DescriptorDatabaseRequest) returns (stream DescriptorDatabaseResponse); |
| 154 | } |
| 155 | ``` |
| 156 | |
| 157 | Any given request must either result in an error code or an answer, usually in |
| 158 | the form of a series of FileDescriptorProtos with the requested file itself |
| 159 | and all previously unsent transitive imports of that file. Servers may track |
| 160 | which FileDescriptorProtos have been sent on a given stream, for a given value |
| 161 | of valid_host, and avoid sending them repeatedly for overlapping requests. |
| 162 | |
| 163 | | message_request message | Result | |
| 164 | | files_for_file_name | transitive closure of file name | |
| 165 | | files_for_symbol_name | transitive closure file containing symbol | |
| 166 | | file_containing_extension | transitive closure of file containing a given extension number of a given symbol | |
| 167 | | list_all_extensions_of_type | ListAllExtensionsResponse containing all known extension numbers of a given type | |
| 168 | |
| 169 | At some point it would make sense to additionally also support any.proto’s |
| 170 | format. Note that known any.proto messages can be queried by symbol using this |
| 171 | protocol even without any such support, by parsing the url and extracting the |
| 172 | symbol name from it. |
| 173 | |
| 174 | ## Language specific implementation thoughts |
| 175 | All of the information needed to implement Proto reflection is available to the |
| 176 | code generator, but I’m not certain we actually generate this in every |
| 177 | language. If the proto implementation in the language doesn’t have something |
| 178 | like google::protobuf::DescriptorPool the grpc implementation for that language |
| 179 | will need to index those FileDescriptorProtos by file and symbol and imports. |
| 180 | |
| 181 | One issue is that some grpc implementations are very loosely coupled with |
| 182 | protobufs; in such implementations it probably makes sense to split apart these |
| 183 | reflection APIs so as not to take an additional proto dependency. |
Jean de Klerk | 4cc2e60 | 2018-07-26 08:20:21 -0700 | [diff] [blame] | 184 | |
| 185 | ## Known Implementations |
| 186 | |
| 187 | Enabling server reflection differs language-to-language. Here are links to docs relevant to |
| 188 | each language: |
| 189 | |
| 190 | - [Java](https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md#enable-server-reflection) |
| 191 | - [Go](https://github.com/grpc/grpc-go/blob/master/Documentation/server-reflection-tutorial.md#enable-server-reflection) |
| 192 | - [C++](https://grpc.io/grpc/cpp/md_doc_server_reflection_tutorial.html) |
Jean de Klerk | 2898c19 | 2018-07-27 08:25:05 -0700 | [diff] [blame] | 193 | - [C#](https://github.com/grpc/grpc/blob/master/doc/csharp/server_reflection.md) |
Jean de Klerk | 29fea1e | 2018-08-07 11:38:53 +0200 | [diff] [blame] | 194 | - [Python](https://github.com/grpc/grpc/blob/master/doc/python/server_reflection.md) |
Jean de Klerk | 4cc2e60 | 2018-07-26 08:20:21 -0700 | [diff] [blame] | 195 | - Ruby: not yet implemented [#2567](https://github.com/grpc/grpc/issues/2567) |
| 196 | - Node: not yet implemented [#2568](https://github.com/grpc/grpc/issues/2568) |