Use GrpcServiceDiscoverer in GrpcServletRequestMatcher

This commit is contained in:
Dave Syer
2025-01-06 16:49:54 +00:00
parent f1ed1ce93a
commit 6cc3a2ab89
3 changed files with 26 additions and 22 deletions

View File

@@ -31,7 +31,7 @@
<java.version>17</java.version>
<spring-javaformat-maven-plugin.version>0.0.39</spring-javaformat-maven-plugin.version>
<protobuf-java.version>3.25.5</protobuf-java.version>
<grpc.version>1.63.2</grpc.version>
<grpc.version>1.69.0</grpc.version>
</properties>
<dependencyManagement>
<dependencies>

View File

@@ -23,13 +23,12 @@ import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.context.ApplicationContext;
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import io.grpc.BindableService;
import jakarta.servlet.http.HttpServletRequest;
/**
@@ -43,10 +42,11 @@ public class GrpcServletRequest {
/**
* Returns a matcher that includes all gRPC services from the application context. The
* {@link GrpcServletRequestMatcher#excluding(Class<?>...) excluding} method can be
* used to remove specific services by class if required. For example:
* {@link GrpcServletRequestMatcher#excluding(String...) excluding} method can be used
* to remove specific services by name if required. For example:
*
* <pre class="code">
* GrpcServletRequest.all().excluding(MyCustomService.class)
* GrpcServletRequest.all().excluding("my-service")
* </pre>
* @return the configured {@link RequestMatcher}
*/
@@ -57,9 +57,10 @@ public class GrpcServletRequest {
/**
* The request matcher used to match against resource locations.
*/
public static final class GrpcServletRequestMatcher extends ApplicationContextRequestMatcher<ApplicationContext> {
public static final class GrpcServletRequestMatcher
extends ApplicationContextRequestMatcher<GrpcServiceDiscoverer> {
private final Set<Class<?>> exclusions;
private final Set<String> exclusions;
private volatile RequestMatcher delegate;
@@ -67,8 +68,8 @@ public class GrpcServletRequest {
this(new HashSet<>());
}
private GrpcServletRequestMatcher(Set<Class<?>> exclusions) {
super(ApplicationContext.class);
private GrpcServletRequestMatcher(Set<String> exclusions) {
super(GrpcServiceDiscoverer.class);
this.exclusions = exclusions;
}
@@ -78,44 +79,43 @@ public class GrpcServletRequest {
* @param rest additional services to exclude
* @return a new {@link GrpcServletRequestMatcher}
*/
public GrpcServletRequestMatcher excluding(Class<?>... rest) {
public GrpcServletRequestMatcher excluding(String... rest) {
return excluding(Set.of(rest));
}
/**
* Return a new {@link GrpcServletRequestMatcher} based on this one but excluding
* the specified services.
* @param exclusions additional services to exclude
* @param exclusions additional service names to exclude
* @return a new {@link GrpcServletRequestMatcher}
*/
public GrpcServletRequestMatcher excluding(Set<Class<?>> exclusions) {
public GrpcServletRequestMatcher excluding(Set<String> exclusions) {
Assert.notNull(exclusions, "Exclusions must not be null");
Set<Class<?>> subset = new LinkedHashSet<>(this.exclusions);
Set<String> subset = new LinkedHashSet<>(this.exclusions);
subset.addAll(exclusions);
return new GrpcServletRequestMatcher(subset);
}
@Override
protected void initialized(Supplier<ApplicationContext> context) {
protected void initialized(Supplier<GrpcServiceDiscoverer> context) {
List<RequestMatcher> matchers = getDelegateMatchers(context.get()).toList();
this.delegate = matchers.isEmpty() ? request -> false : new OrRequestMatcher(matchers);
}
private Stream<RequestMatcher> getDelegateMatchers(ApplicationContext context) {
private Stream<RequestMatcher> getDelegateMatchers(GrpcServiceDiscoverer context) {
return getPatterns(context).map(AntPathRequestMatcher::new);
}
private Stream<String> getPatterns(ApplicationContext context) {
return context.getBeanProvider(BindableService.class)
private Stream<String> getPatterns(GrpcServiceDiscoverer context) {
return context.findServices()
.stream()
.filter(service -> !this.exclusions.stream()
.anyMatch(type -> type.isAssignableFrom(service.getClass())))
.map(BindableService::bindService)
.anyMatch(type -> type.equals(service.getServiceDescriptor().getName())))
.map(service -> "/" + service.getServiceDescriptor().getName() + "/**");
}
@Override
protected boolean matches(HttpServletRequest request, Supplier<ApplicationContext> context) {
protected boolean matches(HttpServletRequest request, Supplier<GrpcServiceDiscoverer> context) {
return this.delegate.matches(request);
}

View File

@@ -22,6 +22,8 @@ import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.grpc.autoconfigure.server.security.GrpcServletRequest.GrpcServletRequestMatcher;
import org.springframework.grpc.server.service.DefaultGrpcServiceDiscoverer;
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext;
@@ -40,6 +42,8 @@ public class GrpcServletRequestTests {
ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder("my-service").build();
when(service.bindService()).thenReturn(serviceDefinition);
this.context.registerBean(BindableService.class, () -> service);
this.context.registerBean(GrpcServiceDiscoverer.class,
() -> new DefaultGrpcServiceDiscoverer((input, info) -> input.bindService(), context));
}
@Test
@@ -58,7 +62,7 @@ public class GrpcServletRequestTests {
@Test
void requestMatcherExcludes() {
GrpcServletRequestMatcher matcher = GrpcServletRequest.all().excluding(MockService.class);
GrpcServletRequestMatcher matcher = GrpcServletRequest.all().excluding("my-service");
MockHttpServletRequest request = mockRequest("/my-service/Method");
assertThat(matcher.matches(request)).isFalse();
};