First HTTP request using em-http

Stephen Paul Weber created

Change summary

.rubocop.yml    |  6 +++
Gemfile         |  2 +
em_promise.rb   | 40 +++++++++++++++++++++++++
sgx-catapult.rb | 79 +++++++++++++++++++++++++++++---------------------
4 files changed, 93 insertions(+), 34 deletions(-)

Detailed changes

.rubocop.yml 🔗

@@ -93,3 +93,9 @@ Style/NegatedIf:
 
 Style/RedundantReturn:
   Enabled: false
+
+Style/MultilineBlockChain:
+  Enabled: false
+
+Style/SpaceAroundEqualsInParameterDefault:
+  EnforcedStyle: no_space

Gemfile 🔗

@@ -2,7 +2,9 @@ source 'https://rubygems.org'
 
 gem 'activesupport', '<5.0.0'
 gem 'blather'
+gem 'em-http-request'
 gem 'eventmachine', '1.0.0'
+gem 'promise.rb'
 
 gem 'hiredis', '~> 0.6.0'
 gem 'redis', '>= 3.2.0'

em_promise.rb 🔗

@@ -0,0 +1,40 @@
+require "eventmachine"
+require "promise"
+
+class EMPromise < Promise
+	def initialize(deferrable=nil)
+		super()
+		fulfill(deferrable) if deferrable
+	end
+
+	def fulfill(value, bind_defer=true)
+		if bind_defer && value.is_a?(EM::Deferrable)
+			value.callback { |x| fulfill(x, false) }
+			value.errback(&method(:reject))
+		else
+			super(value)
+		end
+	end
+
+	def defer
+		EM.next_tick { yield }
+	end
+
+	def self.reject(e)
+		new.tap { |promise| promise.reject(e) }
+	end
+end
+
+module EventMachine
+	module Deferrable
+		def promise
+			EMPromise.new(self)
+		end
+
+		[:then, :rescue, :catch].each do |method|
+			define_method(method) do |*args, &block|
+				promise.public_send(method, *args, &block)
+			end
+		end
+	end
+end

sgx-catapult.rb 🔗

@@ -19,6 +19,7 @@
 # with sgx-catapult.  If not, see <http://www.gnu.org/licenses/>.
 
 require 'blather/client/dsl'
+require 'em-http-request'
 require 'json'
 require 'net/http'
 require 'redis/connection/hiredis'
@@ -31,6 +32,8 @@ require 'goliath/api'
 require 'goliath/server'
 require 'log4r'
 
+require_relative 'em_promise'
+
 $stdout.sync = true
 
 puts "Soprani.ca/SMS Gateway for XMPP - Catapult\n"\
@@ -47,6 +50,14 @@ end
 t = Time.now
 puts "LOG %d.%09d: starting...\n\n" % [t.to_i, t.nsec]
 
+def panic(e)
+	puts "Shutting down gateway due to exception: #{e.message}"
+	puts e.backtrace
+	SGXcatapult.shutdown
+	puts 'Gateway has terminated.'
+	EM.stop
+end
+
 module SGXcatapult
 	extend Blather::DSL
 
@@ -63,7 +74,7 @@ module SGXcatapult
 		client.write(stanza)
 	end
 
-	def self.error_msg(orig, query_node, type, name, text = nil)
+	def self.error_msg(orig, query_node, type, name, text=nil)
 		if not query_node.nil?
 			orig.add_child(query_node)
 			orig.type = :error
@@ -169,39 +180,39 @@ module SGXcatapult
 
 		conn.disconnect
 
-		uri = URI.parse('https://api.catapult.inetwork.com')
-		http = Net::HTTP.new(uri.host, uri.port)
-		http.use_ssl = true
-		request = Net::HTTP::Post.new('/v1/users/' + user_id +
-			'/messages')
-		request.basic_auth api_token, api_secret
-		request.add_field('Content-Type', 'application/json')
-		request.body = JSON.dump(
-			'from'			=> users_num,
-			'to'			=> num_dest,
-			'text'			=> m.body,
-			'tag'			=>
-				# callbacks need both the id and resourcepart
-				WEBrick::HTTPUtils.escape(m.id.to_s) + ' ' +
-				WEBrick::HTTPUtils.escape(
-					m.from.to_s.split('/', 2)[1].to_s
-				),
-			'receiptRequested'	=> 'all',
-			'callbackUrl'		=> ARGV[6]
-		)
-		response = http.request(request)
-
-		puts 'API response to send: ' + response.to_s + ' with code ' +
-			response.code + ', body "' + response.body + '"'
-
-		if response.code != '201'
-			# TODO: add text re unexpected code; mention code number
-			write_to_stream error_msg(
-				m.reply, m.body, :cancel,
-				'internal-server-error'
+		EM::HttpRequest.new(
+			"https://api.catapult.inetwork.com/"\
+			"v1/users/#{user_id}/messages"
+		).post(
+			head: {
+				'Authorization' => [api_token, api_secret],
+				'Content-Type' => 'application/json'
+			},
+			body: JSON.dump(
+				from:             users_num,
+				to:               num_dest,
+				text:             m.body,
+				tag:
+					# callbacks need both the id and resourcepart
+					WEBrick::HTTPUtils.escape(m.id.to_s) + ' ' +
+					WEBrick::HTTPUtils.escape(
+						m.from.to_s.split('/', 2)[1].to_s
+					),
+				receiptRequested: 'all',
+				callbackUrl:      ARGV[6]
 			)
-			next
-		end
+		).then { |http|
+			puts "API response to send: #{http.response} with code "\
+				"response.code #{http.response_header.status}"
+
+			if http.response_header.status != 201
+				# TODO: add text re unexpected code; mention code number
+				write_to_stream error_msg(
+					m.reply, m.body, :cancel,
+					'internal-server-error'
+				)
+			end
+		}.catch(&method(:panic))
 
 	rescue Exception => e
 		puts 'Shutting down gateway due to exception 001: ' + e.message
@@ -950,7 +961,7 @@ end
 end
 
 class ReceiptMessage < Blather::Stanza
-	def self.new(to = nil)
+	def self.new(to=nil)
 		node = super :message
 		node.to = to
 		node