Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
just hold
Browse files Browse the repository at this point in the history
DanielLiu1123 committed Nov 12, 2023
1 parent 0a5c55a commit 01c580e
Showing 14 changed files with 238 additions and 33 deletions.
11 changes: 10 additions & 1 deletion examples/grpc-sample-api/src/main/proto/sample/pet/v1/pet.proto
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ option java_multiple_files = true;
option java_package = "com.freemanan.sample.pet.v1";

import "google/protobuf/wrappers.proto";
import "google/api/annotations.proto";

message Pet {
string name = 1;
@@ -18,6 +19,14 @@ message GetPetRequest {
}

service PetService {
rpc GetPet(GetPetRequest) returns (Pet);
rpc GetPet(GetPetRequest) returns (Pet) {
option(google.api.http) = {
post: "/v1/foo",
body: "*",
additional_bindings: {
post: "/v1/bar"
}
};
}
rpc GetPetName(google.protobuf.StringValue) returns (google.protobuf.StringValue);
}
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ public ResponseEntity<ErrorResponse> handleStatusRuntimeException(StatusRuntimeE
@Data
@NoArgsConstructor
@AllArgsConstructor
static class ErrorResponse {
public static class ErrorResponse {
private int code;
private String message;
private Object data;
Original file line number Diff line number Diff line change
@@ -2,8 +2,6 @@ grpc:
server:
reflection:
enabled: true
exception-handling:
use-default: false
server:
error:
include-message: always
include-message: always
8 changes: 7 additions & 1 deletion examples/proto-validate/src/main/proto/fm/foo/v1/foo.proto
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ package fm.foo.v1;

import "google/protobuf/empty.proto";
import "buf/validate/validate.proto";
import "google/api/annotations.proto";

option java_multiple_files = true;
option java_package = "com.freemanan.foo.v1.api";
@@ -25,5 +26,10 @@ message Foo {
}

service FooService {
rpc InsertFoo(Foo) returns (Foo) {}
rpc InsertFoo(Foo) returns (Foo) {
option(google.api.http) = {
post: "/v1/foo",
body: "*"
};
}
}
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
import io.grpc.TlsServerCredentials;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.internal.GrpcUtil;
import jakarta.annotation.Nullable;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
@@ -133,6 +134,12 @@ public int getPort() {
return server.getPort();
}

@Nullable
@Override
public Object getServer() {
return server;
}

@Override
public void stop() {
if (isRunning.get()) {
Original file line number Diff line number Diff line change
@@ -32,4 +32,9 @@ public boolean isAutoStartup() {
public int getPort() {
return DUMMY_PORT;
}

@Override
public Object getServer() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.freemanan.starter.grpc.server;

import jakarta.annotation.Nullable;
import org.springframework.context.SmartLifecycle;

/**
@@ -13,4 +14,10 @@ public interface GrpcServer extends SmartLifecycle {
* @return port number
*/
int getPort();

/**
* Get the server object.
*/
@Nullable
Object getServer();
}
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -83,12 +84,9 @@ private static Method getWithInterceptorsMethod() {
}
}

@SneakyThrows
protected Object applyInterceptor4Stub(ClientInterceptor clientInterceptor, Object stub) {
try {
return withInterceptorsMethod.invoke(stub, (Object) new ClientInterceptor[] {clientInterceptor});
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
return withInterceptorsMethod.invoke(stub, (Object) new ClientInterceptor[] {clientInterceptor});
}

protected Message convert2ProtobufMessage(Class<?> messageClass, InputStream is) {
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
import com.freemanan.starter.grpc.extensions.jsontranscoder.webflux.GrpcHandlerResultHandler;
import com.freemanan.starter.grpc.extensions.jsontranscoder.webflux.WebFluxGrpcServiceHandlerMapping;
import com.freemanan.starter.grpc.extensions.jsontranscoder.webflux.WebFluxProtobufHandlerAdaptor;
import com.freemanan.starter.grpc.server.GrpcServer;
import io.grpc.BindableService;
import io.grpc.Metadata;
import org.springframework.beans.factory.ObjectProvider;
@@ -46,8 +47,8 @@ static class WebMvc {
@Bean
@ConditionalOnMissingBean
public WebMvcGrpcServiceHandlerMapping webMvcGrpcServiceHandlerMapping(
ObjectProvider<BindableService> grpcServices) {
return new WebMvcGrpcServiceHandlerMapping(grpcServices);
ObjectProvider<BindableService> grpcServices, GrpcServer grpcServer) {
return new WebMvcGrpcServiceHandlerMapping(grpcServices, grpcServer);
}

@Bean
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.server.NotAcceptableStatusException;

@@ -59,12 +60,18 @@ private static Method findMethod(BindableService service, String fullMethodName)

public static Map<String, HandlerMethod> getPathToMethod(ObjectProvider<BindableService> grpcServiceProvider) {
return grpcServiceProvider.stream()
.map(bs -> Tuple2.of(bs.bindService(), bs))
.map(bs -> Tuple2.of(bs.bindService(), bs)) // BindableServiceDefinition, BindableService
.flatMap(en -> en.getT1().getMethods().stream()
.map(m -> Tuple2.of(m.getMethodDescriptor().getFullMethodName(), en.getT2())))
.map(en -> Tuple3.of(en.getT1(), en.getT2(), findMethod(en.getT2(), en.getT1())))
.map(m -> Tuple2.of(
m.getMethodDescriptor().getFullMethodName(),
en.getT2()))) // methodName, BindableService
.map(en -> Tuple3.of(
en.getT1(),
en.getT2(),
findMethod(en.getT2(), en.getT1()))) // methodName, BindableService, Method
.filter(en -> en.getT3() != null)
.map(en -> Tuple2.of("/" + en.getT1(), new HandlerMethod(en.getT2(), en.getT3())))
.map(en ->
Tuple2.of("/" + en.getT1(), new HandlerMethod(en.getT2(), en.getT3()))) // path, HandlerMethod
.collect(Collectors.toMap(Tuple2::getT1, Tuple2::getT2));
}

@@ -77,6 +84,10 @@ public static boolean isGrpcHandleMethod(Object handler) {
* @return true if json string is a json object or json array
*/
public static boolean isJson(String json) {
if (!StringUtils.hasText(json)) {
return false;
}
json = json.trim();
return (json.startsWith("{") && json.endsWith("}")) || (json.startsWith("[") && json.endsWith("]"));
}

Original file line number Diff line number Diff line change
@@ -6,13 +6,13 @@
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.StringValue;
import com.google.protobuf.UInt32Value;
import com.google.protobuf.UInt64Value;
import com.google.protobuf.Value;
import com.google.protobuf.util.JsonFormat;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

/**
@@ -24,10 +24,10 @@ public class ProtoUtil {
private static final JsonFormat.Printer printer = JsonFormat.printer().omittingInsignificantWhitespace();

/**
* Check if protobuf message is simple value.
* Check if the protobuf message is a simple value.
*
* @param message protobuf message
* @return true if message is simple value
* @return true if the message is simple value
*/
public static boolean isSimpleValueMessage(Message message) {
if (isWrapperType(message.getClass())) {
@@ -53,12 +53,9 @@ public static boolean isSimpleValueMessage(Message message) {
* @param message protobuf message
* @return JSON string
*/
@SneakyThrows
public static String toJson(Message message) {
try {
return printer.print(message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Can't convert message to JSON", e);
}
return printer.print(message);
}

private static boolean isWrapperType(Class<?> clz) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.freemanan.starter.grpc.extensions.jsontranscoder.web;

import com.freemanan.starter.grpc.server.GrpcServer;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
* @author Freeman
*/
@RequiredArgsConstructor
public class HttpRuleMapping implements SmartInitializingSingleton, HandlerAdapter, HandlerMapping {

private final GrpcServer grpcServer;
private final RequestMappingHandlerMapping mapping;

@Override
public void afterSingletonsInstantiated() {
// mapping.registerMapping();
}

@Override
public boolean supports(Object handler) {
return false;
}

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return null;
}

@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return 0;
}

@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
package com.freemanan.starter.grpc.extensions.jsontranscoder.web;

import com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil;
import com.freemanan.starter.grpc.server.GrpcServer;
import com.google.api.AnnotationsProto;
import com.google.api.HttpRule;
import com.google.protobuf.Descriptors;
import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.ServerMethodDefinition;
import io.grpc.ServerServiceDefinition;
import io.grpc.protobuf.ProtoFileDescriptorSupplier;
import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
@@ -19,27 +34,116 @@
*
* @author Freeman
*/
public class WebMvcGrpcServiceHandlerMapping extends AbstractHandlerMapping {
public static final int ORDER = 10;
public class WebMvcGrpcServiceHandlerMapping extends AbstractHandlerMapping implements SmartInitializingSingleton {
public static final int ORDER = Ordered.LOWEST_PRECEDENCE;

/**
* path -> service
*
* <p> path: /xx.v1.ServiceName/MethodName
*/
private final Map<String, HandlerMethod> pathToMethod;
private final Map<String, HandlerMethod> pathToMethod = new HashMap<>();

public WebMvcGrpcServiceHandlerMapping(ObjectProvider<BindableService> grpcServiceProvider) {
this.pathToMethod = JsonTranscoderUtil.getPathToMethod(grpcServiceProvider);
private final Map<HttpRule, HandlerMethod> httpRuleToMethod = new HashMap<>();

private final GrpcServer grpcServer;

public WebMvcGrpcServiceHandlerMapping(ObjectProvider<BindableService> grpcServiceProvider, GrpcServer grpcServer) {
this.pathToMethod.putAll(JsonTranscoderUtil.getPathToMethod(grpcServiceProvider));
this.grpcServer = grpcServer;
}

@Override
public void afterSingletonsInstantiated() {
Object server = grpcServer.getServer();
if (server instanceof Server svr) {
List<ServerServiceDefinition> ssds = svr.getImmutableServices();

// 找到 ServerServiceDefinition 对应的 Descriptors.ServiceDescriptor
for (ServerServiceDefinition ssd : ssds) {
Descriptors.ServiceDescriptor serviceDescriptor = getServiceDescriptor(ssd);
if (serviceDescriptor != null) {
Map<String, Descriptors.MethodDescriptor> methodNameToMethodDescriptor =
serviceDescriptor.getMethods().stream()
.collect(Collectors.toMap(
Descriptors.MethodDescriptor::getName, Function.identity()));

for (ServerMethodDefinition<?, ?> method : ssd.getMethods()) {
String methodName = method.getMethodDescriptor().getBareMethodName();
Descriptors.MethodDescriptor methodDescriptor = methodNameToMethodDescriptor.get(methodName);
if (methodDescriptor != null
&& methodDescriptor.getOptions().hasExtension(AnnotationsProto.http)) {
HttpRule httpRule = methodDescriptor.getOptions().getExtension(AnnotationsProto.http);
HandlerMethod hm = pathToMethod.get(
"/" + method.getMethodDescriptor().getFullMethodName());
if (hm != null) {
httpRuleToMethod.put(httpRule, hm);
}
}
}
}
}
}
}

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) {
// Custom routes by google.api.http
for (Map.Entry<HttpRule, HandlerMethod> entry : httpRuleToMethod.entrySet()) {
HttpRule httpRule = entry.getKey();
switch (httpRule.getPatternCase()) {
case GET:
if (HttpMethod.GET.name().equalsIgnoreCase(request.getMethod())) {
String path = httpRule.getGet();
if (path.equals(request.getRequestURI())) {
return entry.getValue();
}
}
break;
case POST:
if (HttpMethod.POST.name().equalsIgnoreCase(request.getMethod())) {
String path = httpRule.getPost();
if (path.equals(request.getRequestURI())) {
return entry.getValue();
}
}
break;
case PUT:
if (HttpMethod.PUT.name().equalsIgnoreCase(request.getMethod())) {
String path = httpRule.getPut();
if (path.equals(request.getRequestURI())) {
return entry.getValue();
}
}
break;
case DELETE:
if (HttpMethod.DELETE.name().equalsIgnoreCase(request.getMethod())) {
String path = httpRule.getDelete();
if (path.equals(request.getRequestURI())) {
return entry.getValue();
}
}
break;
case PATCH:
if (HttpMethod.PATCH.name().equalsIgnoreCase(request.getMethod())) {
String path = httpRule.getPatch();
if (path.equals(request.getRequestURI())) {
return entry.getValue();
}
}
break;
default:
break;
}
}

// Custom routes by @RequestMapping
if (!HttpMethod.POST.name().equalsIgnoreCase(request.getMethod())) {
return null;
}
for (Map.Entry<String, HandlerMethod> entry : pathToMethod.entrySet()) {
if (entry.getKey().equalsIgnoreCase(request.getRequestURI())) {
// TODO(Freeman): ignore case ?
if (entry.getKey().equals(request.getRequestURI())) {
return entry.getValue();
}
}
@@ -55,4 +159,18 @@ protected HandlerMethod getHandlerInternal(HttpServletRequest request) {
public int getOrder() {
return ORDER;
}

@Nullable
private static Descriptors.ServiceDescriptor getServiceDescriptor(ServerServiceDefinition serverServiceDefinition) {
Object descriptor = serverServiceDefinition.getServiceDescriptor().getSchemaDescriptor();
if (descriptor instanceof ProtoFileDescriptorSupplier pfds) {
Descriptors.FileDescriptor fd = pfds.getFileDescriptor();
String serviceName = serverServiceDefinition.getServiceDescriptor().getName();
return fd.getServices().stream()
.filter(s -> s.getFullName().equals(serviceName))
.findFirst()
.orElseThrow();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -129,7 +129,7 @@ private Mono<HandlerResult> handleException(
ArrayList<Throwable> exceptions = new ArrayList<>();
try {
if (log.isDebugEnabled()) {
log.debug(exchange.getLogPrefix() + "Using @ExceptionHandler " + invocable);
log.debug("{} Using @ExceptionHandler {}", exchange.getLogPrefix(), invocable);
}
if (bindingContext != null) {
bindingContext.getModel().asMap().clear();
@@ -149,7 +149,7 @@ private Mono<HandlerResult> handleException(
arguments[arguments.length - 1] = handlerMethod;

return invocable.invoke(exchange, bindingContext, arguments);
} catch (Throwable invocationEx) {
} catch (Exception invocationEx) {
// Any other than the original exception (or a cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (!exceptions.contains(invocationEx) && log.isWarnEnabled()) {

0 comments on commit 01c580e

Please sign in to comment.