Starting package to handle SpringBoot integration. (#69)

This commit is contained in:
Artur Souza 2020-01-07 14:22:30 -08:00 committed by GitHub
parent d4c467aec5
commit 30b784cb86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 313 additions and 217 deletions

View File

@ -7,38 +7,29 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
</parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-examples</artifactId>
<packaging>jar</packaging>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
<name>dapr-sdk-examples</name>
<properties>
<protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory>
<protobuf.input.directory>${project.parent.basedir}/proto</protobuf.input.directory>
<protobuf.input.directory>${project.basedir}/proto</protobuf.input.directory>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<version>2.0.26.Final</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
@ -47,18 +38,27 @@
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-testing</artifactId>
<version>${grpc.version}</version>
<scope>test</scope>
</dependency>
<dependency>
@ -71,10 +71,25 @@
<artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.10.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-springboot</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk</artifactId>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
</dependency>
</dependencies>
@ -96,7 +111,7 @@
<includeMavenTypes>direct</includeMavenTypes>
<includeStdTypes>true</includeStdTypes>
<inputDirectories>
<include>${protobuf.input.directory}/examples</include>
<include>${protobuf.input.directory}</include>
</inputDirectories>
<outputTargets>
<outputTarget>

View File

@ -5,29 +5,14 @@
package io.dapr.examples.actors.http;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.actors.runtime.ActorRuntime;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.RoutingHandler;
import io.undertow.util.Headers;
import io.dapr.springboot.DaprApplication;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Deque;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Service for Actor runtime.
@ -36,193 +21,26 @@ import java.util.Map;
* 2. Run the server:
* dapr run --app-id demoactorservice --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorService -Dexec.args="-p 3000"
*/
@SpringBootApplication
public class DemoActorService {
private static final JsonFactory JSON_FACTORY = new JsonFactory();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final HttpHandler ROUTES = new RoutingHandler()
.get("/", DemoActorService::handleDaprConfig)
.get("/dapr/config", DemoActorService::handleDaprConfig)
.post("/actors/{actorType}/{id}", DemoActorService::handleActorActivate)
.delete("/actors/{actorType}/{id}", DemoActorService::handleActorDeactivate)
.put("/actors/{actorType}/{id}/method/{methodName}", DemoActorService::handleActorInvoke)
.put("/actors/{actorType}/{id}/method/timer/{timerName}", DemoActorService::handleActorTimer)
.put("/actors/{actorType}/{id}/method/remind/{reminderName}", DemoActorService::handleActorReminder);
private final int port;
private final Undertow server;
private DemoActorService(int port) {
this.port = port;
this.server = Undertow
.builder()
.addHttpListener(port, "localhost")
.setHandler(ROUTES)
.build();
ActorRuntime.getInstance().registerActor(DemoActorImpl.class);
}
private void start() {
// Now we handle ctrl+c (or any other JVM shutdown)
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("Server: shutting down gracefully ...");
DemoActorService.this.server.stop();
System.out.println("Server: Bye.");
}
});
System.out.println(String.format("Server: listening on port %d ...", this.port));
this.server.start();
}
private static void handleDaprConfig(HttpServerExchange exchange) throws IOException {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
String result = "";
try (Writer writer = new StringWriter()) {
JsonGenerator generator = JSON_FACTORY.createGenerator(writer);
generator.writeStartObject();
generator.writeArrayFieldStart("entities");
for(String actorClass : ActorRuntime.getInstance().getRegisteredActorTypes()) {
generator.writeString(actorClass);
}
generator.writeEndArray();
generator.writeStringField("actorIdleTimeout", "10s");
generator.writeStringField("actorScanInterval", "1s");
generator.writeStringField("drainOngoingCallTimeout", "1s");
generator.writeBooleanField("drainBalancedActors", true);
generator.writeEndObject();
generator.close();
writer.flush();
result = writer.toString();
}
exchange.getResponseSender().send(result);
}
private static void handleActorActivate(HttpServerExchange exchange) {
if (exchange.isInIoThread()) {
exchange.dispatch(DemoActorService::handleActorActivate);
return;
}
String actorType = findParamValueOrNull(exchange, "actorType");
String actorId = findParamValueOrNull(exchange, "id");
ActorRuntime.getInstance().activate(actorType, actorId).block();
exchange.getResponseSender().send("");
}
private static void handleActorDeactivate(HttpServerExchange exchange) {
if (exchange.isInIoThread()) {
exchange.dispatch(DemoActorService::handleActorDeactivate);
return;
}
String actorType = findParamValueOrNull(exchange, "actorType");
String actorId = findParamValueOrNull(exchange, "id");
ActorRuntime.getInstance().deactivate(actorType, actorId).block();
}
private static void handleActorInvoke(HttpServerExchange exchange) throws IOException {
if (exchange.isInIoThread()) {
exchange.dispatch(DemoActorService::handleActorInvoke);
return;
}
String actorType = findParamValueOrNull(exchange, "actorType");
String actorId = findParamValueOrNull(exchange, "id");
String methodName = findParamValueOrNull(exchange, "methodName");
exchange.startBlocking();
String data = findMethodData(exchange.getInputStream());
String result = ActorRuntime.getInstance().invoke(actorType, actorId, methodName, data).block();
exchange.getResponseSender().send(buildResponse(result));
}
private static void handleActorTimer(HttpServerExchange exchange) throws IOException {
if (exchange.isInIoThread()) {
exchange.dispatch(DemoActorService::handleActorTimer);
return;
}
String actorType = findParamValueOrNull(exchange, "actorType");
String actorId = findParamValueOrNull(exchange, "id");
String timerName = findParamValueOrNull(exchange, "timerName");
ActorRuntime.getInstance().invokeTimer(actorType, actorId, timerName).block();
exchange.getResponseSender().send("");
}
private static void handleActorReminder(HttpServerExchange exchange) throws IOException {
if (exchange.isInIoThread()) {
exchange.dispatch(DemoActorService::handleActorReminder);
return;
}
String actorType = findParamValueOrNull(exchange, "actorType");
String actorId = findParamValueOrNull(exchange, "id");
String reminderName = findParamValueOrNull(exchange, "reminderName");
exchange.startBlocking();
String params = IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8);
ActorRuntime.getInstance().invokeReminder(actorType, actorId, reminderName, params).block();
exchange.getResponseSender().send("");
}
private static String findParamValueOrNull(HttpServerExchange exchange, String name) {
Map<String, Deque<String>> params = exchange.getQueryParameters();
if (params == null) {
return null;
}
Deque<String> values = params.get(name);
if ((values == null) || (values.isEmpty())) {
return null;
}
return values.getFirst();
}
private static String findMethodData(InputStream stream) throws IOException {
JsonNode root = OBJECT_MAPPER.readTree(stream);
if (root == null) {
return null;
}
JsonNode dataNode = root.get("data");
if (dataNode == null) {
return null;
}
return new String(dataNode.binaryValue(), StandardCharsets.UTF_8);
}
private static String buildResponse(String data) throws IOException {
try (Writer writer = new StringWriter()) {
JsonGenerator generator = JSON_FACTORY.createGenerator(writer);
generator.writeStartObject();
if (data != null) {
generator.writeBinaryField("data", data.getBytes());
}
generator.writeEndObject();
generator.close();
writer.flush();
return writer.toString();
}
}
public static void main(String[] args) throws Exception {
Options options = new Options();
options.addRequiredOption("p", "port", true, "Port to listen to.");
options.addRequiredOption("p", "port", true, "Port Dapr will listen to.");
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
// If port string is not valid, it will throw an exception.
int port = Integer.parseInt(cmd.getOptionValue("port"));
final DemoActorService service = new DemoActorService(port);
service.start();
// Register the Actor class.
ActorRuntime.getInstance().registerActor(DemoActorImpl.class);
// Start Dapr's callback endpoint.
DaprApplication.start(port);
// Start application's endpoint.
SpringApplication.run(DemoActorService.class);
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.examples.actors.http;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/")
public String index() {
return "Greetings from your Spring Boot Application!";
}
}

View File

@ -7,7 +7,7 @@
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<packaging>pom</packaging>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
<name>dapr-sdk-parent</name>
<description>SDK for Dapr.</description>
<url>https://dapr.io</url>
@ -33,6 +33,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
@ -79,6 +84,7 @@
<modules>
<module>sdk-autogen</module>
<module>sdk</module>
<module>sdk-springboot</module>
<module>examples</module>
</modules>

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
</parent>
<artifactId>dapr-sdk-autogen</artifactId>
<packaging>jar</packaging>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
<name>dapr-sdk-autogen</name>
<description>Auto-generated SDK for Dapr</description>

64
sdk-springboot/pom.xml Normal file
View File

@ -0,0 +1,64 @@
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>0.2.0</version>
</parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-springboot</artifactId>
<packaging>jar</packaging>
<version>0.2.0</version>
<name>dapr-sdk-springboot</name>
<properties>
<java.version>8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.Properties;
/**
* Dapr's callback implementation via SpringBoot.
*/
@SpringBootApplication
public class DaprApplication {
/**
* Starts Dapr's callback in a given port.
* @param port Port to listen to.
*/
public static void start(int port) {
SpringApplication app = new SpringApplication(DaprApplication.class);
Properties properties = new Properties();
properties.setProperty("server.port", Integer.toString(port));
app.setDefaultProperties(properties);
app.run();
}
/**
* Main for SpringBoot requirements.
* @param args Command line arguments.
*/
public static void main(String[] args) {
SpringApplication.run(DaprApplication.class, args);
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.springboot;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.actors.runtime.ActorRuntime;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
/**
* SpringBoot Controller to handle callback APIs for Dapr.
* TODO: use POJOs instead of String when possible.
* TODO: JavaDocs.
*/
@RestController
public class DaprController {
private static final JsonFactory JSON_FACTORY = new JsonFactory();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@RequestMapping("/")
public String index() {
return "Greetings from Dapr!";
}
@RequestMapping("/dapr/config")
public String daprConfig() throws Exception {
try (Writer writer = new StringWriter()) {
JsonGenerator generator = JSON_FACTORY.createGenerator(writer);
generator.writeStartObject();
generator.writeArrayFieldStart("entities");
for (String actorClass : ActorRuntime.getInstance().getRegisteredActorTypes()) {
generator.writeString(actorClass);
}
generator.writeEndArray();
generator.writeStringField("actorIdleTimeout", "10s");
generator.writeStringField("actorScanInterval", "1s");
generator.writeStringField("drainOngoingCallTimeout", "1s");
generator.writeBooleanField("drainBalancedActors", true);
generator.writeEndObject();
generator.close();
writer.flush();
return writer.toString();
}
}
@RequestMapping(method = RequestMethod.POST, path = "/actors/{type}/{id}")
public Mono<Void> activateActor(@PathVariable("type") String type,
@PathVariable("id") String id) throws Exception {
return ActorRuntime.getInstance().activate(type, id);
}
@RequestMapping(method = RequestMethod.DELETE, path = "/actors/{type}/{id}")
public Mono<Void> deactivateActor(@PathVariable("type") String type,
@PathVariable("id") String id) throws Exception {
return ActorRuntime.getInstance().deactivate(type, id);
}
@RequestMapping(method = RequestMethod.PUT, path = "/actors/{type}/{id}/method/{method}")
public Mono<String> invokeActorMethod(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("method") String method,
@RequestBody(required = false) String body) {
try {
String data = findMethodData(body);
return ActorRuntime.getInstance().invoke(type, id, method, data).map(r -> buildResponse(r));
} catch (Exception e) {
return Mono.error(e);
}
}
@RequestMapping(method = RequestMethod.PUT, path = "/actors/{type}/{id}/method/timer/{timer}")
public Mono<Void> invokeActorTimer(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("timer") String timer) {
return ActorRuntime.getInstance().invokeTimer(type, id, timer);
}
@RequestMapping(method = RequestMethod.PUT, path = "/actors/{type}/{id}/method/remind/{reminder}")
public Mono<Void> invokeActorReminder(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("reminder") String reminder,
@RequestBody(required = false) String body) {
return ActorRuntime.getInstance().invokeReminder(type, id, reminder, body);
}
private static String findMethodData(String body) throws IOException {
if (body == null) {
return null;
}
JsonNode root = OBJECT_MAPPER.readTree(body);
if (root == null) {
return null;
}
JsonNode dataNode = root.get("data");
if (dataNode == null) {
return null;
}
return new String(dataNode.binaryValue(), StandardCharsets.UTF_8);
}
private static String buildResponse(String data) throws RuntimeException {
try {
try (Writer writer = new StringWriter()) {
JsonGenerator generator = JSON_FACTORY.createGenerator(writer);
generator.writeStartObject();
if (data != null) {
generator.writeBinaryField("data", data.getBytes());
}
generator.writeEndObject();
generator.close();
writer.flush();
return writer.toString();
}
} catch (IOException e) {
// Make Mono happy.
throw new RuntimeException(e);
}
}
}

View File

@ -7,12 +7,12 @@
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
</parent>
<artifactId>dapr-sdk</artifactId>
<packaging>jar</packaging>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
<name>dapr-sdk</name>
<description>SDK for Dapr</description>
@ -20,7 +20,7 @@
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-autogen</artifactId>
<version>0.2.0-preview01</version>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>