faastruby 0.4.8 → 0.4.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4a573029053f751c92906881003347d1938c0df71fda2bfa363378375d8f613
4
- data.tar.gz: c9c2d9fdba7d8b786b3065d5f66f9ed8f95830eae4dddc18f67faa91c3cdead4
3
+ metadata.gz: a1643e934126a8b362f512fef2ab59834562fc01cc1d8751f9ee7606d1586503
4
+ data.tar.gz: f363ba8b0befa0a807fd16f29c28893a7c5302457628511d77d9675c2683ce13
5
5
  SHA512:
6
- metadata.gz: 062f7d9358d2a0ed608142a0a6ad1584a18cf1986717ebd626e0462a821749e4badb1c2a5c7568c0fedffe67b817c108c01419d6284a30b487083fa08502fed1
7
- data.tar.gz: b8ba9a39e1ccad877601630709e980bcca3501f5ade59cbb1688872126c2bb0e8a8fd77858924373617521c006a73aa4ba67c881c8c0e5e7e264e12ef5b7fd64
6
+ metadata.gz: adce689b5896a361aed94ea73438d1cf7090afc3638f438fa9744cf9a8cceb34cf5a5c089506c24c47f433c22be0ed35cabb3c3266301fff731dc1a2415ba475
7
+ data.tar.gz: fc39dcf79dc53e71e788233b978f756aab26907562c0fb07cc912496c4b1821a151c2f2f3ba5db65476046f3bc31e284e17fb2a70d3bd304cc8a8c4c1293da89
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.9 - Jan 13 2019
4
+ - Changes in `faastruby server`
5
+ - Cleaned up code
6
+ - Logs are easier to read
7
+ - Function responses show up in the log
8
+ - Support for events with `publish`
9
+
3
10
  ## 0.4.8 - Jan 8 2019
4
11
  - Use the actual param rather than the Sinatra::Params object
5
12
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- faastruby (0.4.7)
4
+ faastruby (0.4.8)
5
5
  colorize (~> 0.8)
6
6
  faastruby-rpc (~> 0.2.1)
7
7
  oj (~> 3.6)
data/exe/faastruby-server CHANGED
@@ -1,200 +1,74 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'sinatra'
4
- require 'sinatra/multi_route'
5
2
  require 'yaml'
6
3
  require 'oj'
7
4
  require 'faastruby-rpc'
8
5
  require 'base64'
9
-
10
- module FaaStRuby
11
- class DoubleRenderError < StandardError; end
6
+ require 'faastruby/server'
7
+ require 'sinatra'
8
+ require 'sinatra/multi_route'
9
+ require 'colorize'
10
+ FaaStRuby::EventHub.listen_for_events!
11
+
12
+ set :port, 3000
13
+ set :bind, '0.0.0.0'
14
+ case ARGV.shift
15
+ when '-p'
16
+ set :port, ARGV.shift
17
+ when '-b', '-o'
18
+ set :bind, ARGV.shift
12
19
  end
