Merge branch 'dev' into kamperiadis/nullableMap
This commit is contained in:
Коммит
727323c655
|
@ -273,3 +273,8 @@ pkg/
|
|||
|
||||
# Mac specific gitignore
|
||||
.DS_Store
|
||||
|
||||
# Azure Functions
|
||||
local.settings.json
|
||||
bin/
|
||||
obj/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче