Merge branch 'dev' into kamperiadis/nullableMap

This commit is contained in:
Kirstyn Joy Amperiadis 2023-01-31 15:37:07 -06:00
Родитель 60bb55a767 1483b116af
Коммит 727323c655
17 изменённых файлов: 870 добавлений и 29 удалений

5
.gitignore поставляемый
Просмотреть файл

@ -273,3 +273,8 @@ pkg/
# Mac specific gitignore
.DS_Store
# Azure Functions
local.settings.json
bin/
obj/

39
samples/spring-cloud-example/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,39 @@
# Build output
target/
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# IDE
.idea/
*.iml
.settings/
.project
.classpath
.vscode/
# macOS
.DS_Store
# Azure Functions
local.settings.json
bin/
obj/

Просмотреть файл

@ -0,0 +1,11 @@
FROM springcloudstream/azure-functions-java17:1.0.0
COPY ./target/azure-functions /src/java-function-app
RUN mkdir -p /home/site/wwwroot && \
cd /src/java-function-app && \
cd $(ls -d */|head -n 1) && \
cp -a . /home/site/wwwroot
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
AzureFunctionsJobHost__Logging__Console__IsEnabled=true

Просмотреть файл

@ -0,0 +1,333 @@
== Running Locally `
You can run this Azure function locally, similar to other Spring Cloud Function samples, however
this time by using the Azure Maven plugin, as the Microsoft Azure functions execution context must be available.
NOTE: To run locally on top of Azure Functions, and to deploy to your live Azure environment, you will need the Azure Functions Core Tools installed along with the Azure CLI (see https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment[here] for details).
.Follow these steps to build and run locally:
[source,bash]
----
mvn clean package
mvn azure-functions:run
----
.console output
[source,bash]
----
[INFO] Azure Function App's staging directory found at: /Users/cbono/repos/spring-cloud-function/spring-cloud-function-samples/function-sample-azure/target/azure-functions/spring-cloud-function-samples
4.0.3971
[INFO] Azure Functions Core Tools found.
Azure Functions Core Tools
Core Tools Version: 4.0.3971 Commit hash: d0775d487c93ebd49e9c1166d5c3c01f3c76eaaf (64-bit)
Function Runtime Version: 4.0.1.16815
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 POST http://127.0.0.1:53836/AzureFunctionsRpcMessages.FunctionRpc/EventStream application/grpc -
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'gRPC - /AzureFunctionsRpcMessages.FunctionRpc/EventStream'
[2022-04-11T03:04:05.143Z] OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
[2022-04-11T03:04:05.247Z] Worker process started and initialized.
Functions:
echo: [GET,POST] http://localhost:7071/api/echo
echoStream: [GET,POST] http://localhost:7071/api/echoStream
uppercase: [GET,POST] http://localhost:7071/api/uppercase
uppercaseReactive: [GET,POST] http://localhost:7071/api/uppercaseReactive
For detailed output, run func with --verbose flag.
[2022-04-11T03:04:10.163Z] Host lock lease acquired by instance ID '000000000000000000000000BEFE21CF'.
----
.Test the _uppercase_ function using the following _curl_ command:
[source,bash]
----
curl -H "Content-Type: application/json" localhost:7071/api/uppercase -d '{"greeting": "hello", "name": "foo"}'
----
.curl response
[source,json]
----
{
"greeting": "HELLO",
"name": "FOO"
}
----
Notice that the URL is of the format `<function-base-url>/api/<function-name>`).
The `uppercase` function signature is `Function<Message<String>, String> uppercase()`. The implementation of `UppercaseHandler` (which extends `FunctionInvoker`) copies the HTTP headers of the incoming request into the input message's _MessageHeaders_ which makes them accessible to the function if needed.
NOTE: Implementation of `FunctionInvoker` (your handler), should contain the least amount of code. It is really a type-safe way to define
and configure function to be recognized as Azure Function.
Everything else should be delegated to the base `FunctionInvoker` via `handleRequest(..)` callback which will invoke your function, taking care of
necessary type conversion, transformation etc. One exception to this rule is when custom result handling is required. In that case, the proper post-process method can be overridden as well in order to take control of the results processing.
.UppercaseHandler.java
[source,java]
----
@FunctionName("uppercase")
public String execute(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET, HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context
) {
Message<String> message = MessageBuilder.withPayload(request.getBody().get())
.copyHeaders(request.getHeaders()).build();
return handleRequest(message, context);
}
----
The `echo` function does the same as the `uppercase` less the actual uppercasing. However, the important difference to notice is that function itself
takes primitive `String` as its input (i.e., `public Function<String, String> echo()`) while the actual handler passes instance of `Message` the same way as with `uppercase`. The framework recognizes that you only care about the payload and extracts it from the `Message` before calling the function.
There is also a reactive version of _uppercase_ (named _uppercaseReactive_) which will produce the same result, but
demonstrates and validates the ability to use reactive functions with Azure.
== Running on Azure
NOTE: The Azure Java functions runtime does not yet support Java 17 but Spring Cloud Function 4.x requires it. To get around this limitation we deploy to Azure in a custom Docker container. Once https://github.com/Azure/azure-functions-java-worker/issues/548[Azure supports] Java 17 we can move back to using non-Docker deployments.
==== Custom Docker Image
The steps below describe the process to create a custom Docker image which is suitable for deployment on Azure and contains the 4.x Azure Functions runtime, the MS Java 17 JVM, and the sample functions in this repo.
====== Image name
Pick an image name for the Docker container (eg. `onobc/function-sample-azure-java17:1.0.0`) and update the _pom.xml_ `functionDockerImageName` property with the image name.
TIP: By default it is expected that the image name is a publicly accessible image on Docker Hub. However, other registries and credentials can be configured as described https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supporte-runtime[here].
.Rebuild the functions (pom.xml was updated):
[source,bash]
----
mvn clean package
----
.Build the Docker image:
[source,bash]
----
docker build -t <image-name> .
----
Test the Docker image locally by starting the container and issuing a request.
.Start the function runtime locally in Docker:
[source,bash]
----
docker run -p 8080:80 <image-name>
----
.console output
[source,bash]
----
cbono@cbono-a01 function-sample-azure % docker run -p 8080:80 onobc/function-sample-azure-java17:1.0.0
info: Host.Triggers.Warmup[0]
Initializing Warmup Extension.
info: Host.Startup[503]
Initializing Host. OperationId: 'e7317c18-4daa-4d69-bf38-beaa51e1a012'.
info: Host.Startup[504]
Host initialization: ConsecutiveErrors=0, StartupCount=1, OperationId=e7317c18-4daa-4d69-bf38-beaa51e1a012
info: Microsoft.Azure.WebJobs.Hosting.OptionsLoggingService[0]
LoggerFilterOptions
{
"MinLevel": "None",
"Rules": [
{
"ProviderName": null,
"CategoryName": null,
"LogLevel": null,
"Filter": "<AddFilter>b__0"
},
{
"ProviderName": "Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.SystemLoggerProvider",
"CategoryName": null,
"LogLevel": "None",
"Filter": null
},
{
"ProviderName": "Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.SystemLoggerProvider",
"CategoryName": null,
"LogLevel": null,
"Filter": "<AddFilter>b__0"
}
]
}
...
...
...
info: Microsoft.Azure.WebJobs.Script.WebHost.WebScriptHostHttpRoutesManager[0]
Initializing function HTTP routes
Mapped function route 'api/echo' [GET,POST] to 'echo'
Mapped function route 'api/echoStream' [GET,POST] to 'echoStream'
Mapped function route 'api/uppercase' [GET,POST] to 'uppercase'
Mapped function route 'api/uppercaseReactive' [GET,POST] to 'uppercaseReactive'
info: Host.Startup[412]
Host initialized (65ms)
info: Host.Startup[413]
Host started (81ms)
info: Host.Startup[0]
Job host started
Hosting environment: Production
Content root path: /azure-functions-host
Now listening on: http://[::]:80
Application started. Press Ctrl+C to shut down.
info: Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcFunctionInvocationDispatcher[0]
Worker process started and initialized.
info: Host.General[337]
Host lock lease acquired by instance ID '000000000000000000000000C4043012'.
----
.Test the _uppercase_ function using the following _curl_ command:
[source,bash]
----
curl -H "Content-Type: application/json" localhost:8080/api/uppercase -d '{"greeting": "hello", "name": "foo"}'
----
.curl response
[source,json]
----
{
"greeting": "HELLO",
"name": "FOO"
}
----
.Push the image to Docker registry:
[source,bash]
----
docker push <image-name>
----
At this point the custom image has been created and pushed to the configured Docker registry.
==== Deploy to Azure
To deploy the functions to your live Azure environment, including automatic provisioning of an _HTTPTrigger_ for each function, do the following.
.Login to Azure:
[source,bash]
----
az login
----
.Deploy to Azure:
[source,bash]
----
mvn azure-functions:deploy
----
.console output
[source,bash]
----
[INFO] ---------------< io.spring.sample:function-sample-azure >---------------
[INFO] Building function-sample-azure 4.0.0.RELEASE
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- azure-functions-maven-plugin:1.16.0:deploy (default-cli) @ function-sample-azure ---
Auth type: AZURE_CLI
Default subscription: SCDF-Azure(b80d18******)
Username: cbono@vmware.com
[INFO] Subscription: SCDF-Azure(*******)
[INFO] Reflections took 123 ms to scan 6 urls, producing 24 keys and 486 values
[INFO] Start creating Resource Group(java-functions-group) in region (West US)...
[INFO] Resource Group(java-functions-group) is successfully created.
[INFO] Reflections took 1 ms to scan 3 urls, producing 12 keys and 12 values
[INFO] Creating app service plan java-functions-app-service-plan...
[INFO] Successfully created app service plan java-functions-app-service-plan.
[INFO] Start creating Application Insight (spring-cloud-function-samples)...
[INFO] Application Insight (spring-cloud-function-samples) is successfully created. You can visit https://ms.portal.azure.com/********providers/Microsoft.Insights/components/spring-cloud-function-samples to view your Application Insights component.
[INFO] Creating function app spring-cloud-function-samples...
[INFO] Set function worker runtime to java.
[INFO] Ignoring decoding of null or empty value to:com.azure.resourcemanager.storage.fluent.models.StorageAccountInner
[INFO] Successfully created function app spring-cloud-function-samples.
[INFO] Skip deployment for docker app service
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:30 min
[INFO] Finished at: 2022-04-04T19:06:24-05:00
[INFO] ------------------------------------------------------------------------
----
TIP: When deployed as a Docker container the function urls are not written to the console. You will need to inspect the functions in the Azure Portal to find the urls.
==== Inspect in Azure Portal
Navigate to the https://portal.azure.com/#blade/HubsExtension/BrowseResource/resourceType/Microsoft.Web%2Fsites/kind/functionapp[Function App] dashboard in the Azure portal and then:
* click on your function app (`"spring-cloud-function-samples"` by default)
* click the left nav `"Functions"` link
* click the `"uppercase"` function
====== Function Url
Click the `"Get Function Url"` link to see the function's url.
====== Test via Portal
* click on the left nav `"Code and Test"`
* click on `"Test/Run"` at top of page
* enter the following input json in the `"Body"` section on the right-hand side:
[source,json]
----
{
"greeting": "hello",
"name": "foo"
}
----
* click "Run" and the output should look like:
[source,json]
----
{
"greeting": "HELLO",
"name": "FOO"
}
----
===== Test via cURL
Armed w/ the function url from above, issue the following curl command in another terminal:
[source,bash]
----
curl -H "Content-Type: application/json" https://spring-cloud-function-samples.azurewebsites.net/api/uppercase -d '{"greeting": "hello", "name": "foo"}'
----
.curl response
[source,json]
----
{
"greeting": "HELLO",
"name": "FOO"
}
----
TIP: The Azure dashboard provides a plethora of information about your functions, including but not limited to execution count, memory consumption and execution time.
==== Custom Result Handling
As noted above, the implementation of `FunctionInvoker` (your handler), should contain the least amount of code possible. However, if custom result handling needs to occur there is a set of methods (named `postProcess**`) that can be overridden in link:../../spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java[FunctionInvoker.java].
One such example can be seen in link:src/main/java/example/ReactiveEchoCustomResultHandler.java[ReactiveEchoCustomResultHandler.java].
Once the function is deployed it can be tested using _curl_:
[source,bash]
----
curl -H "Content-Type: application/json" localhost:7071/api/echoStream -d '["hello","peepz"]'
----
.result
[source,bash]
----
Kicked off job for [hello, peepz]
----
The custom result handling takes the Flux returned from the `echoStream` function and adds logging, uppercase mapping, and then subscribes to the publisher. The Azure logs output the following:
[source,bash]
----
[2022-03-01T01:36:57.439Z] 2022-02-28 19:36:57.439 INFO 20587 --- [pool-2-thread-2] o.s.boot.SpringApplication : Started application in 0.466 seconds (JVM running for 57.906)
[2022-03-01T01:36:57.462Z] BEGIN echo post-processing work ...
[2022-03-01T01:36:57.462Z] HELLO
[2022-03-01T01:36:57.462Z] PEEPZ
[2022-03-01T01:36:57.463Z] END echo post-processing work
[2022-03-01T01:36:57.463Z] Function "echoStream" (Id: 678cff0b-d958-4fab-967b-e19e0d5d67e8) invoked by Java Worker
----

Просмотреть файл

@ -0,0 +1,110 @@
<?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 https://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>3.0.1-SNAPSHOT</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.example.azure.di</groupId>
<artifactId>azure-httptrigger-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>azure-httptrigger-demo</name>
<description>Demo Spring Boot, Azure Function - HttpTrigger (DI adapter)</description>
<properties>
<java.version>17</java.version>
<spring-boot-thin-layout.version>1.0.28.RELEASE</spring-boot-thin-layout.version>
<!-- Spring Boot start class! WARING: correct class must be set! -->
<start-class>example.Application</start-class>
<!-- AZURE FUNCTION CONFIG -->
<azure.functions.maven.plugin.version>1.22.0</azure.functions.maven.plugin.version>
<functionAppName>spring-cloud-function-samples</functionAppName>
<functionAppRegion>westus</functionAppRegion>
<functionResourceGroup>sample-resource-group</functionResourceGroup>
<functionAppServicePlanName>sample-service-plan</functionAppServicePlanName>
<functionPricingTier>EP1</functionPricingTier>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-azure</artifactId>
<version>4.0.0-SNAPSHOT</version> <!-- TODO: replace with GA version once 3.2.9 is released -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>${azure.functions.maven.plugin.version}</version>
<configuration>
<appName>${functionAppName}</appName>
<resourceGroup>${functionResourceGroup}</resourceGroup>
<region>${functionAppRegion}</region>
<appServicePlanName>${functionAppServicePlanName}</appServicePlanName>
<pricingTier>${functionPricingTier}</pricingTier>
<hostJson>${project.basedir}/src/main/resources/host.json</hostJson>
<localSettingsJson>${project.basedir}/src/main/resources/local.settings.json</localSettingsJson>
<runtime>
<os>linux</os>
<javaVersion>17</javaVersion>
</runtime>
<appSettings>
<property>
<name>FUNCTIONS_EXTENSION_VERSION</name>
<value>~4</value>
</property>
</appSettings>
</configuration>
<executions>
<execution>
<id>package-functions</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>${spring-boot-thin-layout.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

Просмотреть файл

@ -0,0 +1,12 @@
package example;
import example.uppercase.Config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Config.class, args);
}
}

Просмотреть файл

@ -0,0 +1,15 @@
package example.hello;
import example.hello.model.*;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.function.Function;
@Component
public class Hello implements Function<Mono<User>, Mono<Greeting>> {
public Mono<Greeting> apply(Mono<User> mono) {
return mono.map(user -> new Greeting("Hello, " + user.getName() + "!\n"));
}
}

Просмотреть файл

@ -0,0 +1,29 @@
package example.hello;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import example.hello.model.*;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import java.util.Optional;
public class HelloHandler extends FunctionInvoker<User, Greeting> {
@FunctionName("hello")
public HttpResponseMessage execute(
@HttpTrigger(name = "request", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request,
ExecutionContext context) {
User user = request.getBody()
.filter(u -> u.getName() != null)
.orElseGet(() -> new User(request.getQueryParameters().getOrDefault("name", "world")));
context.getLogger().info("Greeting user name: " + user.getName());
return request
.createResponseBuilder(HttpStatus.OK)
.body(handleRequest(user, context))
.header("Content-Type", "application/json")
.build();
}
}

Просмотреть файл

@ -0,0 +1,21 @@
package example.hello.model;
public class Greeting {
private String message;
public Greeting() {
}
public Greeting(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

Просмотреть файл

@ -0,0 +1,21 @@
package example.hello.model;
public class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

Просмотреть файл

@ -0,0 +1,61 @@
package example.uppercase;
import java.util.Map;
import java.util.function.Function;
import com.microsoft.azure.functions.ExecutionContext;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
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 org.springframework.messaging.Message;
@Configuration
public class Config {
@Bean
public Function<String, String> echo() {
return payload -> payload;
}
@Bean
public Function<Message<String>, String> uppercase(JsonMapper mapper) {
return message -> {
String value = message.getPayload();
ExecutionContext context = (ExecutionContext) message.getHeaders().get("executionContext");
try {
Map<String, String> map = mapper.fromJson(value, Map.class);
if(map != null)
map.forEach((k, v) -> map.put(k, v != null ? v.toUpperCase() : null));
if(context != null)
context.getLogger().info(new StringBuilder().append("Function: ")
.append(context.getFunctionName()).append(" is uppercasing ").append(value.toString()).toString());
return mapper.toString(map);
} catch (Exception e) {
e.printStackTrace();
if(context != null)
context.getLogger().severe("Function could not parse incoming request");
return ("Function error: - bad request");
}
};
}
@Bean
public Function<Mono<String>, Mono<String>> uppercaseReactive() {
return mono -> mono.map(value -> value.toUpperCase());
}
@Bean
public Function<Flux<String>, Flux<String>> echoStream() {
return flux -> flux.map(value -> value.toUpperCase());
}
}

Просмотреть файл

@ -0,0 +1,26 @@
package example.uppercase;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import java.util.Optional;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
public class EchoHandler extends FunctionInvoker<Message<String>, String> {
@FunctionName("echo")
public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET,
HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
Message<String> message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build();
return handleRequest(message, context);
}
}

Просмотреть файл

@ -0,0 +1,46 @@
package example.uppercase;
import java.util.List;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
/**
* Sample that shows how to customize the default function result handling by operating on the {@link Flux} returned
* from the {@link Config#echoStream()} echoStream} function.
*
*/
public class ReactiveEchoCustomResultHandler extends FunctionInvoker<List<String>, String> {
private static final Log logger = LogFactory.getLog(ReactiveEchoCustomResultHandler.class);
@FunctionName("echoStream")
public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<List<String>> request, ExecutionContext context
) {
return handleRequest(request.getBody(), context);
}
@Override
protected String postProcessFluxFunctionResult(List<String> rawInputs, Object functionInputs, Flux<?> functionResult,
FunctionInvocationWrapper function, ExecutionContext executionContext
) {
functionResult
.doFirst(() -> executionContext.getLogger().info("BEGIN echo post-processing work ..."))
.mapNotNull((v) -> v.toString().toUpperCase())
.doFinally((signalType) -> executionContext.getLogger().info("END echo post-processing work"))
.subscribe((v) -> executionContext.getLogger().info(" " + v));
return "Kicked off job for " + rawInputs;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
package example.uppercase;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import java.util.Optional;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
public class ReactiveUppercaseHandler extends FunctionInvoker<String, String> {
@FunctionName("uppercaseReactive")
public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET,
HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
return handleRequest(request.getBody().get(), context);
}
}

Просмотреть файл

@ -0,0 +1,31 @@
package example.uppercase;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import java.util.Optional;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
public class UppercaseHandler extends FunctionInvoker<Message<String>, String> {
@FunctionName("uppercase")
public String execute(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET, HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
ExecutionContext context
) {
context.getLogger().warning("Using Java (" + System.getProperty("java.version") + ")");
Message<String> message = MessageBuilder.withPayload(request.getBody().get())
.copyHeaders(request.getHeaders()).build();
return handleRequest(message, context);
}
}

Просмотреть файл

@ -0,0 +1,7 @@
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}

Просмотреть файл

@ -32,7 +32,8 @@ message StreamingMessage {
WorkerInitRequest worker_init_request = 17;
// Worker responds after initializing with its capabilities & status
WorkerInitResponse worker_init_response = 16;
// MESSAGE NOT USED
// Worker periodically sends empty heartbeat message to host
WorkerHeartbeat worker_heartbeat = 15;
@ -85,6 +86,13 @@ message StreamingMessage {
// Host gets the list of function load responses
FunctionLoadResponseCollection function_load_response_collection = 32;
// Host sends required metadata to worker to warmup the worker
WorkerWarmupRequest worker_warmup_request = 33;
// Worker responds after warming up with the warmup result
WorkerWarmupResponse worker_warmup_response = 34;
}
}
@ -120,7 +128,7 @@ message WorkerInitRequest {
// Worker responds with the result of initializing itself
message WorkerInitResponse {
// NOT USED
// PROPERTY NOT USED
// TODO: Remove from protobuf during next breaking change release
string worker_version = 1;
@ -173,7 +181,7 @@ message StatusResult {
repeated RpcLog logs = 3;
}
// NOT USED
// MESSAGE NOT USED
// TODO: Remove from protobuf during next breaking change release
message WorkerHeartbeat {}
@ -187,7 +195,7 @@ message WorkerTerminate {
message FileChangeEventRequest {
// Types of File change operations (See link for more info: https://msdn.microsoft.com/en-us/library/t6xf43e0(v=vs.110).aspx)
enum Type {
Unknown = 0;
Unknown = 0;
Created = 1;
Deleted = 2;
Changed = 4;
@ -237,6 +245,13 @@ message FunctionEnvironmentReloadRequest {
}
message FunctionEnvironmentReloadResponse {
// After specialization, worker sends capabilities & metadata.
// Worker metadata captured for telemetry purposes
WorkerMetadata worker_metadata = 1;
// A map of worker supported features/capabilities
map<string, string> capabilities = 2;
// Status of the response
StatusResult result = 3;
}
@ -325,7 +340,7 @@ message RpcFunctionMetadata {
// Properties for function metadata
// They're usually specific to a worker and largely passed along to the controller API for use
// outside the host
map<string,string> Properties = 16;
map<string,string> properties = 16;
}
// Host tells worker it is ready to receive metadata
@ -369,14 +384,14 @@ message InvocationRequest {
// Host sends ActivityId, traceStateString and Tags from host
message RpcTraceContext {
// This corresponds to Activity.Current?.Id
string trace_parent = 1;
// This corresponds to Activity.Current?.Id
string trace_parent = 1;
// This corresponds to Activity.Current?.TraceStateString
string trace_state = 2;
// This corresponds to Activity.Current?.TraceStateString
string trace_state = 2;
// This corresponds to Activity.Current?.Tags
map<string, string> attributes = 3;
// This corresponds to Activity.Current?.Tags
map<string, string> attributes = 3;
}
// Host sends retry context for a function invocation
@ -396,8 +411,8 @@ message InvocationCancel {
// Unique id for invocation
string invocation_id = 2;
// Time period before force shutdown
google.protobuf.Duration grace_period = 1; // could also use absolute time
// PROPERTY NOT USED
google.protobuf.Duration grace_period = 1;
}
// Worker responds with status of Invocation
@ -415,6 +430,15 @@ message InvocationResponse {
StatusResult result = 3;
}
message WorkerWarmupRequest {
// Full path of worker.config.json location
string worker_directory = 1;
}
message WorkerWarmupResponse {
StatusResult result = 1;
}
// Used to encapsulate data which could be a variety of types
message TypedData {
oneof data {
@ -429,6 +453,8 @@ message TypedData {
CollectionString collection_string = 9;
CollectionDouble collection_double = 10;
CollectionSInt64 collection_sint64 = 11;
ModelBindingData model_binding_data = 12;
CollectionModelBindingData collection_model_binding_data = 13;
}
}
@ -496,20 +522,20 @@ message ParameterBinding {
// Used to describe a given binding on load
message BindingInfo {
// Indicates whether it is an input or output binding (or a fancy inout binding)
enum Direction {
in = 0;
out = 1;
inout = 2;
}
// Indicates whether it is an input or output binding (or a fancy inout binding)
enum Direction {
in = 0;
out = 1;
inout = 2;
}
// Indicates the type of the data for the binding
enum DataType {
undefined = 0;
string = 1;
binary = 2;
stream = 3;
}
// Indicates the type of the data for the binding
enum DataType {
undefined = 0;
string = 1;
binary = 2;
stream = 3;
}
// Type of binding (e.g. HttpTrigger)
string type = 2;
@ -518,6 +544,9 @@ message BindingInfo {
Direction direction = 3;
DataType data_type = 4;
// Properties for binding metadata
map<string, string> properties = 5;
}
// Used to send logs back to the Host
@ -582,13 +611,13 @@ message RpcException {
// Textual message describing the exception
string message = 2;
// Worker specifies whether exception is a user exception,
// for purpose of application insights logging. Defaults to false.
// Worker specifies whether exception is a user exception,
// for purpose of application insights logging. Defaults to false.
bool is_user_exception = 4;
// Type of exception. If it's a user exception, the type is passed along to app insights.
// Otherwise, it's ignored for now.
string type = 5;
string type = 5;
}
// Http cookie type. Note that only name and value are used for Http requests
@ -647,3 +676,25 @@ message RpcHttp {
map<string,NullableString> nullable_params = 21;
map<string,NullableString> nullable_query = 22;
}
// Message representing Microsoft.Azure.WebJobs.ParameterBindingData
// Used for hydrating SDK-type bindings in out-of-proc workers
message ModelBindingData
{
// The version of the binding data content
string version = 1;
// The extension source of the binding data
string source = 2;
// The content type of the binding data content
string content_type = 3;
// The binding data content
bytes content = 4;
}
// Used to encapsulate collection model_binding_data
message CollectionModelBindingData {
repeated ModelBindingData model_binding_data = 1;
}