@Test public void contextDeadlineShouldNotOverrideSmallerMetadataTimeout() { long deadlineNanos = TimeUnit.SECONDS.toNanos(2); Context context = Context.current() .withDeadlineAfter(deadlineNanos, TimeUnit.NANOSECONDS, deadlineCancellationExecutor); context.attach(); CallOptions callOpts = CallOptions.DEFAULT.withDeadlineAfter(1, TimeUnit.SECONDS); ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( DESCRIPTOR, MoreExecutors.directExecutor(), callOpts, provider, deadlineCancellationExecutor); Metadata headers = new Metadata(); call.start(callListener, headers); assertTrue(headers.containsKey(GrpcUtil.TIMEOUT_KEY)); Long timeout = headers.get(GrpcUtil.TIMEOUT_KEY); assertNotNull(timeout); long callOptsNanos = TimeUnit.SECONDS.toNanos(1); long deltaNanos = TimeUnit.MILLISECONDS.toNanos(400); assertTimeoutBetween(timeout, callOptsNanos - deltaNanos, callOptsNanos); }
/** * Called by transport implementations when they receive headers. * * @param headers the parsed headers */ protected void inboundHeadersReceived(Metadata headers) { if (headers.containsKey(GrpcUtil.MESSAGE_ENCODING_KEY)) { String messageEncoding = headers.get(GrpcUtil.MESSAGE_ENCODING_KEY); try { setDecompressor(messageEncoding); } catch (IllegalArgumentException e) { Status status = Status.INVALID_ARGUMENT .withDescription("Unable to decompress encoding " + messageEncoding) .withCause(e); abortStream(status, true); return; } } // This checks to see if the client will accept any encoding. If so, a compressor is picked for // the stream, and the decision is recorded. When the Server Call Handler writes the first // headers, the negotiated encoding will be added in #writeHeaders(). It is safe to call // pickCompressor multiple times before the headers have been written to the wire, though in // practice this should never happen. There should only be one call to inboundHeadersReceived. // Alternatively, compression could be negotiated after the server handler is invoked, but that // would mean the inbound header would have to be stored until the first #writeHeaders call. if (headers.containsKey(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY)) { Compressor c = pickCompressor( ACCEPT_ENCODING_SPLITER.split(headers.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY))); if (c != null) { messageEncoding = c.getMessageEncoding(); } } inboundPhase(Phase.MESSAGE); }
@Test public void prepareHeaders_ignoreIdentityEncoding() { Metadata m = new Metadata(); ClientCallImpl.prepareHeaders(m, decompressorRegistry, Codec.Identity.NONE); assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY)); }
@Override public void headersRead(final Metadata headers) { Decompressor decompressor = Codec.Identity.NONE; if (headers.containsKey(MESSAGE_ENCODING_KEY)) { String encoding = headers.get(MESSAGE_ENCODING_KEY); decompressor = decompressorRegistry.lookupDecompressor(encoding); if (decompressor == null) { stream.cancel( Status.INTERNAL.withDescription( String.format("Can't find decompressor for %s", encoding))); return; } } stream.setDecompressor(decompressor); callExecutor.execute( new ContextRunnable(context) { @Override public final void runInContext() { try { if (closed) { return; } observer.onHeaders(headers); } catch (Throwable t) { stream.cancel( Status.CANCELLED.withCause(t).withDescription("Failed to read headers")); return; } } }); }
/** Creates a {@link Metadata} that contains pertinent headers. */ private Metadata createMetadata(String tableName) { Metadata metadata = new Metadata(); if (tableName != null) { metadata.put(GRPC_RESOURCE_PREFIX_KEY, tableName); } return metadata; }
@Test public void prepareHeaders_userAgentAdded() { Metadata m = new Metadata(); ClientCallImpl.prepareHeaders( m, CallOptions.DEFAULT, "user agent", DecompressorRegistry.getDefaultInstance()); assertEquals(m.get(GrpcUtil.USER_AGENT_KEY), "user agent"); }
private void writeStatusToTrailers(Status status) { stashedTrailers.removeAll(Status.CODE_KEY); stashedTrailers.removeAll(Status.MESSAGE_KEY); stashedTrailers.put(Status.CODE_KEY, status); if (status.getDescription() != null) { stashedTrailers.put(Status.MESSAGE_KEY, status.getDescription()); } }
@Test public void prepareHeaders_authorityAdded() { Metadata m = new Metadata(); CallOptions callOptions = CallOptions.DEFAULT.withAuthority("auth"); ClientCallImpl.prepareHeaders( m, callOptions, "user agent", DecompressorRegistry.getDefaultInstance()); assertEquals(m.get(GrpcUtil.AUTHORITY_KEY), "auth"); }
@Test public void prepareHeaders_ignoreIdentityEncoding() { Metadata m = new Metadata(); CallOptions callOptions = CallOptions.DEFAULT.withCompressor(Codec.Identity.NONE); ClientCallImpl.prepareHeaders( m, callOptions, "user agent", DecompressorRegistry.getDefaultInstance()); assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY)); }
@Test public void prepareHeaders_userAgentIgnored() { Metadata m = new Metadata(); m.put(GrpcUtil.USER_AGENT_KEY, "batmobile"); ClientCallImpl.prepareHeaders(m, decompressorRegistry, Codec.Identity.NONE); // User Agent is removed and set by the transport assertThat(m.get(GrpcUtil.USER_AGENT_KEY)).isNotNull(); }
@Test public void prepareHeaders_messageEncodingAdded() { Metadata m = new Metadata(); CallOptions callOptions = CallOptions.DEFAULT.withCompressor(new Codec.Gzip()); ClientCallImpl.prepareHeaders( m, callOptions, "user agent", DecompressorRegistry.getDefaultInstance()); assertEquals(m.get(GrpcUtil.MESSAGE_ENCODING_KEY), new Codec.Gzip().getMessageEncoding()); }
@Test public void prepareHeaders_removeReservedHeaders() { Metadata m = new Metadata(); m.put(GrpcUtil.MESSAGE_ENCODING_KEY, "gzip"); m.put(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY, "gzip"); ClientCallImpl.prepareHeaders(m, DecompressorRegistry.emptyInstance(), Codec.Identity.NONE); assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY)); assertNull(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY)); }
@Test public void prepareHeaders_acceptedEncodingsAdded() { Metadata m = new Metadata(); DecompressorRegistry customRegistry = DecompressorRegistry.emptyInstance() .with( new Decompressor() { @Override public String getMessageEncoding() { return "a"; } @Override public InputStream decompress(InputStream is) throws IOException { return null; } }, true) .with( new Decompressor() { @Override public String getMessageEncoding() { return "b"; } @Override public InputStream decompress(InputStream is) throws IOException { return null; } }, true) .with( new Decompressor() { @Override public String getMessageEncoding() { return "c"; } @Override public InputStream decompress(InputStream is) throws IOException { return null; } }, false); // not advertised ClientCallImpl.prepareHeaders(m, customRegistry, Codec.Identity.NONE); Iterable<String> acceptedEncodings = ACCEPT_ENCODING_SPLITER.split(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY)); // Order may be different, since decoder priorities have not yet been implemented. assertEquals(ImmutableSet.of("b", "a"), ImmutableSet.copyOf(acceptedEncodings)); }
/** Without a context or call options deadline, a timeout should not be set in metadata. */ @Test public void timeoutShouldNotBeSet() { ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( DESCRIPTOR, MoreExecutors.directExecutor(), CallOptions.DEFAULT, provider, deadlineCancellationExecutor); Metadata headers = new Metadata(); call.start(callListener, headers); assertFalse(headers.containsKey(GrpcUtil.TIMEOUT_KEY)); }
/** Based on the deadline, calculate and set the timeout to the given headers. */ private static void updateTimeoutHeaders( @Nullable Deadline effectiveDeadline, @Nullable Deadline callDeadline, @Nullable Deadline outerCallDeadline, Metadata headers) { headers.removeAll(TIMEOUT_KEY); if (effectiveDeadline == null) { return; } long effectiveTimeout = max(0, effectiveDeadline.timeRemaining(TimeUnit.NANOSECONDS)); headers.put(TIMEOUT_KEY, effectiveTimeout); logIfContextNarrowedTimeout( effectiveTimeout, effectiveDeadline, outerCallDeadline, callDeadline); }
@Override public final void writeHeaders(Metadata headers) { Preconditions.checkNotNull(headers, "headers"); headers.removeAll(MESSAGE_ENCODING_KEY); if (messageEncoding != null) { headers.put(MESSAGE_ENCODING_KEY, messageEncoding); } headers.removeAll(MESSAGE_ACCEPT_ENCODING_KEY); if (!decompressorRegistry().getAdvertisedMessageEncodings().isEmpty()) { String acceptEncoding = ACCEPT_ENCODING_JOINER.join(decompressorRegistry().getAdvertisedMessageEncodings()); headers.put(MESSAGE_ACCEPT_ENCODING_KEY, acceptEncoding); } outboundPhase(Phase.HEADERS); headersSent = true; internalSendHeaders(headers); outboundPhase(Phase.MESSAGE); }
@Test public void advertisedEncodingsAreSent() { ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( method, MoreExecutors.directExecutor(), CallOptions.DEFAULT, provider, deadlineCancellationExecutor) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class); verify(transport).newStream(eq(method), metadataCaptor.capture(), same(CallOptions.DEFAULT)); Metadata actual = metadataCaptor.getValue(); Set<String> acceptedEncodings = ImmutableSet.copyOf(actual.getAll(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY)); assertEquals(decompressorRegistry.getAdvertisedMessageEncodings(), acceptedEncodings); }
@VisibleForTesting static void prepareHeaders( Metadata headers, CallOptions callOptions, String userAgent, DecompressorRegistry decompressorRegistry, Compressor compressor) { // Fill out the User-Agent header. headers.removeAll(USER_AGENT_KEY); if (userAgent != null) { headers.put(USER_AGENT_KEY, userAgent); } headers.removeAll(MESSAGE_ENCODING_KEY); if (compressor != Codec.Identity.NONE) { headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding()); } headers.removeAll(MESSAGE_ACCEPT_ENCODING_KEY); if (!decompressorRegistry.getAdvertisedMessageEncodings().isEmpty()) { String acceptEncoding = ACCEPT_ENCODING_JOINER.join(decompressorRegistry.getAdvertisedMessageEncodings()); headers.put(MESSAGE_ACCEPT_ENCODING_KEY, acceptEncoding); } }
@Test public void prepareHeaders_removeReservedHeaders() { Metadata m = new Metadata(); m.put(GrpcUtil.AUTHORITY_KEY, "auth"); m.put(GrpcUtil.USER_AGENT_KEY, "user agent"); m.put(GrpcUtil.MESSAGE_ENCODING_KEY, "gzip"); m.put(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY, "gzip"); ClientCallImpl.prepareHeaders( m, CallOptions.DEFAULT, null, DecompressorRegistry.newEmptyInstance()); assertNull(m.get(GrpcUtil.AUTHORITY_KEY)); assertNull(m.get(GrpcUtil.USER_AGENT_KEY)); assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY)); assertNull(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY)); }
@Test public void advertisedEncodingsAreSent() { MethodDescriptor<Void, Void> descriptor = MethodDescriptor.create( MethodType.UNARY, "service/method", new TestMarshaller<Void>(), new TestMarshaller<Void>()); final ClientTransport transport = mock(ClientTransport.class); final ClientStream stream = mock(ClientStream.class); ClientTransportProvider provider = new ClientTransportProvider() { @Override public ListenableFuture<ClientTransport> get(CallOptions callOptions) { return Futures.immediateFuture(transport); } }; when(transport.newStream( any(MethodDescriptor.class), any(Metadata.class), any(ClientStreamListener.class))) .thenReturn(stream); ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( descriptor, executor, CallOptions.DEFAULT, provider, deadlineCancellationExecutor) .setDecompressorRegistry(decompressorRegistry); call.start(new TestClientCallListener<Void>(), new Metadata()); ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class); verify(transport) .newStream(eq(descriptor), metadataCaptor.capture(), isA(ClientStreamListener.class)); Metadata actual = metadataCaptor.getValue(); Set<String> acceptedEncodings = ImmutableSet.copyOf(actual.getAll(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY)); assertEquals(decompressorRegistry.getAdvertisedMessageEncodings(), acceptedEncodings); }
@Override public void updateHeaders(Metadata headers) throws Exception { headers.put(AUTHORIZATION_HEADER_KEY, getHeader()); }