faastruby 0.4.18 → 0.5.0

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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -3
  3. data/Gemfile.lock +28 -4
  4. data/README.md +63 -5
  5. data/faastruby.gemspec +5 -1
  6. data/lib/faastruby.rb +1 -0
  7. data/lib/faastruby/api.rb +154 -6
  8. data/lib/faastruby/base.rb +3 -9
  9. data/lib/faastruby/cli.rb +39 -12
  10. data/lib/faastruby/cli/base_command.rb +66 -0
  11. data/lib/faastruby/cli/commands.rb +122 -59
  12. data/lib/faastruby/cli/commands/account/base_command.rb +10 -0
  13. data/lib/faastruby/cli/commands/account/confirm.rb +94 -0
  14. data/lib/faastruby/cli/commands/account/login.rb +86 -0
  15. data/lib/faastruby/cli/commands/account/logout.rb +59 -0
  16. data/lib/faastruby/cli/commands/account/signup.rb +76 -0
  17. data/lib/faastruby/cli/commands/{function.rb → function/base_command.rb} +2 -11
  18. data/lib/faastruby/cli/commands/function/build.rb +18 -11
  19. data/lib/faastruby/cli/commands/function/deploy_to.rb +100 -37
  20. data/lib/faastruby/cli/commands/function/new.rb +89 -36
  21. data/lib/faastruby/cli/commands/function/remove_from.rb +21 -6
  22. data/lib/faastruby/cli/commands/function/run.rb +15 -15
  23. data/lib/faastruby/cli/commands/function/test.rb +5 -4
  24. data/lib/faastruby/cli/commands/function/update_context.rb +10 -3
  25. data/lib/faastruby/cli/commands/function/upgrade.rb +62 -61
  26. data/lib/faastruby/cli/commands/help.rb +33 -20
  27. data/lib/faastruby/cli/commands/project/base_command.rb +14 -0
  28. data/lib/faastruby/cli/commands/project/deploy.rb +114 -0
  29. data/lib/faastruby/cli/commands/project/down.rb +58 -0
  30. data/lib/faastruby/cli/commands/project/new.rb +237 -0
  31. data/lib/faastruby/cli/commands/workspace/cp.rb +107 -0
  32. data/lib/faastruby/cli/commands/workspace/create.rb +35 -27
  33. data/lib/faastruby/cli/commands/workspace/destroy.rb +14 -7
  34. data/lib/faastruby/cli/commands/workspace/list.rb +15 -6
  35. data/lib/faastruby/cli/commands/workspace/migrate.rb +93 -0
  36. data/lib/faastruby/cli/commands/workspace/rm.rb +81 -0
  37. data/lib/faastruby/cli/commands/workspace/update.rb +62 -0
  38. data/lib/faastruby/cli/credentials.rb +58 -57
  39. data/lib/faastruby/cli/new_credentials.rb +63 -0
  40. data/lib/faastruby/cli/package.rb +1 -0
  41. data/lib/faastruby/cli/template.rb +7 -7
  42. data/lib/faastruby/local.rb +188 -0
  43. data/lib/faastruby/local/crystal_runtime.cr +170 -0
  44. data/lib/faastruby/local/functions.rb +7 -0
  45. data/lib/faastruby/local/functions/crystal.rb +64 -0
  46. data/lib/faastruby/local/functions/function.rb +173 -0
  47. data/lib/faastruby/local/functions/ruby.rb +28 -0
  48. data/lib/faastruby/local/listeners.rb +5 -0
  49. data/lib/faastruby/local/listeners/listener.rb +104 -0
  50. data/lib/faastruby/local/logger.rb +37 -0
  51. data/lib/faastruby/local/monkey_patch.rb +38 -0
  52. data/lib/faastruby/local/processors.rb +7 -0
  53. data/lib/faastruby/local/processors/function.rb +151 -0
  54. data/lib/faastruby/local/processors/processor.rb +116 -0
  55. data/lib/faastruby/local/processors/static_file.rb +48 -0
  56. data/lib/faastruby/local/static_files.rb +5 -0
  57. data/lib/faastruby/local/static_files/static_file.rb +59 -0
  58. data/lib/faastruby/server.rb +44 -3
  59. data/lib/faastruby/server/app.rb +107 -0
  60. data/lib/faastruby/server/concurrency_controller.rb +50 -50
  61. data/lib/faastruby/server/config.ru +2 -0
  62. data/lib/faastruby/server/event.rb +3 -0
  63. data/lib/faastruby/server/event_hub.rb +7 -6
  64. data/lib/faastruby/server/local.rb +22 -0
  65. data/lib/faastruby/server/logger.rb +50 -0
  66. data/lib/faastruby/server/project_config.rb +44 -0
  67. data/lib/faastruby/server/puma.rb +4 -0
  68. data/lib/faastruby/server/response.rb +40 -0
  69. data/lib/faastruby/server/runner.rb +116 -21
  70. data/lib/faastruby/server/runner_methods.rb +17 -16
  71. data/lib/faastruby/server/sentinel.rb +496 -0
  72. data/lib/faastruby/supported_runtimes.rb +8 -0
  73. data/lib/faastruby/user.rb +77 -0
  74. data/lib/faastruby/version.rb +1 -1
  75. data/lib/faastruby/workspace.rb +36 -3
  76. data/templates/crystal/example-blank/handler.cr +3 -0
  77. data/templates/crystal/example/spec/handler_spec.cr +11 -6
  78. data/templates/public-web/assets/images/background.png +0 -0
  79. data/templates/public-web/assets/images/ruby.png +0 -0
  80. data/templates/public-web/assets/javascripts/main.js +1 -0
  81. data/templates/public-web/assets/stylesheets/main.css +70 -0
  82. data/templates/public-web/favicon.ico +0 -0
  83. data/templates/ruby/api-404/handler.rb +6 -0
  84. data/templates/ruby/api-root/handler.rb +6 -0
  85. data/templates/ruby/example-blank/handler.rb +0 -23
  86. data/templates/ruby/web-404/404.html +36 -0
  87. data/templates/ruby/web-404/handler.rb +3 -0
  88. data/templates/ruby/web-root/handler.rb +10 -0
  89. data/templates/ruby/web-root/index.html.erb +37 -0
  90. data/templates/ruby/web-root/template.rb +13 -0
  91. metadata +102 -21
  92. data/exe/faastruby-server +0 -76
  93. data/lib/faastruby/cli/commands/credentials.rb +0 -11
  94. data/lib/faastruby/cli/commands/credentials/add.rb +0 -58
  95. data/lib/faastruby/cli/commands/credentials/list.rb +0 -58
  96. data/lib/faastruby/cli/commands/workspace.rb +0 -13
  97. data/lib/faastruby/cli/commands/workspace/deploy.rb +0 -50
  98. data/templates/crystal/example-blank/README.md +0 -22
  99. data/templates/crystal/example-blank/spec/handler_spec.cr +0 -8
  100. data/templates/crystal/example-blank/spec/spec_helper.cr +0 -4
  101. data/templates/crystal/example-blank/src/handler.cr +0 -25
  102. data/templates/ruby/example-blank/Gemfile +0 -7
  103. data/templates/ruby/example-blank/README.md +0 -22
  104. data/templates/ruby/example-blank/spec/handler_spec.rb +0 -16
  105. data/templates/ruby/example-blank/spec/spec_helper.rb +0 -3
