Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Unix Stream codec, Split flow from FDK, update integration tests #150

Merged
merged 49 commits into from
Sep 19, 2018
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
dd87c35
http input codec
zootalures Aug 29, 2018
8ab3af4
add jetty-core and UDS deps
zootalures Aug 30, 2018
348792e
PoC jetty core UDS event codec
Sep 3, 2018
d722ebb
revise FDK contract for initial relase, split flow from fn, support n…
zootalures Sep 7, 2018
08d2a4c
First pass of refactor
zootalures Sep 9, 2018
f483f85
added annotations for for features to support flow
zootalures Sep 10, 2018
e1ab94d
moving flow feature to runtime package
zootalures Sep 10, 2018
fc707dd
rename flow testing and add docs, fix race in Stream testing
zootalures Sep 10, 2018
fc9886d
fixing failing tests
zootalures Sep 10, 2018
666cb3f
Update to reflect FN_ID
zootalures Sep 11, 2018
146e83c
more integration test fixing
zootalures Sep 11, 2018
9089035
fix up format name
zootalures Sep 11, 2018
e2a3b90
first stab at home-grown UDS implementation
zootalures Sep 12, 2018
0942c6a
docker build fix
zootalures Sep 13, 2018
f478257
UDS rework, remove old UDS dep and test new one
zootalures Sep 13, 2018
f4abf59
adjust permissions on file on start
zootalures Sep 13, 2018
029f400
new integration tests
zootalures Sep 14, 2018
e5242d7
simlify build and integration tests, add tests for http-stream
zootalures Sep 14, 2018
552d5f2
fix ci
zootalures Sep 15, 2018
c941cdf
typo
zootalures Sep 15, 2018
311d58d
make igntests standalone
zootalures Sep 15, 2018
5b40435
itests run 1.8
zootalures Sep 15, 2018
0fd287d
fix itest result
zootalures Sep 15, 2018
3dbc3ee
env for itests
zootalures Sep 15, 2018
846eeb6
add createDir
zootalures Sep 15, 2018
7ddd360
test failures in circle
zootalures Sep 15, 2018
b3fa77a
fix test interference
zootalures Sep 15, 2018
64f0cbd
Use standard properties in fdk tests, fix versions in itests
zootalures Sep 15, 2018
d878986
hmm no entropy on circle boxes :)
zootalures Sep 15, 2018
561a51a
use consistent properties for versions everywhere
zootalures Sep 15, 2018
de7e460
add waits for start
zootalures Sep 15, 2018
8e2096b
this time with feeling
zootalures Sep 15, 2018
bf7248d
diagnosing timeout
zootalures Sep 16, 2018
16f6802
close input, try more aggressive testing
zootalures Sep 16, 2018
97ffadb
C errors
zootalures Sep 16, 2018
42ea541
fixing up accept and twiddling with integration test start
zootalures Sep 17, 2018
b8abc5c
set DOCKER_LOCALHOST
zootalures Sep 17, 2018
ce96bea
integration test fixing/finessing
zootalures Sep 18, 2018
ff729f2
cludge docker localhost
zootalures Sep 18, 2018
e463adb
no_proxy messing
zootalures Sep 18, 2018
e3f8d1d
set fnserver IP correctly
zootalures Sep 18, 2018
43cf9b5
typo
zootalures Sep 18, 2018
755c69d
nits in C code
zootalures Sep 18, 2018
a4bf3df
review nits
zootalures Sep 18, 2018
78d745a
more C fix nits
zootalures Sep 18, 2018
14d1b46
Make mandatory headers mandatory again
zootalures Sep 18, 2018
e2209d7
minus comment
zootalures Sep 18, 2018
fef06de
remove Content-Length from stripped input
zootalures Sep 18, 2018
df25e51
fix call test
zootalures Sep 18, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@

