diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java index 4a32902085..7a5fa879cd 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java @@ -35,6 +35,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -65,6 +66,7 @@ import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; import io.grpc.ClientStreamTracer; import io.grpc.Context; import io.grpc.Grpc; @@ -85,6 +87,7 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.testing.StatsTestUtils.FakeStatsContextFactory; import io.grpc.internal.testing.StatsTestUtils.MetricsRecord; import io.grpc.protobuf.ProtoUtils; +import io.grpc.stub.ClientCalls; import io.grpc.stub.MetadataUtils; import io.grpc.stub.StreamObserver; import io.grpc.testing.StreamRecorder; @@ -270,6 +273,44 @@ public abstract class AbstractInteropTest { assertEquals(EMPTY, blockingStub.emptyCall(EMPTY)); } + /** Sends a cacheable unary rpc using GET. Requires that the server is behind a caching proxy. */ + public void cacheableUnary() { + // Set safe to true. + MethodDescriptor safeCacheableUnaryCallMethod = + TestServiceGrpc.METHOD_CACHEABLE_UNARY_CALL.toBuilder().setSafe(true).build(); + // Set fake user IP since some proxies (GFE) won't cache requests from localhost. + Metadata.Key userIpKey = Metadata.Key.of("x-user-ip", Metadata.ASCII_STRING_MARSHALLER); + Metadata metadata = new Metadata(); + metadata.put(userIpKey, "1.2.3.4"); + Channel channelWithUserIpKey = + ClientInterceptors.intercept(channel, MetadataUtils.newAttachHeadersInterceptor(metadata)); + SimpleRequest requests1And2 = + SimpleRequest.newBuilder() + .setPayload( + Payload.newBuilder() + .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) + .build(); + SimpleRequest request3 = + SimpleRequest.newBuilder() + .setPayload( + Payload.newBuilder() + .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) + .build(); + + SimpleResponse response1 = + ClientCalls.blockingUnaryCall( + channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); + SimpleResponse response2 = + ClientCalls.blockingUnaryCall( + channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); + SimpleResponse response3 = + ClientCalls.blockingUnaryCall( + channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, request3); + + assertEquals(response1, response2); + assertNotEquals(response1, response3); + } + @Test(timeout = 10000) public void largeUnary() throws Exception { assumeEnoughMemory(); diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestCases.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestCases.java index 03faf935ee..213ca52e47 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/TestCases.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestCases.java @@ -38,6 +38,7 @@ import com.google.common.base.Preconditions; */ public enum TestCases { EMPTY_UNARY("empty (zero bytes) request and response"), + CACHEABLE_UNARY("cacheable unary rpc sent using GET"), LARGE_UNARY("single request and (large) response"), CLIENT_STREAMING("request streaming with single response"), SERVER_STREAMING("single request with response streaming"), diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java index e9f2e7d8f8..f5ab69b7a2 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java @@ -209,6 +209,11 @@ public class TestServiceClient { tester.emptyUnary(); break; + case CACHEABLE_UNARY: { + tester.cacheableUnary(); + break; + } + case LARGE_UNARY: tester.largeUnary(); break; diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/TestCasesTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/TestCasesTest.java index fc4759265d..ab48367501 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/TestCasesTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/TestCasesTest.java @@ -54,11 +54,27 @@ public class TestCasesTest { @Test public void testCaseNamesShouldMapToEnums() { // names of testcases as defined in the interop spec - String[] testCases = {"empty_unary", "large_unary", "client_streaming", "server_streaming", - "ping_pong", "empty_stream", "compute_engine_creds", "service_account_creds", - "jwt_token_creds", "oauth2_auth_token", "per_rpc_creds", "custom_metadata", - "status_code_and_message", "unimplemented_method", "unimplemented_service", - "cancel_after_begin", "cancel_after_first_response", "timeout_on_sleeping_server"}; + String[] testCases = { + "empty_unary", + "cacheable_unary", + "large_unary", + "client_streaming", + "server_streaming", + "ping_pong", + "empty_stream", + "compute_engine_creds", + "service_account_creds", + "jwt_token_creds", + "oauth2_auth_token", + "per_rpc_creds", + "custom_metadata", + "status_code_and_message", + "unimplemented_method", + "unimplemented_service", + "cancel_after_begin", + "cancel_after_first_response", + "timeout_on_sleeping_server" + }; assertEquals(testCases.length, TestCases.values().length); diff --git a/netty/src/main/java/io/grpc/netty/NettyClientStream.java b/netty/src/main/java/io/grpc/netty/NettyClientStream.java index 7c29fed004..2cb1b0f28d 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientStream.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientStream.java @@ -128,6 +128,7 @@ class NettyClientStream extends AbstractClientStream2 { AsciiString httpMethod; if (get) { // Forge the query string + // TODO(ericgribkoff) Add the key back to the query string defaultPath = new AsciiString(defaultPath + "?" + BaseEncoding.base64().encode(requestPayload)); httpMethod = Utils.HTTP_GET_METHOD; diff --git a/testing-proto/src/generated/main/grpc/io/grpc/testing/integration/TestServiceGrpc.java b/testing-proto/src/generated/main/grpc/io/grpc/testing/integration/TestServiceGrpc.java index 9d07cd3e7c..d14c834fdb 100644 --- a/testing-proto/src/generated/main/grpc/io/grpc/testing/integration/TestServiceGrpc.java +++ b/testing-proto/src/generated/main/grpc/io/grpc/testing/integration/TestServiceGrpc.java @@ -56,6 +56,18 @@ public final class TestServiceGrpc { io.grpc.testing.integration.Messages.SimpleResponse.getDefaultInstance())) .build(); @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901") + public static final io.grpc.MethodDescriptor METHOD_CACHEABLE_UNARY_CALL = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName( + "grpc.testing.TestService", "CacheableUnaryCall")) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.grpc.testing.integration.Messages.SimpleRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.grpc.testing.integration.Messages.SimpleResponse.getDefaultInstance())) + .build(); + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901") public static final io.grpc.MethodDescriptor METHOD_STREAMING_OUTPUT_CALL = io.grpc.MethodDescriptor.newBuilder() @@ -167,6 +179,18 @@ public final class TestServiceGrpc { asyncUnimplementedUnaryCall(METHOD_UNARY_CALL, responseObserver); } + /** + *
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * 
+ */ + public void cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request, + io.grpc.stub.StreamObserver responseObserver) { + asyncUnimplementedUnaryCall(METHOD_CACHEABLE_UNARY_CALL, responseObserver); + } + /** *
      * One request followed by a sequence of responses (streamed download).
@@ -241,6 +265,13 @@ public final class TestServiceGrpc {
                 io.grpc.testing.integration.Messages.SimpleRequest,
                 io.grpc.testing.integration.Messages.SimpleResponse>(
                   this, METHODID_UNARY_CALL)))
+          .addMethod(
+            METHOD_CACHEABLE_UNARY_CALL,
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Messages.SimpleRequest,
+                io.grpc.testing.integration.Messages.SimpleResponse>(
+                  this, METHODID_CACHEABLE_UNARY_CALL)))
           .addMethod(
             METHOD_STREAMING_OUTPUT_CALL,
             asyncServerStreamingCall(
@@ -324,6 +355,19 @@ public final class TestServiceGrpc {
           getChannel().newCall(METHOD_UNARY_CALL, getCallOptions()), request, responseObserver);
     }
 
+    /**
+     * 
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * 
+ */ + public void cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request, + io.grpc.stub.StreamObserver responseObserver) { + asyncUnaryCall( + getChannel().newCall(METHOD_CACHEABLE_UNARY_CALL, getCallOptions()), request, responseObserver); + } + /** *
      * One request followed by a sequence of responses (streamed download).
@@ -430,6 +474,18 @@ public final class TestServiceGrpc {
           getChannel(), METHOD_UNARY_CALL, getCallOptions(), request);
     }
 
+    /**
+     * 
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * 
+ */ + public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) { + return blockingUnaryCall( + getChannel(), METHOD_CACHEABLE_UNARY_CALL, getCallOptions(), request); + } + /** *
      * One request followed by a sequence of responses (streamed download).
@@ -498,6 +554,19 @@ public final class TestServiceGrpc {
           getChannel().newCall(METHOD_UNARY_CALL, getCallOptions()), request);
     }
 
+    /**
+     * 
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture cacheableUnaryCall( + io.grpc.testing.integration.Messages.SimpleRequest request) { + return futureUnaryCall( + getChannel().newCall(METHOD_CACHEABLE_UNARY_CALL, getCallOptions()), request); + } + /** *
      * The test server will not implement this method. It will be used
@@ -513,11 +582,12 @@ public final class TestServiceGrpc {
 
   private static final int METHODID_EMPTY_CALL = 0;
   private static final int METHODID_UNARY_CALL = 1;
-  private static final int METHODID_STREAMING_OUTPUT_CALL = 2;
-  private static final int METHODID_UNIMPLEMENTED_CALL = 3;
-  private static final int METHODID_STREAMING_INPUT_CALL = 4;
-  private static final int METHODID_FULL_DUPLEX_CALL = 5;
-  private static final int METHODID_HALF_DUPLEX_CALL = 6;
+  private static final int METHODID_CACHEABLE_UNARY_CALL = 2;
+  private static final int METHODID_STREAMING_OUTPUT_CALL = 3;
+  private static final int METHODID_UNIMPLEMENTED_CALL = 4;
+  private static final int METHODID_STREAMING_INPUT_CALL = 5;
+  private static final int METHODID_FULL_DUPLEX_CALL = 6;
+  private static final int METHODID_HALF_DUPLEX_CALL = 7;
 
   private static final class MethodHandlers implements
       io.grpc.stub.ServerCalls.UnaryMethod,
@@ -544,6 +614,10 @@ public final class TestServiceGrpc {
           serviceImpl.unaryCall((io.grpc.testing.integration.Messages.SimpleRequest) request,
               (io.grpc.stub.StreamObserver) responseObserver);
           break;
+        case METHODID_CACHEABLE_UNARY_CALL:
+          serviceImpl.cacheableUnaryCall((io.grpc.testing.integration.Messages.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver) responseObserver);
+          break;
         case METHODID_STREAMING_OUTPUT_CALL:
           serviceImpl.streamingOutputCall((io.grpc.testing.integration.Messages.StreamingOutputCallRequest) request,
               (io.grpc.stub.StreamObserver) responseObserver);
@@ -596,6 +670,7 @@ public final class TestServiceGrpc {
               .setSchemaDescriptor(new TestServiceDescriptorSupplier())
               .addMethod(METHOD_EMPTY_CALL)
               .addMethod(METHOD_UNARY_CALL)
+              .addMethod(METHOD_CACHEABLE_UNARY_CALL)
               .addMethod(METHOD_STREAMING_OUTPUT_CALL)
               .addMethod(METHOD_STREAMING_INPUT_CALL)
               .addMethod(METHOD_FULL_DUPLEX_CALL)
diff --git a/testing-proto/src/generated/main/java/io/grpc/testing/integration/Test.java b/testing-proto/src/generated/main/java/io/grpc/testing/integration/Test.java
index 114d492a8e..44903d2779 100644
--- a/testing-proto/src/generated/main/java/io/grpc/testing/integration/Test.java
+++ b/testing-proto/src/generated/main/java/io/grpc/testing/integration/Test.java
@@ -26,29 +26,31 @@ public final class Test {
       "\n&io/grpc/testing/integration/test.proto" +
       "\022\014grpc.testing\032\'io/grpc/testing/integrat" +
       "ion/empty.proto\032*io/grpc/testing/integra" +
-      "tion/messages.proto2\372\004\n\013TestService\0225\n\tE" +
+      "tion/messages.proto2\313\005\n\013TestService\0225\n\tE" +
       "mptyCall\022\023.grpc.testing.Empty\032\023.grpc.tes" +
       "ting.Empty\022F\n\tUnaryCall\022\033.grpc.testing.S" +
       "impleRequest\032\034.grpc.testing.SimpleRespon" +
-      "se\022l\n\023StreamingOutputCall\022(.grpc.testing" +
+      "se\022O\n\022CacheableUnaryCall\022\033.grpc.testing." +
+      "SimpleRequest\032\034.grpc.testing.SimpleRespo" +
+      "nse\022l\n\023StreamingOutputCall\022(.grpc.testin",
+      "g.StreamingOutputCallRequest\032).grpc.test" +
+      "ing.StreamingOutputCallResponse0\001\022i\n\022Str" +
+      "eamingInputCall\022\'.grpc.testing.Streaming" +
+      "InputCallRequest\032(.grpc.testing.Streamin" +
+      "gInputCallResponse(\001\022i\n\016FullDuplexCall\022(" +
+      ".grpc.testing.StreamingOutputCallRequest" +
+      "\032).grpc.testing.StreamingOutputCallRespo" +
+      "nse(\0010\001\022i\n\016HalfDuplexCall\022(.grpc.testing" +
       ".StreamingOutputCallRequest\032).grpc.testi" +
-      "ng.StreamingOutputCallResponse0\001\022i\n\022Stre",
-      "amingInputCall\022\'.grpc.testing.StreamingI" +
-      "nputCallRequest\032(.grpc.testing.Streaming" +
-      "InputCallResponse(\001\022i\n\016FullDuplexCall\022(." +
-      "grpc.testing.StreamingOutputCallRequest\032" +
-      ").grpc.testing.StreamingOutputCallRespon" +
-      "se(\0010\001\022i\n\016HalfDuplexCall\022(.grpc.testing." +
-      "StreamingOutputCallRequest\032).grpc.testin" +
-      "g.StreamingOutputCallResponse(\0010\001\022=\n\021Uni" +
-      "mplementedCall\022\023.grpc.testing.Empty\032\023.gr" +
-      "pc.testing.Empty2U\n\024UnimplementedService",
-      "\022=\n\021UnimplementedCall\022\023.grpc.testing.Emp" +
-      "ty\032\023.grpc.testing.Empty2\177\n\020ReconnectServ" +
-      "ice\0221\n\005Start\022\023.grpc.testing.Empty\032\023.grpc" +
-      ".testing.Empty\0228\n\004Stop\022\023.grpc.testing.Em" +
-      "pty\032\033.grpc.testing.ReconnectInfoB\035\n\033io.g" +
-      "rpc.testing.integrationb\006proto3"
+      "ng.StreamingOutputCallResponse(\0010\001\022=\n\021Un",
+      "implementedCall\022\023.grpc.testing.Empty\032\023.g" +
+      "rpc.testing.Empty2U\n\024UnimplementedServic" +
+      "e\022=\n\021UnimplementedCall\022\023.grpc.testing.Em" +
+      "pty\032\023.grpc.testing.Empty2\177\n\020ReconnectSer" +
+      "vice\0221\n\005Start\022\023.grpc.testing.Empty\032\023.grp" +
+      "c.testing.Empty\0228\n\004Stop\022\023.grpc.testing.E" +
+      "mpty\032\033.grpc.testing.ReconnectInfoB\035\n\033io." +
+      "grpc.testing.integrationb\006proto3"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
         new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
diff --git a/testing-proto/src/main/proto/io/grpc/testing/integration/test.proto b/testing-proto/src/main/proto/io/grpc/testing/integration/test.proto
index e4a41b4c9e..c07d8a9ddd 100644
--- a/testing-proto/src/main/proto/io/grpc/testing/integration/test.proto
+++ b/testing-proto/src/main/proto/io/grpc/testing/integration/test.proto
@@ -47,6 +47,11 @@ service TestService {
   // One request followed by one response.
   rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
 
+  // One request followed by one response. Response has cache control
+  // headers set such that a caching HTTP proxy (such as GFE) can
+  // satisfy subsequent requests.
+  rpc CacheableUnaryCall(SimpleRequest) returns (SimpleResponse);
+
   // One request followed by a sequence of responses (streamed download).
   // The server returns the payload with client desired type and sizes.
   rpc StreamingOutputCall(StreamingOutputCallRequest)