Add new feature to initialize a Supplier from a remote HTTP endpoint

Kind of like the SupplierExporter but to create the Supplier itself.
With this in place you can define the templateUrl (destination) and
the originaUrl (source) and use the app as a pipeline for events
from/to HTTP.

Provide functional bean support for HTTP export

Add autoconfig to AWS adapter for custom runtime

Fix HttpSupplier to always supply Message if headers are included

Fix registration of origin supplier in functional beans

Add docs on new AWS features

Add custom runtime sample
This commit is contained in:
Dave Syer
2019-02-13 06:26:12 -06:00
committed by Oleg Zhurakousky
parent cdca44f714
commit 428243ce48
26 changed files with 1239 additions and 42 deletions

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.test;
import java.util.function.Function;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.cloud.function.context.test.FunctionalSpringBootTest;
import org.springframework.cloud.function.test.FunctionalExporterTests.ApplicationConfiguration;
import org.springframework.cloud.function.web.source.SupplierExporter;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*
*/
@RunWith(SpringRunner.class)
@FunctionalSpringBootTest(classes = ApplicationConfiguration.class, webEnvironment = WebEnvironment.NONE, properties = {
"spring.main.web-application-type=none",
"spring.cloud.function.web.export.sink.url=http://localhost:${my.port}",
"spring.cloud.function.web.export.source.url=http://localhost:${my.port}",
"spring.cloud.function.web.export.sink.name=origin|uppercase",
"spring.cloud.function.web.export.debug=true",
"spring.cloud.function.web.export.enabled=true" })
public class FunctionalExporterTests {
@Autowired
private SupplierExporter forwarder;
private static RestConfiguration app;
private static ConfigurableApplicationContext context;
@BeforeClass
public static void init() throws Exception {
String port = "" + SocketUtils.findAvailableTcpPort();
System.setProperty("server.port", port);
System.setProperty("my.port", port);
context = SpringApplication.run(RestConfiguration.class,
"--spring.main.web-application-type=reactive");
app = context.getBean(RestConfiguration.class);
// Sometimes the server doesn't start quick enough
Thread.sleep(500L);
}
@AfterClass
public static void close() {
System.clearProperty("server.port");
if (context != null) {
context.close();
}
}
@Test
public void words() throws Exception {
int count = 0;
while (this.forwarder.isRunning() && count++ < 1000) {
Thread.sleep(20);
}
// It completed
assertThat(this.forwarder.isRunning()).isFalse();
assertThat(FunctionalExporterTests.app.inputs).contains("HELLO");
assertThat(this.forwarder.isOk()).isTrue();
}
@SpringBootConfiguration
protected static class ApplicationConfiguration
implements ApplicationContextInitializer<GenericApplicationContext> {
Function<Message<String>, Message<String>> uppercase() {
return value -> MessageBuilder.withPayload(value.getPayload().toUpperCase())
.copyHeaders(value.getHeaders()).build();
}
@Override
public void initialize(GenericApplicationContext context) {
context.registerBean("uppercase", FunctionRegistration.class,
() -> new FunctionRegistration<>(uppercase()).type(
FunctionType.from(String.class).to(String.class).message()));
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2018-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@SpringBootConfiguration
@EnableAutoConfiguration
@RestController
public class RestConfiguration {
private static Log logger = LogFactory.getLog(RestConfiguration.class);
List<String> inputs = new ArrayList<>();
private Iterator<String> outputs = Arrays.asList("hello", "world").iterator();
@GetMapping("/")
ResponseEntity<String> home() {
logger.info("HOME");
if (this.outputs.hasNext()) {
return ResponseEntity.ok(this.outputs.next());
}
return ResponseEntity.notFound().build();
}
@PostMapping("/")
ResponseEntity<String> accept(@RequestBody String body) {
logger.info("ACCEPT");
this.inputs.add(body);
return ResponseEntity.accepted().body(body);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(RestConfiguration.class, args);
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2018-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.web.source;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.cloud.function.web.source.FunctionAutoConfigurationIntegrationTests.ApplicationConfiguration;
import org.springframework.cloud.function.web.source.FunctionAutoConfigurationIntegrationTests.RestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, properties = {
"spring.cloud.function.web.export.sink.url=http://localhost:${server.port}",
"spring.cloud.function.web.export.source.url=http://localhost:${server.port}",
"spring.cloud.function.web.export.sink.name=origin|uppercase",
"spring.cloud.function.web.export.debug=true",
"spring.cloud.function.web.export.enabled=true" })
@ContextConfiguration(classes = { RestConfiguration.class,
ApplicationConfiguration.class })
public class FunctionAutoConfigurationIntegrationTests {
@Autowired
private SupplierExporter forwarder;
@Autowired
private RestConfiguration app;
@BeforeClass
public static void init() {
System.setProperty("server.port", "" + SocketUtils.findAvailableTcpPort());
}
@AfterClass
public static void close() {
System.clearProperty("server.port");
}
@Test
public void copiesMessages() throws Exception {
int count = 0;
while (this.forwarder.isRunning() && count++ < 100) {
Thread.sleep(20);
}
// It completed
assertThat(this.forwarder.isRunning()).isFalse();
assertThat(this.forwarder.isOk()).isTrue();
assertThat(this.app.inputs).contains("HELLO");
assertThat(this.app.inputs).contains("WORLD");
}
@EnableAutoConfiguration
@TestConfiguration
public static class ApplicationConfiguration {
@Bean
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
}
@TestConfiguration
@RestController
public static class RestConfiguration {
private static Log logger = LogFactory.getLog(RestConfiguration.class);
private List<String> inputs = new ArrayList<>();
private Iterator<String> outputs = Arrays.asList("hello", "world").iterator();
@GetMapping("/")
ResponseEntity<String> home() {
logger.info("HOME");
if (this.outputs.hasNext()) {
return ResponseEntity.ok(this.outputs.next());
}
return ResponseEntity.notFound().build();
}
@PostMapping("/")
void accept(@RequestBody String body) {
logger.info("ACCEPT");
this.inputs.add(body);
}
}
}

View File

@@ -26,7 +26,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.cloud.function.web.RestApplication;
import org.springframework.cloud.function.web.source.SourceAutoConfigurationIntegrationTests.ApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ContextConfiguration;
@@ -39,9 +38,11 @@ import static org.assertj.core.api.Assertions.assertThat;
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.NONE, //
properties = "spring.cloud.function.web.supplier.templateUrl=http://localhost:9999/notthere")
@ContextConfiguration(classes = { RestApplication.class, ApplicationConfiguration.class })
// @formatter:off
@SpringBootTest(webEnvironment = WebEnvironment.NONE,
properties = "spring.cloud.function.web.export.sink.url=http://nosuchhost")
// @formatter:on
@ContextConfiguration(classes = { ApplicationConfiguration.class })
public class SourceAutoConfigurationIntegrationTests {
@Autowired

View File

@@ -24,6 +24,8 @@ import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,6 +39,7 @@ import org.springframework.cloud.function.web.source.WebAppIntegrationTests.Appl
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@@ -48,13 +51,13 @@ import static org.assertj.core.api.Assertions.assertThat;
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, properties = {
"spring.main.web-application-type=reactive",
"spring.cloud.function.web.supplier.templateUrl=http://localhost:${local.server.port}/values",
"spring.cloud.function.web.export.sink.url=http://localhost:${server.port}/values",
// in a webapp we have to explicitly enable the export
"spring.cloud.function.web.supplier.enabled=true",
"spring.cloud.function.web.export.enabled=true",
// manually so we know the webapp is listening when we start
"spring.cloud.function.web.supplier.autoStartup=false" })
"spring.cloud.function.web.export.autoStartup=false" })
@ContextConfiguration(classes = { RestApplication.class, ApplicationConfiguration.class })
public class WebAppIntegrationTests {
@@ -66,6 +69,16 @@ public class WebAppIntegrationTests {
@Autowired
private ApplicationConfiguration app;
@BeforeClass
public static void init() {
System.setProperty("server.port", "" + SocketUtils.findAvailableTcpPort());
}
@AfterClass
public static void close() {
System.clearProperty("server.port");
}
@Test
public void posts() throws Exception {
this.forwarder.start();