Add Supplier Exporter sample

This commit is contained in:
Oleg Zhurakousky
2020-05-07 10:28:25 +02:00
parent 07e4de71d2
commit 5aeb77a073
12 changed files with 460 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
AWS Lambda custom runtime.
```
$ ./build.sh
$ ./mvnw package -P native
```
builds a native-zip ZIP file in target. Upload it to AWS and set the handler to "foobar".
To test locally, run the `TestServer` and then the `DemoApplication` (either in a JVM or natively). Then POST some data into the test server:
```
$ curl localhost:8000/add -d world -H "Content-Type: text/plain"
```
There is a unit test that does the same thing. Also the `build.sh` script orchestrates the same test for the native image.

View File

@@ -0,0 +1,5 @@
# Useful for running locally (hence not in src/main/resources)
spring.cloud.function.web.export.source.url=http://localhost:8000/home
spring.cloud.function.web.export.sink.url=http://localhost:8000/echo
spring.cloud.function.web.export.debug=true
logging.level.org.springframework=DEBUG

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
BLUE='\033[0;34m'
NC='\033[0m'
printf "=== ${BLUE}Building %s sample${NC} ===\n" "${PWD##*/}"
./compile.sh || exit 1
JARDIR=target/native-image
java -cp $JARDIR/BOOT-INF/lib/*:$JARDIR/BOOT-INF/classes:$JARDIR:target/test-classes com.example.test.TestServer &
SPID=$!
sleep 5
${PWD%/*samples/*}/scripts/test.sh --spring.cloud.function.web.export.source.url=http://localhost:8000/home --spring.cloud.function.web.export.sink.url=http://localhost:8000/echo
kill $SPID

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
ARTIFACT=function-aws
MAINCLASS=com.example.demo.DemoApplication
VERSION=0.0.1-SNAPSHOT
FEATURE=../../../../spring-graal-native/target/spring-graal-native-0.7.0.BUILD-SNAPSHOT.jar
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
rm -rf target
mkdir -p target/native-image
echo "Packaging $ARTIFACT with Maven"
mvn -DskipTests package > target/native-image/output.txt
JAR="$ARTIFACT-$VERSION.jar"
rm -f $ARTIFACT
echo "Unpacking $JAR"
cd target/native-image
jar -xvf ../$JAR >/dev/null 2>&1
cp -R META-INF BOOT-INF/classes
LIBPATH=`find BOOT-INF/lib | tr '\n' ':'`
CP=BOOT-INF/classes:$LIBPATH:$FEATURE
if [ ! -f "$FEATURE" ]; then
printf "${RED}FAILURE${NC}: $FEATURE does not exist, please build the root project before building this sample.\n"
exit 1
fi
GRAALVM_VERSION=`native-image --version`
echo "Compiling $ARTIFACT with $GRAALVM_VERSION"
{ time native-image \
--verbose \
--no-server \
--no-fallback \
--initialize-at-build-time \
-H:+PrintMethodHistogram \
-H:+TraceClassInitialization \
-H:Name=$ARTIFACT \
-H:+ReportExceptionStackTraces \
-Dspring.graal.remove-unused-autoconfig=true \
-Dspring.graal.remove-yaml-support=true \
-cp $CP $MAINCLASS >> output.txt ; } 2>> output.txt
if [[ -f $ARTIFACT ]]
then
printf "${GREEN}SUCCESS${NC}\n"
mv ./$ARTIFACT ..
exit 0
else
cat output.txt
printf "${RED}FAILURE${NC}: an error occurred when compiling the native-image.\n"
exit 1
fi

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.M4</version>
<relativePath />
</parent>
<groupId>com.example</groupId>
<artifactId>function-sample-aws-supplier-exporter</artifactId>
<version>1.0.0.RELEASE</version>
<name>function-sample-aws-supplier-exporter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud-function.version>3.1.0.BUILD-SNAPSHOT</spring-cloud-function.version>
<reactor-bom.version>Dysprosium-SR6</reactor-bom.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http2</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-dependencies</artifactId>
<version>${spring-cloud-function.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshot</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,55 @@
package com.example.demo;
import java.util.function.Function;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.cloud.function.context.FunctionalSpringApplication;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
@SpringBootConfiguration(proxyBeanMethods = false)
public class DemoApplication
implements ApplicationContextInitializer<GenericApplicationContext> {
public static void main(String[] args) {
FunctionalSpringApplication.run(DemoApplication.class, args);
}
@Override
public void initialize(GenericApplicationContext context) {
context.registerBean("foobar", FunctionRegistration.class,
() -> new FunctionRegistration<>(new Foobar())
.type(FunctionType.from(Foo.class).to(Foo.class)));
}
}
class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Foo(String name) {
this.name = name;
}
Foo() {
}
}
class Foobar implements Function<Foo, Foo> {
@Override
public Foo apply(Foo input) {
System.err.println("HI: " + input.getName());
return new Foo("hi " + input.getName() + "!");
}
}

View File

@@ -0,0 +1,7 @@
[
{
"name": "com.example.demo.Foo",
"allDeclaredConstructors": true,
"allDeclaredMethods": true
}
]

View File

@@ -0,0 +1,4 @@
spring.cloud.function.web.export.enabled=true
spring.cloud.function.web.export.debug=true
spring.main.web-application-type=none
logging.level.org.springframework.cloud=DEBUG

View File

@@ -0,0 +1,55 @@
package com.example.demo;
import com.example.test.TestServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.function.context.test.FunctionalSpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.SocketUtils;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
@FunctionalSpringBootTest({"spring.cloud.function.web.export.source.url=http://localhost:${export.port}/home",
"spring.cloud.function.web.export.sink.url=http://localhost:${export.port}/echo",
"logging.level.reactor=OFF",
"logging.level.io.netty=OFF"})
public class DemoApplicationTests {
static ConfigurableApplicationContext context;
@Value("${export.port}")
private int port;
@Autowired
private WebClient.Builder builder;
@Test
public void contextLoads() throws Exception {
WebClient client = builder.baseUrl("http://localhost:" + port).build();
client.post().uri("/add").bodyValue("{\"name\":\"Fred\"}").exchange().block();
Thread.sleep(1000L);
String response = client.get().uri("/take").exchange().block().bodyToMono(String.class).block();
assertThat(response).isEqualTo("{\"name\":\"hi Fred!\"}");
}
@AfterAll
static void after() {
if (context != null) {
context.close();
}
}
@BeforeAll
static void before() {
int port = SocketUtils.findAvailableTcpPort();
System.setProperty("export.port", "" + port);
context = SpringApplication.run(TestServer.class, "--server.port="+port, "--spring.cloud.function.web.export.enabled=false", "--spring.main.web-application-type=reactive");
}
}

View File

@@ -0,0 +1,85 @@
package com.example.test;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.function.json.JsonMapper;
import org.springframework.context.annotation.Bean;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
@SpringBootApplication(proxyBeanMethods = false)
public class TestServer {
private MonoProcessor<String> output = MonoProcessor.<String>create();
private String response = "";
public static void main(String[] args) {
Set<String> list = new LinkedHashSet<>(Arrays.asList(args));
list.addAll(Arrays.asList("--server.port=8000", "--spring.cloud.function.web.export.enabled=false", "--spring.main.web-application-type=reactive"));
SpringApplication.run(TestServer.class, list.toArray(new String[0]));
}
@Bean
public Supplier<Mono<String>> home() {
return () -> output;
}
// @Bean
// public Function<String, String> echo(JsonMapper mapper) {
// return input -> {
// response = input;
// return "Echo: " + response;
// };
// }
@Bean
public Function<Foo, String> echo(JsonMapper mapper) {
return input -> {
System.out.println("===> POJO " + input);
response = new String(mapper.toJson(input));
return "Echo: " + response;
};
}
@Bean
public Function<String, String> add() {
return input -> {
System.err.println("Add: " + input);
output.onNext(input);
output = MonoProcessor.<String>create();
return "Added: " + input;
};
}
@Bean
public Supplier<String> take() {
return () -> response;
}
}
class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Foo(String name) {
this.name = name;
}
Foo() {}
}

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
curl -s localhost:8000/add -d '{"name":"world"}' -H "Content-Type: text/plain"
echo
echo Waiting...
sleep 1
RESPONSE=`curl -s localhost:8000/take`
echo Got response: $RESPONSE
if [[ "$RESPONSE" == '{"name":"hi world!"}' ]]; then
exit 0
else
exit 1
fi

View File

@@ -19,6 +19,7 @@
<module>function-sample-pojo</module>
<module>function-sample-aws</module>
<module>function-sample-aws-custom</module>
<module>function-sample-supplier-exporter</module>
<module>function-sample-azure</module>
<!--><module>function-sample-spring-integration</module>-->
<module>function-sample-gcp</module>