@@ -0,0 +1,107 @@
1
+ require 'oj'
2
+ require 'faastruby-rpc'
3
+ require 'base64'
4
+ require 'sinatra'
5
+ require 'sinatra/multi_route'
6
+ require 'securerandom'
7
+ require 'rouge'
8
+ require 'colorize'
9
+ module FaaStRuby
10
+
11
+ class Server < Sinatra::Base
12
+ include FaaStRuby::Logger::Requests
13
+ set :show_exceptions, true
14
+ set :logging, true
15
+ set :root, SERVER_ROOT
16
+ set :public_folder, FaaStRuby::ProjectConfig.public_dir
17
+ set :static, true
18
+ register Sinatra::MultiRoute
19
+ before do
20
+ cache_control :public, :must_revalidate, :max_age => 1
21
+ end
22
+ route :head, :get, :post, :put, :patch, :delete, '/*' do
23
+ request_uuid = SecureRandom.uuid
24
+ splat = params['splat'][0]
25
+ function_name = resolve_function_name(splat)
26
+ headers = parse_headers(env)
27
+ if headers.has_key?("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 = []
33
+ end
34
+ headers['X-Request-Id'] = request_uuid
35
+ headers['Request-Method'] = request.request_method
36
+ original_request_id = headers['X-Original-Request-Id']
37
+ query_params = parse_query(request.query_string)
38
+ context = Oj.dump(FaaStRuby::ProjectConfig.secrets_for_function(function_name))
39
+ event = FaaStRuby::Event.new(body: body, query_params: query_params, headers: headers, context: context)
40
+ log_request_message(function_name, request, request_uuid, query_params, body, context)
41
+ time, response = FaaStRuby::Runner.new(function_name).call(event, rpc_args)
42
+ status response.status
43
+ headers set_response_headers(response, request_uuid, original_request_id, time)
44
+ response_body, print_body = parse_response(response)
45
+ log_response_message(function_name, time, request_uuid, response, print_body)
46
+ body response_body
47
+ end
48
+
49
+ def log_request_message(function_name, request, request_uuid, query_params, body, context)
50
+ puts "[#{function_name}] <- [REQUEST: #{request.request_method} \"#{request.fullpath}\"] request_id=\"#{request_uuid}\" body=\"#{body}\" query_params=#{query_params} headers=#{headers}"
51
+ end
52
+
53
+ def log_response_message(function_name, time, request_uuid, response, print_body)
54
+ puts "[#{function_name}] -> [RESPONSE: #{time}ms] request_id=\"#{request_uuid}\" status=#{response.status} body=#{print_body.inspect} headers=#{response.headers}"
55
+ end
56
+
57
+ def set_response_headers(response, request_uuid, original_request_id = nil, time)
58
+ response.headers['X-Request-Id'] = request_uuid
59
+ response.headers['X-Original-Request-Id'] = original_request_id if original_request_id
60
+ response.headers['X-Execution-time'] = "#{time}ms"
61
+ response.headers
62
+ end
63
+
64
+ def parse_response(response)
65
+ return [Base64.urlsafe_decode64(response.body), "Base64(#{response.body})"] if response.binary?
66
+ return [response.body, "#{response.body}"]
67
+ end
68
+
69
+ def resolve_function_name(splat)
70
+ if splat == ''
71
+ return FaaStRuby::ProjectConfig.root_to
72
+ end
73
+ if !is_a_function?(splat)
74
+ return FaaStRuby::ProjectConfig.catch_all
75
+ end
76
+ return splat
77
+ end
78
+
79
+ def is_a_function?(name)
80
+ File.file?("#{FaaStRuby::ProjectConfig.functions_dir}/#{name}/faastruby.yml")
81
+ end
82
+
83
+ def parse_body(body, content_type, method)
84
+ return nil if method == 'GET'
85
+ return {} if body.nil? && method != 'GET'
86
+ return Oj.load(body) if content_type == 'application/json'
87
+ return body
88
+ end
89
+
90
+ def parse_query(query_string)
91
+ hash = {}
92
+ query_string.split('&').each do |param|
93
+ key, value = param.split('=')
94
+ hash[key] = value
95
+ end
96
+ hash
97
+ end
98
+
99
+ def parse_headers(env)
100
+ Hash[*env.select {|k,v| k.start_with? 'HTTP_'}
101
+ .collect {|k,v| [k.sub(/^HTTP_/, ''), v]}
102
+ .collect {|k,v| [k.split('_').collect{|a|k == 'DNT' ? k : k.capitalize}.join('-'), v]}
103
+ .sort
104
+ .flatten]
105
+ end
106
+ end
107
+ end
@@ -1,51 +1,51 @@
1
- module FaaStRuby
2
- class ConcurrencyController
3
- def self.store
4
- @@store ||= {}
5
- end
6
- attr_accessor :params, :name, :max, :type
7
- def initialize(name, max: 1, type:)
8
- @type = type
9
- @name = name
10
- @max = max
11
- @running = 0
12
- # @mutex = Mutex.new
13
- self.class.store[name] = self
14
- puts "[ConcurrencyController] Started controller for '#{name}' with max_concurrency = #{@max}".yellow
15
- end
16
-
17
- def running
18
- # puts "[ConcurrencyController] [#{name}] Reading runners".red
19
- # wait
20
- # puts "[ConcurrencyController] [#{name}] Locking mutex".red
21
- # @mutex.lock
22
- @running
23
- # ensure
24
- # puts "[ConcurrencyController] [#{name}] Unlocking mutex".red
25
- # @mutex.unlock
26
- end
1
+ # module FaaStRuby
2
+ # class ConcurrencyController
3
+ # def self.store
4
+ # @@store ||= {}
5
+ # end
6
+ # attr_accessor :params, :name, :max, :type
7
+ # def initialize(name, max: 1, type:)
8
+ # @type = type
9
+ # @name = name
10
+ # @max = max
11
+ # @running = 0
12
+ # # @mutex = Mutex.new
13
+ # self.class.store[name] = self
14
+ # puts "(ConcurrencyController) Started controller for '#{name}' with max_concurrency = #{@max}".yellow
15
+ # end
27
16
 
28
- def decr(amount = 1)
29
- incr(0 - amount)
30
- end
31
-
32
- def incr(amount = 1)
33
- # puts "[ConcurrencyController] [#{name}] Incr #{amount}".red
34
- # wait
35
- # puts "[ConcurrencyController] [#{name}] Locking mutex".red
36
- # @mutex.lock
37
- current = @running + amount
38
- return nil if max < current
39
- @running += amount
40
- # ensure
41
- # puts "[ConcurrencyController] [#{name}] Unlocking mutex".red
42
- # @mutex.unlock
43
- end
44
-
45
- # def wait
46
- # puts "[ConcurrencyController] [#{name}] Waiting for mutex lock to release".red
47
- # while @mutex.locked? do;end
48
- # puts "[ConcurrencyController] [#{name}] Mutex released".red
49
- # end
50
- end
51
- end
17
+ # def running
18
+ # # puts "[ConcurrencyController] [#{name}] Reading runners".red
19
+ # # wait
20
+ # # puts "[ConcurrencyController] [#{name}] Locking mutex".red
21
+ # # @mutex.lock
22
+ # @running
23
+ # # ensure
24
+ # # puts "[ConcurrencyController] [#{name}] Unlocking mutex".red
25
+ # # @mutex.unlock
26
+ # end
27
+
28
+ # def decr(amount = 1)
29
+ # incr(0 - amount)
30
+ # end
31
+
32
+ # def incr(amount = 1)
33
+ # # puts "[ConcurrencyController] [#{name}] Incr #{amount}".red
34
+ # # wait
35
+ # # puts "[ConcurrencyController] [#{name}] Locking mutex".red
36
+ # # @mutex.lock
37
+ # current = @running + amount
38
+ # return nil if max < current
39
+ # @running += amount
40
+ # # ensure
41
+ # # puts "[ConcurrencyController] [#{name}] Unlocking mutex".red
42
+ # # @mutex.unlock
43
+ # end
44
+
45
+ # # def wait
46
+ # # puts "[ConcurrencyController] [#{name}] Waiting for mutex lock to release".red
47
+ # # while @mutex.locked? do;end
48
+ # # puts "[ConcurrencyController] [#{name}] Mutex released".red
49
+ # # end
50
+ # end
51
+ # end
@@ -0,0 +1,2 @@
1
+ require 'faastruby/server'
2
+ run FaaStRuby::Server
@@ -15,5 +15,8 @@ module FaaStRuby
15
15
  "context" => @context
16
16
  }