13
-
14
- module FaaStRuby
15
- class Runner
16
- def initialize
17
- @rendered = false
18
- end
19
-
20
- def call(workspace_name, function_name, event, args)
21
- begin
22
- load "./#{workspace_name}/#{function_name}/handler.rb"
23
- response = handler(event, *args)
24
- return response if response.is_a?(FaaStRuby::Response)
25
- body = {
26
- 'error' => "Please use the helpers 'render' or 'respond_with' as your function return value."
27
- }
28
- FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
29
- rescue Exception => e
30
- body = {
31
- 'error' => e.message,
32
- 'location' => e.backtrace&.first,
33
- }
34
- FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
35
- end
36
- end
37
-
38
- def rendered!
39
- @rendered = true
40
- end
41
- def rendered?
42
- @rendered
43
- end
44
-
45
- def respond_with(body, status: 200, headers: {}, binary: false)
46
- raise FaaStRuby::DoubleRenderError.new("You called 'render' or 'respond_with' twice in your handler method") if rendered?
47
- response = FaaStRuby::Response.new(body: body, status: status, headers: headers, binary: binary)
48
- rendered!
49
- response
50
- end
51
-
52
- def render(
53
- js: nil,
54
- body: nil,
55
- inline: nil,
56
- html: nil,
57
- json: nil,
58
- yaml: nil,
59
- text: nil,
60
- data: nil,
61
- png: nil,
62
- svg: nil,
63
- jpeg: nil,
64
- gif: nil,
65
- icon: nil,
66
- status: 200, headers: {}, content_type: nil, binary: false
67
- )
68
- headers["Content-Type"] = content_type if content_type
69
- bin = false
70
- case
71
- when json
72
- headers["Content-Type"] ||= "application/json"
73
- resp_body = json.is_a?(String) ? json : Oj.dump(json)
74
- when html, inline
75
- headers["Content-Type"] ||= "text/html"
76
- resp_body = html
77
- when text
78
- headers["Content-Type"] ||= "text/plain"
79
- resp_body = text
80
- when yaml
81
- headers["Content-Type"] ||= "application/yaml"
82
- resp_body = yaml.is_a?(String) ? yaml : YAML.load(yaml)
83
- when body
84
- headers["Content-Type"] ||= "application/octet-stream"
85
- bin = binary
86
- resp_body = bin ? Base64.urlsafe_encode64(body) : body
87
- when data
88
- headers["Content-Type"] ||= "application/octet-stream"
89
- resp_body = Base64.urlsafe_encode64(data)
90
- bin = true
91
- when js
92
- headers["Content-Type"] ||= "text/javascript"
93
- resp_body = js
94
- when png
95
- headers["Content-Type"] ||= "image/png"
96
- resp_body = Base64.urlsafe_encode64(png)
97
- bin = true
98
- when svg
99
- headers["Content-Type"] ||= "image/svg+xml"
100
- resp_body = svg
101
- when jpeg
102
- headers["Content-Type"] ||= "image/jpeg"
103
- resp_body = Base64.urlsafe_encode64(jpeg)
104
- bin = true
105
- when gif
106
- headers["Content-Type"] ||= "image/gif"
107
- resp_body = Base64.urlsafe_encode64(gif)
108
- bin = true
109
- when icon
110
- headers["Content-Type"] ||= "image/x-icon"
111
- resp_body = Base64.urlsafe_encode64(icon)
112
- bin = true
113
- end
114
- respond_with(resp_body, status: status, headers: headers, binary: bin)
115
- end
20
+ set :server, %w[puma]
21
+ set :show_exceptions, true
22
+
23
+ register Sinatra::MultiRoute
24
+ route :get, :post, :put, :patch, :delete, '/:workspace_name/:function_name' do
25
+ path = "#{params[:workspace_name]}/#{params[:function_name]}"
26
+ headers = env.select { |key, value| key.include?('HTTP_') || ['CONTENT_TYPE', 'CONTENT_LENGTH', 'REMOTE_ADDR', 'REQUEST_METHOD', 'QUERY_STRING'].include?(key) }
27
+ if headers.has_key?("HTTP_FAASTRUBY_RPC")
28
+ body = nil
29
+ rpc_args = parse_body(request.body.read, headers['CONTENT_TYPE'], request.request_method) || []
30
+ else
31
+ body = parse_body(request.body.read, headers['CONTENT_TYPE'], request.request_method)
32
+ rpc_args = []
116
33
  end
117
-
118
- class Event < Struct
119
- end
120
-
121
- class Response
122
- attr_accessor :body, :status, :headers, :binary
123
- def initialize(body:, status: 200, headers: {}, binary: false)
124
- @body = body
125
- @status = status
126
- @headers = headers
127
- @binary = binary
128
- end
129
-
130
- def binary?
131
- @binary
132
- end
34
+ query_params = parse_query(request.query_string)
35
+ context = set_context(params[:workspace_name], params[:function_name])
36
+ event = FaaStRuby::Event.new(body, query_params, headers, context)
37
+ response = FaaStRuby::Runner.new.call(params[:workspace_name], params[:function_name], event, rpc_args)
38
+ status response.status
39
+ headers response.headers
40
+ if response.binary?
41
+ response_body = Base64.urlsafe_decode64(response.body)
42
+ else
43
+ response_body = response.body
133
44
  end
