Added defaultDeadline client property
Signed-off-by: Sergei Batsura <batsura.sa@gmail.com> [resolves #136]
This commit is contained in:
committed by
Dave Syer
parent
ff1b8e3dfa
commit
b004ba6559
@@ -0,0 +1,124 @@
|
||||
package org.springframework.grpc.sample;
|
||||
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import java.io.File;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.experimental.boot.server.exec.CommonsExecWebServerFactoryBean;
|
||||
import org.springframework.experimental.boot.server.exec.MavenClasspathEntry;
|
||||
import org.springframework.experimental.boot.test.context.DynamicProperty;
|
||||
import org.springframework.experimental.boot.test.context.EnableDynamicProperty;
|
||||
import org.springframework.grpc.client.EnableGrpcClients;
|
||||
import org.springframework.grpc.client.GrpcClient;
|
||||
import org.springframework.grpc.sample.proto.HelloRequest;
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class DefaultDeadlineSetupTests {
|
||||
|
||||
@Nested
|
||||
@SpringBootTest(properties = { "spring.grpc.client.default-channel.address=static://0.0.0.0:${local.grpc.port}",
|
||||
"spring.grpc.client.default-channel.default-deadline=1s",
|
||||
"spring.main.allow-bean-definition-overriding=true" })
|
||||
@DirtiesContext
|
||||
@EnabledIf("serverJarAvailable")
|
||||
class Deadline {
|
||||
|
||||
static boolean serverJarAvailable() {
|
||||
return new File("../grpc-server/target/grpc-server-sample-0.6.0-SNAPSHOT.jar").exists();
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
// Real test case in ExtraConfiguration#runner(SimpleGrpc.SimpleBlockingStub)}
|
||||
}
|
||||
|
||||
@EnableGrpcClients(@GrpcClient(types = SimpleGrpc.SimpleBlockingStub.class))
|
||||
@TestConfiguration
|
||||
@EnableDynamicProperty
|
||||
static class ExtraConfiguration {
|
||||
|
||||
@Bean
|
||||
@DynamicProperty(name = "local.grpc.port", value = "port")
|
||||
static CommonsExecWebServerFactoryBean grpcServer() {
|
||||
return CommonsExecWebServerFactoryBean.builder()
|
||||
.classpath(classpath -> classpath
|
||||
.entries(new MavenClasspathEntry("org.springframework.grpc:grpc-server-sample:0.6.0-SNAPSHOT"))
|
||||
.files("target/test-classes"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public CommandLineRunner runner(SimpleGrpc.SimpleBlockingStub stub) {
|
||||
return args -> {
|
||||
var rs = stub.streamHello(HelloRequest.newBuilder().setName("Deadline").build());
|
||||
Assertions.assertNotNull(rs);
|
||||
StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, () -> {
|
||||
while (rs.hasNext()) {
|
||||
System.out.println(rs.next());
|
||||
}
|
||||
});
|
||||
assertEquals(Status.Code.DEADLINE_EXCEEDED, exception.getStatus().getCode());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SpringBootTest(properties = { "spring.grpc.client.default-channel.address=static://0.0.0.0:${local.grpc.port}",
|
||||
"spring.grpc.client.default-channel.default-deadline=1s",
|
||||
"spring.main.allow-bean-definition-overriding=true" })
|
||||
@DirtiesContext
|
||||
@EnabledIf("serverJarAvailable")
|
||||
class WithoutDeadline {
|
||||
|
||||
static boolean serverJarAvailable() {
|
||||
return new File("../grpc-server/target/grpc-server-sample-0.6.0-SNAPSHOT.jar").exists();
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
// Real test case in ExtraConfiguration#runner(SimpleGrpc.SimpleBlockingStub)}
|
||||
}
|
||||
|
||||
@EnableGrpcClients(@GrpcClient(types = SimpleGrpc.SimpleBlockingStub.class))
|
||||
@TestConfiguration
|
||||
@EnableDynamicProperty
|
||||
static class ExtraConfiguration {
|
||||
|
||||
@Bean
|
||||
@DynamicProperty(name = "local.grpc.port", value = "port")
|
||||
static CommonsExecWebServerFactoryBean grpcServer() {
|
||||
return CommonsExecWebServerFactoryBean.builder()
|
||||
.classpath(classpath -> classpath
|
||||
.entries(new MavenClasspathEntry("org.springframework.grpc:grpc-server-sample:0.6.0-SNAPSHOT"))
|
||||
.files("target/test-classes"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public CommandLineRunner runner(SimpleGrpc.SimpleBlockingStub stub) {
|
||||
return args -> {
|
||||
var rs = stub.sayHello(HelloRequest.newBuilder().setName("WithoutDeadline").build());
|
||||
Assertions.assertNotNull(rs);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import org.springframework.grpc.client.ChannelBuilderOptions;
|
||||
import org.springframework.grpc.client.EnableGrpcClients;
|
||||
import org.springframework.grpc.client.GrpcClient;
|
||||
import org.springframework.grpc.client.GrpcClientRegistryCustomizer;
|
||||
import org.springframework.grpc.client.security.BearerTokenAuthenticationInterceptor;
|
||||
import org.springframework.grpc.client.interceptor.security.BearerTokenAuthenticationInterceptor;
|
||||
import org.springframework.grpc.sample.proto.HelloReply;
|
||||
import org.springframework.grpc.sample.proto.HelloRequest;
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc;
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.grpc.client.ChannelBuilderOptions;
|
||||
import org.springframework.grpc.client.EnableGrpcClients;
|
||||
import org.springframework.grpc.client.GrpcClient;
|
||||
import org.springframework.grpc.client.GrpcClientRegistryCustomizer;
|
||||
import org.springframework.grpc.client.security.BasicAuthenticationInterceptor;
|
||||
import org.springframework.grpc.client.interceptor.security.BasicAuthenticationInterceptor;
|
||||
import org.springframework.grpc.sample.proto.HelloReply;
|
||||
import org.springframework.grpc.sample.proto.HelloRequest;
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc;
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.grpc.client.ChannelBuilderOptions;
|
||||
import org.springframework.grpc.client.GrpcChannelFactory;
|
||||
import org.springframework.grpc.client.security.BasicAuthenticationInterceptor;
|
||||
import org.springframework.grpc.client.interceptor.security.BasicAuthenticationInterceptor;
|
||||
import org.springframework.grpc.sample.proto.HelloReply;
|
||||
import org.springframework.grpc.sample.proto.HelloRequest;
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2024-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.grpc.client.interceptor;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.MethodDescriptor;
|
||||
|
||||
/**
|
||||
* A client interceptor configuring the default deadline for each call.
|
||||
*
|
||||
* @author Sergei Batsura (batsura.sa@gmail.com)
|
||||
*/
|
||||
public class DefaultDeadlineSetupClientInterceptor implements ClientInterceptor {
|
||||
|
||||
private final Duration defaultDeadline;
|
||||
|
||||
public DefaultDeadlineSetupClientInterceptor(Duration defaultDeadline) {
|
||||
this.defaultDeadline = requireNonNull(defaultDeadline, "defaultDeadline");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(final MethodDescriptor<ReqT, RespT> method,
|
||||
final CallOptions callOptions, final Channel next) {
|
||||
|
||||
if (callOptions.getDeadline() == null) {
|
||||
return next.newCall(method,
|
||||
callOptions.withDeadlineAfter(this.defaultDeadline.toMillis(), TimeUnit.MILLISECONDS));
|
||||
}
|
||||
else {
|
||||
return next.newCall(method, callOptions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.grpc.client.security;
|
||||
package org.springframework.grpc.client.interceptor.security;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.grpc.client.security;
|
||||
package org.springframework.grpc.client.interceptor.security;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -15,19 +15,18 @@
|
||||
*/
|
||||
package org.springframework.grpc.autoconfigure.client;
|
||||
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.grpc.autoconfigure.client.GrpcClientProperties.ChannelConfig;
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
|
||||
import org.springframework.grpc.client.interceptor.DefaultDeadlineSetupClientInterceptor;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
|
||||
/**
|
||||
* A {@link GrpcChannelBuilderCustomizer} that maps {@link GrpcClientProperties client
|
||||
* properties} to a channel builder.
|
||||
@@ -65,6 +64,9 @@ class ClientPropertiesChannelBuilderCustomizer<T extends ManagedChannelBuilder<T
|
||||
Map<String, ?> healthCheckConfig = Map.of("healthCheckConfig", Map.of("serviceName", serviceNameToCheck));
|
||||
builder.defaultServiceConfig(healthCheckConfig);
|
||||
}
|
||||
if (channel.getDefaultDeadline() != null && channel.getDefaultDeadline().toMillis() > 0L) {
|
||||
builder.intercept(new DefaultDeadlineSetupClientInterceptor(channel.getDefaultDeadline()));
|
||||
}
|
||||
}
|
||||
|
||||
Consumer<Duration> durationProperty(BiConsumer<Long, TimeUnit> setter) {
|
||||
|
||||
@@ -307,6 +307,19 @@ public class GrpcClientProperties implements EnvironmentAware, VirtualTargets {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default deadline for RPCs performed on this channel.
|
||||
*/
|
||||
private Duration defaultDeadline = null;
|
||||
|
||||
public Duration getDefaultDeadline() {
|
||||
return defaultDeadline;
|
||||
}
|
||||
|
||||
public void setDefaultDeadline(final Duration defaultDeadline) {
|
||||
this.defaultDeadline = defaultDeadline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a copy of the channel instance.
|
||||
* @return a copy of the channel instance.
|
||||
@@ -324,6 +337,7 @@ public class GrpcClientProperties implements EnvironmentAware, VirtualTargets {
|
||||
copy.maxInboundMessageSize = this.maxInboundMessageSize;
|
||||
copy.maxInboundMetadataSize = this.maxInboundMetadataSize;
|
||||
copy.userAgent = this.userAgent;
|
||||
copy.defaultDeadline = this.defaultDeadline;
|
||||
copy.health.copyValuesFrom(this.getHealth());
|
||||
copy.ssl.copyValuesFrom(this.getSsl());
|
||||
return copy;
|
||||
|
||||
@@ -208,6 +208,7 @@ class GrpcClientPropertiesTests {
|
||||
defaultChannel.setMaxInboundMessageSize(DataSize.ofMegabytes(100));
|
||||
defaultChannel.setMaxInboundMetadataSize(DataSize.ofMegabytes(200));
|
||||
defaultChannel.setUserAgent("me");
|
||||
defaultChannel.setDefaultDeadline(Duration.ofMinutes(1));
|
||||
defaultChannel.getSsl().setEnabled(true);
|
||||
defaultChannel.getSsl().setBundle("custom-bundle");
|
||||
var properties = newProperties(defaultChannel, Map.of());
|
||||
|
||||
Reference in New Issue
Block a user