From 51f0f1d93ce78388b76f130051b23b258f616463 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Thu, 27 Jul 2023 12:14:11 +1000 Subject: [PATCH] Remove Old Dev UI: gRPC Signed-off-by: Phillip Kruger --- extensions/grpc/deployment/pom.xml | 6 - .../FieldDefinalizingVisitor.java | 2 +- .../grpc/deployment/GrpcServerProcessor.java | 1 - .../devmode/GrpcDevConsoleProcessor.java | 268 --------------- .../GrpcDevConsoleWebSocketListener.java | 316 ------------------ .../deployment/devui/GrpcDevUIProcessor.java | 171 ++++++++++ .../resources/dev-templates/embedded.html | 3 - .../main/resources/dev-templates/service.html | 293 ---------------- .../resources/dev-templates/services.html | 75 ----- .../devconsole/DevConsoleUnaryMethodTest.java | 70 ---- .../devmode/GrpcDevConsoleRecorder.java | 84 ----- .../devconsole/DevConsoleGrpcSmokeTest.java | 44 --- 12 files changed, 172 insertions(+), 1161 deletions(-) rename extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/{devmode => }/FieldDefinalizingVisitor.java (96%) delete mode 100644 extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleProcessor.java delete mode 100644 extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleWebSocketListener.java delete mode 100644 extensions/grpc/deployment/src/main/resources/dev-templates/embedded.html delete mode 100644 extensions/grpc/deployment/src/main/resources/dev-templates/service.html delete mode 100644 extensions/grpc/deployment/src/main/resources/dev-templates/services.html delete mode 100644 extensions/grpc/deployment/src/test/java/io/quarkus/grpc/devconsole/DevConsoleUnaryMethodTest.java delete mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devmode/GrpcDevConsoleRecorder.java delete mode 100644 integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleGrpcSmokeTest.java diff --git a/extensions/grpc/deployment/pom.xml b/extensions/grpc/deployment/pom.xml index f91020a379221..5ec16775a0ed5 100644 --- a/extensions/grpc/deployment/pom.xml +++ b/extensions/grpc/deployment/pom.xml @@ -57,13 +57,7 @@ quarkus-grpc-codegen - - io.quarkus - quarkus-vertx-http-dev-console-spi - - - io.quarkus quarkus-resteasy-reactive-deployment test diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/FieldDefinalizingVisitor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/FieldDefinalizingVisitor.java similarity index 96% rename from extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/FieldDefinalizingVisitor.java rename to extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/FieldDefinalizingVisitor.java index 9c2e1d415e33c..a32716ed4b444 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/FieldDefinalizingVisitor.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/FieldDefinalizingVisitor.java @@ -1,4 +1,4 @@ -package io.quarkus.grpc.deployment.devmode; +package io.quarkus.grpc.deployment; import static java.util.Arrays.asList; diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java index 2952b569d7b74..72c3e6e266a1f 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java @@ -75,7 +75,6 @@ import io.quarkus.grpc.auth.DefaultAuthExceptionHandlerProvider; import io.quarkus.grpc.auth.GrpcSecurityInterceptor; import io.quarkus.grpc.auth.GrpcSecurityRecorder; -import io.quarkus.grpc.deployment.devmode.FieldDefinalizingVisitor; import io.quarkus.grpc.protoc.plugin.MutinyGrpcGenerator; import io.quarkus.grpc.runtime.GrpcContainer; import io.quarkus.grpc.runtime.GrpcServerRecorder; diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleProcessor.java deleted file mode 100644 index e9071e73b62f6..0000000000000 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleProcessor.java +++ /dev/null @@ -1,268 +0,0 @@ -package io.quarkus.grpc.deployment.devmode; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import jakarta.inject.Singleton; - -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget.Kind; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.DotName; -import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.MessageOrBuilder; -import com.google.protobuf.util.JsonFormat; - -import grpc.health.v1.HealthGrpc; -import io.grpc.MethodDescriptor; -import io.grpc.MethodDescriptor.Marshaller; -import io.grpc.MethodDescriptor.PrototypeMarshaller; -import io.grpc.ServiceDescriptor; -import io.quarkus.arc.deployment.AdditionalBeanBuildItem; -import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; -import io.quarkus.arc.deployment.GeneratedBeanBuildItem; -import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; -import io.quarkus.arc.deployment.UnremovableBeanBuildItem; -import io.quarkus.arc.processor.AnnotationsTransformer; -import io.quarkus.arc.processor.DotNames; -import io.quarkus.arc.runtime.BeanLookupSupplier; -import io.quarkus.deployment.IsDevelopment; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.Consume; -import io.quarkus.deployment.annotations.ExecutionTime; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.CombinedIndexBuildItem; -import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem; -import io.quarkus.deployment.builditem.ServiceStartBuildItem; -import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.dev.console.DevConsoleManager; -import io.quarkus.dev.testing.GrpcWebSocketProxy; -import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; -import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.grpc.deployment.DelegatingGrpcBeanBuildItem; -import io.quarkus.grpc.deployment.GrpcDotNames; -import io.quarkus.grpc.protoc.plugin.MutinyGrpcGenerator; -import io.quarkus.grpc.runtime.config.GrpcConfiguration; -import io.quarkus.grpc.runtime.devmode.CollectStreams; -import io.quarkus.grpc.runtime.devmode.DelegatingGrpcBeansStorage; -import io.quarkus.grpc.runtime.devmode.GrpcDevConsoleRecorder; -import io.quarkus.grpc.runtime.devmode.GrpcServices; -import io.quarkus.grpc.runtime.devmode.StreamCollectorInterceptor; -import io.quarkus.vertx.http.runtime.HttpConfiguration; - -public class GrpcDevConsoleProcessor { - - @BuildStep(onlyIf = IsDevelopment.class) - public void devConsoleInfo(BuildProducer infos, - CurateOutcomeBuildItem curateOutcomeBuildItem) { - infos.produce( - new DevConsoleRuntimeTemplateInfoBuildItem("grpcServices", - new BeanLookupSupplier(GrpcServices.class), this.getClass(), curateOutcomeBuildItem)); - } - - @BuildStep(onlyIf = IsDevelopment.class) - public AdditionalBeanBuildItem beans() { - return AdditionalBeanBuildItem.builder() - .addBeanClass(GrpcServices.class) - .addBeanClasses(StreamCollectorInterceptor.class, CollectStreams.class) - .build(); - } - - @BuildStep(onlyIf = IsDevelopment.class) - void prepareDelegatingBeanStorage( - List delegatingBeans, - BuildProducer unremovableBeans, - BuildProducer generatedBeans) { - String className = "io.quarkus.grpc.internal.DelegatingGrpcBeansStorageImpl"; - try (ClassCreator classCreator = ClassCreator.builder() - .className(className) - .classOutput(new GeneratedBeanGizmoAdaptor(generatedBeans)) - .superClass(DelegatingGrpcBeansStorage.class) - .build()) { - classCreator.addAnnotation(Singleton.class.getName()); - MethodCreator constructor = classCreator - .getMethodCreator(io.quarkus.gizmo.MethodDescriptor.ofConstructor(className)); - constructor.invokeSpecialMethod(io.quarkus.gizmo.MethodDescriptor.ofConstructor(DelegatingGrpcBeansStorage.class), - constructor.getThis()); - - for (DelegatingGrpcBeanBuildItem delegatingBean : delegatingBeans) { - constructor.invokeVirtualMethod( - io.quarkus.gizmo.MethodDescriptor.ofMethod(DelegatingGrpcBeansStorage.class, "addDelegatingMapping", - void.class, - String.class, String.class), - constructor.getThis(), - constructor.load(delegatingBean.userDefinedBean.name().toString()), - constructor.load(delegatingBean.generatedBean.name().toString())); - } - constructor.returnValue(null); - } - - unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(className)); - } - - @BuildStep(onlyIf = IsDevelopment.class) - public void collectMessagePrototypes(CombinedIndexBuildItem index, - // Dummy producer to ensure the build step is executed - BuildProducer service) - throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, - IllegalArgumentException, InvocationTargetException, InvalidProtocolBufferException { - Map messagePrototypes = new HashMap<>(); - - Collection> grpcServices = getGrpcServices(index.getIndex()); - for (Class grpcServiceClass : grpcServices) { - - Method method = grpcServiceClass.getDeclaredMethod("getServiceDescriptor"); - ServiceDescriptor serviceDescriptor = (ServiceDescriptor) method.invoke(null); - - for (MethodDescriptor methodDescriptor : serviceDescriptor.getMethods()) { - Marshaller requestMarshaller = methodDescriptor.getRequestMarshaller(); - if (requestMarshaller instanceof PrototypeMarshaller) { - PrototypeMarshaller protoMarshaller = (PrototypeMarshaller) requestMarshaller; - Object prototype = protoMarshaller.getMessagePrototype(); - messagePrototypes.put(methodDescriptor.getFullMethodName() + "_REQUEST", - JsonFormat.printer().includingDefaultValueFields().print((MessageOrBuilder) prototype)); - } - } - } - DevConsoleManager.setGlobal("io.quarkus.grpc.messagePrototypes", messagePrototypes); - - GrpcWebSocketProxy.setWebSocketListener( - new GrpcDevConsoleWebSocketListener(grpcServices, Thread.currentThread().getContextClassLoader())); - } - - @Consume(RuntimeConfigSetupCompleteBuildItem.class) - @Record(ExecutionTime.RUNTIME_INIT) - @BuildStep(onlyIf = IsDevelopment.class) - public DevConsoleRouteBuildItem createWebSocketEndpoint(GrpcDevConsoleRecorder recorder, - HttpConfiguration httpConfiguration, GrpcConfiguration grpcConfiguration) { - recorder.setServerConfiguration(httpConfiguration, grpcConfiguration); - return DevConsoleRouteBuildItem.builder().path("grpc-test").method("GET").handler(recorder.handler()).build(); - } - - @BuildStep(onlyIf = IsDevelopment.class) - AnnotationsTransformerBuildItem transformUserDefinedServices(CombinedIndexBuildItem combinedIndexBuildItem) { - Set servicesToTransform = new HashSet<>(); - IndexView index = combinedIndexBuildItem.getIndex(); - for (AnnotationInstance annotation : index.getAnnotations(GrpcDotNames.GRPC_SERVICE)) { - if (annotation.target().kind() == Kind.CLASS) { - ClassInfo serviceClass = annotation.target().asClass(); - // Transform a service if it's using the grpc-java API directly: - // 1. Must not implement MutinyService - if (getRawTypesInHierarchy(serviceClass, index).contains(GrpcDotNames.MUTINY_SERVICE)) { - continue; - } - // 2. The enclosing class of an extended class that implements BindableService must not implement MutinyGrpc - ClassInfo abstractBindableService = findAbstractBindableService(serviceClass, index); - if (abstractBindableService != null) { - ClassInfo enclosingClass = serviceClass.enclosingClass() != null - ? index.getClassByName(serviceClass.enclosingClass()) - : null; - if (enclosingClass != null - && getRawTypesInHierarchy(enclosingClass, index).contains(GrpcDotNames.MUTINY_GRPC)) { - continue; - } - } - servicesToTransform.add(annotation.target().asClass().name()); - } - } - if (servicesToTransform.isEmpty()) { - return null; - } - return new AnnotationsTransformerBuildItem( - new AnnotationsTransformer() { - @Override - public boolean appliesTo(Kind kind) { - return kind == Kind.CLASS; - } - - @Override - public void transform(TransformationContext context) { - ClassInfo clazz = context.getTarget().asClass(); - if (servicesToTransform.contains(clazz.name())) { - context.transform() - .add(CollectStreams.class) - .done(); - } - } - }); - } - - Collection> getGrpcServices(IndexView index) throws ClassNotFoundException { - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - Set serviceClassNames = new HashSet<>(); - for (ClassInfo mutinyGrpc : index.getAllKnownImplementors(GrpcDotNames.MUTINY_GRPC)) { - // Find the original impl class - // e.g. examples.MutinyGreeterGrpc -> examples.GreeterGrpc - DotName originalImplName = DotName - .createSimple(mutinyGrpc.name().toString().replace(MutinyGrpcGenerator.CLASS_PREFIX, "")); - ClassInfo originalImpl = index.getClassByName(originalImplName); - if (originalImpl == null) { - throw new IllegalStateException( - "The original implementation class of a gRPC service not found:" + originalImplName); - } - // Must declare static io.grpc.ServiceDescriptor getServiceDescriptor() - MethodInfo getServiceDescriptor = originalImpl.method("getServiceDescriptor"); - if (getServiceDescriptor != null && Modifier.isStatic(getServiceDescriptor.flags()) - && getServiceDescriptor.returnType().name().toString().equals(ServiceDescriptor.class.getName())) { - serviceClassNames.add(getServiceDescriptor.declaringClass().name().toString()); - } - } - List> serviceClasses = new ArrayList<>(); - for (String className : serviceClassNames) { - serviceClasses.add(tccl.loadClass(className)); - } - serviceClasses.add(HealthGrpc.class); - return serviceClasses; - } - - private Set getRawTypesInHierarchy(ClassInfo clazz, IndexView index) { - Set rawTypes = new HashSet<>(); - addRawTypes(clazz, index, rawTypes); - return rawTypes; - } - - private void addRawTypes(ClassInfo clazz, IndexView index, Set rawTypes) { - rawTypes.add(clazz.name()); - for (DotName interfaceName : clazz.interfaceNames()) { - rawTypes.add(interfaceName); - ClassInfo interfaceClazz = index.getClassByName(interfaceName); - if (interfaceClazz != null) { - addRawTypes(interfaceClazz, index, rawTypes); - } - } - if (clazz.superName() != null && !clazz.superName().equals(DotNames.OBJECT)) { - ClassInfo superClazz = index.getClassByName(clazz.superName()); - if (superClazz != null) { - addRawTypes(superClazz, index, rawTypes); - } - } - } - - private ClassInfo findAbstractBindableService(ClassInfo clazz, IndexView index) { - if (clazz.interfaceNames().contains(GrpcDotNames.BINDABLE_SERVICE)) { - return clazz; - } - if (clazz.superName() != null && !clazz.superName().equals(DotNames.OBJECT)) { - ClassInfo superClazz = index.getClassByName(clazz.superName()); - if (superClazz != null) { - return findAbstractBindableService(superClazz, index); - } - } - return null; - } - -} diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleWebSocketListener.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleWebSocketListener.java deleted file mode 100644 index e1e8368503ea8..0000000000000 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devmode/GrpcDevConsoleWebSocketListener.java +++ /dev/null @@ -1,316 +0,0 @@ -package io.quarkus.grpc.deployment.devmode; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; - -import org.jboss.logging.Logger; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.protobuf.MessageOrBuilder; -import com.google.protobuf.util.JsonFormat; - -import io.grpc.Channel; -import io.grpc.MethodDescriptor; -import io.grpc.ServiceDescriptor; -import io.grpc.netty.NettyChannelBuilder; -import io.grpc.stub.StreamObserver; -import io.quarkus.dev.console.DevConsoleManager; -import io.quarkus.dev.testing.GrpcWebSocketProxy; -import io.vertx.core.json.JsonObject; - -public class GrpcDevConsoleWebSocketListener implements GrpcWebSocketProxy.WebSocketListener { - - private static final Logger log = Logger.getLogger(GrpcDevConsoleWebSocketListener.class); - - private Map grpcClientStubs; - private Map serviceDescriptors; - - private final ClassLoader deploymentClassLoader; - private final Collection> grpcServices; - - private final Map webSocketConnections = new ConcurrentHashMap<>(); - - public GrpcDevConsoleWebSocketListener(Collection> grpcServices, ClassLoader deploymentClassLoader) { - this.grpcServices = grpcServices; - this.deploymentClassLoader = deploymentClassLoader; - } - - private void handle(String input, WebSocketData websocketData) { - ClassLoader originalCl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(deploymentClassLoader); - try { - JsonObject grpcRequest = new JsonObject(input); - - // each message sent through this websocket has to have an ID - // this is an ID of the gRPC method invocation - // if client-side streaming is used, subsequent calls that should use the same - // stream should have the same ID - Integer id = grpcRequest.getInteger("id"); - String serviceName = grpcRequest.getString("serviceName"); - String methodName = grpcRequest.getString("methodName"); - - if ("DISCONNECT".equals(grpcRequest.getString("command"))) { - GrpcCallData grpcCall = websocketData.callsInProgress.get(id); - if (grpcCall != null && grpcCall.incomingStream != null) { - grpcCall.incomingStream.onCompleted(); - } - return; - } - - GrpcCallData grpcCall; - if (websocketData.callsInProgress.containsKey(id)) { - grpcCall = websocketData.callsInProgress.get(id); - } else { - Optional maybeOldCall = websocketData.callsInProgress.values() - .stream().filter(call -> call.methodName.equals(methodName) && call.serviceName.equals(serviceName)) - .findAny(); - maybeOldCall.ifPresent(call -> { - if (call.incomingStream != null) { - call.incomingStream.onCompleted(); - } - websocketData.callsInProgress.remove(call.requestId); - }); - - grpcCall = new GrpcCallData(); - grpcCall.serviceName = serviceName; - grpcCall.methodName = methodName; - grpcCall.requestId = id; - websocketData.callsInProgress.put(grpcCall.requestId, grpcCall); - } - - String testJsonData = grpcRequest.getString("content"); - Object grpcStub = grpcClientStubs.get(serviceName); - - if (grpcStub == null) { - websocketData.responseConsumer.accept(jsonResponse(id, "NO_STUB").encode()); - } else { - ServiceDescriptor serviceDescriptor = serviceDescriptors.get(serviceName); - MethodDescriptor methodDescriptor = null; - for (MethodDescriptor method : serviceDescriptor.getMethods()) { - if (method.getBareMethodName() != null && method.getBareMethodName().equals(methodName)) { - methodDescriptor = method; - } - } - if (methodDescriptor == null) { - websocketData.responseConsumer.accept(jsonResponse(id, "NO_DESCRIPTOR").encode()); - } else { - Method stubMethod = null; - String realMethodName = decapitalize(methodDescriptor.getBareMethodName()); - - for (Method method : grpcStub.getClass().getDeclaredMethods()) { - if (method.getName().equals(realMethodName)) { - stubMethod = method; - } - } - - if (stubMethod == null) { - websocketData.responseConsumer.accept(jsonResponse(id, "NO_METHOD").encode()); - log.error(realMethodName + " method not declared on the " + grpcStub.getClass()); - } else { - - // Identify the request class - MethodDescriptor.Marshaller requestMarshaller = methodDescriptor.getRequestMarshaller(); - if (requestMarshaller instanceof MethodDescriptor.PrototypeMarshaller) { - MethodDescriptor.PrototypeMarshaller protoMarshaller = (MethodDescriptor.PrototypeMarshaller) requestMarshaller; - Class requestType = protoMarshaller.getMessagePrototype().getClass(); - - try { - // Create a new builder for the request message, e.g. HelloRequest.newBuilder() - Method newBuilderMethod = requestType.getDeclaredMethod("newBuilder"); - Message.Builder builder = (Message.Builder) newBuilderMethod.invoke(null); - - // Use the test data to build the request object - JsonFormat.parser().merge(testJsonData, builder); - - Message message = builder.build(); - if (grpcCall.incomingStream != null) { - // we are already connected with this gRPC endpoint, just send the message - grpcCall.incomingStream.onNext(message); - } else { - // Invoke the stub method and format the response as JSON - - StreamObserver responseObserver = new StreamObserver() { - @Override - public void onNext(Object value) { - String body = null; - try { - body = JsonFormat.printer().print((MessageOrBuilder) value); - } catch (InvalidProtocolBufferException e) { - websocketData.responseConsumer - .accept(jsonResponse(id, "ERROR").put("body", e.getMessage()).encode()); - log.error("Failed to transform response to JSON", e); - } - JsonObject reply = jsonResponse(id, "PAYLOAD"); - reply.put("body", body); - websocketData.responseConsumer.accept(reply.encode()); - } - - @Override - public void onError(Throwable t) { - websocketData.responseConsumer - .accept(jsonResponse(id, "ERROR").put("body", t.getMessage()) - .encode()); - grpcCall.incomingStream = null; - log.error("Failure returned by gRPC service", t); - } - - @Override - public void onCompleted() { - websocketData.responseConsumer.accept(jsonResponse(id, "COMPLETED").encode()); - grpcCall.incomingStream = null; - } - }; - if (stubMethod.getParameterCount() == 1 - && stubMethod.getReturnType() == StreamObserver.class) { - // returned StreamObserver consumes incoming messages - //noinspection unchecked - grpcCall.incomingStream = (StreamObserver) stubMethod.invoke(grpcStub, - responseObserver); - grpcCall.incomingStream.onNext(message); - } else { - // incoming message should be passed as the first parameter of the invocation - stubMethod.invoke(grpcStub, message, responseObserver); - } - } - } catch (Exception e) { - websocketData.responseConsumer - .accept(jsonResponse(id, "ERROR").put("body", - e.getMessage() + "\nCheck application log for more details") - .encode()); - grpcCall.incomingStream = null; - log.error("Failure returned by gRPC service", e); - } - } - } - } - } - } finally { - Thread.currentThread().setContextClassLoader(originalCl); - } - } - - static String decapitalize(String name) { - if (name == null || name.length() == 0) { - return name; - } - if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && - Character.isUpperCase(name.charAt(0))) { - return name; - } - char[] chars = name.toCharArray(); - chars[0] = Character.toLowerCase(chars[0]); - return new String(chars); - } - - private JsonObject jsonResponse(Integer id, String status) { - return new JsonObject() - .put("id", id) - .put("status", status); - } - - public void init() { - Map serverConfig = DevConsoleManager.getGlobal("io.quarkus.grpc.serverConfig"); - - if (serviceDescriptors != null) { - return; - } - if (serverConfig == null) { - log.warnf("Unable to initialize client stubs for gRPC Dev UI - server config not found"); - return; - } - if (Boolean.TRUE.equals(serverConfig.get("ssl"))) { - log.warnf("Unable to initialize client stubs for gRPC Dev UI - SSL is not supported"); - return; - } - - serviceDescriptors = new HashMap<>(); - grpcClientStubs = new HashMap<>(); - try { - for (Class grpcServiceClass : grpcServices) { - Method method = grpcServiceClass.getDeclaredMethod("getServiceDescriptor"); - ServiceDescriptor serviceDescriptor = (ServiceDescriptor) method.invoke(null); - serviceDescriptors.put(serviceDescriptor.getName(), serviceDescriptor); - - // TODO more config options - Channel channel = NettyChannelBuilder - .forAddress(serverConfig.get("host").toString(), (Integer) serverConfig.get("port")) - .usePlaintext() - .build(); - Method stubFactoryMethod; - - try { - stubFactoryMethod = grpcServiceClass.getDeclaredMethod("newStub", Channel.class); - } catch (NoSuchMethodException e) { - log.warnf("Ignoring gRPC service - newStub() method not declared on %s", grpcServiceClass); - continue; - } - - Object stub = stubFactoryMethod.invoke(null, channel); - grpcClientStubs.put(serviceDescriptor.getName(), stub); - } - } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException("Unable to initialize client stubs for gRPC Dev UI"); - } - } - - @Override - public void onOpen(int id, Consumer responseConsumer) { - init(); - webSocketConnections.put(id, new WebSocketData(responseConsumer)); - } - - @Override - public void newMessage(int id, String content) { - WebSocketData webSocketData = webSocketConnections.get(id); - if (webSocketData != null) { - handle(content, webSocketData); - } else { - log.warn("gRPC Dev Console WebSocket message for an unregistered WebSocket id"); - } - } - - @Override - public void onClose(int id) { - closeAllClients(id); - - webSocketConnections.remove(id); - } - - private void closeAllClients(int id) { - WebSocketData webSocketData = webSocketConnections.get(id); - - if (webSocketData != null) { - for (GrpcCallData callData : webSocketData.callsInProgress.values()) { - try { - callData.incomingStream.onCompleted(); - } catch (Exception ignored) { - } - } - } - } - - private static class GrpcCallData { - Integer requestId; - String serviceName; - String methodName; - StreamObserver incomingStream; - } - - // contains information about all the connection done by a single - // browser window, i.e. a single websocket - private static class WebSocketData { - final Consumer responseConsumer; - Map callsInProgress = new HashMap<>(); - - private WebSocketData(Consumer responseConsumer) { - this.responseConsumer = responseConsumer; - } - } -} diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devui/GrpcDevUIProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devui/GrpcDevUIProcessor.java index 635355c8fc5fa..431a7614fb66d 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devui/GrpcDevUIProcessor.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/devui/GrpcDevUIProcessor.java @@ -12,6 +12,10 @@ import java.util.Set; import java.util.concurrent.Flow; +import jakarta.inject.Singleton; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; @@ -32,21 +36,152 @@ import io.grpc.ServiceDescriptor; import io.grpc.netty.NettyChannelBuilder; import io.grpc.stub.StreamObserver; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.DotNames; import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.dev.console.DevConsoleManager; import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; import io.quarkus.devui.spi.page.CardPageBuildItem; import io.quarkus.devui.spi.page.Page; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.grpc.deployment.DelegatingGrpcBeanBuildItem; import io.quarkus.grpc.deployment.GrpcDotNames; import io.quarkus.grpc.protoc.plugin.MutinyGrpcGenerator; +import io.quarkus.grpc.runtime.devmode.CollectStreams; +import io.quarkus.grpc.runtime.devmode.DelegatingGrpcBeansStorage; +import io.quarkus.grpc.runtime.devmode.GrpcServices; +import io.quarkus.grpc.runtime.devmode.StreamCollectorInterceptor; import io.quarkus.grpc.runtime.devui.GrpcJsonRPCService; import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; public class GrpcDevUIProcessor { private static final Logger log = Logger.getLogger(GrpcDevUIProcessor.class); + @BuildStep(onlyIf = IsDevelopment.class) + public AdditionalBeanBuildItem beans() { + return AdditionalBeanBuildItem.builder() + .addBeanClass(GrpcServices.class) + .addBeanClasses(StreamCollectorInterceptor.class, CollectStreams.class) + .build(); + } + + @BuildStep(onlyIf = IsDevelopment.class) + void prepareDelegatingBeanStorage( + List delegatingBeans, + BuildProducer unremovableBeans, + BuildProducer generatedBeans) { + String className = "io.quarkus.grpc.internal.DelegatingGrpcBeansStorageImpl"; + try (ClassCreator classCreator = ClassCreator.builder() + .className(className) + .classOutput(new GeneratedBeanGizmoAdaptor(generatedBeans)) + .superClass(DelegatingGrpcBeansStorage.class) + .build()) { + classCreator.addAnnotation(Singleton.class.getName()); + MethodCreator constructor = classCreator + .getMethodCreator(io.quarkus.gizmo.MethodDescriptor.ofConstructor(className)); + constructor.invokeSpecialMethod(io.quarkus.gizmo.MethodDescriptor.ofConstructor(DelegatingGrpcBeansStorage.class), + constructor.getThis()); + + for (DelegatingGrpcBeanBuildItem delegatingBean : delegatingBeans) { + constructor.invokeVirtualMethod( + io.quarkus.gizmo.MethodDescriptor.ofMethod(DelegatingGrpcBeansStorage.class, "addDelegatingMapping", + void.class, + String.class, String.class), + constructor.getThis(), + constructor.load(delegatingBean.userDefinedBean.name().toString()), + constructor.load(delegatingBean.generatedBean.name().toString())); + } + constructor.returnValue(null); + } + + unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(className)); + } + + @BuildStep(onlyIf = IsDevelopment.class) + public void collectMessagePrototypes(CombinedIndexBuildItem index, + // Dummy producer to ensure the build step is executed + BuildProducer service) + throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException, InvalidProtocolBufferException { + Map messagePrototypes = new HashMap<>(); + + Collection> grpcServices = getGrpcServices(index.getIndex()); + for (Class grpcServiceClass : grpcServices) { + + Method method = grpcServiceClass.getDeclaredMethod("getServiceDescriptor"); + ServiceDescriptor serviceDescriptor = (ServiceDescriptor) method.invoke(null); + + for (MethodDescriptor methodDescriptor : serviceDescriptor.getMethods()) { + MethodDescriptor.Marshaller requestMarshaller = methodDescriptor.getRequestMarshaller(); + if (requestMarshaller instanceof MethodDescriptor.PrototypeMarshaller) { + MethodDescriptor.PrototypeMarshaller protoMarshaller = (MethodDescriptor.PrototypeMarshaller) requestMarshaller; + Object prototype = protoMarshaller.getMessagePrototype(); + messagePrototypes.put(methodDescriptor.getFullMethodName() + "_REQUEST", + JsonFormat.printer().includingDefaultValueFields().print((MessageOrBuilder) prototype)); + } + } + } + DevConsoleManager.setGlobal("io.quarkus.grpc.messagePrototypes", messagePrototypes); + } + + @BuildStep(onlyIf = IsDevelopment.class) + AnnotationsTransformerBuildItem transformUserDefinedServices(CombinedIndexBuildItem combinedIndexBuildItem) { + Set servicesToTransform = new HashSet<>(); + IndexView index = combinedIndexBuildItem.getIndex(); + for (AnnotationInstance annotation : index.getAnnotations(GrpcDotNames.GRPC_SERVICE)) { + if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { + ClassInfo serviceClass = annotation.target().asClass(); + // Transform a service if it's using the grpc-java API directly: + // 1. Must not implement MutinyService + if (getRawTypesInHierarchy(serviceClass, index).contains(GrpcDotNames.MUTINY_SERVICE)) { + continue; + } + // 2. The enclosing class of an extended class that implements BindableService must not implement MutinyGrpc + ClassInfo abstractBindableService = findAbstractBindableService(serviceClass, index); + if (abstractBindableService != null) { + ClassInfo enclosingClass = serviceClass.enclosingClass() != null + ? index.getClassByName(serviceClass.enclosingClass()) + : null; + if (enclosingClass != null + && getRawTypesInHierarchy(enclosingClass, index).contains(GrpcDotNames.MUTINY_GRPC)) { + continue; + } + } + servicesToTransform.add(annotation.target().asClass().name()); + } + } + if (servicesToTransform.isEmpty()) { + return null; + } + return new AnnotationsTransformerBuildItem( + new AnnotationsTransformer() { + @Override + public boolean appliesTo(AnnotationTarget.Kind kind) { + return kind == AnnotationTarget.Kind.CLASS; + } + + @Override + public void transform(AnnotationsTransformer.TransformationContext context) { + ClassInfo clazz = context.getTarget().asClass(); + if (servicesToTransform.contains(clazz.name())) { + context.transform() + .add(CollectStreams.class) + .done(); + } + } + }); + } + @BuildStep(onlyIf = IsDevelopment.class) public CardPageBuildItem pages(CombinedIndexBuildItem index) throws ClassNotFoundException, NoSuchMethodException, @@ -77,6 +212,42 @@ public CardPageBuildItem pages(CombinedIndexBuildItem index) throws ClassNotFoun return cardPageBuildItem; } + private Set getRawTypesInHierarchy(ClassInfo clazz, IndexView index) { + Set rawTypes = new HashSet<>(); + addRawTypes(clazz, index, rawTypes); + return rawTypes; + } + + private void addRawTypes(ClassInfo clazz, IndexView index, Set rawTypes) { + rawTypes.add(clazz.name()); + for (DotName interfaceName : clazz.interfaceNames()) { + rawTypes.add(interfaceName); + ClassInfo interfaceClazz = index.getClassByName(interfaceName); + if (interfaceClazz != null) { + addRawTypes(interfaceClazz, index, rawTypes); + } + } + if (clazz.superName() != null && !clazz.superName().equals(DotNames.OBJECT)) { + ClassInfo superClazz = index.getClassByName(clazz.superName()); + if (superClazz != null) { + addRawTypes(superClazz, index, rawTypes); + } + } + } + + private ClassInfo findAbstractBindableService(ClassInfo clazz, IndexView index) { + if (clazz.interfaceNames().contains(GrpcDotNames.BINDABLE_SERVICE)) { + return clazz; + } + if (clazz.superName() != null && !clazz.superName().equals(DotNames.OBJECT)) { + ClassInfo superClazz = index.getClassByName(clazz.superName()); + if (superClazz != null) { + return findAbstractBindableService(superClazz, index); + } + } + return null; + } + /** * This gets called during runtime from the Dev UI JsonRPC Service to test a grpc call * We go to Flow to stay in the JDK, else we have classpath issues. diff --git a/extensions/grpc/deployment/src/main/resources/dev-templates/embedded.html b/extensions/grpc/deployment/src/main/resources/dev-templates/embedded.html deleted file mode 100644 index f63deefe0232a..0000000000000 --- a/extensions/grpc/deployment/src/main/resources/dev-templates/embedded.html +++ /dev/null @@ -1,3 +0,0 @@ - - - Services diff --git a/extensions/grpc/deployment/src/main/resources/dev-templates/service.html b/extensions/grpc/deployment/src/main/resources/dev-templates/service.html deleted file mode 100644 index ced7ff4867b36..0000000000000 --- a/extensions/grpc/deployment/src/main/resources/dev-templates/service.html +++ /dev/null @@ -1,293 +0,0 @@ -{#include main} - {#style} -.connection-status { - font-style: italic; - font-size: 0.8em; - text-align: right; -} - -.status { - position: fixed; - width: 80%; - z-index: 100; -} -.status span { - background-color: #f0f0c0; - padding: 2px 20px; - border-radius: 3px; -} -.fa.connected-icon { - color: #207020; - padding: 0 1em 0 1em; -} - -.form-control.grpc-output { - color: #f0f0e0; - background-color: #303030; -} - -.fa.connected-icon { - color: #107010; -} - -span.connected-status { - font-size: 0.5em; - margin-left: 1em; -} -.CodeMirror { - height: auto; - border: 1px solid #ddd; -} - {/style} - {#styleref} - - - {/styleref} - {#script} - var grpcWS; - var requestId = 0; - var clearMessageTimeout; - - var connections = new Map(); - - function hideConnected(connection) { - const idBase = connection.queryIdentifier.replace('#', '/'); - const connectedElement = document.getElementById(`$\{idBase}_connected`); - if (connectedElement) { - connectedElement.style.display = 'none'; - document.getElementById(`$\{idBase}_disconnect`).style.display = 'none'; - } - } - - function showConnected(connection) { - const idBase = connection.queryIdentifier.replace('#', '/'); - const connectedElement = document.getElementById(`$\{idBase}_connected`); - if (connectedElement) { - connectedElement.style.display = 'inline'; - document.getElementById(`$\{idBase}_disconnect`).style.display = 'inline'; - } - } - - function connect() { - var wsUri; - if (window.location.protocol === "https:") { - wsUri = "wss:"; - } else { - wsUri = "ws:"; - } - - wsUri += "//" + window.location.host + "{devRootAppend}/io.quarkus.quarkus-grpc/grpc-test"; - grpcWS = new WebSocket(wsUri); - grpcWS.onopen = function (event) { - console.log("websocket connected"); - info("Web Socket bridge to gRPC connected"); - } - - grpcWS.onerror = function (error) { - console.log("error on gRPC websocket", error); - } - - grpcWS.onclose = function () { - info("Web Socket bridge to gRPC disconnected, reconnecting"); - connections.forEach(connection => hideConnected(connection)); - connections.clear(); - setTimeout(connect, 2000); - } - - grpcWS.onmessage = function (event) { - const data = JSON.parse(event.data); - if (data.status == 'RESET') { - connections.clear(); - } else if (data.status == 'PAYLOAD') { - const connection = connections.get(data.id); - const responseElement = connection.responseElement; - if (connection.responseText != '') { - connection.responseText = '\n---------\n' + connection.responseText; - } - - connection.responseText = data.body + connection.responseText; - responseElement.value = connection.responseText; - responseElement.style.height = "1px"; - responseElement.style.height = Math.min(responseElement.scrollHeight + 5, 500) + "px"; - } else if (data.status == 'COMPLETED') { - const connection = connections.get(data.id); - hideConnected(connection); - connections.delete(data.id); - } else if (data.status == 'ERROR') { - console.log("failure!", data); - const connection = connections.get(data.id); - const responseElement = connection.responseElement; - if (connection.responseText != '') { - connection.responseText = '\n---------\n' + connection.responseText; - } - connection.responseText = data.body + connection.responseText; - connection.responseText = "FAILURE:\n" + connection.responseText; - responseElement.value = connection.responseText; - } - } - } - - function info(message) { - clearTimeout(clearMessageTimeout); - const statusText = document.getElementById('status-text') - statusText.innerText = message; - const status = document.getElementById('status-info') - status.style.display = 'block'; - clearMessageTimeout = setTimeout(() => status.style.display = 'none', 2000); - } - - $(document).ready(function(){ - connect(); - if (!ideKnown()) { - return; - } - $(".class-candidate").each(function() { - var className = $(this).text(); - if (appClassLocation(className)) { - $(this).addClass("app-class"); - } - }); - - $(".app-class").on("click", function() { - openInIDE($(this).text()); - }); - - // Use codemirror editors - document.querySelectorAll('.grpc-input') - .forEach(function(textArea) { - const editor = CodeMirror.fromTextArea(textArea, { - mode: { name: "javascript", json: true }, - styleActiveLine: true, - lineNumbers: true, - lineWrapping: true, - extraKeys: {"Ctrl-Space": "autocomplete"} - }); - editor.on("blur", function(codeMirror) { codeMirror.save(); }); - editor.refresh(); - }); - - }); - - function disconnect(serviceName, methodName, methodType) { - const queryIdentifier = `$\{serviceName}#$\{methodName}`; - const connection = Array.from(connections.values()).find(conn => conn.queryIdentifier == queryIdentifier); - connections.delete(connection.id); - const request = { - id: connection.id, - command: 'DISCONNECT' - } - hideConnected(connection); - grpcWS.send(JSON.stringify(request)); - } - - function sendTestRequest(serviceName, methodName, methodType) { - const queryIdentifier = `$\{serviceName}#$\{methodName}`; - var connection = Array.from(connections.values()).find(conn => conn.queryIdentifier == queryIdentifier); - const testRequest = document.getElementById(serviceName + "/" + methodName + "_request"); - if (!connection || methodType == 'UNARY') { - requestId ++; - connection = { - id: requestId, - queryIdentifier: queryIdentifier, - responseElement: document.getElementById(serviceName + "/" + methodName + "_response"), - responseText: '', - unary: methodType == 'UNARY' - }; - connections.set(requestId, connection); - } - - const request = { - serviceName: serviceName, - methodName: methodName, - id: requestId, - content: testRequest.value - }; - - grpcWS.send(JSON.stringify(request)); - - if (methodType != 'UNARY') { - showConnected(connection); - } - } - - {/script} - - {#scriptref} - - - - - {/scriptref} - - {#breadcrumbs} Services{/breadcrumbs} - {#title}{info:grpcServices.get(currentRequest.params.get('name')).name}{/title} - {#body} -
- -
- - {#let service=info:grpcServices.get(currentRequest.params.get('name'))} -

- {#when service.status} - {#is SERVING} - - {#is NOT_SERVING} - - {#is in UNKNOWN UNRECOGNIZED} - - {/when} - {service.name} -

-
- Implemented by: {service.serviceClass} -
- - {#for method in service.methodsWithPrototypes} -
-
-

{method.type} {method.bareMethodName} - {#when method.type} - {#is in BIDI_STREAMING CLIENT_STREAMING} - - {#is SERVER_STREAMING} - - {/when} -

-
- {#if method.isTestable} -
-
-
- -
-
-
- -
-
-
-
- - - -
-
-
- {/if} - {/for} - - {/let} - - {/body} -{/include} \ No newline at end of file diff --git a/extensions/grpc/deployment/src/main/resources/dev-templates/services.html b/extensions/grpc/deployment/src/main/resources/dev-templates/services.html deleted file mode 100644 index 718e987e4a7cd..0000000000000 --- a/extensions/grpc/deployment/src/main/resources/dev-templates/services.html +++ /dev/null @@ -1,75 +0,0 @@ -{#include main} - {#style} - span.app-class { - cursor:pointer; - color:blue; - text-decoration:underline; - } - span.larger-badge { - font-size: 1em; - } - {/style} - {#script} - $(document).ready(function(){ - if (!ideKnown()) { - return; - } - $(".class-candidate").each(function() { - var className = $(this).text(); - if (appClassLocation(className)) { - $(this).addClass("app-class"); - } - }); - - $(".app-class").on("click", function() { - openInIDE($(this).text()); - }); - }); - {/script} - {#title}Services{/title} - {#body} - - - - - - - - - - - - {#for service in info:grpcServices.infos} - - - - - - - {/for} - -
#Name and StatusImplementation ClassMethods
{service_count}. - {#when service.status} - {#is SERVING} - - {#is NOT_SERVING} - - {#is in UNKNOWN UNRECOGNIZED} - - {/when} - {service.name} - - {service.serviceClass} - -
    - {#each service.methodsWithPrototypes} -
  • {it.type} {it.bareMethodName}
  • - {/each} -
      -
- {#if service.hasTestableMethod} - Test - {/if} -
- {/body} -{/include} \ No newline at end of file diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/devconsole/DevConsoleUnaryMethodTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/devconsole/DevConsoleUnaryMethodTest.java deleted file mode 100644 index 8693518dbb3ab..0000000000000 --- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/devconsole/DevConsoleUnaryMethodTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.quarkus.grpc.devconsole; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.jboss.logging.Logger; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.grpc.examples.helloworld.MutinyGreeterGrpc; -import io.quarkus.grpc.server.services.MutinyHelloService; -import io.quarkus.test.QuarkusDevModeTest; -import io.vertx.core.Vertx; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.WebSocket; - -public class DevConsoleUnaryMethodTest { - - private static final Logger log = Logger.getLogger(DevConsoleUnaryMethodTest.class); - - @RegisterExtension - static final QuarkusDevModeTest config = new QuarkusDevModeTest() - .withApplicationRoot((jar) -> jar.addPackage(MutinyGreeterGrpc.class.getPackage()) - .addClass(MutinyHelloService.class)); - - @Test - public void websocketTest() throws Exception { - Vertx vertx = Vertx.vertx(); - - try { - List incomingMessages = new CopyOnWriteArrayList<>(); - HttpClient client = vertx.createHttpClient(); - - client.webSocket(8080, "localhost", "/q/dev-v1/io.quarkus.quarkus-grpc/grpc-test", result -> { - if (result.failed()) { - log.error("failure making a web socket connection", result.cause()); - return; - } - WebSocket webSocket = result.result(); - webSocket.handler(buffer -> incomingMessages.add(buffer.toString())); - webSocket - .writeTextMessage("{\"id\": 123, \"serviceName\": \"helloworld.Greeter\",\"methodName\": \"SayHello\"" + - ", \"content\": \"{\\\"name\\\": \\\"Martin\\\"}\"}"); - }); - - await().atMost(5, TimeUnit.SECONDS) - .until(() -> incomingMessages.size() > 1); - - assertThat(incomingMessages).hasSize(2); - - Optional payloadMessage = incomingMessages.stream().filter(msg -> msg.contains("PAYLOAD")).findFirst(); - assertThat(payloadMessage).isNotEmpty(); - assertThat(payloadMessage.get()).contains("Hello Martin"); - - } finally { - CountDownLatch latch = new CountDownLatch(1); - vertx.close(whatever -> latch.countDown()); - if (!latch.await(30, TimeUnit.SECONDS)) { - log.warn("Waiting for the test vertx instance to stop failed"); - } - } - } - -} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devmode/GrpcDevConsoleRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devmode/GrpcDevConsoleRecorder.java deleted file mode 100644 index 8404628a0cff3..0000000000000 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/devmode/GrpcDevConsoleRecorder.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.quarkus.grpc.runtime.devmode; - -import java.util.HashMap; -import java.util.Map; - -import org.jboss.logging.Logger; - -import io.quarkus.dev.console.DevConsoleManager; -import io.quarkus.dev.testing.GrpcWebSocketProxy; -import io.quarkus.grpc.runtime.config.GrpcConfiguration; -import io.quarkus.grpc.runtime.config.GrpcServerConfiguration; -import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.vertx.http.runtime.HttpConfiguration; -import io.quarkus.vertx.http.runtime.HttpConfiguration.InsecureRequests; -import io.vertx.core.AsyncResult; -import io.vertx.core.Handler; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.ServerWebSocket; -import io.vertx.ext.web.RoutingContext; - -@Recorder -public class GrpcDevConsoleRecorder { - - private static final Logger LOG = Logger.getLogger(GrpcDevConsoleRecorder.class); - - public void setServerConfiguration(HttpConfiguration httpConfiguration, GrpcConfiguration grpcConfiguration) { - GrpcServerConfiguration serverConfig = grpcConfiguration.server; - Map map = new HashMap<>(); - if (serverConfig.useSeparateServer) { - map.put("host", serverConfig.host); - map.put("port", serverConfig.port); - map.put("ssl", serverConfig.ssl.certificate.isPresent() || serverConfig.ssl.keyStore.isPresent()); - } else { - map.put("host", httpConfiguration.host); - map.put("port", httpConfiguration.port); - map.put("ssl", httpConfiguration.insecureRequests != InsecureRequests.ENABLED); - } - DevConsoleManager.setGlobal("io.quarkus.grpc.serverConfig", map); - } - - public Handler handler() { - return new Handler() { - @Override - public void handle(RoutingContext context) { - context.request().toWebSocket(webSocket -> { - if (webSocket.failed()) { - LOG.error("failed to connect web socket", webSocket.cause()); - } else { - ServerWebSocket serverWebSocket = webSocket.result(); - Integer socketId = GrpcWebSocketProxy.addWebSocket( - message -> serverWebSocket.writeTextMessage(message) - .onFailure(e -> LOG - .info("failed to send back message to the gRPC Dev Console WebSocket", e)), - runnable -> { - if (!serverWebSocket.isClosed()) { - serverWebSocket.close(new Handler>() { - @Override - public void handle(AsyncResult event) { - runnable.run(); - } - }); - } else { - runnable.run(); - } - }); - - if (socketId == null) { - LOG.error("No gRPC dev console WebSocketListener"); - serverWebSocket.close(); - return; - } - serverWebSocket.closeHandler(ignored -> GrpcWebSocketProxy.closeWebSocket(socketId)); - serverWebSocket.handler(new Handler() { - @Override - public void handle(Buffer event) { - GrpcWebSocketProxy.addMessage(socketId, event.toString()); - } - }); - } - }); - } - }; - } -} diff --git a/integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleGrpcSmokeTest.java b/integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleGrpcSmokeTest.java deleted file mode 100644 index 3b16aeaffd909..0000000000000 --- a/integration-tests/devmode/src/test/java/io/quarkus/test/devconsole/DevConsoleGrpcSmokeTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.quarkus.test.devconsole; - -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.grpc.examples.helloworld.Greeter; -import io.grpc.examples.helloworld.HelloReply; -import io.grpc.examples.helloworld.HelloRequest; -import io.quarkus.grpc.GrpcService; -import io.quarkus.test.QuarkusDevModeTest; -import io.restassured.RestAssured; -import io.smallrye.mutiny.Uni; - -/** - * Note that this test cannot be placed under the relevant {@code -deployment} module because then the DEV UI processor would - * not be able to locate the template resources correctly. - */ -public class DevConsoleGrpcSmokeTest { - - @RegisterExtension - static final QuarkusDevModeTest config = new QuarkusDevModeTest() - .withApplicationRoot((jar) -> jar.addPackage(Greeter.class.getPackage()) - .addClass(TestGreeter.class)) - .setCodeGenSources("proto"); - - @Test - public void testServices() { - RestAssured.get("q/dev-v1/io.quarkus.quarkus-grpc/services") - .then() - .statusCode(200).body(Matchers.containsString("helloworld.Greeter")); - } - - @GrpcService - public static class TestGreeter implements Greeter { - - @Override - public Uni sayHello(HelloRequest request) { - return Uni.createFrom().item(HelloReply.newBuilder().setMessage("Hola!").build()); - } - - } - -}