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.
- data/bin/spomskyd +80 -0
- data/lib/spomskyd.rb +171 -0
- 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
|
+
|