adammck-spomskyd 0.2

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 (3) hide show
  1. data/bin/spomskyd +80 -0
  2. data/lib/spomskyd.rb +171 -0
  3. metadata +93 -0
data/bin/spomskyd ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: noet
3
+
4
+
5
+ begin
6
+
7
+ # if this file is a symlink (which is probably is, since rubygems
8
+ # symlinks bin files into /usr/bin), find the original source file
9
+ this_file = File.symlink?(__FILE__) ? \
10
+ File.readlink(__FILE__) : __FILE__
11
+
12
+ # try loading the lib via a relative path
13
+ # first, in case we're running on the trunk
14
+ require (dev_path = File.expand_path(
15
+ File.dirname(this_file) + "/../lib/spomskyd.rb"))
16
+
17
+ rescue LoadError
18
+ begin
19
+
20
+ # couldn't load via relative
21
+ # path, so try loading the gem
22
+ require "rubygems"
23
+ require "spomskyd"
24
+
25
+ rescue LoadError
26
+
27
+ # nothing worked, so re-raise
28
+ # with more useful information
29
+ raise LoadError.new(
30
+ "Couldn't load spomskyd.rb relatively (from " +\
31
+ "#{dev_path.inspect}) or via RubyGems")
32
+ end
33
+ end
34
+
35
+
36
+ # load option parser
37
+ # from the ruby stdlib
38
+ require "optparse"
39
+ options = {}
40
+
41
+ # parse the command-line options
42
+ OptionParser.new do |opts|
43
+ options[:backend] = "HTTP"
44
+ options[:port] = 8100
45
+
46
+ # display summary usage info
47
+ opts.banner = "Usage:\n" +\
48
+ " spomskyd [OPTIONS] [ARGUMENTS]"
49
+ opts.separator " "
50
+ opts.separator "Options:"
51
+
52
+ # parse the BACKEND option
53
+ opts.on("-b", "--backend=BACKEND", "Start a RubySMS backend of the given type (default=HTTP)") do |b|
54
+ options[:backend] = b
55
+ end
56
+
57
+ # parse the PORT option
58
+ opts.on("-p", "--port=PORT", Integer, "Listen on a given TCP port (default=8100)") do |p|
59
+ options[:port] = p
60
+ end
61
+
62
+ # print usage information
63
+ opts.on_tail("-h", "--help", "Show this message") do
64
+ puts opts
65
+ exit
66
+ end
67
+ end.parse!
68
+
69
+
70
+ # create a router with a single spomsky app. we
71
+ # deliberately only support a single spomsky per
72
+ # process, to keep the command-line args simple
73
+ app = SpomskyApp.new(options[:port])
74
+ router = SMS::Router.new
75
+ router.add_app(app)
76
+
77
+ # pass the remaining (non option) arguments
78
+ # from the command line directly to
79
+ router.add_backend(options[:backend], *ARGV)
80
+ router.serve_forever
data/lib/spomskyd.rb ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: noet
3
+
4
+
5
+ require "rubygems"
6
+ require "net/http"
7
+ require "mongrel"
8
+ require "rack"
9
+ require "uuid"
10
+
11
+
12
+ begin
13
+ # try loading rubysms via a relative path
14
+ # first, in case we're running on the trunk
15
+ projects_dir = File.dirname(__FILE__) + "/../.."
16
+ rubysms_path = "#{projects_dir}/rubysms/lib/rubysms.rb"
17
+ require File.expand_path(rubysms_path)
18
+
19
+ rescue LoadError
20
+ begin
21
+
22
+ # couldn't load via relative
23
+ # path, so try loading the gem
24
+ require "rubysms"
25
+ end
26
+ end
27
+
28
+
29
+ class SpomskyApp < SMS::App
30
+
31
+ def initialize(port)
32
+ @uuid = UUID.new()
33
+ @subscribers = {}
34
+ @port = port.to_i
35
+
36
+ # to hold messages which were received while
37
+ # no subscribers were available to relay to
38
+ @pending = []
39
+ end
40
+
41
+ def start
42
+ @rack_thread = Thread.new do
43
+ Rack::Handler::Mongrel.run(
44
+ method(:rack_app), :Port=>@port)
45
+ end
46
+
47
+ # add the uri of this spomsky server
48
+ # to the screen log, for the curious
49
+ log [
50
+ "Started SPOMSKYd Application",
51
+ "URI: http://localhost:#{@port}/"
52
+ ], :init
53
+ end
54
+
55
+
56
+ def rack_app(env)
57
+ req = Rack::Request.new(env)
58
+ path = req.path_info
59
+ post = req.POST
60
+
61
+ # only POST is supported
62
+ unless req.post?
63
+ return resp "Method not allowed", 405
64
+ end
65
+
66
+ begin
67
+ if path == "/send"
68
+ router.backends.each do |backend|
69
+ begin
70
+ dest = post["destination"]
71
+ log("Relaying to #{dest} via #{backend.label}")
72
+ SMS::Outgoing.new(backend, dest, post["body"]).send!
73
+ rescue StandardError => err
74
+ return resp "Error while sending SMS: #{err}", 500
75
+ end
76
+ end
77
+
78
+ resp "Message Sent"
79
+
80
+ elsif path == "/receive/subscribe"
81
+ uuid = subscribe("http://#{post["host"]}:#{post["port"]}/#{post["path"]}")
82
+ resp "Subscribed", 200, { "x-subscription-uuid" => uuid }
83
+
84
+ elsif path == "/receive/unsubscribe"
85
+ unsubscribe(post["uuid"])
86
+ resp "Unsubscribed"
87
+
88
+ # no other urls are supported
89
+ else
90
+ warn "Invalid Request: #{path}"
91
+ resp "Not Found", 404
92
+ end
93
+
94
+ rescue Exception => err
95
+ log_exception(err)
96
+ resp("Error: #{err.message}", 500)
97
+ end
98
+ end
99
+
100
+
101
+ # Notify each of @subscribers that an
102
+ # incoming SMS has arrived
103
+ def incoming(msg)
104
+ data = { :source => msg.sender.phone_number, :body => msg.text }
105
+
106
+ # "What?! There is NO USE CASE for discarding incoming
107
+ # messages. Hold on to them or something!" -- Jonathan
108
+ if @subscribers.empty?
109
+ log("Message held (no subscribers)", :warn)
110
+ @pending.push(msg)
111
+ end
112
+
113
+ @subscribers.each do |uuid, uri|
114
+ begin
115
+ res = Net::HTTP.post_form(URI.parse(uri), data)
116
+
117
+ # if something goes wrong... do nothing. a client
118
+ # has probably vanished without unsubscribing. TODO:
119
+ # count these errors per-client, and drop after a few
120
+ rescue StandardError => err
121
+ log_exception(err, "Error while relaying to: #{uri}")
122
+ end
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ # Adds a URI to the @subscribers hash, to be
129
+ # notified (via HTTP) when an SMS arrives. Does
130
+ # nothing if the URI is already subscribed.
131
+ def subscribe(uri)
132
+ log "Subscribed: #{uri}"
133
+
134
+ # remote any existing subscribers to
135
+ # the same url, to prevent duplicates
136
+ @subscribers.each do |the_uuid, the_uri|
137
+ @subscribers.delete(the_uuid) if\
138
+ the_uri == uri
139
+ end
140
+
141
+ # add this subscriber
142
+ uuid = @uuid.generate
143
+ @subscribers[uuid] = uri
144
+
145
+ # if there are any pending messages,
146
+ # log and relay them to this subscriber
147
+ unless @pending.empty?
148
+ log "Relaying #{@pending.length} held messages"
149
+
150
+ @pending.each do |msg|
151
+ incoming(msg)
152
+ end
153
+ end
154
+
155
+ uuid
156
+ end
157
+
158
+
159
+ # Removes a URI from the @subscribers hash, or
160
+ # does nothing in the URI is not subscribed.
161
+ def unsubscribe(uuid)
162
+ log "Unubscribed: #{uuid}"
163
+ @subscribers.delete(uuid)
164
+ end
165
+
166
+ # Return a valid Rack response using sensible default
167
+ # arguments, so they don't have to be provided each time.
168
+ def resp(body, code=200, more_headers = {})
169
+ [code, {"content-type" => "text/plain"}.merge(more_headers), body]
170
+ end
171
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: adammck-spomskyd
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.2"
5
+ platform: ruby
6
+ authors:
7
+ - Adam Mckaig
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-29 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: adammck-rubysms
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mongrel
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rack
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: uuid
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description:
56
+ email: amckaig@unicef.org
57
+ executables:
58
+ - spomskyd
59
+ extensions: []
60
+
61
+ extra_rdoc_files: []
62
+
63
+ files:
64
+ - lib/spomskyd.rb
65
+ - bin/spomskyd
66
+ has_rdoc: false
67
+ homepage: http://github.com/adammck/spomskyd
68
+ post_install_message:
69
+ rdoc_options: []
70
+
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.2.0
89
+ signing_key:
90
+ specification_version: 2
91
+ summary: RubySMS application to implement the OMC SMS Proxy Protocol
92
+ test_files: []
93
+