45
+ puts "[#{path}] #=> status=#{response.status} body=#{response_body.inspect} headers=#{Oj.dump response.headers}".light_blue
46
+ body response_body
134
47
  end
135
48
 
136
- class FaaStRubyServer < Sinatra::Application
137
- set :port, 3000
138
- set :bind, '0.0.0.0'
139
- case ARGV.shift
140
- when '-p'
141
- set :port, ARGV.shift
142
- when '-b'
143
- set :bind, ARGV.shift
144
- end
145
- set :server, %w[puma]
146
- set :run, false
147
- set :show_exceptions, true
148
-
149
- register Sinatra::MultiRoute
150
- route :get, :post, :put, :patch, :delete, '/:workspace_name/:function_name' do
151
- e = FaaStRuby::Event.new(:body, :query_params, :headers, :context)
152
- headers = env.select { |key, value| key.include?('HTTP_') || ['CONTENT_TYPE', 'CONTENT_LENGTH', 'REMOTE_ADDR', 'REQUEST_METHOD', 'QUERY_STRING'].include?(key) }
153
- if headers.has_key?("HTTP_FAASTRUBY_RPC")
154
- body = nil
155
- rpc_args = parse_body(request.body.read, headers['CONTENT_TYPE'], request.request_method) || []
156
- else
157
- body = parse_body(request.body.read, headers['CONTENT_TYPE'], request.request_method)
158
- rpc_args = []
159
- end
160
- query_params = parse_query(request.query_string)
161
- context = set_context(params[:workspace_name], params[:function_name])
162
- event = e.new(body, query_params, headers, context)
163
- response = FaaStRuby::Runner.new.call(params[:workspace_name], params[:function_name], event, rpc_args)
164
- status response.status
165
- headers response.headers
166
- if response.binary?
167
- body Base64.urlsafe_decode64(response.body)
168
- else
169
- body response.body
170
- end
171
- end
172
-
173
- def parse_body(body, content_type, method)
174
- return nil if method == 'GET'
175
- return {} if body.nil? && method != 'GET'
176
- return Oj.load(body) if content_type == 'application/json'
177
- return body
178
- end
49
+ def parse_body(body, content_type, method)
50
+ return nil if method == 'GET'
51
+ return {} if body.nil? && method != 'GET'
52
+ return Oj.load(body) if content_type == 'application/json'
53
+ return body
54
+ end
179
55
 
180
- def set_context(workspace_name, function_name)
181
- return nil unless File.file?('context.yml')
182
- yaml = YAML.load(File.read('context.yml'))
183
- return nil unless yaml.has_key?(workspace_name)
184
- yaml[workspace_name][function_name]
185
- end
56
+ def set_context(workspace_name, function_name)
57
+ return nil unless File.file?('context.yml')
58
+ yaml = YAML.load(File.read('context.yml'))
59
+ return nil unless yaml.has_key?(workspace_name)
60
+ yaml[workspace_name][function_name]
61
+ end
186
62
 
187
- def parse_query(query_string)
188
- hash = {}
189
- query_string.split('&').each do |param|
190
- key, value = param.split('=')
191
- hash[key] = value
192
- end
193
- hash
194
- end
195
- def self.run?
196
- true
63
+ def parse_query(query_string)
64
+ hash = {}
65
+ query_string.split('&').each do |param|
66
+ key, value = param.split('=')
67
+ hash[key] = value
197
68
  end
69
+ hash
70
+ end
71
+ def self.run?
72
+ true
198
73
  end
199
74
 