17
17
  end
18
+ def to_json
19
+ Oj.dump(to_h)
20
+ end
18
21
  end
19
22
  end
@@ -1,5 +1,6 @@
1
1
  module FaaStRuby
2
2
  class EventHub
3
+ extend FaaStRuby::Logger::System
3
4
  @@queue = Queue.new
4
5
  def self.queue
5
6
  @@queue
@@ -15,7 +16,7 @@ module FaaStRuby
15
16
 
16
17
  def self.load_subscribers
17
18
  Dir.glob('*/*/faastruby.yml').each do |file|
18
- workspace_name, function_name, _ = file.split('/')
19
+ workspace_name, function_name, _ = file.split('/')
19
20
  path = "#{workspace_name}/#{function_name}"
20
21
  config = YAML.load(File.read(file))
21
22
  next unless config['channels'].is_a?(Array)
@@ -25,8 +26,8 @@ module FaaStRuby
25
26
  channel.subscribe(path)
26
27
  end
27
28
  end
28
- puts "[EventHub] Channel subscriptions: #{EventChannel.channels}".yellow
29
- puts "[EventHub] If you modify 'faastruby.yml' in any function, you will need to restart the server to apply the changes.".yellow
29
+ puts "#{tag} Channel subscriptions: #{EventChannel.channels}"
30
+ puts "#{tag} Please restart the server if you modify channel subscriptions in 'faastruby.yml' for any function."
30
31
  end
