Skip to content

Commit 56172de

Browse files
authored
Merge pull request #173 from zZHorizonZz/transcoding-optimazations
Transcoding optimizations
2 parents de9e162 + c65aa0b commit 56172de

10 files changed

Lines changed: 1076 additions & 236 deletions

File tree

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<jprotoc.version>1.2.2</jprotoc.version>
2525
<protoc.version>4.29.3</protoc.version>
2626
<protobuf.version>4.29.3</protobuf.version>
27+
<jmh.version>1.37</jmh.version>
2728
</properties>
2829

2930
<dependencyManagement>

vertx-grpc-transcoding/pom.xml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,103 @@
8181
<artifactId>testcontainers</artifactId>
8282
<scope>test</scope>
8383
</dependency>
84+
85+
<!-- JMH -->
86+
<dependency>
87+
<groupId>org.openjdk.jmh</groupId>
88+
<artifactId>jmh-core</artifactId>
89+
<version>${jmh.version}</version>
90+
<scope>test</scope>
91+
</dependency>
8492
</dependencies>
93+
94+
<profiles>
95+
<profile>
96+
<id>benchmarks</id>
97+
<build>
98+
<pluginManagement>
99+
<plugins>
100+
<plugin>
101+
<artifactId>maven-compiler-plugin</artifactId>
102+
<executions>
103+
<execution>
104+
<id>default-testCompile</id>
105+
<configuration>
106+
<annotationProcessorPaths>
107+
<annotationProcessorPath>
108+
<groupId>org.openjdk.jmh</groupId>
109+
<artifactId>jmh-generator-annprocess</artifactId>
110+
<version>${jmh.version}</version>
111+
</annotationProcessorPath>
112+
</annotationProcessorPaths>
113+
</configuration>
114+
</execution>
115+
</executions>
116+
</plugin>
117+
</plugins>
118+
</pluginManagement>
119+
<plugins>
120+
<plugin>
121+
<artifactId>maven-assembly-plugin</artifactId>
122+
<executions>
123+
<execution>
124+
<id>assemble-benchmarks</id>
125+
<phase>package</phase>
126+
<goals>
127+
<goal>single</goal>
128+
</goals>
129+
<configuration>
130+
<archive>
131+
<manifest>
132+
<mainClass>org.openjdk.jmh.Main</mainClass>
133+
</manifest>
134+
</archive>
135+
<inlineDescriptors>
136+
<inlineDescriptor>
137+
<id>benchmarks</id>
138+
<formats>
139+
<format>jar</format>
140+
</formats>
141+
<includeBaseDirectory>false</includeBaseDirectory>
142+
<fileSets>
143+
<fileSet>
144+
<directory>${project.build.testOutputDirectory}</directory>
145+
<includes>
146+
<include>io/vertx/benchmarks/**</include>
147+
<include>META-INF/BenchmarkList</include>
148+
<include>META-INF/CompilerHints</include>
149+
</includes>
150+
<outputDirectory>/</outputDirectory>
151+
</fileSet>
152+
</fileSets>
153+
<dependencySets>
154+
<dependencySet>
155+
<outputDirectory>/</outputDirectory>
156+
<scope>test</scope>
157+
<includes>
158+
<include>io.netty:*</include>
159+
<include>com.fasterxml.jackson.core:*</include>
160+
<include>io.vertx:*</include>
161+
<include>org.openjdk.jmh:*</include>
162+
<include>net.sf.jopt-simple:*</include>
163+
<include>org.apache.commons:*</include>
164+
</includes>
165+
<unpack>true</unpack>
166+
<unpackOptions>
167+
<excludes>
168+
<exclude>**/module-info.class</exclude>
169+
</excludes>
170+
</unpackOptions>
171+
</dependencySet>
172+
</dependencySets>
173+
</inlineDescriptor>
174+
</inlineDescriptors>
175+
</configuration>
176+
</execution>
177+
</executions>
178+
</plugin>
179+
</plugins>
180+
</build>
181+
</profile>
182+
</profiles>
85183
</project>
Lines changed: 93 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,137 @@
11
package io.vertx.grpc.transcoding.impl;
22