200
- FaaStRubyServer.run! rescue nil # this will suppress some of the errors messages
@@ -0,0 +1,230 @@
1
+ module FaaStRuby
2
+ class DoubleRenderError < StandardError; end
3
+ class EventChannel
4
+ @@channels = {}
5
+ def self.channels
6
+ @@channels
7
+ end
8
+ attr_accessor :name
9
+ def initialize(channel)
10
+ @name = channel
11
+ @@channels[channel] ||= []
12
+ end
13
+ def subscribe(function_path)
14
+ @@channels[@name] << function_path
15
+ end
16
+ def subscribers
17
+ @@channels[@name] || []
18
+ end
19
+ end
20
+ class Subscriber
21
+ attr_accessor :path
22
+ def initialize(path)
23
+ @path = path
24
+ @workspace_name, @function_name = @path.split("/")
25
+ end
26
+
27
+ def call(encoded_data)
28
+ data = Base64.urlsafe_decode64(encoded_data)
29
+ headers = {'X-Origin' => 'event_hub', 'Content-Transfer-Encoding' => 'base64'}
30
+ event = Event.new(data, {}, headers, nil)
31
+ Runner.new.call(@workspace_name, @function_name, event, [])
32
+ end
33
+ end
34
+
35
+ class EventHub
36
+ @@queue = Queue.new
37
+ def self.queue
38
+ @@queue
39
+ end
40
+
41
+ def self.push(payload)
42
+ @@queue << payload
43
+ end
44
+
45
+ def self.thread
46
+ @@thread
47
+ end
48
+
49
+ def self.load_subscribers
50
+ Dir.glob('*/*/faastruby.yml').each do |file|
51
+ workspace_name, function_name, _ = file.split('/')
52
+ path = "#{workspace_name}/#{function_name}"
53
+ config = YAML.load(File.read(file))
54
+ next unless config['channels'].is_a?(Array)
55
+ config['channels'].compact!
56
+ config['channels'].each do |c|
57
+ channel = EventChannel.new(c)
58
+ channel.subscribe(path)
59
+ end
60
+ end
61
+ puts "[EventHub] Channel subscriptions: #{EventChannel.channels}".yellow
62
+ puts "[EventHub] If you modify 'faastruby.yml' in any function, you will need to restart the server to apply the changes.".yellow
63
+ end
64
+
65
+ def self.listen_for_events!
66
+ load_subscribers
67
+ @@thread = Thread.new do
68
+ loop do
69
+ encoded_channel, encoded_data = @@queue.pop.split(',')
70
+ channel = EventChannel.new(Base64.urlsafe_decode64(encoded_channel))
71
+ puts "[EventHub] Event channel=#{channel.name.inspect}".yellow
72
+ channel.subscribers.each do |s|
73
+ subscriber = Subscriber.new(s)
74
+ puts "[EventHub] Trigger function=#{subscriber.path.inspect} base64_payload=#{encoded_data.inspect}".yellow
75
+ response = subscriber.call(encoded_data)
76
+ puts "[#{subscriber.path}] #=> status=#{response.status} body=#{response.body.inspect} headers=#{Oj.dump response.headers}".light_blue
77
+ end
78
+ end
79
+ end
80
+ puts "[EventHub] Events thread started.".yellow
81
+ end
82
+ end
83
+
84
+ class Runner
85
+ def initialize
86
+ @rendered = false
87
+ end
88
+
89
+ def path
90
+ @path
91
+ end
92
+
93
+ def call(workspace_name, function_name, event, args)
94
+ @path = "#{workspace_name}/#{function_name}"
95
+ begin
96
+ load "./#{workspace_name}/#{function_name}/handler.rb"
97
+ response = handler(event, *args)
98
+ return response if response.is_a?(FaaStRuby::Response)
99
+ body = {
100
+ 'error' => "Please use the helpers 'render' or 'respond_with' as your function return value."
101
+ }
102
+ FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
103
+ rescue Exception => e
104
+ body = {
105
+ 'error' => e.message,
106
+ 'location' => e.backtrace&.first,
107
+ }
108
+ FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
109
+ end
110
+ end
111
+
112
+ def rendered!
113
+ @rendered = true
114
+ end
115
+ def rendered?
116
+ @rendered
117
+ end
118
+
119
+ def respond_with(body, status: 200, headers: {}, binary: false)
120
+ raise FaaStRuby::DoubleRenderError.new("You called 'render' or 'respond_with' twice in your handler method") if rendered?
121
+ response = FaaStRuby::Response.new(body: body, status: status, headers: headers, binary: binary)
122
+ rendered!
123
+ response
124
+ end
125
+
126
+ def render(
127
+ js: nil,
128
+ body: nil,
129
+ inline: nil,
130
+ html: nil,
131
+ json: nil,
132
+ yaml: nil,
133
+ text: nil,
134
+ data: nil,
135
+ png: nil,
136
+ svg: nil,
137
+ jpeg: nil,
138
+ gif: nil,
139
+ icon: nil,
140
+ status: 200, headers: {}, content_type: nil, binary: false
141
+ )
142
+ headers["Content-Type"] = content_type if content_type
143
+ bin = false
144
+ case
145
+ when json
146
+ headers["Content-Type"] ||= "application/json"
147
+ resp_body = json.is_a?(String) ? json : Oj.dump(json)
148
+ when html, inline
149
+ headers["Content-Type"] ||= "text/html"
150
+ resp_body = html
151
+ when text
152
+ headers["Content-Type"] ||= "text/plain"
153
+ resp_body = text
154
+ when yaml
155
+ headers["Content-Type"] ||= "application/yaml"
156
+ resp_body = yaml.is_a?(String) ? yaml : YAML.load(yaml)
157
+ when body
158
+ headers["Content-Type"] ||= "application/octet-stream"
159
+ bin = binary
160
+ resp_body = bin ? Base64.urlsafe_encode64(body) : body
161
+ when data
162
+ headers["Content-Type"] ||= "application/octet-stream"
163
+ resp_body = Base64.urlsafe_encode64(data)
164
+ bin = true
165
+ when js
166
+ headers["Content-Type"] ||= "text/javascript"
167
+ resp_body = js
168
+ when png
169
+ headers["Content-Type"] ||= "image/png"
170
+ resp_body = Base64.urlsafe_encode64(png)
171
+ bin = true
172
+ when svg
173
+ headers["Content-Type"] ||= "image/svg+xml"
174
+ resp_body = svg
175
+ when jpeg
176
+ headers["Content-Type"] ||= "image/jpeg"
177
+ resp_body = Base64.urlsafe_encode64(jpeg)
178
+ bin = true
179
+ when gif
180
+ headers["Content-Type"] ||= "image/gif"
181
+ resp_body = Base64.urlsafe_encode64(gif)
182
+ bin = true
183
+ when icon
184
+ headers["Content-Type"] ||= "image/x-icon"
185
+ resp_body = Base64.urlsafe_encode64(icon)
186
+ bin = true
187
+ end
188
+ respond_with(resp_body, status: status, headers: headers, binary: bin)
189
+ end
190
+
191
+ def puts(msg)
192
+ super "[#{@path}] #{msg}".green
193
+ end
194
+
195
+ def publish(channel, data: nil)
196
+ begin
197
+ encoded_data = data ? Base64.urlsafe_encode64(data, padding: false) : ""
198
+ payload = %(#{Base64.urlsafe_encode64(channel, padding: false)},#{encoded_data})
199
+ EventHub.queue.push payload
200
+ true
201
+ rescue
202
+ false
203
+ end
204
+ end
205
+ end
206
+
207
+ class Event
208
+ attr_accessor :body, :query_params, :headers, :context
209
+ def initialize(body, query_params, headers, context)
210
+ @body = body
211
+ @query_params = query_params
212
+ @headers = headers
213
+ @context = context
214
+ end
215
+ end
216
+
217
+ class Response
218
+ attr_accessor :body, :status, :headers, :binary
219
+ def initialize(body:, status: 200, headers: {}, binary: false)
220
+ @body = body
221
+ @status = status
222
+ @headers = headers
223
+ @binary = binary
224
+ end
225
+
226
+ def binary?
227
+ @binary
228
+ end
229
+ end
230
+ end
@@ -1,3 +1,3 @@
1
1
  module FaaStRuby
2
- VERSION = '0.4.8'
2
+ VERSION = '0.4.9'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faastruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.8
4
+ version: 0.4.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-01-08 00:00:00.000000000 Z
11
+ date: 2019-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -256,6 +256,7 @@ files:
256
256
  - lib/faastruby/cli/credentials.rb
257
257
  - lib/faastruby/cli/package.rb
258
258
  - lib/faastruby/function.rb
259
+ - lib/faastruby/server.rb
259
260
  - lib/faastruby/version.rb
260
261
  - lib/faastruby/workspace.rb
261
262
  - templates/crystal/example-blank/spec/handler_spec.cr