Skip to content

Commit 582395b

Browse files
authored
Merge pull request #65 from ignorant05/enhancement/configurable-inbound-message-size
Fix: configurable max message inbound size
2 parents dc96abc + 1910cb9 commit 582395b

File tree

11 files changed

+329
-8
lines changed

11 files changed

+329
-8
lines changed

documents/configuration/ojp-jdbc-configuration.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ ojp.connection.pool.maximumPoolSize=20
4949
ojp.connection.pool.minimumIdle=5
5050
ojp.connection.pool.idleTimeout=600000
5151
```
52+
#### Default Maximum Inbound Message Size Configuration
53+
54+
```properties
55+
# Default Maximum Inbound Message Size Configuration
56+
ojp.grpc.maxInboundMessageSize=16777216
57+
```
5258

5359
### How to Use DataSources
5460

@@ -332,4 +338,4 @@ The multi-datasource feature is fully backward compatible:
332338
## Related Documentation
333339

334340
- **[OJP Server Configuration](ojp-server-configuration.md)** - Server startup options and runtime configuration
335-
- **[Example Configuration Properties](ojp-server-example.properties)** - Complete example configuration file with all settings
341+
- **[Example Configuration Properties](ojp-server-example.properties)** - Complete example configuration file with all settings

ojp-grpc-commons/pom.xml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@
2626
</properties>
2727

2828
<dependencies>
29+
<!-- JUnit 5 API -->
30+
<dependency>
31+
<groupId>org.junit.jupiter</groupId>
32+
<artifactId>junit-jupiter-api</artifactId>
33+
<version>5.10.0</version>
34+
<scope>test</scope>
35+
</dependency>
36+
37+
<!-- JUnit 5 Engine for running tests -->
38+
<dependency>
39+
<groupId>org.junit.jupiter</groupId>
40+
<artifactId>junit-jupiter-engine</artifactId>
41+
<version>5.10.0</version>
42+
<scope>test</scope>
43+
</dependency>
44+
2945
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
3046
<dependency>
3147
<groupId>org.slf4j</groupId>
@@ -103,7 +119,17 @@
103119
</execution>
104120
</executions>
105121
</plugin>
122+
<plugin>
123+
<groupId>org.apache.maven.plugins</groupId>
124+
<artifactId>maven-surefire-plugin</artifactId>
125+
<version>3.1.2</version>
126+
<configuration>
127+
<includes>
128+
<include>**/*Test.java</include>
129+
</includes>
130+
</configuration>
131+
</plugin>
106132
</plugins>
107133
</build>
108134

109-
</project>
135+
</project>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.openjproxy.config;
2+
3+
import java.io.FileNotFoundException;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.util.Properties;
7+
8+
/**
9+
* Configuration class for gRPC client settings.
10+
* <p>
11+
* Loads values such as maximum inbound message sizes
12+
* from a properties file named {@code ojp.properties} located in the classpath.
13+
* </p>
14+
* <p>
15+
* If properties are missing, default values (16MB) are applied.
16+
* </p>
17+
*/
18+
public class GrpcClientConfig {
19+
/** Default message size in bytes (16MB) */
20+
private static final String DEFAULT_SIZE = "16777216";
21+
22+
private int maxInboundMessageSize;
23+
24+
/**
25+
* Constructs a new {@code GrpcClientConfig} using the provided {@link Properties}.
26+
* <p>
27+
* If expected keys are missing, default values are used.
28+
* </p>
29+
*
30+
* @param props the {@link Properties} object containing configuration values
31+
*/
32+
public GrpcClientConfig(Properties props) {
33+
this.maxInboundMessageSize = Integer.parseInt(
34+
props.getProperty("ojp.grpc.maxInboundMessageSize", DEFAULT_SIZE));
35+
}
36+
37+
/**
38+
* Returns the maximum allowed inbound message size (in bytes).
39+
*
40+
* @return the max inbound message size
41+
*/
42+
public int getMaxInboundMessageSize() {
43+
return this.maxInboundMessageSize;
44+
}
45+
46+
/**
47+
* Loads the gRPC client configuration from a {@code ojp.properties} file
48+
* located in the classpath.
49+
*
50+
* @return a new instance of {@code GrpcClientConfig} with loaded values
51+
* @throws IOException if the file is not found or cannot be read
52+
*/
53+
public static GrpcClientConfig load() throws IOException {
54+
try (InputStream in = GrpcClientConfig.class.getClassLoader().getResourceAsStream("ojp.properties")) {
55+
if (in == null) {
56+
throw new FileNotFoundException("Could not find ojp.properties in classpath");
57+
}
58+
Properties props = new Properties();
59+
props.load(in);
60+
return new GrpcClientConfig(props);
61+
}
62+
}
63+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.openjproxy.grpc;
2+
3+
import org.openjproxy.config.GrpcClientConfig;
4+
import io.grpc.ManagedChannel;
5+
import io.grpc.ManagedChannelBuilder;
6+
import java.io.IOException;
7+
import java.util.Properties;
8+
/**
9+
* Factory class for creating and configuring gRPC {@link ManagedChannel} instances.
10+
*
11+
* <p>This class reads configuration from {@link GrpcClientConfig} and provides overloaded methods
12+
* to create channels with default or custom settings.</p>
13+
*
14+
* <p>By default, it uses a maximum inbound message size of 16MB.</p>
15+
*/
16+
public class GrpcChannelFactory {
17+
/** Default maximum inbound message size (16MB) */
18+
private static int maxInboundMessageSize = 16777216;
19+
20+
/** gRPC client configuration loaded from properties file */
21+
static GrpcClientConfig grpcConfig;
22+
23+
/**
24+
* Constructor that initializes gRPC client configuration.
25+
*/
26+
public GrpcChannelFactory() {
27+
initializeGrpcConfig();
28+
}
29+
30+
/**
31+
* Initializes the gRPC client configuration from external properties.
32+
*
33+
* <p>If loading fails, it falls back to an empty configuration.</p>
34+
*/
35+
public static void initializeGrpcConfig() {
36+
try {
37+
grpcConfig = GrpcClientConfig.load();
38+
} catch (IOException e) {
39+
e.printStackTrace();
40+
grpcConfig = new GrpcClientConfig(new Properties());
41+
}
42+
43+
maxInboundMessageSize = grpcConfig.getMaxInboundMessageSize();
44+
}
45+
46+
/**
47+
* Creates a new {@link ManagedChannel} for the given host and port with specified
48+
* inbound message size limits.
49+
*
50+
* @param host The gRPC server host
51+
* @param port The gRPC server port
52+
* @param maxInboundSize Maximum allowed inbound message size in bytes
53+
* @return A configured {@link ManagedChannel} instance
54+
*/
55+
public static ManagedChannel createChannel(String host, int port, int maxInboundSize) {
56+
return ManagedChannelBuilder.forAddress(host, port)
57+
.usePlaintext()
58+
.maxInboundMessageSize(maxInboundSize)
59+
.build();
60+
}
61+
62+
/**
63+
* Creates a new {@link ManagedChannel} for the given host and port using default message size limits.
64+
*
65+
* @param host The gRPC server host
66+
* @param port The gRPC server port
67+
* @return A configured {@link ManagedChannel} instance
68+
*/
69+
public static ManagedChannel createChannel(String host, int port) {
70+
return createChannel(host, port, maxInboundMessageSize);
71+
}
72+
73+
/**
74+
* Creates a new {@link ManagedChannel} for the given target string (e.g., "localhost:50051").
75+
* Uses default message size limits.
76+
*
77+
* @param target A target string in the form "host:port"
78+
* @return A configured {@link ManagedChannel} instance
79+
*/
80+
public static ManagedChannel createChannel(String target) {
81+
return ManagedChannelBuilder.forTarget(target)
82+
.usePlaintext()
83+
.maxInboundMessageSize(maxInboundMessageSize)
84+
.build();
85+
}
86+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
syntax = "proto3";
2+
package org.openjproxy.grpc;
3+
option java_multiple_files = true;
4+
option java_outer_classname = "EchoServiceProto";
5+
6+
service EchoService {
7+
rpc Echo (EchoRequest) returns (EchoResponse);
8+
}
9+
10+
message EchoRequest {
11+
string message = 1;
12+
}
13+
14+
message EchoResponse {
15+
string message = 1;
16+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# 16MB is the default max inbound message size
2+
ojp.grpc.maxInboundMessageSize=16777216
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.openjproxy.grpc;
2+
3+
import org.openjproxy.grpc.EchoRequest;
4+
import org.openjproxy.grpc.EchoResponse;
5+
import org.openjproxy.grpc.EchoServiceGrpc;
6+
import io.grpc.stub.StreamObserver;
7+
8+
// Just echoing back the message
9+
public class DummyEchoService extends EchoServiceGrpc.EchoServiceImplBase {
10+
@Override
11+
public void echo(EchoRequest request, StreamObserver<EchoResponse> responseObserver) {
12+
responseObserver.onNext(EchoResponse.newBuilder()
13+
.setMessage(request.getMessage())
14+
.build());
15+
responseObserver.onCompleted();
16+
}
17+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.openjproxy.grpc;
2+
3+
import org.openjproxy.grpc.EchoRequest;
4+
import org.openjproxy.grpc.EchoResponse;
5+
import org.openjproxy.grpc.EchoServiceGrpc;
6+
import io.grpc.ManagedChannel;
7+
import io.grpc.Server;
8+
import io.grpc.ServerBuilder;
9+
import io.grpc.StatusRuntimeException;
10+
import org.junit.jupiter.api.*;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
public class GrpcMessageSizeTest {
15+
16+
static final int PORT = 5555; // Just a random port
17+
static final int SIZE_LIMIT = 16 * 1024 * 1024; // Since 16MB is the default
18+
static Server server;
19+
20+
@BeforeAll
21+
static void startServer() throws Exception {
22+
server = ServerBuilder.forPort(PORT)
23+
.addService(new DummyEchoService())
24+
.maxInboundMessageSize(SIZE_LIMIT)
25+
.build()
26+
.start();
27+
}
28+
29+
@AfterAll
30+
static void stopServer() throws Exception {
31+
server.shutdownNow();
32+
}
33+
34+
EchoServiceGrpc.EchoServiceBlockingStub getStub(int inboundSize) {
35+
ManagedChannel channel = GrpcChannelFactory.createChannel("localhost", PORT, inboundSize);
36+
return EchoServiceGrpc.newBlockingStub(channel);
37+
}
38+
39+
String generateMessageOfSize(int sizeInBytes) {
40+
return "a".repeat(sizeInBytes);
41+
}
42+
43+
// This should pass
44+
@Test
45+
void testMessageBelowSizeLimit() {
46+
String message = generateMessageOfSize(SIZE_LIMIT - 100); // Just under 16MB
47+
EchoServiceGrpc.EchoServiceBlockingStub stub = getStub(SIZE_LIMIT);
48+
EchoResponse response = stub.echo(EchoRequest.newBuilder().setMessage(message).build());
49+
assertEquals(message, response.getMessage());
50+
}
51+
52+
// This should pass
53+
@Test
54+
void testMessageAtSizeLimit() {
55+
String message = generateMessageOfSize(SIZE_LIMIT - 10); // Exactly 16MB
56+
EchoServiceGrpc.EchoServiceBlockingStub stub = getStub(SIZE_LIMIT);
57+
EchoResponse response = stub.echo(EchoRequest.newBuilder().setMessage(message).build());
58+
assertEquals(message, response.getMessage());
59+
}
60+
61+
// This should fail
62+
@Test
63+
void testMessageExceedsSizeLimit() {
64+
String message = generateMessageOfSize(SIZE_LIMIT + 1); // Just over 16MB
65+
EchoServiceGrpc.EchoServiceBlockingStub stub = getStub(SIZE_LIMIT);
66+
67+
Exception exception = assertThrows(StatusRuntimeException.class, () -> {
68+
stub.echo(EchoRequest.newBuilder().setMessage(message).build());
69+
});
70+
71+
System.out.println("Expected exception: " + exception.getMessage());
72+
assertTrue(exception.getMessage().contains("RESOURCE_EXHAUSTED"));
73+
}
74+
}

ojp-jdbc-driver/src/main/java/org/openjproxy/grpc/client/StatementServiceGrpcClient.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import lombok.extern.slf4j.Slf4j;
2323
import org.openjproxy.constants.CommonConstants;
2424
import org.openjproxy.grpc.dto.Parameter;
25+
import org.openjproxy.grpc.GrpcChannelFactory;
2526
import org.openjproxy.jdbc.Connection;
2627
import org.openjproxy.jdbc.LobGrpcIterator;
2728

@@ -80,9 +81,7 @@ private void grpcChannelOpenAndStubsInitialized(String url) {
8081

8182
//Once channel is open it remains open and is shared among all requests.
8283
String target = DNS_PREFIX + host + COLON + port;
83-
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
84-
.usePlaintext()
85-
.build();
84+
ManagedChannel channel = GrpcChannelFactory.createChannel(target);
8685

8786
this.statemetServiceBlockingStub = StatementServiceGrpc.newBlockingStub(channel);
8887
this.statemetServiceStub = StatementServiceGrpc.newStub(channel);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.openjproxy.grpc.client;
2+
3+
import org.openjproxy.grpc.GrpcChannelFactory;
4+
import com.openjproxy.grpc.ConnectionDetails;
5+
import com.openjproxy.grpc.SessionInfo;
6+
import com.openjproxy.grpc.StatementServiceGrpc;
7+
import io.grpc.ManagedChannel;
8+
import io.grpc.ManagedChannelBuilder;
9+
import io.grpc.StatusRuntimeException;
10+
11+
import java.sql.SQLException;
12+
13+
import static org.openjproxy.grpc.client.GrpcExceptionHandler.handle;
14+
15+
public class StatementGrpcClient {
16+
public static void main(String[] args) throws SQLException {
17+
ManagedChannel channel = GrpcChannelFactory.createChannel("localhost", 8080);
18+
19+
StatementServiceGrpc.StatementServiceBlockingStub stub
20+
= StatementServiceGrpc.newBlockingStub(channel);
21+
22+
try {
23+
SessionInfo sessionInfo = stub.connect(ConnectionDetails.newBuilder()
24+
.setUrl("jdbc:ojp_h2:~/test")
25+
.setUser("sa")
26+
.setPassword("").build());
27+
sessionInfo.getConnHash();
28+
} catch (StatusRuntimeException e) {
29+
handle(e);
30+
}
31+
channel.shutdown();
32+
}
33+
}

0 commit comments

Comments
 (0)