31
32
 
32
33
  def self.listen_for_events!
@@ -35,16 +36,16 @@ module FaaStRuby
35
36
  loop do
36
37
  encoded_channel, encoded_data = @@queue.pop.split(',')
37
38
  channel = EventChannel.new(Base64.urlsafe_decode64(encoded_channel))
38
- puts "[EventHub] Event channel=#{channel.name.inspect}".yellow
39
+ puts "#{tag} Event channel=#{channel.name.inspect}"
39
40
  channel.subscribers.each do |s|
40
41
  subscriber = Subscriber.new(s)
41
- puts "[EventHub] Trigger function=#{subscriber.path.inspect} base64_payload=#{encoded_data.inspect}".yellow
42
+ puts "#{tag} Trigger function=#{subscriber.path.inspect} base64_payload=#{encoded_data.inspect}"
42
43
  response = subscriber.call(encoded_data)
43
44
  puts "[#{subscriber.path}] #=> status=#{response.status} body=#{response.body.inspect} headers=#{Oj.dump response.headers}".light_blue
44
45
  end
45
46
  end
46
47
  end
47
- puts "[EventHub] Events thread started.".yellow
48
+ puts "#{tag} Events thread started."
48
49
  end
49
50
  end
50
51
  end
@@ -0,0 +1,22 @@
1
+ module FaaStRuby
2
+ module Local
3
+ def self.start!(sync: false, deploy_env: 'stage', debug: false)
4
+ Thread.new do
5
+ env = {
6
+ 'SYNC' => sync,
7
+ 'DEPLOY_ENVIRONMENT' => deploy_env
8
+ }
9
+ cmd = []
10
+ cmd << "SYNC=true" if sync
11
+ cmd << "DEBUG=true" if debug
12
+ cmd << "DEPLOY_ENVIRONMENT=#{deploy_env}"
13
+ cmd << "faastruby watch"
14
+ 5.times do
15
+ system(cmd.join(" "))
16
+ sleep 1
17
+ end
18
+ puts "FaaStRuby Local exited. Please press CTRL+C."
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ module FaaStRuby
2
+ STDOUT_MUTEX = Mutex.new
3
+ module Logger
4
+ module Requests
5
+
6
+ def puts(msg)
7
+ msg = Rouge.highlight(msg, 'ruby', Rouge::Formatters::Terminal256.new(Rouge::Themes::Monokai.new))
8
+ STDOUT_MUTEX.synchronize do
9
+ STDOUT.puts "#{Time.now} | #{msg}"
10
+ STDOUT.puts "---"
11
+ end
12
+ end
13
+
14
+ def self.rougify(string, kind)
15
+ Rouge.highlight(string, kind, Rouge::Formatters::Terminal256.new(Rouge::Themes::Monokai.new))
16
+ end
17
+
18
+ def rougify(string, kind)
19
+ FaaStRuby::Logger.rougify(string, kind)
20
+ end
21
+
22
+ def tag
23
+ return "(#{self.name.split('::').last})" if self.is_a? Class
24
+ return "(#{self.class.name.split('::').last})"
25
+ end
26
+
27
+ end
28
+
29
+ module System
30
+ def tag
31
+ return "(#{self.name.split('::').last})" if self.is_a? Class
32
+ return "(#{self.class.name.split('::').last})"
33
+ end
34
+
35
+ def self.puts(msg)
36
+ STDOUT_MUTEX.synchronize do
37
+ STDOUT.puts "#{Time.now} | #{msg}".yellow
38
+ STDOUT.puts "---".yellow
39
+ end
40
+ end
41
+
42
+ def puts(msg)
43
+ STDOUT_MUTEX.synchronize do
44
+ STDOUT.puts "#{Time.now} | #{msg}".yellow
45
+ STDOUT.puts "---".yellow
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ module FaaStRuby
2
+ class ProjectConfig
3
+
4
+ def self.public_dir(absolute: true)
5
+ path = "#{SERVER_ROOT}/#{project_config['public_dir'] || 'public'}" if absolute
6
+ path ||= project_config['public_dir'] || 'public'
7
+ path.gsub(/\/$/, '')
8
+ end
9
+
10
+ def self.public_dir?
11
+ File.directory? "#{SERVER_ROOT}/#{project_config['public_dir'] || 'public'}"
12
+ end
13
+
14
+ def self.functions_dir(absolute: true)
15
+ path = "#{SERVER_ROOT}/#{project_config['functions_dir'] || 'functions'}" if absolute
16
+ path ||= project_config['functions_dir'] || 'functions'
17
+ path.gsub(/\/$/, '')
18
+ end
19
+
20
+ def self.project_config
21
+ YAML.load(File.read(PROJECT_YAML_FILE))['project']
22
+ end
23
+
24
+ def self.root_to
25
+ project_config['root_to'] || 'root'
26
+ end
27
+
28
+ def self.catch_all
29
+ project_config['catch_all'] || 'catch-all'
30
+ end
31
+
32
+ def self.secrets
33
+ YAML.load(File.read(SECRETS_FILE))['secrets'] || {}
34
+ end
35
+
36
+ def self.deploy_environment_secrets
37
+ secrets[DEPLOY_ENVIRONMENT] || {}
38
+ end
39
+
40
+ def self.secrets_for_function(function_name)
41
+ deploy_environment_secrets[function_name] || {}
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,4 @@
1
+ threads 0,32
2
+ workers 0
3
+ environment "production"
4
+ bind "tcp://0.0.0.0:3000"
@@ -1,5 +1,18 @@
1
1
  module FaaStRuby
