adammck-spomskyd 0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+