Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ mvn test -pl subsystem
The helloworld example provisions a WildFly server with the gRPC subsystem:

```bash
# Start server (ssl: none, oneway, twoway)
mvn wildfly:run -pl examples/helloworld/service -Dssl=none
# Build and start server (ssl: none, oneway, twoway)
cd examples/helloworld/service
mvn clean package -Dssl=none
./target/wildfly/bin/standalone.sh --stability preview

# Run client
mvn exec:java -pl examples/helloworld/client -Dexec.args="Bob none"
# Run client (in another terminal)
cd examples/helloworld/client
mvn package
java -jar target/helloworld-client.jar Bob
```

The chat example uses a TamboUI terminal UI client:
Expand Down
54 changes: 42 additions & 12 deletions examples/helloworld/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ and deploy the service, run:

```shell
cd service
mvn clean package -Dssl=<*SSL*>
mvn clean package -Dssl=<SSL>
```

where *SSL* is either
where `SSL` is either

* none: plaintext
* oneway: server identity is verified
* twoway: both server and client identities are verified
* `none`: plaintext
* `oneway`: server identity is verified
* `twoway`: both server and client identities are verified

Then, run the server with:

Expand All @@ -26,17 +26,47 @@ Then, run the server with:

## Client

The `helloworld` client is a simple Java application. To build the client and call to the gRPC service, run:
The `helloworld` client is a simple Java application. To build it, run:

```shell
cd client
mvn package
mvn exec:java -Dexec.args="Bob *SSL*"
```
where, again, *SSL* is either "none", "oneway", or "twoway"

