faastruby 0.4.8 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
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