lib/faastruby/server/runner.rb in faastruby-0.4.18 vs lib/faastruby/server/runner.rb in faastruby-0.5.0
- old
+ new
@@ -1,43 +1,138 @@
require 'base64'
-
+require 'open3'
module FaaStRuby
+ # require 'faastruby/server/response'
+ # require 'faastruby/server/runner_methods'
+ # require 'faastruby/server/function_object'
class Runner
- include RunnerMethods
- def initialize
+ def initialize(function_name)
+ # puts "initializing runner for function name #{function_name}"
@rendered = false
+ @function_name = function_name
+ @function_folder = "#{FaaStRuby::ProjectConfig.functions_dir}/#{function_name}"
+ # puts "function_folder: #{@function_folder}"
+ @config_file = "#{@function_folder}/faastruby.yml"
+ # puts "reading config file #{@config_file}"
+ @config = YAML.load(File.read(@config_file))
+ @language, @version = (@config['runtime'] || DEFAULT_RUBY_RUNTIME).split(':')
end
- def path
- @path
- end
-
def load_function(path)
- eval "Module.new do; #{File.read(path)};end"
+ eval %(
+ Module.new do
+ def self.require(path)
+ return load("\#{path}.rb") if File.file?("\#{path}.rb")
+ Kernel.require path
+ end
+ #{File.read(path)}
+ end
+ )
end
- def call(workspace_name, function_name, event, args)
- @short_path = "#{workspace_name}/#{function_name}"
- @path = "#{FaaStRuby::PROJECT_ROOT}/#{workspace_name}/#{function_name}"
- short_path = "#{workspace_name}/#{function_name}"
+ def call(event, args)
begin
- Dir.chdir(@path)
- function = load_function("#{@path}/handler.rb")
- runner = FunctionObject.new(short_path)
- runner.extend(function)
- response = runner.handler(event, *args)
- # response = handler(event, args)
- return response if response.is_a?(FaaStRuby::Response)
+ case @language
+ when 'ruby'
+ time, response = call_ruby(event, args)
+ when 'crystal'
+ time, response = call_crystal(event, args)
+ else
+ puts "[Runner] ERROR: could not determine the language for function #{@function_name}.".red
+ end
+ return [time, response] if response.is_a?(FaaStRuby::Response)
body = {
'error' => "Please use the helpers 'render' or 'respond_with' as your function return value."
}
- FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
+ [time, FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})]
rescue Exception => e
+ STDOUT.puts e.full_message
body = {
'error' => e.message,
'location' => e.backtrace&.first,
}
- FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})
+ [0.0, FaaStRuby::Response.new(body: Oj.dump(body), status: 500, headers: {'Content-Type' => 'application/json'})]
end
+ end
+ def call_ruby(event, args)
+ function_object = FunctionObject.new(@function_name)
+ reader, writer = IO.pipe
+ time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
+ pid = fork do
+ # puts "loading #{@function_folder}/handler.rb"
+ Dir.chdir(@function_folder)
+ begin
+ function = load_function("#{@function_folder}/handler.rb")
+ function_object.extend(function)
+ response = function_object.handler(event, *args)
+ raise FaaStRuby::Response::InvalidResponseError unless response.is_a?(FaaStRuby::Response)
+ rescue Exception => e
+ error = Oj.dump({
+ 'error' => e.message,
+ 'location' => e.backtrace&.first
+ })
+ response = FaaStRuby::Response.error(error)
+ end
+ writer.puts response.payload
+ writer.close
+ exit 0
+ end
+ response = FaaStRuby::Response.from_payload reader.gets.chomp
+ reader.close
+ Process.wait(pid)
+ time_finish = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
+ time = (time_finish - time_start).round(2)
+ [time, response]
+ end
+
+ def chdir
+ CHDIR_MUTEX.synchronize do
+ # puts "Switching to directory #{@function_folder}"
+ Dir.chdir(@function_folder)
+ yield
+ end
+ end
+
+ def call_crystal(event, args)
+ ####
+ # This is a hack to address the bug https://github.com/crystal-lang/crystal/issues/7052
+ event.query_params.each do |k, v|
+ event.query_params[k] = '' if v.nil?
+ end
+ event.headers.each do |k, v|
+ event.headers[k] = '' if v.nil?
+ end
+ ####
+ payload_json = Oj.dump({'event' => event.to_h, 'args' => []})
+ payload = Base64.urlsafe_encode64(payload_json, padding: false)
+ cmd = "./handler"
+ # STDOUT.puts "Running #{cmd}"
+ # STDOUT.puts "From #{@function_folder}"
+ output = nil
+ time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
+ Open3.popen2(cmd, chdir: @function_folder) do |stdin, stdout, status|
+ stdin.puts payload
+ stdout.each_line do |line|
+ if line[0..1] == 'R,'
+ output = line.chomp
+ break
+ else
+ puts line.chomp
+ end
+ end
+ end
+ time_finish = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
+ time = (time_finish - time_start).round(2)
+ tag, o = output.split(',')
+ decoded_response = Base64.urlsafe_decode64(o)
+ response_obj = Oj.load(decoded_response)
+ # STDOUT.puts response_obj
+ response = FaaStRuby::Response.new(
+ body: response_obj['response'],
+ status: response_obj['status'],
+ headers: response_obj['headers'],
+ binary: response_obj['binary']
+ )
+ [time, response]
end
end
end
\ No newline at end of file