diff --git a/fdks/README.md b/fdks/README.md deleted file mode 100644 index 1c81ccb..0000000 --- a/fdks/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## Fn Project Logo FDK Documentation - -FDKs are language kits designed to help make working with Fn easier in each language. - -Select one of the FDK folders above to view documentation on that specific FDK. - -For general information on our FDK's, go here: https://github.com/fnproject/docs/blob/master/fn/develop/fdks.md \ No newline at end of file diff --git a/fdks/fdk-go/README.md b/fdks/fdk-go/README.md deleted file mode 100644 index 807d923..0000000 --- a/fdks/fdk-go/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# Go Fn Development Kit (FDK) - -[![GoDoc](https://godoc.org/github.com/fnproject/fdk-go?status.svg)](https://godoc.org/github.com/fnproject/fdk-go) - -fdk-go provides convenience functions for writing Go fn code - -For getting started with fn, please refer to https://github.com/fnproject/fn/blob/master/README.md - -# Installing fdk-go - -```sh -go get github.com/fnproject/fdk-go -``` - -or your favorite vendoring solution :) - -# Examples - -For a simple getting started, see the [examples](https://github.com/fnproject/fdk-go/tree/master/examples) and follow -the [README](https://github.com/fnproject/fdk-go/tree/master/examples). If you already have `fn` set up it -will take 2 minutes! - -# Advanced example - -TODO going to move to [examples](examples/) too :) - -```go -package main - -import ( - "context" - "fmt" - "io" - "encoding/json" - - fdk "github.com/fnproject/fdk-go" - "net/http" -) - -func main() { - fdk.Handle(fdk.HandlerFunc(myHandler)) -} - -func myHandler(ctx context.Context, in io.Reader, out io.Writer) { - fnctx := fdk.Context(ctx) - - contentType := fnctx.Header.Get("Content-Type") - if contentType != "application/json" { - fdk.WriteStatus(out, 400) - fdk.SetHeader(out, "Content-Type", "application/json") - io.WriteString(out, `{"error":"invalid content type"}`) - return - } - - if fnctx.Method != "PUT" { - fdk.WriteStatus(out, 404) - fdk.SetHeader(out, "Content-Type", "application/json") - io.WriteString(out, `{"error":"route not found"}`) - return - } - - var person struct { - Name string `json:"name"` - } - json.NewDecoder(in).Decode(&person) - - // you can write your own headers & status, if you'd like to - fdk.WriteStatus(out, 201) - fdk.SetHeader(out, "Content-Type", "application/json") - - all := struct { - Name string `json:"name"` - Header http.Header `json:"header"` - Config map[string]string `json:"config"` - }{ - Name: person.Name, - Header: fnctx.Header, - Config: fnctx.Config, - } - - json.NewEncoder(out).Encode(&all) -} -``` \ No newline at end of file diff --git a/fdks/fdk-java/DataBinding.md b/fdks/fdk-java/DataBinding.md deleted file mode 100644 index 8543748..0000000 --- a/fdks/fdk-java/DataBinding.md +++ /dev/null @@ -1,208 +0,0 @@ -# Data Binding for function input and output -The Fn Java FDK provides some standard tools for handling binding input to your functions to Java objects and types - You can also configure your own data binding either per-function or publish function data binding libraries, see [Extending Data Binding](ExtendingDataBinding.md) for details of how to do this. - - -## Simple data binding - -Fn functions are invoked from a HTTP request whose body can provide input to the function. - -The Fn Java FDK includes functionality to convert the data provided in the body of the HTTP request into frequently used types directly. - -Add a string parameter to your function to receive the HTTP body of the function call as a string and return a string value to set the body of the HTTP response: - -```java -package com.example.fn; - -public class HelloFunction { - public String handleRequest(String input) { - String name = (input == null || input.isEmpty()) ? "world" : input; - return "Hello, " + name + "!"; - } -} -``` - -Similarly, you can send and receive `byte[]`s , and have functions which do not return data with a `void` return type. - -Details of how these basic builtin types are processed are defined as follows. - -### Input (function parameter) - -| Java Type | Accepted Input Content Types | Interpretation | -| ------ | ---------------------------- | -------------- | -| `String` | Any | The request body is parsed as a UTF-8 String | -| `byte[]` | Any | The request body is provided as a raw `byte` Array | - -### Output (function return value) - -| Java Type | Output content type | Interpretation | -| ------ | ------------------------ | -------------- | -| `String` | text/plain | The string is serialized as bytes (with `.getBytes()`) | -| `byte[]` | application/octet-stream | The raw bytes are used as the body of the response | -| `void` | None | The response has no content | - - -## JSON data binding - -The FDK supports binding JSON data types to POJOs. This is implemented using the Jackson API. - -If the function has a POJO parameter, the function will only accept HTTP requests with an `application/json` content type and it will use the default Jackson bindings to deserialize the body of the request into the POJO. - -Similarly, if the function has a POJO return value it will produce an HTTP response with the `application/json` content type and it will use the default Jackson bindings to serialize the returned POJO into the body of the response. - -The following function returns `Greeting` as a JSON object containing a salutation and a name using the default jackson object binding: - -```java -package com.example.fn; - -public class PojoFunction { - - public static class Greeting { - public final String name; - public final String salutation; - - public Greeting(String salutation, String name) { - this.salutation = salutation; - this.name = name; - } - } - - public Greeting greet(String name) { - if (name == null || name.isEmpty()) - name = "World"; - - return new Greeting("Hello", name); - } - -} -``` - -## Customizing the Jackson object mapper - -You can customize the Jackson ObjectMapper used by your function via a [function configuration method](FunctionConfiguration.md): - -```java -package com.example.fn; - -import com.fnproject.fn.api.FnConfiguration; -import com.fnproject.fn.api.RuntimeContext; - -public class TestFn { - - @FnConfiguration - void configureObjectMapper(RuntimeContext ctx){ - ObjectMapper om = new ObjectMapper(); - om.addMixin(JsonBean.class,JsonBeanMixin.class); - - ctx.setAttribute("com.fnproject.fn.runtime.coercion.jackson.JacksonCoercion.om",om); - - } - - public static class JsonBean { - public String paramA; - public int paramB; - } - public JsonBean testFn(JsonBean req){ - return req; - } -} -``` - -## Working with raw function events -To get the most flexibility in handling data in and out of your function, the FDK also provides an abstraction of the raw Fn Java FDK events received or returned by the function by means of the [InputEvent](../api/src/main/java/com/fnproject/fn/api/InputEvent.java) and [OutputEvent](../api/src/main/java/com/fnproject/fn/api/OutputEvent.java) interfaces. - -Using this approach allows you to: - -- access the headers of the incoming request; -- read the request body as an `InputStream`; -- control content type (and other headers) and status of the response. - -The Fn Java FDK can automatically convert the input HTTP request into an `InputEvent` and provide it as the parameter of the function. Similarly, it can take the `OutputEvent` returned by the function and construct an appropriate HTTP response. - -Below is a function that looks at both the body of the request and a custom `X-My-Header` header. - -```java -package com.example.fn; -import com.fnproject.fn.api.OutputEvent; -import com.fnproject.fn.api.InputEvent; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.BufferedReader; -import java.util.stream.Collectors; - -public class Events { - private String readData(InputStream s) { - return new BufferedReader(new InputStreamReader(s)) - .lines().collect(Collectors.joining(" / ")); - } - - public OutputEvent handleRequest(InputEvent rawInput) { - String header = rawInput.getHeaders().get("X-My-Header").get(); - String text = rawInput.consumeBody(this::readData); - - String responseBody = "Received '" + text + "' with my header '" + header + "'.\n"; - - Map customHeaders = new HashMap<>(); - customheaders.put("X-My-Response-Header", "any value here"); - - OutputEvent out = OutputEvent.fromBytes( - responseBody.getBytes(), // Data - OutputEvent.SUCCESS, // Any numeric HTTP status code can be used here - "text/plain", // Content type - Headers.fromMap(customHeaders) // and additional custom headers on output - ); - return out; - } -} -``` - -After you have [deployed the function](../README.md) you can call it via HTTP, for example: - -```shell -$ curl -X POST --data-ascii "ABCD" -H "X-My-Header: EFGH" http://localhost:8080/r/java-app/events -Received 'ABCD' with my header 'EFGH'. -``` - -## Combining data binding and raw events -It is possible to combine a simple or POJO function parameter with an `InputEvent` parameter. This can be useful to easily have the body of the request converted to a type of your choice but still have access to the HTTP request headers. - -The following function takes both a string as the request body and an input event: - -```java -package com.example.fn; -import com.fnproject.fn.api.OutputEvent; -import com.fnproject.fn.api.InputEvent; - -public class Events { - public OutputEvent handleRequest(String text, InputEvent rawInput) { - String header = rawInput.getHeaders().get("X-My-Header").get(); - - String responseBody = "Received '" + text + "' with my header '" + header + "'.\n"; - - OutputEvent out = OutputEvent.fromBytes( - responseBody.getBytes(), // Data - OutputEvent.SUCCESS, // Status code of 200 - "text/plain" // Content type - ); - return out; - } -} -``` - -Verify the function works as expected: - -```shell -$ curl -X POST --data-ascii "ABCD" -H "X-My-Header: EFGH" http://localhost:8080/r/java-app/events -Received 'ABCD' with my header 'EFGH'. -``` - -### Caveats - -To avoid buffering of input data in the function's heap, _the HTTP request body can only be consumed once_. Attempting to consume the body twice (for example by using an automated conversion and also reading the body from the `InputEvent`) will cause an `IllegalStateException` to be thrown and your function will fail. - - -## More advanced data binding - -The `InputEvent` and `OutputEvent` abstractions are very flexible but do not offer the same ease of use as the builtin conversions to data types. - -To help with this, you can extend the functionality of the FDK by providing your own functionality to convert between data types. See the [Extending Data Binding](ExtendingDataBinding.md) tutorial for details of how to do this. \ No newline at end of file diff --git a/fdks/fdk-java/ExtendingDataBinding.md b/fdks/fdk-java/ExtendingDataBinding.md deleted file mode 100644 index 3c47227..0000000 --- a/fdks/fdk-java/ExtendingDataBinding.md +++ /dev/null @@ -1,319 +0,0 @@ -# Extending the Data Binding functionality - -By following this step-by-step guide you will learn to configure custom handling for input and output types of a simple Java function running on the Fn platform. - - -## Overview - -In the [Data Binding](DataBinding.md) tutorial you have seen how the raw data received and returned by the function is represented by [InputEvent](../api/src/main/java/com.fnproject.fn/api/InputEvent.java)s and [OutputEvent](../api/src/main/java/com.fnproject.fn/api/OutputEvent.java)s. The Fn Java FDK provides out-of-the-box functionality to convert these to some simple types and POJOs, but you might want to customize the way your input and output data is marshalled from the HTTP request and to the HTTP response. - -This is done through the *Coercion* abstractions. - -An [InputCoercion](../api/src/main/java/com/fnproject/fn/api/InputCoercion.java) is used to process an [InputEvent](../api/src/main/java/com/fnproject/fn/api/InputEvent.java) and turn it into the custom type required by a user function parameter. - -Similarly, an [OutputCoercion](../api/src/main/java/com/fnproject/fn/api/OutputCoercion.java) is the abstraction used to take the return value of the user function and create an [OutputEvent](../api/src/main/java/com/fnproject/fn/api/OutputEvent.java) from it. - -To make use of a custom coercion, you have to write a class that implements the appropriate interface, and then instruct your function to use it. This tutorial will explain how to do this. - -First of all, let's create a new function project. If you haven't done it already, start a local Fn server and create an app: - -```shell -$ fn start & -$ fn create app java-app -Successfully created app: java-app -``` - -Then create a new project for the custom I/O tutorial. - -```shell -$ mkdir custom-io && cd custom-io -$ fn init --runtime=java your_dockerhub_account/custom-io -Runtime: java -function boilerplate generated. -func.yaml created -$ mv src/main/java/com.example.fn/HelloFunction.java src/main/java/com.example.fn/CustomIO.java -``` - -Again, you will have to provide your Docker Hub account name. - -Set the name, path and cmd accordingly in `func.yaml`: - -``` -name: your_dockerhub_account/custom-io -version: 0.0.1 -runtime: java -cmd: com.example.fn.CustomIO::handleRequest -path: /custom-io -``` - -Edit `CustomIO.java` and create a function that merely echoes the input to the output: - -```java -package com.example.fn; - -public class CustomIO { - public String handleRequest(String input) { - return input; - } -} -``` - -Then build and deploy the function, and test it with `curl`: - -```shell -$ fn deploy java-app -... -Updating route /custom-io using image your_dockerhub_account/custom-io:0.0.2... - -$ curl -X POST --data-ascii "ABCDE" -H "Content-type: text/plain" http://localhost:8080/r/java-app/custom-io -ABCDE -``` - -## Creating custom coercions - -Coercion classes must have a zero-arguments public constructor. - -Input coercions must implement the `tryCoerceParam` method, while output coercions must implement the `wrapFunctionResult` method. - -Both these methods return a `java.util.Optional`. The contract for this return value is as follows: - -- If an empty `Optional` is returned, the coercion is not the appropriate coercion to be used for the provided data, and the runtime is allowed to attempt to use another coercion. -- If the `Optional` contains an object, the coercion has successfully performed the required conversion. -- If a `RuntimeException` is thrown, the coercion was supposed to work with the provided data but some kind of error has occurred. Because the data may have been partially consumed it is not safe to continue so the function invocation will be completed unsucessfully and no further action will be attempted. - -A common pattern for coercions is to first check that they can process the provided data into the required type, and return an empty `Optional` if they cannot. Then they can try to perform the conversion and wrap any exception (for example I/O exceptions) into an input/output handling exception. - -### Creating an input coercion - -We will write our first input coercion, which takes the body of the HTTP request and converts it to a String, but then reverses the order of the characters in the string. - -We're going to use an Apache Commons library so let's add it to our `pom.xml`: - -``` - - org.apache.commons - commons-io - 1.3.2 - -``` - -Create and edit `src/main/java/com/example/fn/ReverseStringInputCoercion.java`: - -```java -package com.example.fn; - -import com.fnproject.fn.api.InvocationContext; -import com.fnproject.fn.api.InputCoercion; -import com.fnproject.fn.api.InputEvent; -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - -public class ReverseStringInputCoercion implements InputCoercion { - - @Override - public Optional tryCoerceParam(InvocationContext currentContext, int arg, InputEvent input) { - if (!currentContext.getRuntimeContext().getTargetMethod().getParameterTypes()[arg].equals(String.class)) { - return Optional.empty(); - } - Optional ct = input.getHeaders().get("Content-type"); - if (!ct.isPresent() || !ct.get().toLowerCase().startsWith("text/plain")) { - return Optional.empty(); - } - String reversedString = input.consumeBody(is -> { - try { - return new StringBuilder(IOUtils.toString(is, StandardCharsets.UTF_8.toString())).reverse().toString(); - } catch (IOException e) { - throw new IllegalArgumentException("Error reading input as string", e); - } - }); - return Optional.of(reversedString); - } -} -``` - -Input coercions receive three parameters to the `tryCoerceParam` method: - -- The [InvocationContext](../api/src/main/java/com.fnproject.fn/api/InvocationContext.java) representing a description of the current invocation of the user function. -- An `int` which represents the index of the function parameter currently being considered for binding the data. -- The `InputEvent` representing the data coming from the HTTP request. - -First of all, our new coercion examines the type of the function parameter currently being considered for data binding. It is possible to get the function `Method` data via `currentContext.getRuntimeContext().getTargetMethod()`, at which point the Java reflection API can be used to check the type of the parameter, using `arg` as the index of the parameter. - -If the required type is not a `String`, this is not the correct coercion to use for the parameter, and therefore the coercion exits early by returning an empty `Optional`. - -In addition to this, the coercion only handles input data with a `text/plain` content type, therefore it also exits early if this is the not the case. - -Then, the coercion attempts to consume the body of the `InputEvent` and reverse the string. A `RuntimeException` may be thrown. - -Finally, a valid `Optional` is returned. - -### Using an input coercion - -To apply a specific coercion to a given input parameter, you can use an [@InputBinding](../api/src/main/java/com.fnproject.fn/api/InputBinding.java) annotation with the `coercion` parameter to specify the coercion class. - -Edit `CustomIO.java` and add this annotation: - -```java -package com.example.fn; - -import com.fnproject.fn.api.InputBinding; - -public class CustomIO { - public String handleRequest(@InputBinding(coercion=ReverseStringInputCoercion.class) String input) { - return input; - } -} -``` - -Now redeploy the function and test it with `curl`: - -```shell -$ fn deploy java-app -... -Updating route /custom-io using image your_dockerhub_account/custom-io:0.0.3... - -$ curl -X POST --data-ascii "ABCDE" -H "Content-type: text/plain" http://localhost:8080/r/java-app/custom-io -EDCBA -``` - -The input was reversed! - -### Creating an output coercion - -We will now write an output coercion, which reverses the string result again before returning it as the body of the HTTP response. - -Create and edit `src/main/java/com.example.fn/ReverseStringOutputCoercion.java`: - -```java -package com.example.fn; - -import com.fnproject.fn.api.InvocationContext; -import com.fnproject.fn.api.OutputCoercion; -import com.fnproject.fn.api.OutputEvent; -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - -public class ReverseStringOutputCoercion implements OutputCoercion { - @Override - public Optional wrapFunctionResult(InvocationContext ctx, Object output) { - if (!ctx.getRuntimeContext().getTargetMethod().getReturnType().equals(String.class)) { - return Optional.empty(); - } - OutputEvent event = OutputEvent.fromBytes( - new StringBuilder((String) output).reverse().toString().getBytes(), - true, - "text/plain")); - return Optional.of(event); - } -} -``` - -Output coercions receive two parameters to the `wrapFunctionResult` method: - -- The [InvocationContext](../api/src/main/java/com.fnproject.fn/api/InvocationContext.java) representing a description of the current invocation of the user function. -- An `Object` which is actually the return value of the user function. - -First of all, our output coercion examines the type of the function return value. Similarly to the input coercion seen above, it gets the function `Method` data via `currentContext.getRuntimeContext().getTargetMethod()`, at which point the Java reflection API can be used to check the type of the return value. - -If the type is not a `String`, this is not the correct coercion to use for the return value, and therefore the coercion exits early by returning an empty `Optional`. - -Otherwise, the coercion now knows that the `Object` it received must actually be a `String`, so it can cast it, reverse it, and construct an `OutputEvent` from its bytes. - -Finally, a valid `Optional` is returned. - -### Using an output coercion - -To apply a specific coercion from the return type of the function, you can provide an [@OutputBinding](../api/src/main/java/com.fnproject.fn/api/OutputBinding.java) annotation, which (similarly to the input one) has a `coercion` parameter. This annotation is attached to your function method. - -Let's edit `CustomIO.java` again: - -```java -package com.example.fn; - -import com.fnproject.fn.api.InputBinding; -import com.fnproject.fn.api.OutputBinding; - -public class CustomIO { - @OutputBinding(coercion=ReverseStringOutputCoercion.class) - public String handleRequest(@InputBinding(coercion=ReverseStringInputCoercion.class) String input) { - return input; - } -} -``` - -The input and output coercion should neutralize each other, therefore the combination of the two functionalities should revert back to being a simple echo of the input. Let's test this: - -```shell -$ fn deploy java-app -... -Updating route /custom-io using image your_dockerhub_account/custom-io:0.0.4... - -$ curl -X POST --data-ascii "ABCDE" -H "Content-type: text/plain" http://localhost:8080/r/java-app/custom-io -ABCDE -``` - - -## Using a function configuration method to specify custom coercions - -In the [Function Configuration](FunctionConfiguration.md) tutorial, you have seen how to decorate your function classes with a configuration method that can be used to initialize their state from configuration data. - -That same method can be used to specify input and output coercions by using the API provided in the [RuntimeContext](../api/src/main/java/com.fnproject.fn/api/RuntimeContext.java). - -Let's edit our `CustomIO.java` again: - -```java -package com.example.fn; - -import com.fnproject.fn.api.FnConfiguration; -import com.fnproject.fn.api.RuntimeContext; - -public class CustomIO { - @FnConfiguration - public void configure(RuntimeContext ctx){ - ctx.addInputCoercion(new ReverseStringInputCoercion()); - ctx.addOutputCoercion(new ReverseStringOutputCoercion()); - } - - public String handleRequest(String input) { - return input; - } -} -``` - -We have removed the input and output binding annotations, but we have provided our custom coercions in the configuration method. - -Let's test that this new code has the same effect (a double reversal, i.e. a simple echo). - -```shell -$ fn deploy java-app -... -Updating route /custom-io using image your_dockerhub_account/custom-io:0.0.5... - -$ curl -X POST --data-ascii "ABCDE" -H "Content-type: text/plain" http://localhost:8080/r/java-app/custom-io -ABCDE -``` - -### What's the difference? - -There is a difference in using the function configuration method to specify coercions compared to the annotations. - -When directly annotating the function method with the [@InputBinding](../api/src/main/java/com.fnproject.fn/api/InputBinding.java) and [@OutputBinding](../api/src/main/java/com.fnproject.fn/api/OutputBinding.java) annotations, _only the specified coercion will be tried_. - -In other words, the annotations are an explicit requirement on the input and output conversions to perform. - -When using the function configuration method, the provided coercions _will be tried in order_ and _before any builtin coercion is tried_. As soon as one of the provided coercions is successful, no other coercions will be tried. - -In other words, the additional coercions specified in the function configuration methods form a priority list but they are not exclusive. The runtime will: - -- Try the first coercion provided in the configuration method. - - If it returns a full `Optional`, it successfully converted the input and no other coercion is tried. - - Otherwise, the runtime will proceed to the next coercion. -- Try the second coercion provided in the configuration method... -- ... -- Only if no custom coercion has succeeded so far, try the builtin coercions (to `String`, `byte[]` and to a POJO). \ No newline at end of file diff --git a/fdks/fdk-java/FunctionConfiguration.md b/fdks/fdk-java/FunctionConfiguration.md deleted file mode 100644 index b9cb61a..0000000 --- a/fdks/fdk-java/FunctionConfiguration.md +++ /dev/null @@ -1,171 +0,0 @@ -# Function initialization and configuration - -Function objects and classes may be re-used for multiple function events in the same Java virtual machine depending on how your function is configured and how frequently it is called. -The Fn Java FDK allows you to perform once-per-runtime initialization behaviour that allows you to create long-lived objects such as static data, and database connections that can be re-used across multiple function events. - -You can set configuration values on your function's routes and the function app using the Fn tool or in your `func.yaml`. This configuration can contain deployment-specific data which you do not want to store in your source code. - -The function runtime can provide you a [RuntimeContext](../api/src/main/java/com/fnproject/fn/api/RuntimeContext.java) instance that exposes an API to access these configuration variables or [configure data binding](DataBinding.md); this object can be used either in your class's constructor or via configuration methods, as described below. - -## Configuring your function in the constructor - -All function classes where the function method is not static must have a public constructor. - -The easiest way to initialize your function class is to specify a no-arg constructor on your function class: - -```java -package com.example.fn; - -public class ConstructorConfig { - private final String greeting ; - - public ConstructorConfig(){ - greeting = "Hello"; - } - - public String handleRequest() { - return greeting + " world!"; - } -} -``` - -You can also add a `RuntimeContext` parameter to your constructor as follows: - -```java -package com.example.fn; -import com.fnproject.fn.api.RuntimeContext; - -public class ConstructorConfig { - private final String greeting ; - - public ConstructorConfig(RuntimeContext ctx) { - greeting = ctx.getConfigurationByKey("GREETING").orElse("Hello"); - } - - public String handleRequest() { - return greeting + " world!"; - } -} -``` - -## Using function configuration methods - -In addition to using the constructor you can also annotate instance or static methods with [@FnConfiguration](../api/src/main/java/com/fnproject/fn/api/FnConfiguration.java) to initialize your function object or class state. - -The configuration method will be called for you once per runtime (i.e. once for multiple invocations of a "hot function") so changes made within this method will apply to all invocations of your function. - -This method must have a `void` return value and if your function method is static, the configuration method must also be static. - -Given a simple function class: - -```java -package com.example.fn; - -public class WithConfig { - public String handleRequest(String input) { - String name = (input == null || input.isEmpty()) ? "world" : input; - return "hello" + name + "!"; - } -} -``` - -Add configuration to the function class by creating a method annotated with `@FnConfiguration` as follows: - -```java -package com.example.fn; - -import com.fnproject.fn.api.FnConfiguration; - -public class WithConfig { - private String greeting; - - @FnConfiguration - public void setUp() { - greeting = "Hello "; - } - - public String handleRequest(String input) { - String name = (input == null || input.isEmpty()) ? "world" : input; - return greeting + name + "!"; - } -} -``` - -As with constructors, if your function configuration method takes a single `RuntimeContext` argument this can be used to get function configuration and configure data binding. - -```java -package com.example.fn; - -import com.fnproject.fn.api.FnConfiguration; -import com.fnproject.fn.api.RuntimeContext; - -public class WithConfig { - private String greeting; - - @FnConfiguration - public void setUp(RuntimeContext ctx) { - greeting = ctx.getConfigurationByKey("GREETING").orElse("Hello"); - } - - public String handleRequest(String input) { - String name = (input == null || input.isEmpty()) ? "world" : input; - return greeting + name + "!"; - } -} -``` - -### Example: Configuring a Database Connection - -Function configuration methods can be used to configure and create persisted database connections that are re-used for multiple function events. - -For example: - -```java -package com.example.fn; - -import com.fnproject.fn.api.FnConfiguration; -import com.fnproject.fn.api.RuntimeContext; -import java.sql.DriverManager; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; - -public class FunctionUsingSomeDatabase { - private Connection connection; - - @FnConfiguration - public void setUp(RuntimeContext ctx) throws Exception { - Class.forName("com.mysql.jdbc.Driver"); - - connection = DriverManager.getConnection(ctx.getConfiguration().get("DB_URL"), - ctx.getConfiguration().get("DB_USER"), ctx.getConfiguration().get("DB_PASSWORD")); - } - - public String handleRequest(String user) throws Exception { - try(PreparedStatement userQuery = connection.prepareStatement("SELECT name from USERS where user=?")) { - userQuery.setString(1, user); - ResultSet rs = userQuery.executeQuery(); - if(rs.next()) { - return rs.getString("name"); - } else { - return "Unknown"; - } - } - } -} -``` - -Note that unless you have concurrent database access within your code, there is no need to use a connection pool as only one event will be processed by a given function runtime at a time. - - -## Configuration methods and inheritance - -Function classes may have multiple configuration methods and may inherit from classes with configuration methods. - -When multiple methods are declared in a class hierarchy, all static configuration methods will be called before instance configuration methods and methods declared in super-classes will be called before those in sub-classes. - -Multiple configuration methods in the same class are supported but no guarantees are made about the ordering of calls. - -When configuration methods are overridden, the `@FnConfiguration` annotation must be added to the overridden method in order for it to be called. - -`@FnConfiguration` annotations on interface methods (including default methods and static methods) will be ignored. \ No newline at end of file diff --git a/fdks/fdk-java/README.md b/fdks/fdk-java/README.md deleted file mode 100644 index 9138c1b..0000000 --- a/fdks/fdk-java/README.md +++ /dev/null @@ -1,327 +0,0 @@ -# Fn Java Functions Developer Kit (FDK) -[![CircleCI](https://circleci.com/gh/fnproject/fdk-java.svg?style=svg&circle-token=348bec5610c34421f6c436ab8f6a18e153cb1c01)](https://circleci.com/gh/fnproject/fdk-java) - -This project adds support for writing functions in Java on the [Fn -platform](https://github.com/fnproject/fn), with full support for Java 9 -as the default out of the box. - -# FAQ -Some common questions are answered in [our FAQ](../../fn/general/faq.md). - -# Quick Start Tutorial - -By following this step-by-step guide you will learn to create, run and deploy -a simple app written in Java on Fn. - -## Pre-requisites - -Before you get started you will need the following things: - -* The [Fn CLI](https://github.com/fnproject/cli) tool -* [Docker-ce 17.06+ installed locally](https://docs.docker.com/engine/installation/) - -### Install the Fn CLI tool - -To install the Fn CLI tool, just run the following: - -``` -curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh -``` - -This will download a shell script and execute it. If the script asks for -a password, that is because it invokes sudo. - -## Your first Function - -### 1. Create your first Java Function: - -```bash -$ mkdir hello-java-function && cd hello-java-function -$ fn init --runtime=java --name your_dockerhub_account/hello -Runtime: java -function boilerplate generated. -func.yaml created -``` - -This creates the boilerplate for a new Java Function based on Maven and Oracle -Java 9. The `pom.xml` includes a dependency on the latest version of the Fn -Java FDK that is useful for developing your Java functions. - -You can now import this project into your favourite IDE as normal. - -### 2. Deep dive into your first Java Function: -We'll now take a look at what makes up our new Java Function. First, lets take -a look at the `func.yaml`: - -```bash -$ cat func.yaml -name: your_dockerhub_account/hello -version: 0.0.1 -runtime: java -cmd: com.example.fn.HelloFunction::handleRequest -``` - -The `cmd` field determines which method is called when your funciton is -invoked. In the generated Function, the `func.yaml` references -`com.example.fn.HelloFunction::handleRequest`. Your functions will likely live -in different classes, and this field should always point to the method to -execute, with the following syntax: - -```text -cmd: :: -``` - -For more information about the fields in `func.yaml`, refer to the [Fn platform -documentation](../../fn/develop/func-file.md) -about it. - -Let's also have a brief look at the source: -`src/main/java/com/example/fn/HelloFunction.java`: - -```java -package com.example.fn; - -public class HelloFunction { - - public String handleRequest(String input) { - String name = (input == null || input.isEmpty()) ? "world" : input; - - return "Hello, " + name + "!"; - } - -} -``` - -The function takes some optional input and returns a greeting dependent on it. - -### 3. Run your first Java Function: -You are now ready to run your Function locally using the Fn CLI tool. - -```bash -$ fn build -Building image your_dockerhub_account/hello:0.0.1 -Sending build context to Docker daemon 14.34kB -Step 1/11 : FROM fnproject/fn-java-fdk-build:jdk9-latest as build-stage - ---> 5435658a63ac -Step 2/11 : WORKDIR /function - ---> 37340c5aa451 - -... - -Step 5/11 : RUN mvn package dependency:copy-dependencies -DincludeScope=runtime -DskipTests=true -Dmdep.prependGroupId=true -DoutputDirectory=target --fail-never ----> Running in 58b3b1397ba2 -[INFO] Scanning for projects... -Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.3/maven-compiler-plugin-3.3.pom -Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.3/maven-compiler-plugin-3.3.pom (11 kB at 21 kB/s) - -... - -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD SUCCESS -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 2.228 s -[INFO] Finished at: 2017-06-27T12:06:59Z -[INFO] Final Memory: 18M/143M -[INFO] ------------------------------------------------------------------------ - -... - -Function your_dockerhub_account/hello:0.0.1 built successfully. - -$ fn run -Hello, world! -``` - -The next time you run this, it will execute much quicker as your dependencies -are cached. Try passing in some input this time: - -```bash -$ echo -n "Universe" | fn run -... -Hello, Universe! -``` - -### 4. Testing your function -The Fn Java FDK includes a testing library providing useful [JUnit -4](http://junit.org/junit4/) rules to test functions. Look at the test in -`src/test/java/com/example/fn/HelloFunctionTest.java`: - -```java -package com.example.fn; - -import com.fnproject.fn.testing.*; -import org.junit.*; - -import static org.junit.Assert.*; - -public class HelloFunctionTest { - - @Rule - public final FnTestingRule testing = FnTestingRule.createDefault(); - - @Test - public void shouldReturnGreeting() { - testing.givenEvent().enqueue(); - testing.thenRun(HelloFunction.class, "handleRequest"); - - FnResult result = testing.getOnlyResult(); - assertEquals("Hello, world!", result.getBodyAsString()); - } - -} -``` - -This test is very simple: it just enqueues an event with empty input and then -runs the function, checking its output. Under the hood, the `FnTestingRule` is -actually instantiating the same runtime wrapping function invocations, so that -during the test your function will be invoked in exactly the same way that it -would when deployed. - -### 5. Run using HTTP and the local Fn server -The previous example used `fn run` to run a function directly via docker, you -can also use the Fn server locally to test the deployment of your function and -the HTTP calls to your functions. - -Open another terminal and start the Fn server: - -```bash -$ fn start -``` - -Then in your original terminal create an app: - -```bash -$ fn create app java-app -Successfully created app: java-app -``` - -Now deploy your Function using the `fn deploy` command. This will bump the -function's version up, rebuild it, and push the image to the Docker registry, -ready to be used in the function deployment. Finally it will create a route on -the local Fn server, corresponding to your function. - -We are using the `--local` flag to tell fn to skip pushing the image anywhere -as we are just going to run this on our local fn server that we started with -`fn start` above. - -```bash -$ fn deploy --app java-app --local -... -Bumped to version 0.0.2 -Building image hello:0.0.2 -Sending build context to Docker daemon 14.34kB - -... - -Successfully built bf2b7fa55520 -Successfully tagged your_dockerhub_account/hello:0.0.2 -Updating route /hello-java-function using image your_dockerhub_account/hello:0.0.2... -``` - -Call the Function via the Fn CLI: - -```bash -$ fn call java-app /hello-java-function -Hello, world! -``` - -You can also call the Function via curl: - -```bash -$ curl http://localhost:8080/r/java-app/hello-java-function -Hello, world! -``` - -### 6. Something more interesting -The Fn Java FDK supports [flexible data binding](DataBinding.md) to make -it easier for you to map function input and output data to Java objects. - -Below is an example to of a Function that returns a POJO which will be -serialized to JSON using Jackson: - -```java -package com.example.fn; - -public class PojoFunction { - - public static class Greeting { - public final String name; - public final String salutation; - - public Greeting(String salutation, String name) { - this.salutation = salutation; - this.name = name; - } - } - - public Greeting greet(String name) { - if (name == null || name.isEmpty()) - name = "World"; - - return new Greeting("Hello", name); - } - -} -``` - -Update your `func.yaml` to reference the new method: - -```yaml -cmd: com.example.fn.PojoFunction::greet -``` - -Now run your new function: - -```bash -$ fn run -... -{"name":"World","salutation":"Hello"} - -$ echo -n Michael | fn run -... -{"name":"Michael","salutation":"Hello"} -``` - -## 7. Where do I go from here? - -Learn more about the Fn Java FDK by reading the next tutorials in the series. -Also check out the examples in the [`examples` directory](examples) for some -functions demonstrating different features of the Fn Java FDK. - -### Configuring your function - -If you want to set up the state of your function object before the function is -invoked, and to use external configuration variables that you can set up with -the Fn tool, have a look at the [Function -Configuration](FunctionConfiguration.md) tutorial. - -### Input and output bindings - -You have the option of taking more control of how serialization and -deserialization is performed by defining your own bindings. - -See the [Data Binding](DataBinding.md) tutorial for other out-of-the-box -options and the [Extending Data Binding](ExtendingDataBinding.md) tutorial -for how to define and use your own bindings. - -### Asynchronous workflows - -Suppose you want to call out to some other function from yours - perhaps -a function written in a different language, or even one maintained by -a different team. Maybe you then want to do some processing on the result. Or -even have your function interact asynchronously with a completely different -system. Perhaps you also need to maintain some state for the duration of your -function, but you don't want to pay for execution time while you're waiting for -someone else to do their work. - -If this sounds like you, then have a look at the [Fn Flow -quickstart](https://github.com/fnproject/fdk-java/blob/master/docs/FnFlowsUserGuide.md). - -# Get help - - * Come over and chat to us on the [fnproject Slack](https://join.slack.com/t/fnproject/shared_invite/enQtMjIwNzc5MTE4ODg3LTdlYjE2YzU1MjAxODNhNGUzOGNhMmU2OTNhZmEwOTcxZDQxNGJiZmFiMzNiMTk0NjU2NTIxZGEyNjI0YmY4NTA). - * Raise an issue in [our github](https://github.com/fnproject/fn-java-fdk/). - -# Contributing - -Please see "[For Contributers](https://github.com/fnproject/fn/tree/master/docs#for-contributors)". \ No newline at end of file diff --git a/fdks/fdk-node/README.md b/fdks/fdk-node/README.md deleted file mode 100644 index 15a563d..0000000 --- a/fdks/fdk-node/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# Fn Function Developer Kit for Node.js - -This Function Developer Kit makes it easy to deploy Node.js functions to Fn. -It currently supports default (cold) and hot functions using the JSON format. - -## Creating a Node Function - -Writing a Node.js function is simply a matter of writing a handler function -that you pass to the FDK to invoke each time your function is called. - -Start by creating a node function with `fn init` and installing the FDK: - -```sh -fn init --runtime node nodefunc -cd nodefunc -``` - -This creates a simple hello world function in `func.js`: - -```javascript -var fdk=require('@fnproject/fdk'); - -fdk.handle(function(input){ - var name = 'World'; - if (input.name) { - name = input.name; - } - response = {'message': 'Hello ' + name} - return response -}) -``` - -The handler function takes the string input that is sent to the function -and returns a response string. Using the FDK you don't have to worry about reading -input from standard input and writing to standard output to return your response. -The FDK let's you focus on your function logic and not the mechanics. - -Now run it! - -```sh -fn run -``` - -Now you have a basic running Node function that you can modify and add what you want. - -From this point on, it's the same as any other function in any language. -deploy the function: - -```sh -fn deploy --app fnfdk --local -``` - -Once deployed, you and invoke the function: - -```sh -echo -n "Tom" | fn call fdkdemo /hello -``` - -or - -```sh -curl -d "Tom" http://localhost:8080/r/fdkdemo/hello -``` - -In both cases you'll get the response: - -```sh -Hello Tom from Node! -``` - -## Function Context - -Function invocation context details are available through an optional function argument. -To receive a context object, simply add a second argument to your handler function. -in the following example the `call_id` is obtained from the context and included in -the response message: - -```javascript -var fdk=require('@fnproject/fdk'); - -fdk.handle(function(input, ctx){ - var name = 'World'; - if (input) { - name = input; - } - return 'Hello ' + name + ' from Node call ' + ctx.callId + '!'; -}) -``` - -In the case of `default` format functions the context give you access to all environment variables -including those defined through function or app config as well as those automatically provided -by Fn like `app_name`, `path`, `memory`, etc. - - -## Asynchronous function responses - -You return an asynchronous response from a function by returning a Javascript `Promise` from the function body: - -```javascript -var fdk=require('@fnproject/fdk'); - -fdk.handle(function(input, ctx){ - return new Promise((resolve,reject)=>{ - setTimeout(()=>resolve("Hello"),1000); - }); -}) -``` - -## Handling non-json input and output - -By default the FDK will convert input with a content-type matching `application/json` into a JSON object as the function input, if the incoming content type is different from `application/json` then the input will be the raw string value of the input. In both cases, the raw (string) version of the input is also available in `ctx.body`. - -Likewise by default the output of a function will be treated as a JSON object and converted using JSON.stringify - you can change this behaviour by setting the content type of the response in the context using `ctx.responseContentType='application/text-plain'`. Changing the content type to non-json will result in the output being treated as a string. - -## Using HTTP headers and setting HTTP status codes - -You can read http headers passed into a function invocation using `ctx.protocol.header(key)`, this returns the first header value of the header matching `key` (after canonicalization) and `ctx.protocol.headers` which returns an object containing all headers. - -```javascript -var fdk=require('@fnproject/fdk'); - -fdk.handle(function(input, ctx){ - console.log("Authorization header:" , ctx.protocol.header("Authorization")) - console.log( ctx.protocol.headers) // prints e.g. { "Content-Type": ["application/json"],"Accept":["application/json","text/plain"] } -}) -``` - -Outbound headers and the HTTP status code can be modified when the function uses the `json` request format. - -To update the outbound status-code set `ctx.protocol.statusCode`. To modify outbound headers use `ctx.protocol.setHeader(k,v)` or `ctx.protocol.addHeader(k,v)` which set (overwriting existing headers) or add (preserving existing headers) headers to the response respectively. - - -```javascript -var fdk=require('@fnproject/fdk'); - -fdk.handle(function(input, ctx){ - ctx.protocol.setHeader("Location","http://example.com") - ctx.protocol.statusCode = 302 -}) -``` - -## Fn and Node.js Dependencies -Fn handles Node.js dependencies in the following way: - -* If a `package.json` is present without a `node_modules` directory, an Fn build runs an `npm install` within the build process and installs your dependencies. -* If the `node_modules` is present, Fn assumes you have provided the dependencies yourself and no installation is performed. \ No newline at end of file diff --git a/fdks/fdk-python/README.md b/fdks/fdk-python/README.md deleted file mode 100644 index ee7f1ce..0000000 --- a/fdks/fdk-python/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# FN development kit for Python - -Purpose of this library to provide simple interface to parse HTTP 1.1 requests. - -Following examples are showing how to use API of this library to work with streaming HTTP requests from Fn service. - -## Handling Hot JSON Functions - -A main loop is supplied that can repeatedly call a user function with a series of HTTP requests. -In order to utilise this, you can write your `func.py` as follows: - -```py -import json -import io - -from fdk import response - - -def handler(ctx, data: io.BytesIO=None): - name = "World" - try: - body = json.loads(data.getvalue()) - name = body.get("name") - except (Exception, ValueError) as ex: - print(str(ex)) - - return response.Response( - ctx, response_data=json.dumps( - {"message": "Hello {0}".format(name)}), - headers={"Content-Type": "application/json"} - ) - -``` diff --git a/fdks/fdk-ruby/README.md b/fdks/fdk-ruby/README.md deleted file mode 100644 index e83873f..0000000 --- a/fdks/fdk-ruby/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# Ruby Function Developer Kit (FDK) - -This provides a Ruby framework for deleloping functions for us with [Fn](https://fnproject.github.io). - -## Function Handler - -To use this FDK, you simply need to require this gem. - -```ruby -require 'fdk` -``` - -Then create a function with with the following syntax: - -```ruby -def myfunc(context, input) - # Do some work here - return output -end -``` - -* context - provides runtime information for your function, such as configuration values, headers, etc. -* input – This parameter is a string containing body of the request. -* output - is where you can return data back to the caller. Whatever you return will be sent back to the caller. - * Default output format should be in JSON, as Content-Type header will be `application/json` by default. You can be more flexible if you create and return - an FDK::Response object instead of a string. - -Then simply pass that function to the FDK: - -```ruby -FDK.handle(myfunction) -``` - -## Full Example - -```ruby -require 'fdk' - -def myfunction(context:, input:) - input_value = input.respond_to?(:fetch) ? input.fetch('name') : input - name = input_value.to_s.strip.empty? ? 'World' : input_value - { message: "Hello #{name}!" } -end - -FDK.handle(function: :myfunction) -``` - -## Running the example that is in the root directory of this repo - -```sh -$ echo '{"name":"coolio"}' | fn run -{"message":"Hello coolio!"} -``` - -You can also specify the format (the default is JSON) - -```sh -$ echo '{"name":"coolio"}' | fn run --format json -{"message":"Hello coolio!"} -``` - -If you want to just pass plain text to the function, specify a format of __default__: - -```sh -$ echo 'coolio' | fn run --format default -{"message":"Hello coolio!"} -``` - -Deploy: - -```sh -fn deploy --app myapp --local && echo '{"name":"coolio"}' | fn call myapp /fdk-ruby -``` - -Change to hot: - -Update func.yaml: `format: json` - -```sh -fn deploy --app myapp --local && echo '{"name":"coolio"}' | fn call myapp /fdk-ruby -``` - -## Compare cold and hot - -Run - -```sh -ruby loop.rb -``` \ No newline at end of file diff --git a/fn/develop/fdks.md b/fn/develop/fdks.md index 439b0fa..c99a9ca 100644 --- a/fn/develop/fdks.md +++ b/fn/develop/fdks.md @@ -8,11 +8,11 @@ The Fn team has chosen a set of FDKs to initially support, while other FDKs will ## Officially Supported FDKs -* [Go](https://github.com/fnproject/docs/tree/master/fdks/fdk-go) -* [Java](https://github.com/fnproject/docs/blob/master/fdks/fdk-java/README.md) -* [Python](https://github.com/fnproject/docs/blob/master/fdks/fdk-python/README.md) -* [Ruby](https://github.com/fnproject/docs/tree/master/fdks/fdk-ruby) -* [NodeJS](https://github.com/fnproject/docs/tree/master/fdks/fdk-node) +* [Go](https://github.com/fnproject/fdk-go/) +* [Java](https://github.com/fnproject/fdk-java) +* [Python](https://github.com/fnproject/fdk-python) +* [Ruby](https://github.com/fnproject/fdk-ruby) +* [NodeJS](https://github.com/fnproject/fdk-node) To search for all FDK work, you can [search the fnproject GitHub org](https://github.com/fnproject?utf8=%E2%9C%93&q=FDK).