From 76744d6babaa4c76629fbbbb446a0579bca0c2e0 Mon Sep 17 00:00:00 2001 From: Daniel Fiala Date: Wed, 24 Apr 2024 14:17:07 +0200 Subject: [PATCH] chore(transcoding): moved some generic methods to util classes --- devtools/pom.xml | 2 +- extensions/grpc/api/pom.xml | 6 +- .../quarkus/grpc/deployment/GrpcCodeGen.java | 30 ++++-- extensions/grpc/deployment/pom.xml | 1 - .../protoc/plugin/MutinyGrpcGenerator.java | 19 ++-- .../main/resources/MutinyMarshalling.mustache | 1 + extensions/grpc/runtime/pom.xml | 10 +- .../grpc/runtime/GrpcServerRecorder.java | 2 - .../runtime/config/GrpcTranscodingConfig.java | 20 ++++ .../transcoding/GrpcTranscodingHttpUtils.java | 84 +++++++++++++++ .../GrpcTranscodingMessageWriter.java | 79 ++++++++++++++ .../transcoding/GrpcTranscodingRequest.java | 46 +------- .../transcoding/GrpcTranscodingServer.java | 102 +----------------- extensions/grpc/stubs/pom.xml | 1 - .../src/main/resources/application.properties | 1 + integration-tests/pom.xml | 5 +- 16 files changed, 221 insertions(+), 188 deletions(-) create mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcTranscodingConfig.java create mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingHttpUtils.java create mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingMessageWriter.java diff --git a/devtools/pom.xml b/devtools/pom.xml index 1b180ca912c764..a01063c3f5c10b 100644 --- a/devtools/pom.xml +++ b/devtools/pom.xml @@ -24,7 +24,7 @@ platform-properties bom-descriptor-json maven - + gradle cli diff --git a/extensions/grpc/api/pom.xml b/extensions/grpc/api/pom.xml index 9df21dd867004b..1ab12b251f7d8c 100644 --- a/extensions/grpc/api/pom.xml +++ b/extensions/grpc/api/pom.xml @@ -38,14 +38,10 @@ protobuf-java-util 3.24.3 - - jakarta.ws.rs - jakarta.ws.rs-api - jakarta.enterprise jakarta.enterprise.cdi-api - \ No newline at end of file + diff --git a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcCodeGen.java b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcCodeGen.java index f8931e5f942348..79c71314156ce0 100644 --- a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcCodeGen.java +++ b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcCodeGen.java @@ -64,6 +64,8 @@ public class GrpcCodeGen implements CodeGenProvider { private static final String DESCRIPTOR_SET_OUTPUT_DIR = "quarkus.generate-code.grpc.descriptor-set.output-dir"; private static final String DESCRIPTOR_SET_FILENAME = "quarkus.generate-code.grpc.descriptor-set.name"; + private static final String TRANSCODING_ENABLED = "quarkus.grpc.transcoding.enabled"; + private static final String USE_ARG_FILE = "quarkus.generate-code.grpc.use-arg-file"; private Executables executables; @@ -141,7 +143,7 @@ public boolean trigger(CodeGenContext context) throws CodeGenException { } if (!protoFiles.isEmpty()) { - initExecutables(workDir, context.applicationModel()); + initExecutables(context, workDir, context.applicationModel()); Collection protosToImport = gatherDirectoriesWithImports(workDir.resolve("protoc-dependencies"), context); @@ -241,7 +243,6 @@ private void postprocessing(CodeGenContext context, Path outDir) { } new GrpcPostProcessing(context, outDir).postprocess(); - } private Collection gatherProtosFromDependencies(Path workDir, Set protoDirectories, @@ -411,11 +412,12 @@ private String escapeWhitespace(String path) { } } - private void initExecutables(Path workDir, ApplicationModel model) throws CodeGenException { + private void initExecutables(CodeGenContext context, Path workDir, ApplicationModel model) throws CodeGenException { if (executables == null) { Path protocPath; String protocPathProperty = System.getProperty("quarkus.grpc.protoc-path"); String classifier = System.getProperty("quarkus.grpc.protoc-os-classifier", osClassifier()); + if (protocPathProperty == null) { protocPath = findArtifactPath(model, PROTOC_GROUPID, PROTOC, classifier, EXE); } else { @@ -426,7 +428,7 @@ private void initExecutables(Path workDir, ApplicationModel model) throws CodeGe Path protocGrpcPluginExe = prepareExecutable(workDir, model, "io.grpc", "protoc-gen-grpc-java", classifier, "exe"); - Path quarkusGrpcPluginExe = prepareQuarkusGrpcExecutable(model, workDir); + Path quarkusGrpcPluginExe = prepareQuarkusGrpcExecutable(context, model, workDir); executables = new Executables(protocExe, protocGrpcPluginExe, quarkusGrpcPluginExe); } @@ -493,26 +495,28 @@ private String osClassifier() throws CodeGenException { } } - private static Path prepareQuarkusGrpcExecutable(ApplicationModel appModel, Path buildDir) throws CodeGenException { + private static Path prepareQuarkusGrpcExecutable(CodeGenContext context, ApplicationModel appModel, Path buildDir) + throws CodeGenException { Path pluginPath = findArtifactPath(appModel, "io.quarkus", "quarkus-grpc-protoc-plugin", "shaded", "jar"); if (pluginPath == null) { throw new CodeGenException("Failed to find Quarkus gRPC protoc plugin among dependencies"); } if (OS.determineOS() != OS.WINDOWS) { - return writeScript(buildDir, pluginPath, "#!/bin/sh\n", ".sh"); + return writeScript(context, buildDir, pluginPath, "#!/bin/sh\n", ".sh"); } else { - return writeScript(buildDir, pluginPath, "@echo off\r\n", ".cmd"); + return writeScript(context, buildDir, pluginPath, "@echo off\r\n", ".cmd"); } } - private static Path writeScript(Path buildDir, Path pluginPath, String shebang, String suffix) throws CodeGenException { + private static Path writeScript(CodeGenContext context, Path buildDir, Path pluginPath, String shebang, String suffix) + throws CodeGenException { Path script; try { script = Files.createTempFile(buildDir, "quarkus-grpc", suffix); try (BufferedWriter writer = Files.newBufferedWriter(script)) { writer.write(shebang); - writePluginExeCmd(pluginPath, writer); + writePluginExeCmd(context, pluginPath, writer); } } catch (IOException e) { throw new CodeGenException("Failed to create a wrapper script for quarkus-grpc plugin", e); @@ -523,9 +527,13 @@ private static Path writeScript(Path buildDir, Path pluginPath, String shebang, return script; } - private static void writePluginExeCmd(Path pluginPath, BufferedWriter writer) throws IOException { + private static void writePluginExeCmd(CodeGenContext context, Path pluginPath, BufferedWriter writer) throws IOException { + Config properties = context.config(); + boolean enableTranscoding = properties.getOptionalValue(TRANSCODING_ENABLED, Boolean.class).orElse(false); + writer.write("\"" + JavaBinFinder.findBin() + "\" -cp \"" + - pluginPath.toAbsolutePath() + "\" " + quarkusProtocPluginMain); + pluginPath.toAbsolutePath() + "\" " + quarkusProtocPluginMain + + (enableTranscoding ? " --enableTranscoding" : "")); writer.newLine(); } diff --git a/extensions/grpc/deployment/pom.xml b/extensions/grpc/deployment/pom.xml index 3a171f90cb815c..c589a0130abf7e 100644 --- a/extensions/grpc/deployment/pom.xml +++ b/extensions/grpc/deployment/pom.xml @@ -133,7 +133,6 @@ quarkus-grpc-protoc-plugin ${project.version} io.quarkus.grpc.protoc.plugin.MutinyGrpcGenerator - --skipTranscoding diff --git a/extensions/grpc/protoc/src/main/java/io/quarkus/grpc/protoc/plugin/MutinyGrpcGenerator.java b/extensions/grpc/protoc/src/main/java/io/quarkus/grpc/protoc/plugin/MutinyGrpcGenerator.java index 7fe3bbe94cd8b8..888c4e2f16ecec 100644 --- a/extensions/grpc/protoc/src/main/java/io/quarkus/grpc/protoc/plugin/MutinyGrpcGenerator.java +++ b/extensions/grpc/protoc/src/main/java/io/quarkus/grpc/protoc/plugin/MutinyGrpcGenerator.java @@ -162,8 +162,6 @@ private MethodContext buildMethodContext(DescriptorProtos.MethodDescriptorProto methodContext.httpPath = methodContext.methodName; methodContext.httpMethod = "GET"; - log.info("HTTP rule: " + methodProto.getOptions().hasExtension(AnnotationsProto.http) + " Skip transcoding: " - + skipTranscoding); if (!skipTranscoding && methodProto.getOptions().hasExtension(AnnotationsProto.http)) { // Extract the HTTP rule from the method options (if present) HttpRule httpRule = getHttpRule(methodProto); @@ -171,8 +169,6 @@ private MethodContext buildMethodContext(DescriptorProtos.MethodDescriptorProto throw new IllegalArgumentException("HTTP rule is not defined for method " + methodProto.getName()); } - log.info("HTTP rule: " + httpRule); - // Determine HTTP method and path based on the HTTP rule if (httpRule.hasGet()) { methodContext.httpMethod = "GET"; @@ -473,22 +469,19 @@ public String methodHeader() { public static void main(String[] args) { Set argSet = new HashSet<>(Arrays.asList(args)); if (argSet.contains("help") || argSet.contains("--help") || argSet.contains("-h")) { - System.out.println("Usage: MutinyGrpcGenerator [skipTranscoding]"); - System.out.println("--skipTranscoding: Skip the generation of transcoding classes"); + System.out.println("Usage: MutinyGrpcGenerator [enableTranscoding]"); + System.out.println("--enableTranscoding: Enable transcoding support"); return; } - boolean skipTranscoding = false; - if (args.length > 0) { - skipTranscoding = argSet.contains("--skipTranscoding"); - log.info("Skipping transcoding..."); - } + boolean skipTranscoding = !argSet.contains("--enableTranscoding"); + log.info("Transcoding will be " + (skipTranscoding ? "skipped" : "enabled")); - String[] finalArgs = skipTranscoding ? Arrays.stream(args).skip(1).toArray(String[]::new) : args; + String[] finalArgs = skipTranscoding ? args : Arrays.copyOfRange(args, 1, args.length); if (finalArgs.length == 0) { ProtocPlugin.generate(List.of(new MutinyGrpcGenerator(skipTranscoding)), List.of(AnnotationsProto.http)); } else { - ProtocPlugin.debug(List.of(new MutinyGrpcGenerator()), List.of(AnnotationsProto.http), args[0]); + ProtocPlugin.debug(List.of(new MutinyGrpcGenerator()), List.of(AnnotationsProto.http), finalArgs[0]); } } } diff --git a/extensions/grpc/protoc/src/main/resources/MutinyMarshalling.mustache b/extensions/grpc/protoc/src/main/resources/MutinyMarshalling.mustache index ee0b3244c50b31..df3c2efeddfd2d 100644 --- a/extensions/grpc/protoc/src/main/resources/MutinyMarshalling.mustache +++ b/extensions/grpc/protoc/src/main/resources/MutinyMarshalling.mustache @@ -20,6 +20,7 @@ public class {{serviceName}}Marshalling implements GrpcTranscoding { return "{{packageName}}.{{serviceName}}"; } + @SuppressWarnings("unchecked") @Override public GrpcTranscodingDescriptor findTranscodingDescriptor(String method) { switch (method) { diff --git a/extensions/grpc/runtime/pom.xml b/extensions/grpc/runtime/pom.xml index 014fba618985df..f200dddc867d13 100644 --- a/extensions/grpc/runtime/pom.xml +++ b/extensions/grpc/runtime/pom.xml @@ -17,14 +17,6 @@ com.google.api.grpc proto-google-common-protos - - jakarta.ws.rs - jakarta.ws.rs-api - - - jakarta.enterprise - jakarta.enterprise.cdi-api - jakarta.annotation jakarta.annotation-api @@ -108,7 +100,7 @@ io.smallrye.common smallrye-common-vertx-context - + io.quarkus diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java index 98a61b9775338f..a9c93eef87ad77 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java @@ -189,8 +189,6 @@ private void buildGrpcServer(Vertx vertx, GrpcServerConfiguration configuration, LOGGER.info("Starting new Quarkus gRPC server (using Vert.x transport)..."); Route route = routerSupplier.getValue().route().handler(ctx -> { - LOGGER.info("Request received => " + ctx.request().path()); - if (!isGrpc(ctx)) { ctx.next(); } else { diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcTranscodingConfig.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcTranscodingConfig.java new file mode 100644 index 00000000000000..fa7390b9b7a2fd --- /dev/null +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/GrpcTranscodingConfig.java @@ -0,0 +1,20 @@ +package io.quarkus.grpc.runtime.config; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Configuration root for gRPC Transcoding feature in Quarkus. gRPC Transcoding allows you to create + * RESTful JSON APIs that are backed by existing gRPC services. + */ +@ConfigRoot(name = "grpc.transcoding", phase = ConfigPhase.BUILD_TIME) +public class GrpcTranscodingConfig { + + /** + * Flag to enable or disable the gRPC Transcoding feature. + * The default value is `false` (disabled). + */ + @ConfigItem(defaultValue = "false") + public boolean enabled; +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingHttpUtils.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingHttpUtils.java new file mode 100644 index 00000000000000..6499fe808ad220 --- /dev/null +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingHttpUtils.java @@ -0,0 +1,84 @@ +package io.quarkus.grpc.transcoding; + +import java.util.HashMap; +import java.util.Map; + +/** + * The `GrpcTranscodingHttpUtils` class provides utility functions for path handling + * and parameter extraction during the gRPC message transcoding process. Its key + * functions include: + *

+ * Checking if a request path matches a given gRPC path template. + * Extracting path parameters from both gRPC path templates and concrete HTTP paths. + */ +public class GrpcTranscodingHttpUtils { + + /** + * Determines if a given HTTP request path conforms to a specified gRPC path template. + * + * @param requestPath The actual HTTP request path to be checked. + * @param pathTemplate The gRPC path template defining the expected structure. + * @return `true` if the paths match, `false` otherwise. + */ + public static boolean isPathMatch(String requestPath, String pathTemplate) { + int pathIndex = 0; + int templateIndex = 0; + + while (pathIndex < requestPath.length() && templateIndex < pathTemplate.length()) { + int pathEnd = requestPath.indexOf('/', pathIndex); + int templateEnd = pathTemplate.indexOf('/', templateIndex); + + // Extract the current segment from both paths + String requestPart = pathEnd == -1 ? requestPath.substring(pathIndex) : requestPath.substring(pathIndex, pathEnd); + String templatePart = templateEnd == -1 ? pathTemplate.substring(templateIndex) + : pathTemplate.substring(templateIndex, templateEnd); + + // Check if the template part is a variable segment + if (templatePart.startsWith("{") && templatePart.endsWith("}")) { + if (requestPart.isEmpty()) { + return false; + } + // Skip to the end of the next segment + pathIndex = pathEnd != -1 ? pathEnd + 1 : requestPath.length(); + templateIndex = templateEnd != -1 ? templateEnd + 1 : pathTemplate.length(); + } else { + if (!requestPart.equals(templatePart)) { + return false; + } + + // Skip to the end of the next segment + pathIndex = pathEnd != -1 ? pathEnd + 1 : requestPath.length(); + templateIndex = templateEnd != -1 ? templateEnd + 1 : pathTemplate.length(); + } + } + + // Ensure both paths have been fully consumed + return pathIndex == requestPath.length() && templateIndex == pathTemplate.length(); + } + + /** + * Extracts path parameters from a gRPC path template and an associated HTTP path. + * + * @param pathTemplate The gRPC path template defining the parameter structure. + * @param httpPath The actual HTTP path from which to extract the parameter values. + * @return A `Map` containing the extracted parameter names and their corresponding values. + */ + public static Map extractPathParams(String pathTemplate, String httpPath) { + Map extractedParams = new HashMap<>(); + + String[] pathParts = httpPath.split("/"); + String[] templateParts = pathTemplate.split("/"); + + for (int i = 0; i < pathParts.length; i++) { + String pathPart = pathParts[i]; + String templatePart = templateParts[i]; + + if (templatePart.startsWith("{") && templatePart.endsWith("}")) { + String paramName = templatePart.substring(1, templatePart.length() - 1); + extractedParams.put(paramName, pathPart); + } + } + + return extractedParams; + } +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingMessageWriter.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingMessageWriter.java new file mode 100644 index 00000000000000..192bd6ab28d53a --- /dev/null +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingMessageWriter.java @@ -0,0 +1,79 @@ +package io.quarkus.grpc.transcoding; + +import java.util.HashMap; +import java.util.Map; + +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.Json; + +/** + * The `GrpcTranscodingMessageWriter` class assists with the manipulation of gRPC + * message payloads during the transcoding process. Its responsibilities include: + *

+ * Merging existing JSON payloads, path parameters, and query parameters into a + * unified map representation. + * Providing the logic for inserting nested parameters within the generated map. + */ +public class GrpcTranscodingMessageWriter { + + private final static String SEPARATOR = "\\."; + + /** + * Merges path parameters, query parameters, and an optional existing JSON payload + * into a single `Map` object. This method provides a centralized way to combine + * parameters during gRPC message transcoding. + * + * @param pathParams A map containing path parameters extracted from the request. + * @param queryParams A map containing query parameters extracted from the request. + * @param existingPayload An optional Vert.x `Buffer` containing an existing JSON payload. + * @return A `Map` representing the merged parameters. + * @throws IllegalArgumentException If the provided `existingPayload` cannot be parsed as valid JSON. + */ + public static Map mergeParameters(Map pathParams, Map queryParams, + Buffer existingPayload) { + Map allParams = new HashMap<>(); + + if (existingPayload != null && existingPayload.getBytes().length > 0) { + try { + String existingPayloadJson = new String(existingPayload.getBytes()); + allParams = new HashMap(Json.decodeValue(existingPayloadJson, Map.class)); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid payload", e); + } + } + + for (Map.Entry entry : pathParams.entrySet()) { + insertNestedParam(allParams, entry.getKey(), entry.getValue()); + } + + for (Map.Entry entry : queryParams.entrySet()) { + insertNestedParam(allParams, entry.getKey(), entry.getValue()); + } + + return allParams; + } + + /** + * Inserts a key-value pair into a nested structure within a `Map`. This method supports + * the creation of hierarchical parameter structures during the transcoding process. + * Key components are separated by periods ('.'). + * + * @param paramsMap The `Map` object where the nested parameter will be inserted. + * @param key The parameter key, potentially containing periods for nested structures. + * @param value The parameter value to be inserted. + */ + public static void insertNestedParam(Map paramsMap, String key, String value) { + String[] pathComponents = key.split(SEPARATOR); + + Map currentLevel = paramsMap; + for (int i = 0; i < pathComponents.length - 1; i++) { + String component = pathComponents[i]; + if (!currentLevel.containsKey(component)) { + currentLevel.put(component, new HashMap<>()); + } + currentLevel = (Map) currentLevel.get(component); + } + + currentLevel.put(pathComponents[pathComponents.length - 1], value); + } +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingRequest.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingRequest.java index 8af75490b65cec..c4912ff6cd60a0 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingRequest.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingRequest.java @@ -2,7 +2,6 @@ import static io.vertx.grpc.common.GrpcError.mapHttp2ErrorCode; -import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; import java.util.stream.Collector; @@ -108,7 +107,7 @@ public void init() { } private GrpcMessage mergeParametersIntoMessage(GrpcMessage msg) { - Map allParams = mergeParameters( + Map allParams = GrpcTranscodingMessageWriter.mergeParameters( pathParams, queryParams, msg.payload()); @@ -117,49 +116,6 @@ private GrpcMessage mergeParametersIntoMessage(GrpcMessage msg) { return GrpcMessage.message(msg.encoding(), Buffer.buffer(jsonPayload)); } - private Map mergeParameters(Map pathParams, Map queryParams, - Buffer existingPayload) { - Map allParams = new HashMap<>(); - - if (existingPayload != null && existingPayload.getBytes().length > 0) { - try { - String existingPayloadJson = new String(existingPayload.getBytes()); - allParams = new HashMap(Json.decodeValue(existingPayloadJson, Map.class)); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid payload", e); - } - } - - // Process pathParams - for (Map.Entry entry : pathParams.entrySet()) { - insertNestedParam(allParams, entry.getKey(), entry.getValue()); - } - - // Process queryParams - for (Map.Entry entry : queryParams.entrySet()) { - insertNestedParam(allParams, entry.getKey(), entry.getValue()); - } - - return allParams; - } - - private void insertNestedParam(Map paramsMap, String key, String value) { - String[] pathComponents = key.split("\\."); // Split by periods - - // Iterate until the last component - Map currentLevel = paramsMap; - for (int i = 0; i < pathComponents.length - 1; i++) { - String component = pathComponents[i]; - if (!currentLevel.containsKey(component)) { - currentLevel.put(component, new HashMap<>()); // Create nested object - } - currentLevel = (Map) currentLevel.get(component); - } - - // Insert the final value - currentLevel.put(pathComponents[pathComponents.length - 1], value); - } - protected Req decodeMessage(GrpcMessage msg) throws CodecException { return messageDecoder.decode(msg); } diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingServer.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingServer.java index bafbafc7acd175..5983ee4751c77d 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingServer.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/transcoding/GrpcTranscodingServer.java @@ -1,10 +1,7 @@ package io.quarkus.grpc.transcoding; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import io.grpc.MethodDescriptor; import io.vertx.core.Handler; @@ -24,9 +21,9 @@ public class GrpcTranscodingServer implements GrpcServer { private final Vertx vertx; private Handler> requestHandler; - private Map methodMapping = new HashMap<>(); - private Map> methodCallHandlers = new HashMap<>(); - private Map> metadataHandlers = new HashMap<>(); + private final Map methodMapping = new HashMap<>(); + private final Map> methodCallHandlers = new HashMap<>(); + private final Map> metadataHandlers = new HashMap<>(); public GrpcTranscodingServer(Vertx vertx) { this.vertx = vertx; @@ -39,7 +36,7 @@ public void handle(HttpServerRequest httpRequest) { for (Map.Entry entry : methodMapping.entrySet()) { String pathTemplate = entry.getKey(); String mappedMethod = entry.getValue(); - if (isPathMatch(requestPath, pathTemplate)) { + if (GrpcTranscodingHttpUtils.isPathMatch(requestPath, pathTemplate)) { GrpcTranscodingMetadata metadata = metadataHandlers.get(mappedMethod); if (metadata != null) { if (metadata.getHttpMethodName().equals(httpRequest.method().name())) { @@ -67,7 +64,7 @@ private void handleWithMappedMethod(HttpServerRequest httpRequest, String pathTe private void handle(String pathTemplate, MethodCallHandler method, HttpServerRequest httpRequest, GrpcMethodCall methodCall) { - Map pathParams = extractPathParams(pathTemplate, httpRequest.path()); + Map pathParams = GrpcTranscodingHttpUtils.extractPathParams(pathTemplate, httpRequest.path()); Map queryParameters = new HashMap<>(httpRequest.params().entries().stream() .collect(HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), HashMap::putAll)); @@ -119,95 +116,6 @@ public MethodDescriptor.Marshaller findResponseMarshaller(String fullMeth return (MethodDescriptor.Marshaller) metadata.getResponseMarshaller(); } - private boolean isPathMatch(String requestPath, String pathTemplate) { - String[] requestParts = requestPath.split("/"); - String[] templateParts = parsePath(pathTemplate).split("/"); // Use the enhanced parsePath - - if (requestParts.length != templateParts.length) { - return false; // Mismatch in number of segments - } - - for (int i = 0; i < requestParts.length; i++) { - String requestPart = requestParts[i]; - String templatePart = templateParts[i]; - - if (templatePart.startsWith("{") && templatePart.endsWith("}")) { - // It's a variable, any non-empty segment would match - if (requestPart.isEmpty()) { - return false; - } - } else { - // It's a literal segment - if (!requestPart.equals(templatePart)) { - return false; - } - } - } - - return true; // All segments matched - } - - public String parsePath(String path) { - Map extractedParams = extractPathParams(path); - StringBuilder parsedPathBuilder = new StringBuilder(); - - for (Map.Entry entry : extractedParams.entrySet()) { - parsedPathBuilder.append("/").append(entry.getValue()); - } - - return parsedPathBuilder.toString(); - } - - public Map extractPathParams(String pathTemplate, String httpPath) { - Map extractedParams = new HashMap<>(); - - String[] pathParts = httpPath.split("/"); - String[] templateParts = pathTemplate.split("/"); - - for (int i = 0; i < pathParts.length; i++) { - String pathPart = pathParts[i]; - String templatePart = templateParts[i]; - - if (templatePart.startsWith("{") && templatePart.endsWith("}")) { - String paramName = templatePart.substring(1, templatePart.length() - 1); - extractedParams.put(paramName, pathPart); - } - } - - return extractedParams; - } - - public Map extractPathParams(String path) { - // Regular expressions for template segments - String singleSegmentVarPattern = "\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}"; - String multiSegmentVarPattern = "\\{\\+([a-zA-Z_][a-zA-Z0-9_]*)\\}"; - - Map extractedParams = new LinkedHashMap<>(); - - String[] parts = path.split("/"); - - for (String part : parts) { - if (part.isEmpty()) { - continue; // Skip empty segments - } - - Matcher singleMatcher = Pattern.compile(singleSegmentVarPattern).matcher(part); - Matcher multiMatcher = Pattern.compile(multiSegmentVarPattern).matcher(part); - - if (singleMatcher.matches()) { - String paramName = singleMatcher.group(1); - extractedParams.put(paramName, "{" + paramName + "}"); - } else if (multiMatcher.matches()) { - String paramName = multiMatcher.group(1); - extractedParams.put(paramName, "{+" + paramName + "}"); - } else { - extractedParams.put(part, part); - } - } - - return extractedParams; - } - private static class MethodCallHandler implements Handler> { final MethodDescriptor def; diff --git a/extensions/grpc/stubs/pom.xml b/extensions/grpc/stubs/pom.xml index d494224d19e469..d3a387d2272203 100644 --- a/extensions/grpc/stubs/pom.xml +++ b/extensions/grpc/stubs/pom.xml @@ -98,7 +98,6 @@ quarkus-grpc-protoc-plugin ${project.version} io.quarkus.grpc.protoc.plugin.MutinyGrpcGenerator - --skipTranscoding diff --git a/integration-tests/grpc-transcoding/src/main/resources/application.properties b/integration-tests/grpc-transcoding/src/main/resources/application.properties index 747229c0a246cb..298b78be5325cd 100644 --- a/integration-tests/grpc-transcoding/src/main/resources/application.properties +++ b/integration-tests/grpc-transcoding/src/main/resources/application.properties @@ -1,6 +1,7 @@ quarkus.grpc.server.port=9001 quarkus.grpc.server.instances=2 quarkus.grpc.server.use-separate-server=false +quarkus.grpc.transcoding.enabled=true %vertx.quarkus.grpc.server.use-separate-server=false %n2o.quarkus.grpc.server.use-separate-server=true diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 84544a0a183a4a..a79214f5901fbb 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -56,8 +56,7 @@ classpath:enforcer-rules/quarkus-banned-dependencies-okhttp.xml - + @@ -180,7 +179,7 @@ infinispan-cache devtools devtools-registry-client - + gradle main kafka kafka-ssl