33
import io.vertx.core.buffer.Buffer;
4-
import io.vertx.core.internal.buffer.BufferInternal;
54
import io.vertx.core.json.DecodeException;
65
import io.vertx.core.json.JsonObject;
76
import io.vertx.grpc.transcoding.impl.config.HttpVariableBinding;
87

98
import java.util.List;
9+
import java.util.Objects;
1010

11-
public class MessageWeaver {
11+
/**
12+
* The MessageWeaver class handles the merging and transformation of gRPC messages during HTTP-to-gRPC transcoding operations.
13+
*
14+
* @see HttpVariableBinding
15+
*/
16+
public final class MessageWeaver {
1217

18+
private static final String ROOT_LEVEL = "*";
19+
20+
private MessageWeaver() {
21+
}
22+
23+
/**
24+
* Weaves HTTP variable bindings and request body into a gRPC message.
25+
*
26+
* @param message The original message buffer
27+
* @param bindings The HTTP variable bindings
28+
* @param transcodingRequestBody The transcoding request body path
29+
* @return The modified buffer with weaved content
30+
* @throws DecodeException If JSON decoding fails
31+
*/
1332
public static Buffer weaveRequestMessage(Buffer message, List<HttpVariableBinding> bindings, String transcodingRequestBody) throws DecodeException {
14-
if (bindings.isEmpty() && transcodingRequestBody == null) {
33+
if ((bindings == null || bindings.isEmpty()) && (transcodingRequestBody == null || transcodingRequestBody.isEmpty())) {
1534
return message;
1635
}
17-
JsonObject result = weaveRequestMessage2(message, bindings, transcodingRequestBody);
18-
BufferInternal buffer = BufferInternal.buffer();
19-
buffer.appendString(result.encode());
20-
return buffer;
21-
}
22-
23-
public static JsonObject weaveRequestMessage2(Buffer message, List<HttpVariableBinding> bindings, String transcodingRequestBody) throws DecodeException {
2436

2537
JsonObject result = new JsonObject();
2638

27-
// First handle the bindings
39+
if (bindings != null && !bindings.isEmpty()) {
40+
applyBindings(result, bindings);
41+
}
42+
43+
JsonObject messageJson = null;
44+
if (message != null && !message.toString().isBlank()) {
45+
messageJson = message.toJsonObject();
46+
}
47+
48+
if (messageJson != null && !messageJson.isEmpty()) {
49+
if (transcodingRequestBody == null || transcodingRequestBody.isEmpty()) {
50+
// No specific path, merge at root level with deep copy
51+
result.mergeIn(messageJson, true);
52+
} else if (ROOT_LEVEL.equals(transcodingRequestBody)) {
53+
// Wildcard, merge at root level without overwriting bindings
54+
result.mergeIn(messageJson);
55+
} else {
56+
applyAtPath(result, transcodingRequestBody.split("\\."), messageJson);
57+
}
58+
}
59+
60+
return result.toBuffer();
61+
}
62+
63+
/**
64+
* Applies HTTP variable bindings to the result object.
65+
*/
66+
private static void applyBindings(JsonObject result, List<HttpVariableBinding> bindings) {
2867
for (HttpVariableBinding binding : bindings) {
29-
JsonObject current = result;
3068
List<String> fieldPath = binding.getFieldPath();
69+
if (fieldPath == null || fieldPath.isEmpty()) {
70+
continue;
71+
}
3172

32-
// Navigate to parent object, creating path if needed
73+
// Navigate to parent object, creating nested structure as needed
74+
JsonObject current = result;
3375
for (int i = 0; i < fieldPath.size() - 1; i++) {
3476
String fieldName = fieldPath.get(i);
35-
if (!current.containsKey(fieldName)) {
36-
current.put(fieldName, new JsonObject());
37-
}
38-
Object fieldValue = current.getValue(fieldName);
39-
if (!(fieldValue instanceof JsonObject)) {
40-
// If the field exists but is not an object, overwrite it with an empty object
41-
current.put(fieldName, new JsonObject());
77+
JsonObject next = current.getJsonObject(fieldName);
78+
if (next == null) {
79+
next = new JsonObject();
80+
current.put(fieldName, next);
4281
}
43-
current = current.getJsonObject(fieldName);
82+
current = next;
4483
}
4584

4685
// Set the value at the final path position
47-
current.put(fieldPath.get(fieldPath.size() - 1), binding.getValue());
86+
String lastField = fieldPath.get(fieldPath.size() - 1);
87+
current.put(lastField, binding.getValue());
4888
}
89+
}
4990

50-
// Then handle the transcoding request body
51-
if (transcodingRequestBody != null && !transcodingRequestBody.isEmpty()) {
52-
JsonObject messageJson = message.toString().isBlank() ? new JsonObject() : new JsonObject(message.toString());
53-
if (!messageJson.isEmpty()) {
54-
if (transcodingRequestBody.equals("*")) {
55-
result.mergeIn(messageJson);
56-
} else {
57-
JsonObject current = result;
58-
String[] path = transcodingRequestBody.split("\\.");
59-
for (int i = 0; i < path.length - 1; i++) {
60-
String fieldName = path[i];
61-
if (!current.containsKey(fieldName)) {
62-
current.put(fieldName, new JsonObject());
63-
}
64-
current = current.getJsonObject(fieldName);
65-
}
66-
current.put(path[path.length - 1], messageJson);
67-
}
68-
}
69-
} else {
70-
JsonObject messageJson = message.toString().isBlank() ? new JsonObject() : message.toJsonObject();
71-
if (!messageJson.isEmpty()) {
72-
result.mergeIn(messageJson, true);
91+
/**
92+
* Applies an object at a specific path in the JSON structure.
93+
*/
94+
private static void applyAtPath(JsonObject root, String[] path, Object value) {
95+
JsonObject current = root;
96+
for (int i = 0; i < path.length - 1; i++) {
97+
String fieldName = path[i];
98+
JsonObject next = current.getJsonObject(fieldName);
99+
if (next == null) {
100+
next = new JsonObject();
101+
current.put(fieldName, next);
73102
}
103+
current = next;
74104
}
75-
76-
return result;
105+
current.put(path[path.length - 1], value);
77106
}
78107

79-
public static Buffer weaveResponseMessage(Buffer message, String transcodingResponseBody) {
80-
if (transcodingResponseBody == null || transcodingResponseBody.isEmpty()) {
108+
/**
109+
* Extracts a response message portion based on the transcoding path.
110+
*
111+
* @param message The original message buffer
112+
* @param transcodingResponseBody The path to extract from the response
113+
* @return The modified buffer with the extracted content
114+
*/
115+
public static Buffer weaveResponseMessage(Buffer message, String transcodingResponseBody) throws DecodeException {
116+
Objects.requireNonNull(message, "Message cannot be null");
117+
118+
if (transcodingResponseBody == null || transcodingResponseBody.isEmpty() || transcodingResponseBody.equals(ROOT_LEVEL)) {
81119
return message;
82120
}
83121

84122
JsonObject json = message.toJsonObject();
85-
86-
if (transcodingResponseBody.equals("*")) {
87-
return message;
88-
}
89-
90123
String[] path = transcodingResponseBody.split("\\.");
91-
JsonObject current = json;
92124

93-
// Navigate to the specified path
125+
JsonObject current = json;
94126
for (String field : path) {
95-
if (current.containsKey(field)) {
96-
Object value = current.getValue(field);
97-
if (value instanceof JsonObject) {
98-
current = (JsonObject) value;
99-
} else {
100-
throw new IllegalStateException("Invalid transcodingResponseBody path: " + transcodingResponseBody);
101-
}
127+
Object value = current.getValue(field);
128+
if (value instanceof JsonObject) {
129+
current = (JsonObject) value;
102130
} else {
103-
throw new IllegalStateException("Invalid transcodingResponseBody path: " + transcodingResponseBody);
131+
throw new IllegalArgumentException("Path segment '" + field + "' in transcodingResponseBody does not refer to a JSON object");
104132
}
105133
}
106134

107-
// Return just the object at the specified path
108-
return Buffer.buffer(current.encode());
135+
return current.toBuffer();
109136
}
110137
}

0 commit comments

Comments
 (0)