Alternatively you could also use tools like [BloomRPC](https://github.com/uw-labs/bloomrpc)
or [gRPCurl](https://github.com/fullstorydev/grpcurl) to invoke the service:
This produces a fat JAR that can be launched directly:

```shell
java -jar target/helloworld-client.jar [options] [name]
```

### Command Line Arguments

| Argument | Default | Description |
|----------|---------|-------------|
| `--ssl=<mode>` | `none` | SSL mode: `none`, `oneway`, or `twoway` (defaults to `none`, must match the server) |
| `name` | `world` | Name to greet (defaults to `world`) |

### Examples

Start the server in one terminal:

```shell
cd service
mvn clean package -Dssl=none
./target/wildfly/bin/standalone.sh --stability preview
```

Run the client in another terminal:

```shell
cd client
java -jar target/helloworld-client.jar Bob
```

### Using gRPCurl

Alternatively, you can use [gRPCurl](https://github.com/fullstorydev/grpcurl) to invoke the service.
Run these commands from the `examples/helloworld` directory:

```shell
# clear text connection

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still keep the grpcurl commands as an alternative to the client

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grpcurl commands are back in the REAMDE.md

Expand All @@ -45,15 +75,15 @@ grpcurl -proto proto/src/main/proto/helloworld.proto \
-d '{"name":"Bob"}' \
localhost:9555 helloworld.Greeter/SayHello
```
or

```shell
# tls (oneway)
grpcurl -proto proto/src/main/proto/helloworld.proto \
-cacert ../../ssl-gen/target/generated-ssl/client.truststore.pem \
-d '{"name":"Bob"}' \
localhost:9555 helloworld.Greeter/SayHello
```
or

```shell
# mutual TLS (twoway)
grpcurl -proto proto/src/main/proto/helloworld.proto \
Expand Down
50 changes: 40 additions & 10 deletions examples/helloworld/client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,50 @@
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>failureaccess</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>org.wildfly.extension.grpc.example.helloworld.GreeterClient</mainClass>
</configuration>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>helloworld-client</finalName>
<artifactSet>
<excludes>
<exclude>com.google.protobuf:protobuf-javalite</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
<exclude>META-INF/versions/*/module-info.class</exclude>
</excludes>
</filter>
<filter>
<artifact>${project.groupId}:wildfly-grpc-examples-helloworld-proto</artifact>
<excludes>
<exclude>helloworld.proto</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.wildfly.extension.grpc.example.helloworld.GreeterClient</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand All @@ -55,7 +85,7 @@
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
<overwrite>true</overwrite>
<changeDetection>always</changeDetection>
<resources>
<resource>
<directory>${maven.multiModuleProjectDirectory}/ssl-gen/target/generated-ssl</directory>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,24 @@
*/
package org.wildfly.extension.grpc.example.helloworld;

import java.io.InputStream;
import java.security.KeyStore;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.KeyManagerFactory;

import io.grpc.Channel;
import io.grpc.ChannelCredentials;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.TlsChannelCredentials;

public class GreeterClient {

private static final Logger logger = Logger.getLogger(GreeterClient.class.getName());

private final GreeterGrpc.GreeterBlockingStub blockingStub;

/**
* Construct client for accessing HelloWorld server using the existing channel.
*/
public GreeterClient(Channel channel) {
// 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
// shut it down.

// Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

/**
* Say hello to server.
*/
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
Expand All @@ -53,69 +35,33 @@ public void greet(String name) {
logger.info("Greeting: " + response.getMessage());
}

/**
* Greet server. If provided, the first element of {@code args} is the name to use in the greeting. The second argument is
* the target server.
*/
public static void main(String[] args) throws Exception {
String user = "world";
// Access a service running on the local machine on port 50051
String target = "localhost:9555";
String ssl = "none";
ManagedChannel channel = null;
String sslArg = "none";
String name = "world";

// Allow passing in the user and target strings as command line arguments
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [name ssl [target]]");
System.err.println("");
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
System.err.println(" ssl none, oneway, or twoway");
System.err.println(" target The server to connect to. Defaults to " + target);
for (int i = 0; i < args.length; i++) {
if ("--help".equals(args[i]) || "-h".equals(args[i])) {
System.err.println("Usage: [options] [name]");
System.err.println(" name name to greet (default: world)");
System.err.println(" --ssl=<mode> none, oneway, or twoway (default: none)");
System.exit(1);
} else if (args[i].startsWith("--ssl=")) {
sslArg = args[i].substring("--ssl=".length());
} else if (args[i].startsWith("-")) {
System.err.println("Unknown option: " + args[i]);
System.err.println("Try --help for usage.");
System.exit(1);
} else {
name = args[i];
}
user = args[0];
ssl = args[1];
}
if (args.length > 2) {
target = args[2];
}

ClassLoader classLoader = GreeterClient.class.getClassLoader();
if ("none".equals(ssl)) {
channel = ManagedChannelBuilder.forTarget(target)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext().build();
} else if ("oneway".equals(ssl)) {
try (InputStream trustStore = classLoader.getResourceAsStream("client.truststore.pem")) {
ChannelCredentials creds = TlsChannelCredentials.newBuilder().trustManager(trustStore).build();
channel = Grpc.newChannelBuilderForAddress("localhost", 9555, creds).build();
}
} else if ("twoway".equals(ssl)) {
KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
try (InputStream clientKeyStoreStream = classLoader.getResourceAsStream("client.keystore.p12")) {
clientKeyStore.load(clientKeyStoreStream, "secret".toCharArray());
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientKeyStore, "secret".toCharArray());
try (InputStream trustStore = classLoader.getResourceAsStream("client.truststore.pem")) {
ChannelCredentials creds = TlsChannelCredentials.newBuilder().trustManager(trustStore)
.keyManager(kmf.getKeyManagers())
.build();
channel = Grpc.newChannelBuilderForAddress("localhost", 9555, creds).build();
}
} else {
System.err.println("unrecognized ssl value: " + ssl);
}

SslMode sslMode = SslMode.fromString(sslArg);
ManagedChannel channel = sslMode.createChannel();
try {
GreeterClient client = new GreeterClient(channel);
client.greet(user);
client.greet(name);
} finally {
// ManagedChannels use resources like threads and TCP connections. To prevent leaking these
// resources the channel should be shut down when it will no longer be used. If it may be used
// again leave it running.
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/
package org.wildfly.extension.grpc.example.helloworld;

import java.io.InputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;

import io.grpc.ChannelCredentials;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.TlsChannelCredentials;

public enum SslMode {

NONE("none"),
ONEWAY("oneway"),
TWOWAY("twoway");

private static final String HOST = "localhost";
private static final int PORT = 9555;
private static final String TARGET = HOST + ":" + PORT;

private final String label;

SslMode(String label) {
this.label = label;
}

public static SslMode fromString(String value) {
for (SslMode mode : values()) {
if (mode.label.equals(value)) {
return mode;
}
}
throw new IllegalArgumentException("Unrecognized ssl value: " + value
+ ". Use none, oneway, or twoway.");
}

public ManagedChannel createChannel() {
try {
ClassLoader classLoader = SslMode.class.getClassLoader();
switch (this) {
case NONE:
return ManagedChannelBuilder.forTarget(TARGET)
.usePlaintext().build();
case ONEWAY:
try (InputStream trustStore = classLoader.getResourceAsStream("client.truststore.pem")) {
ChannelCredentials creds = TlsChannelCredentials.newBuilder().trustManager(trustStore).build();
return Grpc.newChannelBuilderForAddress(HOST, PORT, creds).build();
}
case TWOWAY:
KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
try (InputStream clientKeyStoreStream = classLoader.getResourceAsStream("client.keystore.p12")) {
clientKeyStore.load(clientKeyStoreStream, "secret".toCharArray());
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientKeyStore, "secret".toCharArray());
try (InputStream trustStore = classLoader.getResourceAsStream("client.truststore.pem")) {
ChannelCredentials creds = TlsChannelCredentials.newBuilder().trustManager(trustStore)
.keyManager(kmf.getKeyManagers())
.build();
return Grpc.newChannelBuilderForAddress(HOST, PORT, creds).build();
}
default:
throw new IllegalStateException("Unexpected ssl mode: " + this);
}
} catch (Exception e) {
throw new RuntimeException("Failed to create gRPC channel for ssl mode: " + this, e);
}
}

@Override
public String toString() {
return label;
}
}
Loading