Skip to content

Commit 47041c5

Browse files
authored
feat: ability to set connect-rpc protocol (#285)
1 parent d337704 commit 47041c5

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.opentdf.platform.sdk;
2+
3+
import com.connectrpc.protocols.NetworkProtocol;
4+
5+
/**
6+
* Enumeration of supported network protocols for SDK communication.
7+
*
8+
* This enum provides a mapping between SDK protocol types and the underlying
9+
* Connect-RPC NetworkProtocol values, allowing flexible configuration of the
10+
* communication protocol used for platform services.
11+
*/
12+
public enum ProtocolType {
13+
/**
14+
* Connect's native protocol - HTTP-based with support for HTTP/1.1, HTTP/2, and HTTP/3.
15+
* Supports both JSON and binary Protobuf encoding with streaming capabilities.
16+
* This is the recommended default for new applications.
17+
*/
18+
CONNECT(NetworkProtocol.CONNECT),
19+
20+
/**
21+
* Standard gRPC protocol - requires HTTP/2 and uses binary Protobuf encoding.
22+
* Provides full gRPC compatibility including streaming, trailers, and error details.
23+
*/
24+
GRPC(NetworkProtocol.GRPC),
25+
26+
/**
27+
* gRPC-Web protocol - designed for web browsers, works over HTTP/1.1 and HTTP/2.
28+
* Eliminates the need for a translating proxy like Envoy.
29+
*/
30+
GRPC_WEB(NetworkProtocol.GRPC_WEB);
31+
32+
private final NetworkProtocol networkProtocol;
33+
34+
ProtocolType(NetworkProtocol networkProtocol) {
35+
this.networkProtocol = networkProtocol;
36+
}
37+
38+
/**
39+
* Get the underlying Connect-RPC NetworkProtocol value.
40+
*
41+
* @return the NetworkProtocol corresponding to this ProtocolType
42+
*/
43+
public NetworkProtocol getNetworkProtocol() {
44+
return networkProtocol;
45+
}
46+
}

sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public class SDKBuilder {
6565
private Boolean usePlainText;
6666
private SSLFactory sslFactory;
6767
private AuthorizationGrant authzGrant;
68+
private ProtocolType protocolType = ProtocolType.CONNECT;
6869

6970
private static final Logger logger = LoggerFactory.getLogger(SDKBuilder.class);
7071

@@ -160,6 +161,22 @@ public SDKBuilder useInsecurePlaintextConnection(Boolean usePlainText) {
160161
return this;
161162
}
162163

164+
/**
165+
* Set the network protocol to use for communication with platform services.
166+
*
167+
* @param protocolType the protocol type to use (CONNECT, GRPC, or GRPC_WEB)
168+
* @return this builder instance for method chaining
169+
* @throws IllegalArgumentException if protocolType is null
170+
* @see ProtocolType for available protocol options
171+
*/
172+
public SDKBuilder protocol(ProtocolType protocolType) {
173+
if (protocolType == null) {
174+
throw new IllegalArgumentException("ProtocolType cannot be null");
175+
}
176+
this.protocolType = protocolType;
177+
return this;
178+
}
179+
163180
private Interceptor getAuthInterceptor(RSAKey rsaKey) {
164181
if (platformEndpoint == null) {
165182
throw new SDKException("cannot build an SDK without specifying the platform endpoint");
@@ -231,6 +248,13 @@ static class ServicesAndInternals {
231248
}
232249

233250
ServicesAndInternals buildServices() {
251+
// Validate configuration compatibility
252+
if (Boolean.TRUE.equals(usePlainText) && protocolType == ProtocolType.GRPC_WEB) {
253+
throw new SDKException("gRPC-Web protocol is not compatible with useInsecurePlaintextConnection(true). " +
254+
"gRPC-Web is designed for web browsers and typically operates over HTTP/1.1, " +
255+
"while plaintext connections force HTTP/2 prior knowledge.");
256+
}
257+
234258
RSAKey dpopKey;
235259
try {
236260
dpopKey = new RSAKeyGenerator(2048)
@@ -329,7 +353,7 @@ private ProtocolClient getProtocolClient(String endpoint, OkHttpClient httpClien
329353
var protocolClientConfig = new ProtocolClientConfig(
330354
endpoint,
331355
new GoogleJavaProtobufStrategy(),
332-
NetworkProtocol.GRPC,
356+
protocolType.getNetworkProtocol(),
333357
null,
334358
GETConfiguration.Enabled.INSTANCE,
335359
authInterceptor == null ? Collections.emptyList() : List.of(ignoredConfig -> authInterceptor)
@@ -343,7 +367,8 @@ private OkHttpClient getHttpClient() {
343367
// have the same protocols
344368
var httpClient = new OkHttpClient.Builder();
345369
if (usePlainText) {
346-
// we can only connect using HTTP/2 without any negotiation when using plain test
370+
// For plaintext connections, we need HTTP/2 prior knowledge because gRPC servers
371+
// expect HTTP/2, and Connect protocol can communicate with gRPC servers over HTTP/2
347372
httpClient.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE));
348373
}
349374
if (sslFactory != null) {

sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class KASClientTest {
4545
BiFunction<OkHttpClient, String, ProtocolClient> aclientFactory = (OkHttpClient client, String endpoint) -> {
4646
return new ProtocolClient(
4747
new ConnectOkHttpClient(httpClient),
48-
new ProtocolClientConfig(endpoint, new GoogleJavaProtobufStrategy(), NetworkProtocol.GRPC, null, GETConfiguration.Enabled.INSTANCE)
48+
new ProtocolClientConfig(endpoint, new GoogleJavaProtobufStrategy(), ProtocolType.GRPC.getNetworkProtocol(), null, GETConfiguration.Enabled.INSTANCE)
4949
);
5050
};
5151

sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, Re
249249
SDKBuilder servicesBuilder = SDKBuilder
250250
.newBuilder()
251251
.clientSecret("client-id", "client-secret")
252-
.platformEndpoint("http" + (useSSLPlatform ? "s" : "") + "://localhost:" + platformServicesServer.getPort());
252+
.platformEndpoint("http" + (useSSLPlatform ? "s" : "") + "://localhost:" + platformServicesServer.getPort())
253+
.protocol(ProtocolType.GRPC); // Use gRPC protocol for test servers
253254

254255
if (!useSSLPlatform) {
255256
servicesBuilder = servicesBuilder.useInsecurePlaintextConnection(true);
@@ -396,6 +397,7 @@ public void getNamespace(GetNamespaceRequest request,
396397
.clientSecret("user", "password")
397398
.platformEndpoint("http://localhost:" + platformServices.getPort())
398399
.useInsecurePlaintextConnection(true)
400+
.protocol(ProtocolType.GRPC) // Use gRPC protocol for test server
399401
.build();
400402
assertThat(sdk.getAuthInterceptor()).isEmpty();
401403

@@ -412,6 +414,32 @@ public void getNamespace(GetNamespaceRequest request,
412414
}
413415
}
414416

417+
@Test
418+
void testProtocolConfiguration() {
419+
// Test protocol setter and getter functionality
420+
SDKBuilder builder = SDKBuilder.newBuilder();
421+
422+
// Test setting different protocol types
423+
builder.protocol(ProtocolType.GRPC);
424+
builder.protocol(ProtocolType.GRPC_WEB);
425+
builder.protocol(ProtocolType.CONNECT);
426+
427+
// Test null validation
428+
assertThrows(IllegalArgumentException.class, () -> {
429+
builder.protocol(null);
430+
});
431+
432+
// Test invalid configuration validation
433+
assertThrows(SDKException.class, () -> {
434+
SDKBuilder.newBuilder()
435+
.clientSecret("user", "password")
436+
.platformEndpoint("http://localhost:8080")
437+
.useInsecurePlaintextConnection(true)
438+
.protocol(ProtocolType.GRPC_WEB)
439+
.buildServices();
440+
});
441+
}
442+
415443
public static int getRandomPort() throws IOException {
416444
int randomPort;
417445
try (ServerSocket socket = new ServerSocket(0)) {

0 commit comments

Comments
 (0)