2
+ require 'base64'
3
+ class InvalidResponseError < StandardError
4
+ def initialize(msg)
5
+ msg = "You must use the method 'render' within your function handler."
6
+ end
7
+ end
2
8
  class Response
9
+ def self.error(error)
10
+ new(
11
+ body: error,
12
+ status: 500,
13
+ headers: {'Content-Type' => 'application/json'}
14
+ )
15
+ end
3
16
  def self.request_limit_reached(workspace: nil, function: nil)
4
17
  body = {'error' => "Concurrent requests limit reached. Please add more runners to the workspace #{workspace}."} if workspace
5
18
  # body = {'error' => "Concurrent requests limit reached for function '#{workspace}/#{function}'. Please associate more runners."} if function
@@ -10,6 +23,19 @@ module FaaStRuby
10
23
  headers: {'Content-Type' => 'application/json'}
11
24
  )
12
25
  end
26
+
27
+ def self.from_payload(payload)
28
+ from_json Base64.urlsafe_decode64(payload)
29
+ end
30
+ def self.from_json(json)
31
+ hash = Oj.load(json)
32
+ new(
33
+ body: hash['body'],
34
+ status: hash['status'],
35
+ headers: hash['headers'],
36
+ binary: hash['binary']
37
+ )
38
+ end
13
39
  attr_accessor :body, :status, :headers, :binary
14
40
  def initialize(body:, status: 200, headers: {}, binary: false)
15
41
  @body = body
@@ -18,6 +44,20 @@ module FaaStRuby
18
44
  @binary = binary
19
45
  end
20
46
 
47
+ def to_json
48
+ hash = {
49
+ 'body' => body,
50
+ 'status' => status,
51
+ 'headers' => headers,
52
+ 'binary' => binary
53
+ }
54
+ Oj.dump(hash)
55
+ end
56
+
57
+ def payload
58
+ Base64.urlsafe_encode64(to_json, padding: false)
59
+ end
60
+
21
61
  def binary?
22
62
  @binary
23
63
  end