diff --git a/src/grpc/zero_copy_stream.cc b/src/grpc/zero_copy_stream.cc index 3a037ea4c..d70f6be9b 100644 --- a/src/grpc/zero_copy_stream.cc +++ b/src/grpc/zero_copy_stream.cc @@ -36,26 +36,29 @@ GrpcZeroCopyInputStream::GrpcZeroCopyInputStream() : current_buffer_(nullptr), current_buffer_size_(0), position_(0), + bytes_read_(0), finished_(false) {} -void GrpcZeroCopyInputStream::AddMessage(grpc_byte_buffer* message, +void GrpcZeroCopyInputStream::AddMessage(grpc_byte_buffer *message, bool take_ownership) { serializer_.AddMessage(message, take_ownership); } -bool GrpcZeroCopyInputStream::Next(const void** data, int* size) { +bool GrpcZeroCopyInputStream::Next(const void **data, int *size) { if (position_ >= current_buffer_size_) { + position_ = 0; if (!serializer_.Next(¤t_buffer_, ¤t_buffer_size_)) { // No data *size = 0; + current_buffer_size_ = 0; return !finished_; } - position_ = 0; } // Return [position_, current_buffer_size_) interval of the current buffer *data = current_buffer_ + position_; *size = static_cast(current_buffer_size_ - position_); + bytes_read_ += *size; // Move the position to the end of the current buffer position_ = current_buffer_size_; @@ -65,9 +68,40 @@ bool GrpcZeroCopyInputStream::Next(const void** data, int* size) { void GrpcZeroCopyInputStream::BackUp(int count) { if (0 < count && static_cast(count) <= position_) { position_ -= count; + bytes_read_ -= count; } } +bool GrpcZeroCopyInputStream::Skip(int count) { + if (count < 0) { + // Safe guard against wrong usage. + return false; + } + size_t count_left = static_cast(count); + while (position_ + count_left > current_buffer_size_) { + // Skipping past the current buffer, read the next one. + int delta = static_cast(current_buffer_size_ - position_); + count_left -= delta; + bytes_read_ += delta; + position_ = 0; + if (!serializer_.Next(¤t_buffer_, ¤t_buffer_size_)) { + // No data. We are potentially not at the end of the stream yet, but we + // don't know that and can only skip to the end and return an error. + current_buffer_size_ = 0; + return false; + } + } + + // Move the position ahead the requested number of bytes. + position_ += count_left; + bytes_read_ += count_left; + return true; +} + +::google::protobuf::int64 GrpcZeroCopyInputStream::ByteCount() const { + return static_cast<::google::protobuf::int64>(bytes_read_); +} + int64_t GrpcZeroCopyInputStream::BytesAvailable() const { return (current_buffer_size_ - position_) + serializer_.ByteCount(); } diff --git a/src/grpc/zero_copy_stream.h b/src/grpc/zero_copy_stream.h index b5700772b..9595873dc 100644 --- a/src/grpc/zero_copy_stream.h +++ b/src/grpc/zero_copy_stream.h @@ -44,7 +44,7 @@ class GrpcZeroCopyInputStream GrpcZeroCopyInputStream(); // Add a message to the end of the stream - void AddMessage(grpc_byte_buffer* message, bool take_ownership); + void AddMessage(grpc_byte_buffer *message, bool take_ownership); // Marks the end of the stream, which means that ZeroCopyInputStream will // return false after all the existing messages are consumed. @@ -52,18 +52,19 @@ class GrpcZeroCopyInputStream // ZeroCopyInputStream implementation - bool Next(const void** data, int* size); + bool Next(const void **data, int *size); void BackUp(int count); - bool Skip(int count) { return false; } // not supported - ::google::protobuf::int64 ByteCount() const { return 0; } // Not implemented + bool Skip(int count); + ::google::protobuf::int64 ByteCount() const; int64_t BytesAvailable() const; bool Finished() const { return finished_; } private: GrpcMessageSerializer serializer_; - const unsigned char* current_buffer_; + const unsigned char *current_buffer_; size_t current_buffer_size_; size_t position_; + size_t bytes_read_; bool finished_; }; diff --git a/src/grpc/zero_copy_stream_test.cc b/src/grpc/zero_copy_stream_test.cc index 3bb677a3a..3276007da 100644 --- a/src/grpc/zero_copy_stream_test.cc +++ b/src/grpc/zero_copy_stream_test.cc @@ -38,6 +38,9 @@ namespace grpc { namespace testing { namespace { +// gRPC frame size is 5 bytes: 1 byte flag + 4 byte data length +const int kFrameSize = 5; + class GrpcZeroCopyInputStreamTest : public ::testing::Test { public: GrpcZeroCopyInputStreamTest() {} @@ -93,63 +96,76 @@ TEST_F(GrpcZeroCopyInputStreamTest, SimpleRead) { stream.AddMessage(CreateByteBuffer(SliceData{slice21, slice22}), true); stream.Finish(); - // Test BytesAvailable() + // Test BytesAvailable() and ByteCount() EXPECT_EQ(slice11.size() + slice12.size() + slice21.size() + slice22.size() + 10, // +10 bytes for two delimiters stream.BytesAvailable()); + EXPECT_EQ(0, stream.ByteCount()); // Test the message1 delimiter - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(5, size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(kFrameSize, size); EXPECT_EQ(slice11.size() + slice12.size(), DelimiterToSize(reinterpret_cast(data))); - // Test BytesAvailable() + // Test BytesAvailable() and ByteCount() EXPECT_EQ(slice11.size() + slice12.size() + slice21.size() + slice22.size() + - 5, // +5 bytes for one delimiter + kFrameSize, // +kFrameSize bytes for one delimiter stream.BytesAvailable()); + EXPECT_EQ(kFrameSize, stream.ByteCount()); // Test the slices - ASSERT_TRUE(stream.Next(&data, &size)); - // ASSERT_EQ(slice11.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + // EXPECT_EQ(slice11.size(), size); EXPECT_EQ(slice11, std::string(reinterpret_cast(data), size)); - // Test BytesAvailable() + // Test BytesAvailable() and ByteCount() EXPECT_EQ(slice12.size() + slice21.size() + slice22.size() + - 5, // +5 bytes for one delimiter + kFrameSize, // +kFrameSize bytes for one delimiter stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice11.size(), stream.ByteCount()); - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice12.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice12.size(), size); EXPECT_EQ(slice12, std::string(reinterpret_cast(data), size)); - // Test BytesAvailable() - EXPECT_EQ(slice21.size() + slice22.size() + 5, // +5 bytes for one delimiter + // Test BytesAvailable() and ByteCount() + EXPECT_EQ(slice21.size() + slice22.size() + + kFrameSize, // +kFrameSize bytes for one delimiter stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice11.size() + slice12.size(), stream.ByteCount()); // Test the message2 delimiter - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(5, size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(kFrameSize, size); EXPECT_EQ(slice21.size() + slice22.size(), DelimiterToSize(reinterpret_cast(data))); - // Test BytesAvailable() + // Test BytesAvailable() and ByteCount() EXPECT_EQ(slice21.size() + slice22.size(), stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice11.size() + slice12.size() + kFrameSize, + stream.ByteCount()); // Test the slices - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice21.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice21.size(), size); EXPECT_EQ(slice21, std::string(reinterpret_cast(data), size)); - // Test BytesAvailable() + // Test BytesAvailable() and ByteCount() EXPECT_EQ(slice22.size(), stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice11.size() + slice12.size() + kFrameSize + + slice21.size(), + stream.ByteCount()); - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice22.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice22.size(), size); EXPECT_EQ(slice22, std::string(reinterpret_cast(data), size)); // Test the end of the stream EXPECT_EQ(0, stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice11.size() + slice12.size() + kFrameSize + + slice21.size() + slice22.size(), + stream.ByteCount()); EXPECT_FALSE(stream.Next(&data, &size)); } @@ -166,61 +182,67 @@ TEST_F(GrpcZeroCopyInputStreamTest, Backups) { // Check the message delimiter const void *data = nullptr; int size = -1; - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(5, size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(kFrameSize, size); EXPECT_EQ(slice1.size() + slice2.size(), DelimiterToSize(reinterpret_cast(data))); // Back up - stream.BackUp(5); + stream.BackUp(kFrameSize); - // Test the BytesAvailable() - EXPECT_EQ(slice1.size() + slice2.size() + 5, // +5 bytes for the delimiter + // Test BytesAvailable() and ByteCount() + EXPECT_EQ(slice1.size() + slice2.size() + + kFrameSize, // +kFrameSize bytes for the delimiter stream.BytesAvailable()); + EXPECT_EQ(0, stream.ByteCount()); // Test the slice again - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(5, size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(kFrameSize, size); EXPECT_EQ(slice1.size() + slice2.size(), DelimiterToSize(reinterpret_cast(data))); // Test the first slice - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice1.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice1.size(), size); EXPECT_EQ(slice1, std::string(reinterpret_cast(data), size)); // Back up & test again stream.BackUp(size); EXPECT_EQ(slice1.size() + slice2.size(), stream.BytesAvailable()); - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice1.size(), size); + EXPECT_EQ(kFrameSize, stream.ByteCount()); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice1.size(), size); EXPECT_EQ(slice1, std::string(reinterpret_cast(data), size)); // Now Back up 10 bytes & test again stream.BackUp(10); EXPECT_EQ(10 + slice2.size(), stream.BytesAvailable()); - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(10, size); + EXPECT_EQ(kFrameSize + slice1.size() - 10, stream.ByteCount()); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(10, size); EXPECT_EQ(slice1.substr(slice1.size() - 10), std::string(reinterpret_cast(data), size)); // Test the second slice - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice2.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice2.size(), size); EXPECT_EQ(slice2, std::string(reinterpret_cast(data), size)); // Back up and test again stream.BackUp(size); EXPECT_EQ(slice2.size(), stream.BytesAvailable()); - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice2.size(), size); + EXPECT_EQ(kFrameSize + slice1.size(), stream.ByteCount()); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice2.size(), size); EXPECT_EQ(slice2, std::string(reinterpret_cast(data), size)); // Now Back up size - 1 bytes (all but 1) and check again stream.BackUp(size - 1); EXPECT_EQ(slice2.size() - 1, stream.BytesAvailable()); - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice2.size() - 1, size); + EXPECT_EQ(kFrameSize + slice1.size() + 1, stream.ByteCount()); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice2.size() - 1, size); EXPECT_EQ(slice2.substr(1), std::string(reinterpret_cast(data), size)); @@ -228,6 +250,138 @@ TEST_F(GrpcZeroCopyInputStreamTest, Backups) { EXPECT_FALSE(stream.Next(&data, &size)); } +TEST_F(GrpcZeroCopyInputStreamTest, Skips) { + GrpcZeroCopyInputStream stream; + + // Create and add a message + std::string slice1 = "This is the first slice of the message."; + std::string slice2 = "This is the second slice of the message."; + std::string slice3 = "This is the third slice of the message."; + std::string slice4 = "This is the fourth slice of the message."; + + stream.AddMessage(CreateByteBuffer(SliceData{slice1, slice2, slice3, slice4}), + true); + stream.Finish(); + + const void *data = nullptr; + int size = -1; + + // Skip the message delimiter + EXPECT_TRUE(stream.Skip(kFrameSize)); + + // Test BytesAvailable() and ByteCount() + EXPECT_EQ(slice1.size() + slice2.size() + slice3.size() + slice4.size(), + stream.BytesAvailable()); + EXPECT_EQ(kFrameSize, stream.ByteCount()); + + // Skip backwards (impossible -- no effect) + EXPECT_FALSE(stream.Skip(-2)); + // Skip zero (no effect) + EXPECT_TRUE(stream.Skip(0)); + + // Test BytesAvailable() and ByteCount() + EXPECT_EQ(slice1.size() + slice2.size() + slice3.size() + slice4.size(), + stream.BytesAvailable()); + EXPECT_EQ(kFrameSize, stream.ByteCount()); + + // Read the first slice + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice1.size(), size); + EXPECT_EQ(slice1, std::string(reinterpret_cast(data), size)); + + // Back up to the start of slice1 & skip forward some bytes + // Skip to the middle of slice1 + stream.BackUp(size); + const int kSkip1 = slice1.size() - 10; + EXPECT_TRUE(stream.Skip(kSkip1)); + + EXPECT_EQ( + slice1.size() - kSkip1 + slice2.size() + slice3.size() + slice4.size(), + stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + kSkip1, stream.ByteCount()); + + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice1.size() - kSkip1, size); + EXPECT_EQ(slice1.substr(kSkip1), + std::string(reinterpret_cast(data), size)); + + // Back up some and skip even more, into slice + stream.BackUp(10); + const int kInSlice2 = 3; + const int kSkip2 = 10 + kInSlice2; + EXPECT_TRUE(stream.Skip(kSkip2)); + + EXPECT_EQ(slice2.size() - kInSlice2 + slice3.size() + slice4.size(), + stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice1.size() + kInSlice2, stream.ByteCount()); + + ASSERT_TRUE(stream.Next(&data, &size)); + ASSERT_EQ(slice2.size() - kInSlice2, size); + EXPECT_EQ(slice2.substr(kInSlice2), + std::string(reinterpret_cast(data), size)); + + // Back up 10 bytes in slice2 some and skip to end of slice2. + stream.BackUp(10); + ASSERT_TRUE(stream.Skip(10)); + EXPECT_EQ(slice3.size() + slice4.size(), stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice1.size() + slice2.size(), stream.ByteCount()); + + // Back up some and skip to last byte of second slice. + stream.BackUp(3); + EXPECT_TRUE(stream.Skip(2)); + EXPECT_EQ(1 + slice3.size() + slice4.size(), stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice1.size() + slice2.size() - 1, stream.ByteCount()); + + // Test the last byte of the second slice + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(1, size); + EXPECT_EQ(slice2.substr(slice2.size() - 1), + std::string(reinterpret_cast(data), size)); + + // Back up and skip over all of slice 3 and then into slice 4 + stream.BackUp(10); + EXPECT_TRUE(stream.Skip(10 + slice3.size() + 8)); + EXPECT_EQ(slice4.size() - 8, stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice1.size() + slice2.size() + slice3.size() + 8, + stream.ByteCount()); + + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice4.size() - 8, size); + EXPECT_EQ(slice4.substr(8), + std::string(reinterpret_cast(data), size)); + + // Now Back up, skip exactly to end of stream and back up again + stream.BackUp(4); + EXPECT_TRUE(stream.Skip(4)); + stream.BackUp(4); + EXPECT_EQ(4, stream.BytesAvailable()); + EXPECT_EQ(kFrameSize - 4 + slice1.size() + slice2.size() + slice3.size() + + slice4.size(), + stream.ByteCount()); + + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(4, size); + EXPECT_EQ(slice4.substr(slice4.size() - 4), + std::string(reinterpret_cast(data), size)); + + // Skip past the end of the stream + EXPECT_FALSE(stream.Skip(100)); + EXPECT_EQ(0, stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice1.size() + slice2.size() + slice3.size() + + slice4.size(), + stream.ByteCount()); + + // Check the end of the stream + EXPECT_FALSE(stream.Next(&data, &size)); + + // Skip even some more + EXPECT_FALSE(stream.Skip(100)); + EXPECT_EQ(0, stream.BytesAvailable()); + EXPECT_EQ(kFrameSize + slice1.size() + slice2.size() + slice3.size() + + slice4.size(), + stream.ByteCount()); +} + TEST_F(GrpcZeroCopyInputStreamTest, NotOwnedMessages) { // Create and add a messages std::string slice1 = "000000000000000000000Message 1"; @@ -244,25 +398,25 @@ TEST_F(GrpcZeroCopyInputStreamTest, NotOwnedMessages) { // Test the message1 delimiter const void *data = nullptr; int size = -1; - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(5, size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(kFrameSize, size); EXPECT_EQ(slice1.size(), DelimiterToSize(reinterpret_cast(data))); // Test slice1 - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice1.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice1.size(), size); EXPECT_EQ(slice1, std::string(reinterpret_cast(data), size)); // Test the message2 delimiter - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(5, size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(kFrameSize, size); EXPECT_EQ(slice2.size(), DelimiterToSize(reinterpret_cast(data))); // Test slice2 - ASSERT_TRUE(stream.Next(&data, &size)); - ASSERT_EQ(slice2.size(), size); + EXPECT_TRUE(stream.Next(&data, &size)); + EXPECT_EQ(slice2.size(), size); EXPECT_EQ(slice2, std::string(reinterpret_cast(data), size)); // Test the end of the stream diff --git a/src/nginx/t/transcoding_ignore_unknown_fields.t b/src/nginx/t/transcoding_ignore_unknown_fields.t index 551adc0ee..27887c1a1 100644 --- a/src/nginx/t/transcoding_ignore_unknown_fields.t +++ b/src/nginx/t/transcoding_ignore_unknown_fields.t @@ -40,7 +40,7 @@ my $NginxPort = ApiManager::pick_port(); my $ServiceControlPort = ApiManager::pick_port(); my $GrpcServerPort = ApiManager::pick_port(); -my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(5); +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(6); $t->write_file('service.pb.txt', ApiManager::get_transcoding_test_service_config( @@ -103,6 +103,16 @@ EOF ok(ApiManager::verify_http_json_response($response, {'id'=>'100', 'theme' => 'Classics'}), "The shelf was created successfully."); +# Test unknown field in response. ResponseMessage UnknownBook has a field "unknown" +# which is known to gRPC server, but not to ESP. +# Expected result is: only that field is skipped, other fields should be fine. +my $response1 = ApiManager::http_get($NginxPort,'/unknownbook?key=this-is-an-api-key'); + +# With bug in https://github.com/cloudendpoints/esp/issues/795, the first field +# after the uknown is discarded. Once the bug is fixed, should get all known fields. +ok(ApiManager::verify_http_json_response($response1, + {'knownInt'=>'100', 'knownStr' => 'Book'}), "Got the unknown book successfully."); + $t->stop_daemons(); # Verify the translated request diff --git a/test/transcoding/README.md b/test/transcoding/README.md index b6beb09d7..cb96578f4 100644 --- a/test/transcoding/README.md +++ b/test/transcoding/README.md @@ -61,5 +61,7 @@ id: "2016-08-25r1" + } } ``` - - Replace /tmp/out.txt with test/transcoding/service.pb.txt + * remove type `endpoints.examples.bookstore.UnknownMsg` + * remove `unknown` field in type `endpoints.examples.bookstore.UnknownBook` + - Replace /tmp/out.txt with test/transcoding/service.pb.txt diff --git a/test/transcoding/bookstore-api.yaml b/test/transcoding/bookstore-api.yaml index 583e909c6..fc0acd513 100644 --- a/test/transcoding/bookstore-api.yaml +++ b/test/transcoding/bookstore-api.yaml @@ -63,6 +63,8 @@ http: - selector: "endpoints.examples.bookstore.Bookstore.EchoShelfId" post: "/echoshelfid" body: "*" + - selector: "endpoints.examples.bookstore.Bookstore.GetUnknownBook" + get: "/unknownbook" system_parameters: rules: - selector: "endpoints.examples.bookstore.Bookstore.*" diff --git a/test/transcoding/bookstore-server.cc b/test/transcoding/bookstore-server.cc index 81b4399b3..c93617c3c 100644 --- a/test/transcoding/bookstore-server.cc +++ b/test/transcoding/bookstore-server.cc @@ -361,6 +361,23 @@ class BookstoreServiceImpl : public Bookstore::Service { return ::grpc::Status::OK; } + ::grpc::Status GetUnknownBook(::grpc::ServerContext* ctx, + const ::google::protobuf::Empty* request, + UnknownBook* reply) { + std::cerr << "GRPC-BACKEND: UnknownBook" << std::endl; + PrintRequest(*request, *ctx, printmetadata_); + + reply->mutable_unknown()->set_unknown_int(1000); + // Have to send a big message in order to trigger a call to Skip() + // in src/grpc/zero_copy_stream.h which is not implemented. + // Otherwise it is handled by CodedInputStream which read in one slice + // buffer and handle it. + reply->mutable_unknown()->set_unknown_str(std::string(32 * 1024, '0')); + reply->set_known_int(100); + reply->set_known_str("Book"); + return ::grpc::Status::OK; + } + private: // A helper to create shelves static Shelf CreateShelfObject(std::string theme) { diff --git a/test/transcoding/bookstore.proto b/test/transcoding/bookstore.proto index 514d8325e..a7728084c 100644 --- a/test/transcoding/bookstore.proto +++ b/test/transcoding/bookstore.proto @@ -78,6 +78,9 @@ service Bookstore { // EchoShelfId rpc EchoShelfId(ShelfId) returns (ShelfId) { } + // Returns the special UnknownBook message with an unknown field to ESP + rpc GetUnknownBook(google.protobuf.Empty) returns (UnknownBook) { + } } // A shelf resource. @@ -189,3 +192,20 @@ message ShelfNotFoundDetail { message ShelfId { int64 shelf_id = 1; } + +// This message is unknown to ESP +message UnknownMsg { + int64 unknown_int = 1; + // The ID of the book to retrieve. + string unknown_str = 2; +} + +// This Book message has a unknown field for ESP +message UnknownBook { + // This field is removed for ESP, but used by gRPC server + UnknownMsg unknown = 1; + // known int + int64 known_int = 2; + // known string + string known_str = 3; +} diff --git a/test/transcoding/service.pb.txt b/test/transcoding/service.pb.txt index 9a0055767..1c06b0069 100644 --- a/test/transcoding/service.pb.txt +++ b/test/transcoding/service.pb.txt @@ -71,6 +71,11 @@ apis { request_type_url: "type.googleapis.com/endpoints.examples.bookstore.ShelfId" response_type_url: "type.googleapis.com/endpoints.examples.bookstore.ShelfId" } + methods { + name: "GetUnknownBook" + request_type_url: "type.googleapis.com/google.protobuf.Empty" + response_type_url: "type.googleapis.com/endpoints.examples.bookstore.UnknownBook" + } version: "v1" source_context { file_name: "bookstore.proto" @@ -481,6 +486,27 @@ types { } syntax: SYNTAX_PROTO3 } +types { + name: "endpoints.examples.bookstore.UnknownBook" + fields { + kind: TYPE_INT64 + cardinality: CARDINALITY_OPTIONAL + number: 2 + name: "known_int" + json_name: "knownInt" + } + fields { + kind: TYPE_STRING + cardinality: CARDINALITY_OPTIONAL + number: 3 + name: "known_str" + json_name: "knownStr" + } + source_context { + file_name: "bookstore.proto" + } + syntax: SYNTAX_PROTO3 +} enums { name: "google.protobuf.NullValue" enumvalue { @@ -491,319 +517,48 @@ enums { } syntax: SYNTAX_PROTO3 } -documentation { - rules { - selector: "google.protobuf.Empty" - description: "A generic empty message that you can re-use to avoid defining duplicated\nempty messages in your APIs. A typical example is to use it as the request\nor the response type of an API method. For instance:\n\n service Foo {\n rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);\n }\n\nThe JSON representation for `Empty` is empty JSON object `{}`." - } - rules { - selector: "google.protobuf.Struct" - description: "`Struct` represents a structured data value, consisting of fields\nwhich map to dynamically typed values. In some languages, `Struct`\nmight be supported by a native representation. For example, in\nscripting languages like JS a struct is represented as an\nobject. The details of that representation are described together\nwith the proto support for the language.\n\nThe JSON representation for `Struct` is JSON object." - } - rules { - selector: "google.protobuf.Struct.fields" - description: "Unordered map of dynamically typed values." - } - rules { - selector: "google.protobuf.Value" - description: "`Value` represents a dynamically typed value which can be either\nnull, a number, a string, a boolean, a recursive struct value, or a\nlist of values. A producer of value is expected to set one of that\nvariants, absence of any variant indicates an error.\n\nThe JSON representation for `Value` is JSON value." - } - rules { - selector: "google.protobuf.Value.null_value" - description: "Represents a null value." - } - rules { - selector: "google.protobuf.kind" - description: "The kind of value." - } - rules { - selector: "google.protobuf.Value.number_value" - description: "Represents a double value." - } - rules { - selector: "google.protobuf.Value.string_value" - description: "Represents a string value." - } - rules { - selector: "google.protobuf.Value.bool_value" - description: "Represents a boolean value." - } - rules { - selector: "google.protobuf.Value.struct_value" - description: "Represents a structured value." - } - rules { - selector: "google.protobuf.Value.list_value" - description: "Represents a repeated `Value`." - } - rules { - selector: "google.protobuf.ListValue" - description: "`ListValue` is a wrapper around a repeated field of values.\n\nThe JSON representation for `ListValue` is JSON array." - } - rules { - selector: "google.protobuf.ListValue.values" - description: "Repeated field of dynamically typed values." - } - rules { - selector: "google.protobuf.NullValue" - description: "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`." - } - rules { - selector: "google.protobuf.NullValue.NULL_VALUE" - description: "Null value." - } - rules { - selector: "endpoints.examples.bookstore" - description: "Copyright (C) Extensible Service Proxy Authors\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS\'\' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n//////////////////////////////////////////////////////////////////////////////" - } - rules { - selector: "endpoints.examples.bookstore.Shelf" - description: "A shelf resource." - } - rules { - selector: "endpoints.examples.bookstore.Shelf.id" - description: "A unique shelf id." - } - rules { - selector: "endpoints.examples.bookstore.Shelf.theme" - description: "A theme of the shelf (fiction, poetry, etc)." - } - rules { - selector: "endpoints.examples.bookstore.Book" - description: "A book resource." - } - rules { - selector: "endpoints.examples.bookstore.Book.id" - description: "A unique book id." - } - rules { - selector: "endpoints.examples.bookstore.Book.author" - description: "An author of the book." - } - rules { - selector: "endpoints.examples.bookstore.Book.title" - description: "A book title." - } - rules { - selector: "endpoints.examples.bookstore.Book.quotes" - description: "Quotes from the book." - } - rules { - selector: "endpoints.examples.bookstore.ListShelvesResponse" - description: "Response to ListShelves call." - } - rules { - selector: "endpoints.examples.bookstore.ListShelvesResponse.shelves" - description: "Shelves in the bookstore." - } - rules { - selector: "endpoints.examples.bookstore.CreateShelfRequest" - description: "Request message for CreateShelf method." - } - rules { - selector: "endpoints.examples.bookstore.CreateShelfRequest.shelf" - description: "The shelf resource to create." - } - rules { - selector: "endpoints.examples.bookstore.GetShelfRequest" - description: "Request message for GetShelf method." - } - rules { - selector: "endpoints.examples.bookstore.GetShelfRequest.shelf" - description: "The ID of the shelf resource to retrieve." - } - rules { - selector: "endpoints.examples.bookstore.DeleteShelfRequest" - description: "Request message for DeleteShelf method." - } - rules { - selector: "endpoints.examples.bookstore.DeleteShelfRequest.shelf" - description: "The ID of the shelf to delete." - } - rules { - selector: "endpoints.examples.bookstore.ListBooksRequest" - description: "Request message for ListBooks method." - } - rules { - selector: "endpoints.examples.bookstore.ListBooksRequest.shelf" - description: "ID of the shelf which books to list." - } - rules { - selector: "endpoints.examples.bookstore.ListBooksResponse" - description: "Response message to ListBooks method." - } - rules { - selector: "endpoints.examples.bookstore.ListBooksResponse.books" - description: "The books on the shelf." - } - rules { - selector: "endpoints.examples.bookstore.CreateBookRequest" - description: "Request message for CreateBook method." - } - rules { - selector: "endpoints.examples.bookstore.CreateBookRequest.shelf" - description: "The ID of the shelf on which to create a book." - } - rules { - selector: "endpoints.examples.bookstore.CreateBookRequest.book" - description: "A book resource to create on the shelf." - } - rules { - selector: "endpoints.examples.bookstore.GetBookRequest" - description: "Request message for GetBook method." - } - rules { - selector: "endpoints.examples.bookstore.GetBookRequest.shelf" - description: "The ID of the shelf from which to retrieve a book." - } - rules { - selector: "endpoints.examples.bookstore.GetBookRequest.book" - description: "The ID of the book to retrieve." - } - rules { - selector: "endpoints.examples.bookstore.DeleteBookRequest" - description: "Request message for DeleteBook method." - } - rules { - selector: "endpoints.examples.bookstore.DeleteBookRequest.shelf" - description: "The ID of the shelf from which to delete a book." - } - rules { - selector: "endpoints.examples.bookstore.DeleteBookRequest.book" - description: "The ID of the book to delete." - } - rules { - selector: "endpoints.examples.bookstore.QueryShelvesRequest" - description: "Request message for QueryShelves method." - } - rules { - selector: "endpoints.examples.bookstore.QueryShelvesRequest.shelf" - description: "The shelf to match" - } - rules { - selector: "endpoints.examples.bookstore.QueryBooksRequest" - description: "Request message for QueryBooks method." - } - rules { - selector: "endpoints.examples.bookstore.QueryBooksRequest.shelf" - description: "The shelf to look in" - } - rules { - selector: "endpoints.examples.bookstore.QueryBooksRequest.book" - description: "The book to match" - } - rules { - selector: "endpoints.examples.bookstore.ShelfNotFoundDetail" - description: "The custom error message returned in status \"details\"" - } - rules { - selector: "endpoints.examples.bookstore.ShelfNotFoundDetail.for_shelf_id" - description: "the sheld ID that trying to find." - } - rules { - selector: "endpoints.examples.bookstore.ShelfNotFoundDetail.why" - description: "the why message." - } - rules { - selector: "endpoints.examples.bookstore.ShelfId" - description: "This message is used to test preserve_proto_field_name flag\nNormally, shelf_id is converted to ShelfId in proto field name\nBut if preserve_proto_field_name is set, it will keep the\noriginal name" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore" - description: "A simple Bookstore API.\n\nThe API manages shelves and books resources. Shelves contain books." - } +backend { rules { selector: "endpoints.examples.bookstore.Bookstore.ListShelves" - description: "Returns a list of all shelves in the bookstore." } rules { selector: "endpoints.examples.bookstore.Bookstore.CreateShelf" - description: "Creates a new shelf in the bookstore." } rules { selector: "endpoints.examples.bookstore.Bookstore.BulkCreateShelf" - description: "Creates multiple shelves with one streaming call" } rules { selector: "endpoints.examples.bookstore.Bookstore.GetShelf" - description: "Returns a specific bookstore shelf." } rules { selector: "endpoints.examples.bookstore.Bookstore.DeleteShelf" - description: "Deletes a shelf, including all books that are stored on the shelf." } rules { selector: "endpoints.examples.bookstore.Bookstore.ListBooks" - description: "Returns a list of books on a shelf." } rules { selector: "endpoints.examples.bookstore.Bookstore.CreateBook" - description: "Creates a new book." } rules { selector: "endpoints.examples.bookstore.Bookstore.GetBook" - description: "Returns a specific book." } rules { selector: "endpoints.examples.bookstore.Bookstore.DeleteBook" - description: "Deletes a book from a shelf." } rules { selector: "endpoints.examples.bookstore.Bookstore.QueryShelves" - description: "Query for matching shelves" } rules { selector: "endpoints.examples.bookstore.Bookstore.QueryBooks" - description: "Query for matching books" } rules { selector: "endpoints.examples.bookstore.Bookstore.EchoStruct" - description: "Echo with a struct" } rules { selector: "endpoints.examples.bookstore.Bookstore.EchoShelfId" - description: "EchoShelfId" } -} -backend { rules { - selector: "endpoints.examples.bookstore.Bookstore.ListShelves" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.CreateShelf" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.BulkCreateShelf" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.GetShelf" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.DeleteShelf" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.ListBooks" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.CreateBook" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.GetBook" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.DeleteBook" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.QueryShelves" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.QueryBooks" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.EchoStruct" - } - rules { - selector: "endpoints.examples.bookstore.Bookstore.EchoShelfId" + selector: "endpoints.examples.bookstore.Bookstore.GetUnknownBook" } } http { @@ -868,10 +623,10 @@ http { post: "/echoshelfid" body: "*" } -} -endpoints { - name: "bookstore.endpoints.qiwzhang-authz.cloud.goog" - features: "googleapis.com/endpoint/grpc-autobahn" + rules { + selector: "endpoints.examples.bookstore.Bookstore.GetUnknownBook" + get: "/unknownbook" + } } config_version { value: 3 @@ -1075,4 +830,19 @@ system_parameters { url_query_parameter: "api_key" } } + rules { + selector: "endpoints.examples.bookstore.Bookstore.GetUnknownBook" + parameters { + name: "api_key" + http_header: "x-api-key" + } + parameters { + name: "api_key" + url_query_parameter: "key" + } + parameters { + name: "api_key" + url_query_parameter: "api_key" + } + } }