fdk 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +3 -11
- data/Rakefile +5 -0
- data/lib/fdk/context.rb +122 -24
- data/lib/fdk/runner.rb +132 -57
- data/lib/fdk/version.rb +1 -1
- metadata +13 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a50737dfa816cbaa0d9de365617db0b1c393d47984529e6de1ac3fdf6e1f9167
|
4
|
+
data.tar.gz: 8e843ba19e95fc057ec571ec05dc6e4460761bfc5aa2f3bce81a4f068bfb31ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67c53e7e572ca2a7f3c894ebe111b07fbf86df79e407ab10e2484e0479a0f67d02f45e504b46af9c5a2c81763994c6986e7f9a7d768ee8c341d175800b2bf013
|
7
|
+
data.tar.gz: ef3b16817052b5eee6ea90d7c0bbd8158446097a87df2db0d2dfad071beed80044c6bad14a8731064340909b3f9ae5ca6338498874864fae1d2709f6a3559f7e
|
data/README.md
CHANGED
@@ -26,7 +26,7 @@ an FDK::Response object instead of a string.
|
|
26
26
|
Then simply pass that function to the FDK:
|
27
27
|
|
28
28
|
```ruby
|
29
|
-
FDK.handle(:myfunction)
|
29
|
+
FDK.handle(target: :myfunction)
|
30
30
|
```
|
31
31
|
|
32
32
|
## Examples
|
@@ -46,7 +46,7 @@ def myfunction(context:, input:)
|
|
46
46
|
{ message: "Hello #{name}!" }
|
47
47
|
end
|
48
48
|
|
49
|
-
FDK.handle(
|
49
|
+
FDK.handle(target: :myfunction)
|
50
50
|
```
|
51
51
|
|
52
52
|
## Deploying functions
|
@@ -85,15 +85,7 @@ $ fn invoke examples hello
|
|
85
85
|
To get a more personal message, send a name in a JSON format and set the
|
86
86
|
`content-type 'application/json'`:
|
87
87
|
```
|
88
|
-
echo '{"name":"Joe"}' | fn invoke examples hello --content-type
|
89
|
-
'application/json'
|
88
|
+
echo '{"name":"Joe"}' | fn invoke examples hello --content-type 'application/json'
|
90
89
|
{"message":"Hello Joe!"}
|
91
90
|
```
|
92
91
|
|
93
|
-
## Compare cold and hot functions
|
94
|
-
|
95
|
-
Run [loop.rb](examples/loop.rb)
|
96
|
-
|
97
|
-
```sh
|
98
|
-
ruby loop.rb
|
99
|
-
```
|
data/Rakefile
ADDED
data/lib/fdk/context.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
1
3
|
module FDK
|
2
4
|
|
3
5
|
# Config looks up values in the env vars
|
@@ -7,50 +9,146 @@ module FDK
|
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
|
-
class Context
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
class InHeaders
|
14
|
+
def initialize (h, key_fn)
|
15
|
+
@headers = h
|
16
|
+
@key_fn = key_fn
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def headerKey(key)
|
21
|
+
if @key_fn
|
22
|
+
key = @key_fn.call(key)
|
23
|
+
end
|
24
|
+
key.downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
h = @headers[headerKey(key)]
|
29
|
+
unless h.nil?
|
30
|
+
return h[0]
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def each (&block)
|
36
|
+
@headers.each &block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class OutHeaders < InHeaders
|
41
|
+
|
42
|
+
def initialize(h, key_in_fn)
|
43
|
+
super(h, key_in_fn)
|
44
|
+
end
|
45
|
+
|
14
46
|
|
47
|
+
def []=(key, value)
|
48
|
+
if value.is_a? Array
|
49
|
+
h = []
|
50
|
+
value.each {|x| h.push(x.to_s)}
|
51
|
+
@headers[headerKey(key)] = h
|
52
|
+
else
|
53
|
+
@headers[headerKey(key)] = [value.to_s]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete(key)
|
58
|
+
@headers.delete headerKey(key)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class Context
|
64
|
+
|
65
|
+
# FN_CALL_ID - a unique ID for each function execution.
|
15
66
|
# FN_REQUEST_URL - the full URL for the request (parsing example)
|
67
|
+
# FN_HEADER_$X - the HTTP headers that were set for this request. Replace $X with the upper cased name of the header and replace dashes in the header with underscores.
|
68
|
+
# $X - any configuration values you've set for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: minio_secret=secret will be exposed via MINIO_SECRET env var.
|
16
69
|
# FN_APP_NAME - the name of the application that matched this route, eg: myapp
|
17
|
-
# FN_PATH - the matched route, eg: /hello
|
18
70
|
# FN_METHOD - the HTTP method for the request, eg: GET or POST
|
19
|
-
# FN_CALL_ID - a unique ID for each function execution.
|
20
|
-
# FN_FORMAT - a string representing one of the function formats, currently either default or http. Default is default.
|
21
71
|
# FN_MEMORY - a number representing the amount of memory available to the call, in MB
|
22
|
-
# FN_TYPE - the type for this call, currently 'sync' or 'async'
|
23
|
-
# FN_HEADER_$X - the HTTP headers that were set for this request. Replace $X with the upper cased name of the header and replace dashes in the header with underscores.
|
24
|
-
# $X - any configuration values you've set for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: minio_secret=secret will be exposed via MINIO_SECRET env var.
|
25
|
-
# FN_PARAM_$Y
|
26
72
|
|
27
|
-
# CloudEvent format: https://github.com/cloudevents/spec/blob/master/serialization.md#json
|
28
73
|
|
29
|
-
attr_reader :
|
74
|
+
attr_reader :headers
|
75
|
+
attr_reader :response_headers
|
30
76
|
|
31
|
-
def initialize(
|
32
|
-
@
|
77
|
+
def initialize(headers_in, headers_out)
|
78
|
+
@headers = headers_in
|
79
|
+
@response_headers = headers_out
|
80
|
+
@config ||= Config.new
|
33
81
|
end
|
34
82
|
|
35
|
-
|
36
|
-
def
|
37
|
-
|
83
|
+
|
84
|
+
def call_id
|
85
|
+
@headers['fn-call-id']
|
38
86
|
end
|
39
87
|
|
40
|
-
|
41
|
-
|
88
|
+
|
89
|
+
def app_id
|
90
|
+
@config['FN_APP_ID']
|
42
91
|
end
|
43
92
|
|
44
|
-
|
45
|
-
|
93
|
+
|
94
|
+
def fn_id
|
95
|
+
@config['FN_FN_ID']
|
96
|
+
end
|
97
|
+
|
98
|
+
def deadline
|
99
|
+
DateTime.iso8601(@headers['fn-deadline'])
|
100
|
+
end
|
101
|
+
|
102
|
+
def memory
|
103
|
+
@config['FN_MEMORY'].to_i
|
46
104
|
end
|
47
105
|
|
48
106
|
def content_type
|
49
|
-
|
107
|
+
@headers['content-type']
|
108
|
+
end
|
109
|
+
|
110
|
+
def http_context
|
111
|
+
HTTPContext.new(self)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
class HTTPContext
|
117
|
+
|
118
|
+
attr_reader :headers
|
119
|
+
attr_reader :response_headers
|
120
|
+
|
121
|
+
def initialize(ctx)
|
122
|
+
|
123
|
+
@ctx = ctx
|
124
|
+
|
125
|
+
|
126
|
+
http_headers = {}
|
127
|
+
ctx.headers.each {|k, v|
|
128
|
+
if k.downcase.start_with?('fn-http-h-')
|
129
|
+
new_key = k['fn-http-h-'.length..k.length]
|
130
|
+
http_headers[new_key] = v
|
131
|
+
end
|
132
|
+
}
|
133
|
+
|
134
|
+
@headers = InHeaders.new(http_headers, nil)
|
135
|
+
@response_headers = OutHeaders.new(ctx.response_headers, lambda {|s| 'fn-http-h-' + s})
|
50
136
|
end
|
51
137
|
|
52
|
-
|
53
|
-
|
138
|
+
|
139
|
+
def request_url
|
140
|
+
@ctx.headers['fn-http-request-url']
|
54
141
|
end
|
142
|
+
|
143
|
+
def method
|
144
|
+
@ctx.headers['fn-http-method']
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
def status_code=(val)
|
149
|
+
@ctx.response_headers['fn-http-status'] = val.to_i
|
150
|
+
end
|
151
|
+
|
55
152
|
end
|
56
153
|
end
|
154
|
+
|
data/lib/fdk/runner.rb
CHANGED
@@ -1,75 +1,150 @@
|
|
1
|
+
require "webrick"
|
2
|
+
require "fileutils"
|
3
|
+
require "json"
|
4
|
+
require "set"
|
5
|
+
|
1
6
|
# Looks for call(context, input) function
|
2
7
|
# Executes it with input
|
3
8
|
# Responds with output
|
9
|
+
module FDK
|
10
|
+
@filter_headers = Set["content-length", "te", "transfer-encoding",
|
11
|
+
"upgrade", "trailer"]
|
4
12
|
|
5
|
-
|
6
|
-
|
13
|
+
def self.check_format
|
14
|
+
f = ENV["FN_FORMAT"]
|
15
|
+
raise "'#{f}' not supported in Ruby FDK." unless f == "http-stream"
|
7
16
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# Skipping json parsing of body because it would already be a parsed map according to the format spec defined here: https://github.com/cloudevents/spec/blob/master/serialization.md#json
|
18
|
-
se = FDK.single_event(function: function, context: context, input: body)
|
19
|
-
|
20
|
-
# Respond with modified event
|
21
|
-
event['data'] = se
|
22
|
-
event['extensions']['protocol'] = {
|
23
|
-
headers: {
|
24
|
-
'Content-Type' => ['application/json']
|
25
|
-
},
|
26
|
-
'status_code' => 200
|
27
|
-
}
|
28
|
-
output_stream.puts event.to_json
|
29
|
-
output_stream.puts
|
30
|
-
output_stream.flush
|
31
|
-
end
|
17
|
+
f
|
18
|
+
end
|
19
|
+
private_class_method :check_format
|
20
|
+
|
21
|
+
def self.listener
|
22
|
+
l = ENV["FN_LISTENER"]
|
23
|
+
if l.nil? || !l.start_with?("unix:/")
|
24
|
+
raise "Missing or invalid socket URL in FN_LISTENER."
|
25
|
+
end
|
32
26
|
|
33
|
-
|
27
|
+
l
|
28
|
+
end
|
29
|
+
private_class_method :listener
|
34
30
|
|
35
|
-
|
36
|
-
parser = Yajl::Parser.new
|
31
|
+
@dbg = ENV["FDK_DEBUG"]
|
37
32
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
33
|
+
def self.debug(msg)
|
34
|
+
STDERR.puts(msg) if @dbg
|
35
|
+
end
|
36
|
+
private_class_method :debug
|
37
|
+
|
38
|
+
def self.handle(target:)
|
39
|
+
# To avoid Fn trying to connect to the socket before
|
40
|
+
# it's ready, the FDK creates a socket on (tmp_file).
|
41
|
+
#
|
42
|
+
# When the socket is ready to accept connections,
|
43
|
+
# the FDK links the tmp_file to the socket_file.
|
44
|
+
#
|
45
|
+
# Fn waits for the socket_file to be created and then connects
|
46
|
+
check_format
|
47
|
+
l = listener
|
48
|
+
socket_file = l[5..l.length]
|
49
|
+
tmp_file = socket_file + ".tmp"
|
50
|
+
|
51
|
+
debug tmp_file
|
52
|
+
debug socket_file
|
53
|
+
UNIXServer.open(tmp_file) do |serv|
|
54
|
+
File.chmod(0o666, tmp_file)
|
55
|
+
debug "listening on #{tmp_file}->#{socket_file}"
|
56
|
+
FileUtils.ln_s(File.basename(tmp_file), socket_file)
|
57
|
+
|
58
|
+
loop do
|
59
|
+
s = serv.accept
|
60
|
+
begin
|
61
|
+
loop do
|
62
|
+
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
63
|
+
req.parse s
|
64
|
+
debug "got request #{req}"
|
65
|
+
resp = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP)
|
66
|
+
resp.status = 200
|
67
|
+
handle_call(target, req, resp)
|
68
|
+
resp.send_response s
|
69
|
+
debug "sending resp #{resp.status}, #{resp.header}"
|
70
|
+
break unless req.keep_alive?
|
71
|
+
end
|
72
|
+
rescue StandardError => e
|
73
|
+
STDERR.puts "Error in request handling #{e}"
|
74
|
+
STDERR.puts e.backtrace
|
43
75
|
end
|
44
|
-
|
45
|
-
response = {
|
46
|
-
headers: {
|
47
|
-
'Content-Type' => ['application/json']
|
48
|
-
},
|
49
|
-
'status_code' => 200,
|
50
|
-
body: se.to_json
|
51
|
-
}
|
52
|
-
output_stream.puts response.to_json
|
53
|
-
output_stream.puts
|
54
|
-
output_stream.flush
|
76
|
+
s.close
|
55
77
|
end
|
78
|
+
end
|
79
|
+
end
|
56
80
|
|
57
|
-
|
81
|
+
def self.set_error(resp, error)
|
82
|
+
STDERR.puts "Error in function: \"#{error}\""
|
83
|
+
STDERR.puts error.backtrace
|
58
84
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
85
|
+
resp["content-type"] = "application/json"
|
86
|
+
resp.status = 502
|
87
|
+
resp.body = { message: "An error occurred in the function",
|
88
|
+
detail: error.to_s }.to_json
|
89
|
+
end
|
90
|
+
private_class_method :set_error
|
91
|
+
|
92
|
+
def self.handle_call(target, req, resp)
|
93
|
+
headers = {}
|
94
|
+
req.header.map do |k, v|
|
95
|
+
headers[k] = v unless @filter_headers.include? k
|
96
|
+
end
|
97
|
+
|
98
|
+
headers_out_hash = {}
|
99
|
+
headers_out = FDK::OutHeaders.new(headers_out_hash, nil)
|
100
|
+
headers_in = FDK::InHeaders.new(headers, nil)
|
101
|
+
context = FDK::Context.new(headers_in, headers_out)
|
102
|
+
input = ParsedInput.new(raw_input: req.body.to_s)
|
103
|
+
|
104
|
+
begin
|
105
|
+
rv = if target.respond_to? :call
|
106
|
+
target.call(context: context, input: input.parsed)
|
107
|
+
else
|
108
|
+
send(target, context: context, input: input.parsed)
|
109
|
+
end
|
110
|
+
rescue StandardError => e
|
111
|
+
set_error(resp, e)
|
112
|
+
return
|
113
|
+
end
|
114
|
+
|
115
|
+
resp.status = 200
|
116
|
+
headers_out_hash.map do |k, v|
|
117
|
+
resp[k] = v.join(",") unless @filter_headers.include? k
|
118
|
+
end
|
119
|
+
|
120
|
+
# TODO: gimme a bit me flexibility on response handling
|
121
|
+
# binary, streams etc
|
122
|
+
if !rv.nil? && rv.respond_to?("to_json")
|
123
|
+
resp.body = rv.to_json
|
124
|
+
# don't override content type if already set
|
125
|
+
resp["content-type"] = "application/json" unless resp["content-type"]
|
67
126
|
else
|
68
|
-
|
127
|
+
resp.body = rv.to_s
|
69
128
|
end
|
70
129
|
end
|
71
130
|
|
72
|
-
|
73
|
-
|
131
|
+
# Stores raw input and can parse it as
|
132
|
+
# JSON (add extra formats as required)
|
133
|
+
class ParsedInput
|
134
|
+
attr_reader :raw
|
135
|
+
|
136
|
+
def initialize(raw_input:)
|
137
|
+
@raw = raw_input
|
138
|
+
end
|
139
|
+
|
140
|
+
def as_json
|
141
|
+
@json ||= JSON.parse(raw)
|
142
|
+
rescue JSON::ParserError
|
143
|
+
@json = false
|
144
|
+
end
|
145
|
+
|
146
|
+
def parsed
|
147
|
+
as_json || raw
|
148
|
+
end
|
74
149
|
end
|
75
150
|
end
|
data/lib/fdk/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Travis Reeder
|
8
8
|
- Ewan Slater
|
9
|
+
- Owen Cliffe
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2018-
|
13
|
+
date: 2018-10-05 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: json
|
@@ -32,35 +33,37 @@ dependencies:
|
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: 2.1.0
|
34
35
|
- !ruby/object:Gem::Dependency
|
35
|
-
name:
|
36
|
+
name: net_http_unix
|
36
37
|
requirement: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
39
|
- - "~>"
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
+
version: '0.2'
|
41
42
|
- - ">="
|
42
43
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
44
|
-
type: :
|
44
|
+
version: 0.2.1
|
45
|
+
type: :development
|
45
46
|
prerelease: false
|
46
47
|
version_requirements: !ruby/object:Gem::Requirement
|
47
48
|
requirements:
|
48
49
|
- - "~>"
|
49
50
|
- !ruby/object:Gem::Version
|
50
|
-
version: '
|
51
|
+
version: '0.2'
|
51
52
|
- - ">="
|
52
53
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
54
|
+
version: 0.2.1
|
54
55
|
description: Ruby Function Developer Kit for Fn Project.
|
55
56
|
email:
|
56
57
|
- treeder@gmail.com
|
57
58
|
- ewan.slater@gmail.com
|
59
|
+
- owen.cliffe@oracle.com
|
58
60
|
executables: []
|
59
61
|
extensions: []
|
60
62
|
extra_rdoc_files: []
|
61
63
|
files:
|
62
64
|
- LICENSE
|
63
65
|
- README.md
|
66
|
+
- Rakefile
|
64
67
|
- lib/fdk.rb
|
65
68
|
- lib/fdk/context.rb
|
66
69
|
- lib/fdk/runner.rb
|
@@ -77,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
80
|
requirements:
|
78
81
|
- - ">="
|
79
82
|
- !ruby/object:Gem::Version
|
80
|
-
version: '2.
|
83
|
+
version: '2.4'
|
81
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
85
|
requirements:
|
83
86
|
- - ">="
|
@@ -85,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
88
|
version: '0'
|
86
89
|
requirements: []
|
87
90
|
rubyforge_project:
|
88
|
-
rubygems_version: 2.6
|
91
|
+
rubygems_version: 2.7.6
|
89
92
|
signing_key:
|
90
93
|
specification_version: 4
|
91
94
|
summary: Ruby FDK for Fn Project
|