<artifactId>api</artifactId>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
Expand Down
305 changes: 54 additions & 251 deletions api/src/main/api/snapshot.sigfile

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions api/src/main/java/com/fnproject/fn/api/FnFeature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.fnproject.fn.api;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to be used in user function classes to enable runtime-wide feature.
*
* Runtime features are initialized at the point that the function class is loaded but prior to the call chain.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FnFeature {
/**
* The feature class to load this must have a zero-arg public constructor
* @return feature class
*/
Class<? extends RuntimeFeature> value();
}
16 changes: 16 additions & 0 deletions api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
* Handles the invocation of a given function call
*/
public interface FunctionInvoker {
/**
* Phase determines a loose ordering for invocation handler processing
* this should be used with {@link RuntimeContext#addInvoker(FunctionInvoker, Phase)} to add new invoke handlers to a runtime
*/
enum Phase {
/**
* The Pre-Call phase runs before the main function call, all {@link FunctionInvoker} handlers added at this phase are tried prior to calling the {@link Phase#Call} phase
* This phase is typically used for handlers that /may/ intercept the request based on request attributes
*/
PreCall,
/**
* The Call Phase indicates invokers that should handle call values - typically a given runtime will only be handled by one of these
*/
Call
}

/**
* Optionally handles an invocation chain for this function
* <p>
Expand Down
235 changes: 200 additions & 35 deletions api/src/main/java/com/fnproject/fn/api/Headers.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,51 @@
package com.fnproject.fn.api;

import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
* Represents the headers on an HTTP request or response. Multiple headers with the same key are collapsed into a single
* entry where the values are concatenated by commas as per the HTTP spec (RFC 7230).
* Represents a set of String-String[] header attributes, per HTTP headers.
* <p>
* Internally header keys are always canonicalized using HTTP header conventions
* <p>
* <p>
* Headers are immutable
* <p>
* keys are are stored and compared in a case-insensitive way
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

*/
public final class Headers {
private Map<String, String> headers;
private static final Headers emptyHeaders = new Headers(Collections.emptyMap());
private Map<String, List<String>> headers;

private Headers(Map<String, List<String>> headersIn) {
this.headers = headersIn;
}

private static Pattern headerName = Pattern.compile("[A-Za-z0-9!#%&'*+-.^_`|~]+");


/**
* Calculates the canonical key (cf RFC 7230) for a header
* <p>
* If the header contains invalid characters it returns the original header
*
* @param key the header key to canonicalise
* @return a canonical key or the original key if the input contains invalid character
*/
public static String canonicalKey(String key) {
if (!headerName.matcher(key).matches()) {
return key;
}
String parts[] = key.split("-", -1);
for (int i = 0; i < parts.length; i++) {
String p = parts[i];
if (p.length() > 0) {
parts[i] = p.substring(0, 1).toUpperCase() + p.substring(1).toLowerCase();
}
}
return String.join("-", parts);

private Headers(Map<String, String> headers) {
this.headers = headers;
}

/**
Expand All @@ -21,6 +56,23 @@ private Headers(Map<String, String> headers) {
* @return {@code Headers} built from headers map
*/
public static Headers fromMap(Map<String, String> headers) {
Objects.requireNonNull(headers, "headersIn");
Map<String, List<String>> h = new HashMap<>();
headers.forEach((k, v) -> h.put(canonicalKey(k), Collections.singletonList(v)));
return new Headers(Collections.unmodifiableMap(new HashMap<>(h)));
}

/**
* Build a headers object from a map composed of (name, value) entries, we take a copy of the map and
* disallow any further modification
*
* @param headers underlying collection of header entries to copy
* @return {@code Headers} built from headers map
*/
public static Headers fromMultiHeaderMap(Map<String, List<String>> headers) {
Map<String, List<String>> hm = new HashMap<>();

headers.forEach((k, vs) -> hm.put(canonicalKey(k), new ArrayList<>(vs)));
return new Headers(Collections.unmodifiableMap(new HashMap<>(Objects.requireNonNull(headers))));
}

Expand All @@ -30,64 +82,177 @@ public static Headers fromMap(Map<String, String> headers) {
* @return empty headers
*/
public static Headers emptyHeaders() {
return new Headers(Collections.emptyMap());
return emptyHeaders;
}


/**
* Creates a new headers object with the specified header added
* Creates a new headers object with the specified header added - if a header with the same key existed it the new value is appended
* <p>
* This will overwrite an existing header with an exact name match
*
* @param key new header key
* @param v1 new header value
* @param vs additional header values to set
* @return a new headers object with the specified header added
*/
public Headers addHeader(String key, String v1, String... vs) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(key, "value");

String canonKey = canonicalKey(key);

Map<String, List<String>> nm = new HashMap<>(headers);
List<String> current = nm.get(canonKey);

if (current == null) {
List<String> s = new ArrayList<>();
s.add(v1);
s.addAll(Arrays.asList(vs));

nm.put(canonKey, Collections.unmodifiableList(s));
} else {
List<String> s = new ArrayList<>(current);
s.add(v1);
s.addAll(Arrays.asList(vs));
nm.put(canonKey, Collections.unmodifiableList(s));
}
return new Headers(nm);

}

/**
* Creates a new headers object with the specified header set - this overwrites any existin values
* <p>
* This will overwrite an existing header with an exact name match
*
* @param key new header key
* @param value new header value
* @param v1 new header value
* @param vs more header values to set
* @return a new headers object with the specified header added
*/
public Headers withHeader(String key, String value){
Map<String,String> newHeaders = new HashMap<>();
newHeaders.putAll(getAll());
newHeaders.put(key,value);
return new Headers(newHeaders);
public Headers setHeader(String key, String v1, String... vs) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(v1, "v1");
Stream.of(vs).forEach((v) -> Objects.requireNonNull(v, "vs"));

Map<String, List<String>> nm = new HashMap<>(headers);
List<String> s = new ArrayList<>();
s.add(v1);
s.addAll(Arrays.asList(vs));
nm.put(canonicalKey(key), Collections.unmodifiableList(s));
return new Headers(Collections.unmodifiableMap(nm));
}


/**
* Creates a new headers object with the specified headers set - this overwrites any existin values
* <p>
* This will overwrite an existing header with an exact name match
*
* @param key new header key
* @param vs header values to set
* @return a new headers object with the specified header added
*/
public Headers setHeader(String key, Collection<String> vs) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(vs, "vs");
if (vs.size() == 0) {
throw new IllegalArgumentException("can't set keys to an empty list");
}
vs.forEach((v) -> Objects.requireNonNull(v, "vs"));

Map<String, List<String>> nm = new HashMap<>(headers);
nm.put(canonicalKey(key), Collections.unmodifiableList(new ArrayList<>(vs)));
return new Headers(Collections.unmodifiableMap(nm));

}

/**
* Creates a new headers object with the specified headers remove - this overwrites any existin values
* <p>
* This will overwrite an existing header with an exact name match
*
* @param key new header key
* @return a new headers object with the specified header removed
*/
public Headers removeHeader(String key) {
Objects.requireNonNull(key, "key");

String canonKey = canonicalKey(key);
if (!headers.containsKey(canonKey)) {
return this;
}

Map<String, List<String>> nm = new HashMap<>(headers);
nm.remove(canonKey);
return new Headers(Collections.unmodifiableMap(nm));

}

/**
* Returns the header matching the specified key. This matches headers in a case-insensitive way and substitutes
* underscore and hyphen characters such that : "CONTENT_TYPE" and "Content-type" are equivalent. If no matching
* header is found then {@code Optional.empty} is returned.
* <p>
* Multiple headers are collapsed by {@code fn} into a single header entry delimited by commas (see
* <a href="https://tools.ietf.org/html/rfc7230#section-3.2.2">RFC7230 Sec 3.2.2</a> for details), for example
*
* <pre>
* Accept: text/html
* Accept: text/plain
* </pre>
*
* is collapsed into
*
* <pre>
* Accept: text/html, text/plain
* </pre>
* When multiple headers are present then the first value is returned- see { #getAllValues(String key)} to get all values for a header
*
* @param key match key
* @return a header matching key or empty if no header matches.
* @throws NullPointerException if {@code key} is null.
*/
public Optional<String> get(String key) {
Objects.requireNonNull(key, "Key cannot be null");
return getAll().entrySet().stream()
.filter((e) -> e.getKey()
.replaceAll("-", "_")
.equalsIgnoreCase(key.replaceAll("-", "_")))
.map(Map.Entry::getValue)
.findFirst();
String canonKey = canonicalKey(key);

return headers.entrySet().stream()
.filter((e) -> e.getKey()
.equals(canonKey))
.map(Map.Entry::getValue)
.map((v) -> v.get(0))
.findFirst();
}

/**
* Returns a collection of current header keys
*
* @return a collection of keys
*/
public Collection<String> keys() {
return headers.keySet();
}

/**
* The function invocation headers passed on the request
* Returns the headers as a map
*
* @return a map of Invocation headers.
* @return a map of key-values
*/
public Map<String, String> getAll() {
public Map<String, List<String>> asMap() {
return headers;
}

public List<String> getAllValues(String key) {
return headers.getOrDefault(key, Collections.emptyList());
}

public int hashCode() {
return headers.hashCode();
}


public boolean equals(Object other) {
if (!(other instanceof Headers)) {
return false;
}
if (other == this) {
return true;
}
return headers.equals(((Headers) other).headers);
}

@Override
public String toString() {
return Objects.toString(headers);
}

}
Loading