From ff64421bdfd70bc4d1c5e6ee4c72bb6c7021d81c Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 8 Apr 2017 19:47:27 -0500 Subject: [PATCH] Refactor mpx-catapult to use em_promise Instead of writing our own, just use the built-in Goliath runner. This changes the usage, new usage is: REDIS_URL=redis://localhost:6379/0 bundle exec mpx-catapult.rb -p $PORT -s -v There is a --help for more information (provided by Goliath). Instead of using blocking I/O, use EventMachine. To be consistent with the rest of the project, use em_promise --- .rubocop.yml | 3 + Gemfile | 2 +- mpx-catapult.rb | 169 +++++++++++++++++++++--------------------------- 3 files changed, 77 insertions(+), 97 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4fa0adeeceaa2e351d503c9d1df1557e0fc41839..f03ac690ad7d47a58257e7950772fa01ce2f5c37 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -103,5 +103,8 @@ Style/SpaceAroundEqualsInParameterDefault: Style/IndentArray: EnforcedStyle: consistent +Style/SymbolArray: + EnforcedStyle: brackets + Style/FirstParameterIndentation: EnforcedStyle: consistent diff --git a/Gemfile b/Gemfile index 9fe5c18de0dd255875a97aced5421f1196938efe..d4a7109af3ffff61c8b3b47387a4c791f2664423 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gem 'activesupport', '<5.0.0' gem 'blather' gem 'em-http-request' -gem 'eventmachine', '1.0.0' +gem 'eventmachine', '1.0.1' gem 'promise.rb' gem 'em-hiredis' diff --git a/mpx-catapult.rb b/mpx-catapult.rb index db58070d624bb71c91a52f307cf9e033491b8020..330321b10470e162beb956cee0f43103ae941b0c 100755 --- a/mpx-catapult.rb +++ b/mpx-catapult.rb @@ -22,114 +22,91 @@ $stdout.sync = true puts "Soprani.ca/MMS Proxy for XMPP - Catapult\n"\ "==>> last commit of this version is " + `git rev-parse HEAD` + "\n" +require 'em-hiredis' +require 'em-http-request' require 'goliath' -require 'net/http' -require 'redis/connection/hiredis' require 'uri' -require 'webrick' -if ARGV.size != 3 - puts "Usage: mpx-catapult.rb "\ - " " - exit 0 -end +require_relative 'em_promise' t = Time.now puts "LOG %d.%09d: starting...\n\n" % [t.to_i, t.nsec] -class WebhookHandler < Goliath::API - def response(env) - puts 'ENV: ' + env.to_s - puts 'path: ' + env['REQUEST_PATH'] - puts 'method: ' + env['REQUEST_METHOD'] - puts 'BODY: ' + Rack::Request.new(env).body.read - - cred_key = "catapult_cred-" + WEBrick::HTTPUtils.unescape( - env['REQUEST_PATH'].split('/', 3)[1]) - - # TODO: connect at start of program instead - conn = Hiredis::Connection.new - begin - conn.connect(ARGV[1], ARGV[2].to_i) - rescue => e - puts 'ERROR: Redis connection failed: ' + e.inspect - return [ - 500, - {'Content-Type' => 'text/plain'}, - e.inspect - ] - end - - conn.write ["EXISTS", cred_key] - if conn.read == 0 - conn.disconnect - - puts 'ERROR: invalid path rqst: ' + env['REQUEST_PATH'] - return [ - 404, - {'Content-Type' => 'text/plain'}, - 'not found' - ] - end - - conn.write ["LRANGE", cred_key, 0, 2] - user_id, api_token, api_secret = conn.read - conn.disconnect +EM.next_tick do + REDIS = EM::Hiredis.connect +end - uri = URI.parse('https://api.catapult.inetwork.com') - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - request = '' - if env['REQUEST_METHOD'] == 'GET' - request = Net::HTTP::Get.new('/v1/users/' + user_id + - '/media/' +env['REQUEST_PATH'].split('/', 3)[2]) - elsif env['REQUEST_METHOD'] == 'HEAD' - request = Net::HTTP::Head.new('/v1/users/' + user_id + - '/media/' +env['REQUEST_PATH'].split('/', 3)[2]) - else - puts 'ERROR: received non-HEAD/-GET request' - return [ - 500, - {'Content-Type' => 'text/plain'}, - e.inspect - ] +class WebhookHandler < Goliath::API + def media_request(env, user_id, token, secret, method, media_id) + if ![:get, :head].include?(method) + env.logger.debug 'ERROR: received non-HEAD/-GET request' + return EMPromise.reject(405) end - request.basic_auth api_token, api_secret - response = http.request(request) - - puts 'API response to send: ' + response.to_s + ' with code ' + - response.code + ', body ' - if response.code != '200' - puts 'ERROR: unexpected return code ' + response.code - - if response.code == '404' - return [ - 404, - {'Content-Type' => 'text/plain'}, - 'not found' - ] + EM::HttpRequest.new( + "https://api.catapult.inetwork.com/v1/users/"\ + "#{user_id}/media/#{media_id}" + ).public_send( + method, + head: { + 'Authorization' => [token, secret] + } + ).then { |http| + env.logger.debug "API response to send: #{http.response} "\ + "with code #{http.response_header.status}" + + case http.response_header.status + when 200 + http + else + EMPromise.reject(http.response_header.status) end + } + end - return [ - response.code, - {'Content-Type' => 'text/plain'}, - 'unexpected error' + def response(env) + env.logger.debug 'ENV: ' + env.to_s + env.logger.debug 'path: ' + env['REQUEST_PATH'] + env.logger.debug 'method: ' + env['REQUEST_METHOD'] + env.logger.debug 'BODY: ' + Rack::Request.new(env).body.read + + jid, media_id = env['REQUEST_PATH'].split('/')[-2..-1] + cred_key = "catapult_cred-#{URI.unescape(jid)}" + + REDIS.lrange(cred_key, 0, 2).then { |creds| + if creds.length < 3 + EMPromise.reject(404) + else + media_request( + env, + *creds, + env['REQUEST_METHOD'].downcase.to_sym, + media_id + ) + end + }.then { |http| + clength = http.response_header['content-length'] + [200, {'Content-Length' => clength}, http.response] + }.catch { |code| + if code.is_a?(Integer) + EMPromise.reject(code) + else + env.logger.error("ERROR: #{code.inspect}") + EMPromise.reject(500) + end + }.catch { |code| + [ + code, + {'Content-Type' => 'text/plain;charset=utf-8'}, + case code + when 404 + "not found\n" + when 405 + "only HEAD and GET are allowed\n" + else + "unexpected error\n" + end ] - end - - # TODO: maybe need to reflect more headers (multi-part?) - [200, {'Content-Length' => response['content-length']}, - response.body] + }.sync end end - -EM.run do - server = Goliath::Server.new('0.0.0.0', ARGV[0].to_i) - server.api = WebhookHandler.new - server.app = Goliath::Rack::Builder.build(server.api.class, server.api) - server.logger = Log4r::Logger.new('goliath') - server.logger.add(Log4r::StdoutOutputter.new('console')) - server.logger.level = Log4r::INFO - server.start -end