adhearsion 0.7.7 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +9 -42
- data/EVENTS +11 -0
- data/README.txt +5 -0
- data/Rakefile +94 -84
- data/adhearsion.gemspec +148 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +87 -0
- data/app_generators/ahn/templates/.ahnrc +34 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +23 -0
- data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
- data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/restful_rpc/spec/restful_rpc_spec.rb +263 -0
- data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
- data/app_generators/ahn/templates/config/startup.rb +50 -0
- data/app_generators/ahn/templates/dialplan.rb +3 -0
- data/app_generators/ahn/templates/events.rb +32 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +42 -0
- data/examples/asterisk_manager_interface/standalone.rb +51 -0
- data/lib/adhearsion.rb +35 -953
- data/lib/adhearsion/cli.rb +223 -0
- data/lib/adhearsion/component_manager.rb +208 -0
- data/lib/adhearsion/component_manager/component_tester.rb +55 -0
- data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
- data/lib/adhearsion/events_support.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +9 -0
- data/lib/adhearsion/foundation/blank_slate.rb +5 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/foundation/event_socket.rb +203 -0
- data/lib/adhearsion/foundation/future_resource.rb +36 -0
- data/lib/adhearsion/foundation/global.rb +1 -0
- data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
- data/lib/adhearsion/foundation/numeric.rb +13 -0
- data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
- data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
- data/lib/adhearsion/foundation/string.rb +26 -0
- data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
- data/lib/adhearsion/foundation/thread_safety.rb +7 -0
- data/lib/adhearsion/host_definitions.rb +67 -0
- data/lib/adhearsion/initializer.rb +373 -0
- data/lib/adhearsion/initializer/asterisk.rb +81 -0
- data/lib/adhearsion/initializer/configuration.rb +254 -0
- data/lib/adhearsion/initializer/database.rb +49 -0
- data/lib/adhearsion/initializer/drb.rb +31 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks.rb +16 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/deprecations.rb +59 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
- data/lib/adhearsion/voip/call.rb +440 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +218 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/theatre.rb +151 -0
- data/lib/theatre/README.markdown +64 -0
- data/lib/theatre/callback_definition_loader.rb +84 -0
- data/lib/theatre/guid.rb +23 -0
- data/lib/theatre/invocation.rb +121 -0
- data/lib/theatre/namespace_manager.rb +153 -0
- data/lib/theatre/version.rb +2 -0
- metadata +160 -140
- data/.version +0 -1
- data/TODO +0 -71
- data/ahn +0 -223
- data/apps/default/Rakefile +0 -65
- data/apps/default/config/adhearsion.sqlite3 +0 -0
- data/apps/default/config/adhearsion.yml +0 -95
- data/apps/default/config/database.rb +0 -50
- data/apps/default/config/database.yml +0 -12
- data/apps/default/config/helpers/drb_server.yml +0 -43
- data/apps/default/config/helpers/factorial.alien.c.yml +0 -1
- data/apps/default/config/helpers/growler.yml +0 -21
- data/apps/default/config/helpers/lookup.yml +0 -1
- data/apps/default/config/helpers/manager_proxy.yml +0 -8
- data/apps/default/config/helpers/micromenus.yml +0 -1
- data/apps/default/config/helpers/micromenus/collab.rb +0 -60
- data/apps/default/config/helpers/micromenus/images/arrow-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/arrow-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/error.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder.png +0 -0
- data/apps/default/config/helpers/micromenus/images/ggbridge.jpg +0 -0
- data/apps/default/config/helpers/micromenus/images/green.png +0 -0
- data/apps/default/config/helpers/micromenus/images/microbrowser.bg.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/red.png +0 -0
- data/apps/default/config/helpers/micromenus/images/tux.bmp +0 -0
- data/apps/default/config/helpers/micromenus/images/url-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/url-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/yellow.png +0 -0
- data/apps/default/config/helpers/micromenus/javascripts/animation.js +0 -1341
- data/apps/default/config/helpers/micromenus/javascripts/carousel.js +0 -1238
- data/apps/default/config/helpers/micromenus/javascripts/columnav.js +0 -306
- data/apps/default/config/helpers/micromenus/javascripts/connection.js +0 -965
- data/apps/default/config/helpers/micromenus/javascripts/container.js +0 -4727
- data/apps/default/config/helpers/micromenus/javascripts/container_core.js +0 -2915
- data/apps/default/config/helpers/micromenus/javascripts/dom.js +0 -892
- data/apps/default/config/helpers/micromenus/javascripts/dragdrop.js +0 -2958
- data/apps/default/config/helpers/micromenus/javascripts/event.js +0 -1771
- data/apps/default/config/helpers/micromenus/javascripts/yahoo.js +0 -433
- data/apps/default/config/helpers/micromenus/stylesheets/carousel.css +0 -78
- data/apps/default/config/helpers/micromenus/stylesheets/columnav.css +0 -135
- data/apps/default/config/helpers/micromenus/stylesheets/microbrowsers.css +0 -42
- data/apps/default/config/helpers/multi_messenger.yml +0 -9
- data/apps/default/config/helpers/weather.yml +0 -1
- data/apps/default/config/helpers/xbmc.yml +0 -2
- data/apps/default/config/migration.rb +0 -59
- data/apps/default/extensions.rb +0 -41
- data/apps/default/helpers/factorial.alien.c +0 -32
- data/apps/default/helpers/growler.rb +0 -53
- data/apps/default/helpers/lookup.rb +0 -44
- data/apps/default/helpers/manager_proxy.rb +0 -112
- data/apps/default/helpers/micromenus.rb +0 -514
- data/apps/default/helpers/multi_messenger.rb +0 -53
- data/apps/default/helpers/oscar_wilde_quotes.rb +0 -197
- data/apps/default/helpers/weather.rb +0 -85
- data/apps/default/helpers/xbmc.rb +0 -39
- data/apps/default/logs/adhearsion.log +0 -0
- data/apps/default/logs/database.log +0 -0
- data/lib/constants.rb +0 -24
- data/lib/core_extensions.rb +0 -180
- data/lib/drb_server.rb +0 -101
- data/lib/logging.rb +0 -85
- data/lib/phone_number.rb +0 -85
- data/lib/rami.rb +0 -823
- data/lib/servlet_container.rb +0 -174
- data/lib/sexy_migrations.rb +0 -70
- data/test/asterisk_module_test.rb +0 -14
- data/test/core_extensions_test.rb +0 -26
- data/test/dial_test.rb +0 -43
- data/test/specs/numerical_string_spec.rb +0 -53
- data/test/test_micromenus.rb +0 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
# Adhearsion Runtime Configuration.
|
2
|
+
|
3
|
+
# You can use the "gems" section to force Adhearsion to load a particular version of a gem.
|
4
|
+
# This is useful when a component require()s a gem, but you don't want it to use the latest one installed on the system.
|
5
|
+
# For example, if a component require()s activerecord, sucking in the latest on the system, and then you enable a Rails app
|
6
|
+
# which wants a particular version of activerecord, RubyGems will raise an error saying "you cannot activate two versions of
|
7
|
+
# the same gem". Note: specifying the version, source and require names is optional, but you must include a : after the gem name to make it a YAML key/value pair (with a nil value).
|
8
|
+
gems:
|
9
|
+
# twitter:
|
10
|
+
# hpricot:
|
11
|
+
# rack:
|
12
|
+
# # require() one library when initializing:
|
13
|
+
# require: rack
|
14
|
+
# memcache-client:
|
15
|
+
# version >= 1.5.2
|
16
|
+
# require:
|
17
|
+
# # require() an Array of libraries when initializing:
|
18
|
+
# - memcache
|
19
|
+
# - memcache_util
|
20
|
+
# activerecord:
|
21
|
+
# version: >= 2.1.0
|
22
|
+
# aasm:
|
23
|
+
# source: http://gems.github.com
|
24
|
+
|
25
|
+
paths:
|
26
|
+
|
27
|
+
# All paths are relative to this file's directory
|
28
|
+
init: config/startup.rb
|
29
|
+
|
30
|
+
dialplan: dialplan.rb
|
31
|
+
|
32
|
+
events: events.rb
|
33
|
+
|
34
|
+
models: models/*.rb
|
@@ -0,0 +1,8 @@
|
|
1
|
+
Start your new app with "ahn start /path/to/your/app"
|
2
|
+
|
3
|
+
If you wish to use Adhearsion to control Asterisk's dialplan,
|
4
|
+
change the contexts you wish to be affected in your
|
5
|
+
/etc/asterisk/extensions.conf file to the following:
|
6
|
+
|
7
|
+
[your_context_name]
|
8
|
+
exten => _X.,1,AGI(agi://1.2.3.4) ; This IP here
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'adhearsion'
|
5
|
+
require 'adhearsion/tasks'
|
6
|
+
rescue LoadError
|
7
|
+
STDERR.puts "\nCannot load Adhearsion! Not all Rake tasks will be loaded!\n\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Writes a .gitignore file that ignores certain SCM annoyances such as log files"
|
11
|
+
task :gitignore do
|
12
|
+
ignore_file = "#{Dir.pwd}/.gitignore"
|
13
|
+
if File.exists? ignore_file
|
14
|
+
STDERR.puts "File #{ignore_file} already exists!"
|
15
|
+
else
|
16
|
+
File.open ignore_file, 'w' do |file|
|
17
|
+
# Add other files to the Array below
|
18
|
+
%w[ log ].each do |pattern|
|
19
|
+
file.puts pattern
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
methods_for :rpc do
|
2
|
+
|
3
|
+
# Simply create proxy methods for the high-level AMI methods
|
4
|
+
|
5
|
+
[:send_action, :introduce, :originate, :call_into_context, :call_and_exec].each do |method_name|
|
6
|
+
define_method(method_name) do |*args|
|
7
|
+
if VoIP::Asterisk.manager_interface
|
8
|
+
VoIP::Asterisk.manager_interface.send(method_name, *args)
|
9
|
+
else
|
10
|
+
ahn_log.ami_remote.error "AMI has not been enabled in startup.rb!"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
What is Stomp?
|
2
|
+
==============
|
3
|
+
|
4
|
+
Stomp is a very simple message-queue protocol with which two separate systems can communicate. Because the protocol is so simple, there are many Stomp server implementations from which you can choose. Some of these include
|
5
|
+
|
6
|
+
- ActiveMQ (http://activemq.com)
|
7
|
+
- Ruby "stompserver" gem (gem install stompserver)
|
8
|
+
- RabbitMQ (http://rabbitmq.com)
|
9
|
+
|
10
|
+
If you wish to get up and running with a development environment, the Ruby stompserver gem is a fantastic starting point. For a critical production system, ActiveMQ should probably be used but it bears the cumbersome paradigm of many "enterprisey" Java applications.
|
11
|
+
|
12
|
+
How does it work?
|
13
|
+
=================
|
14
|
+
|
15
|
+
Stomp is used when certain processes have defined responsibilities. For example, your Adhearsion application's responsibility is to communicate with your Asterisk machine. Other processes (e.g. a Rails web application) will probably need to instruct Adhearsion to do something. Instructions may include
|
16
|
+
|
17
|
+
- Start a new call between two given phone numbers
|
18
|
+
- Have a particular call do something based on a new event
|
19
|
+
- Hangup a call
|
20
|
+
|
21
|
+
Below is a diagram which should give you a better idea of how it works.
|
22
|
+
|
23
|
+
Process Process Process (e.g. Rails)
|
24
|
+
\ | /
|
25
|
+
\ | /
|
26
|
+
Stomp Server (e.g. ActiveMQ)
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Process (e.g. Adhearsion)
|
30
|
+
|
31
|
+
Note: Adhearsion could also be the sender of messages through the Stomp server which are consumed by a number of handlers.
|
32
|
+
|
33
|
+
Setting up a Ruby Stomp server
|
34
|
+
==============================
|
35
|
+
|
36
|
+
Install the pure-Ruby Stomp server by doing "gem install stompserver". This will add the "stompserver" command to your system. When running it without any parameters, it starts without requiring authentication. If you're wanting to get a quick experiment running, I recommend simply doing that.
|
37
|
+
|
38
|
+
Open the config.yml file in the stomp_gateway component folder. Comment out the four settings at the top of the file named "user", "pass", "host" and "port" by prepending a "#" to their line. This will cause the component to choose defaults for those properties. The component's defaults will match the expected credentials for the experimental stompserver you're already running on your computer.
|
39
|
+
|
40
|
+
You also need specify a subscription name in
|
41
|
+
|
42
|
+
events.stomp.start_call.each do |event|
|
43
|
+
# The "event" variable holds a Stomp::Message object.
|
44
|
+
name = event.headers
|
45
|
+
end
|
46
|
+
|
47
|
+
You a
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Comment out any of these properties to use a default.
|
2
|
+
|
3
|
+
user: stomp_user
|
4
|
+
pass: secret_password
|
5
|
+
host: localhost
|
6
|
+
port: 61613
|
7
|
+
|
8
|
+
### Add your list of subscriptions below that this gateway should proxy to events.rb
|
9
|
+
|
10
|
+
# subscriptions:
|
11
|
+
# - start_call
|
12
|
+
# - hangup_call
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'stomp'
|
2
|
+
|
3
|
+
# TODO: Recover from a disconnect!
|
4
|
+
|
5
|
+
initialization do
|
6
|
+
user = COMPONENTS.stomp_gateway[:user] || ""
|
7
|
+
pass = COMPONENTS.stomp_gateway[:pass] || ""
|
8
|
+
host = COMPONENTS.stomp_gateway[:host] || "localhost"
|
9
|
+
port = COMPONENTS.stomp_gateway[:port] || 61613
|
10
|
+
|
11
|
+
::StompGatewayConnection = Stomp::Client.open(user, pass, host, port)
|
12
|
+
|
13
|
+
subscriptions = COMPONENTS.stomp_gateway["subscriptions"]
|
14
|
+
|
15
|
+
ahn_log.stomp_gateway "Connection established. Subscriptions: #{subscriptions.inspect}"
|
16
|
+
|
17
|
+
Events.register_namespace_name "/stomp"
|
18
|
+
|
19
|
+
subscriptions.each do |subscription|
|
20
|
+
Events.register_namespace_name "/stomp/#{subscription}"
|
21
|
+
::StompGatewayConnection.subscribe subscription do |event|
|
22
|
+
Adhearsion::Events.trigger ["stomp", subscription], event
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
methods_for :global do
|
29
|
+
def send_stomp(destination, message, headers={})
|
30
|
+
::StompGatewayConnection.send(destination, message, headers)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# In the future, I may add a methods_for(:events) method which allows synchronous messaging.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Adhearsion RESTful RPC Component
|
2
|
+
================================
|
3
|
+
|
4
|
+
This is a component for people want to integrate their telephony systems with non-Ruby systems. When enabled, this component
|
5
|
+
will start up a HTTP server within the Adhearsion process and accept POST requests to invoke Ruby methods shared in the
|
6
|
+
`methods_for(:rpc)` context.
|
7
|
+
|
8
|
+
Protocol Notes
|
9
|
+
--------------
|
10
|
+
|
11
|
+
When POSTing your data to.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Use path_nesting to specify an arbitrarily nested. Could be used for additional security or in HTTP reverse proxy server.
|
2
|
+
path_nesting: /
|
3
|
+
|
4
|
+
port: 5000
|
5
|
+
|
6
|
+
# The "handler" option here can be any valid Rack::Handler constant name.
|
7
|
+
# Other options: WEBrick, EventedMongrel.
|
8
|
+
# If you don't know the differences between these, "Mongrel" is definitely a good choice.
|
9
|
+
handler: Mongrel
|
10
|
+
|
11
|
+
# In a production system, you should make this "false" since
|
12
|
+
show_exceptions: true
|
13
|
+
|
14
|
+
# The "authentication" config option can either be "false" or key/value pairs representing allowed usernames and passwords.
|
15
|
+
|
16
|
+
#authentication: false
|
17
|
+
authentication:
|
18
|
+
jicksta: roflcopterz
|
19
|
+
foo: bar6213671682
|
20
|
+
|
21
|
+
access: everyone # When allowing "everyone" access, no IPs are blocked.
|
22
|
+
#access: whitelist # When using a whitelist, the "whitelist" data below will be used.
|
23
|
+
#access: blacklist # When using a blacklist, the "blacklist" data below will be used.
|
24
|
+
|
25
|
+
# This is a list of IPs which are exclusively allowed to call this web service.
|
26
|
+
# Note: whitelists are far more secure than blacklists.
|
27
|
+
whitelist:
|
28
|
+
- 127.0.0.1
|
29
|
+
- 192.168.*.*
|
30
|
+
|
31
|
+
# This is a list of the IPs which are explicitly NOT allowed to call this web service. This will only be used if "access" is
|
32
|
+
# set to "blacklist" above.
|
33
|
+
blacklist:
|
34
|
+
- 100.200.100.200
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# You must have the "rest-client" and "json" gems installed for this file to work.
|
6
|
+
|
7
|
+
class RESTfulAdhearsion
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
# Note: :user and :password are non-existent by default
|
11
|
+
:host => "localhost",
|
12
|
+
:port => "5000",
|
13
|
+
:path_nesting => "/"
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(options={})
|
17
|
+
@options = DEFAULT_OPTIONS.merge options
|
18
|
+
|
19
|
+
@path_nesting = @options.delete :path_nesting
|
20
|
+
@host = @options.delete :host
|
21
|
+
@port = @options.delete :port
|
22
|
+
|
23
|
+
@url_beginning = "http://#{@host}:#{@port}#{@path_nesting}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method_name, *args)
|
27
|
+
JSON.parse RestClient::Resource.new(@url_beginning + method_name.to_s, @options).post(args.to_json)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
Adhearsion = RESTfulAdhearsion.new :host => "localhost", :port => 5000, :user => "jicksta", :password => "roflcopterz"
|
33
|
+
|
34
|
+
# ### Sample component code. Try doing "ahn create component testing123" and pasting this code in.
|
35
|
+
#
|
36
|
+
# methods_for :rpc do
|
37
|
+
# def i_like_hashes(options={})
|
38
|
+
# options.has_key?(:foo)
|
39
|
+
# end
|
40
|
+
# def i_like_arrays(*args)
|
41
|
+
# args.reverse
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
|
45
|
+
# Note: everything returned will be wrapped in an Array
|
46
|
+
|
47
|
+
p Adhearsion.i_like_hashes(:foo => "bar")
|
48
|
+
p Adhearsion.i_like_arrays(1,2,3,4,5)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Don't you love regular expressions? Matches only 0-255 octets. Recognizes "*" as an octet wildcard.
|
5
|
+
VALID_IP_ADDRESS = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)$/
|
6
|
+
|
7
|
+
def ip_allowed?(ip)
|
8
|
+
raise ArgumentError, "#{ip.inspect} is not a valid IP address!" unless ip.kind_of?(String) && ip =~ VALID_IP_ADDRESS
|
9
|
+
|
10
|
+
octets = ip.split "."
|
11
|
+
|
12
|
+
case COMPONENTS.restful_rpc["access"]
|
13
|
+
when "everyone"
|
14
|
+
true
|
15
|
+
when "whitelist"
|
16
|
+
whitelist = COMPONENTS.restful_rpc["whitelist"]
|
17
|
+
!! whitelist.find do |pattern|
|
18
|
+
pattern_octets = pattern.split "."
|
19
|
+
# Traverse both arrays in parallel
|
20
|
+
octets.zip(pattern_octets).map do |octet, octet_pattern|
|
21
|
+
octet_pattern == "*" ? true : (octet == octet_pattern)
|
22
|
+
end == [true, true, true, true]
|
23
|
+
end
|
24
|
+
when "blacklist"
|
25
|
+
blacklist = COMPONENTS.restful_rpc["blacklist"]
|
26
|
+
! blacklist.find do |pattern|
|
27
|
+
pattern_octets = pattern.split "."
|
28
|
+
# Traverse both arrays in parallel
|
29
|
+
octets.zip(pattern_octets).map do |octet, octet_pattern|
|
30
|
+
octet_pattern == "*" ? true : (octet == octet_pattern)
|
31
|
+
end == [true, true, true, true]
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise Adhearsion::Components::ConfigurationError, 'Unrecognized "access" configuration value!'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
RESTFUL_API_HANDLER = lambda do |env|
|
39
|
+
json = env["rack.input"].read
|
40
|
+
|
41
|
+
# Return "Bad Request" HTTP error if the client forgot
|
42
|
+
return [400, {}, "You must POST a valid JSON object!"] if json.blank?
|
43
|
+
|
44
|
+
json = JSON.parse json
|
45
|
+
|
46
|
+
nesting = COMPONENTS.restful_rpc["path_nesting"]
|
47
|
+
path = env["PATH_INFO"]
|
48
|
+
|
49
|
+
return [404, {}, "This resource does not respond to #{path.inspect}"] unless path[0...nesting.size] == nesting
|
50
|
+
|
51
|
+
path = path[nesting.size..-1]
|
52
|
+
|
53
|
+
return [404, {"Content-Type" => "application/json"}, "You cannot nest method names!"] if path.include?("/")
|
54
|
+
|
55
|
+
rpc_object = Adhearsion::Components.component_manager.extend_object_with(Object.new, :rpc)
|
56
|
+
|
57
|
+
# TODO: set the content-type and other HTTP headers
|
58
|
+
response_object = Array rpc_object.send(path, *json)
|
59
|
+
[200, {"Content-Type" => "application/json"}, response_object.to_json]
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
initialization do
|
64
|
+
config = COMPONENTS.restful_rpc
|
65
|
+
|
66
|
+
api = RESTFUL_API_HANDLER
|
67
|
+
|
68
|
+
port = config["port"] || 5000
|
69
|
+
authentication = config["authentication"]
|
70
|
+
show_exceptions = config["show_exceptions"]
|
71
|
+
handler = Rack::Handler.const_get(config["handler"] || "Mongrel")
|
72
|
+
|
73
|
+
if authentication
|
74
|
+
api = Rack::Auth::Basic.new(api) do |username, password|
|
75
|
+
authentication[username] == password
|
76
|
+
end
|
77
|
+
api.realm = "Adhearsion API"
|
78
|
+
end
|
79
|
+
|
80
|
+
if show_exceptions
|
81
|
+
api = Rack::ShowStatus.new(Rack::ShowExceptions.new(api))
|
82
|
+
end
|
83
|
+
|
84
|
+
Thread.new do
|
85
|
+
handler.run api, :Port => port
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
unless defined? Adhearsion
|
2
|
+
if File.exists? File.dirname(__FILE__) + "/../../../adhearsion/lib/adhearsion.rb"
|
3
|
+
# If you wish to freeze a copy of Adhearsion to this app, simply place a copy of Adhearsion
|
4
|
+
# into a folder named "adhearsion" within this app's main directory.
|
5
|
+
require File.dirname(__FILE__) + "/../../../adhearsion/lib/adhearsion.rb"
|
6
|
+
elsif File.exists? File.dirname(__FILE__) + "/../../../../../../lib/adhearsion.rb"
|
7
|
+
# This file may be ran from the within the Adhearsion framework code (before a project has been generated)
|
8
|
+
require File.dirname(__FILE__) + "/../../../../../../lib/adhearsion.rb"
|
9
|
+
else
|
10
|
+
require 'rubygems'
|
11
|
+
gem 'adhearsion', '>= 0.7.999'
|
12
|
+
require 'adhearsion'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'adhearsion/component_manager/spec_framework'
|
17
|
+
|
18
|
+
RESTFUL_RPC = ComponentTester.new("restful_rpc", File.dirname(__FILE__) + "/../..")
|
19
|
+
|
20
|
+
##### This is here for a reference
|
21
|
+
#{"CONTENT_LENGTH" => "12",
|
22
|
+
# "CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
23
|
+
# "GATEWAY_INTERFACE" => "CGI/1.1",
|
24
|
+
# "HTTP_ACCEPT" => "application/xml",
|
25
|
+
# "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
26
|
+
# "HTTP_AUTHORIZATION" => "Basic amlja3N0YTpyb2ZsY29wdGVyeg==",
|
27
|
+
# "HTTP_HOST" => "localhost:5000",
|
28
|
+
# "HTTP_VERSION" => "HTTP/1.1",
|
29
|
+
# "PATH_INFO" => "/rofl",
|
30
|
+
# "QUERY_STRING" => "",
|
31
|
+
# "rack.errors" => StringIO.new(""),
|
32
|
+
# "rack.input" => StringIO.new('["o","hai!"]'),
|
33
|
+
# "rack.multiprocess" => false,
|
34
|
+
# "rack.multithread" => true,
|
35
|
+
# "rack.run_once" => false,
|
36
|
+
# "rack.url_scheme" => "http",
|
37
|
+
# "rack.version" => [0, 1],
|
38
|
+
# "REMOTE_ADDR" => "::1",
|
39
|
+
# "REMOTE_HOST" => "localhost",
|
40
|
+
# "REMOTE_USER" => "jicksta",
|
41
|
+
# "REQUEST_METHOD" => "POST"
|
42
|
+
# "REQUEST_PATH" => "/",
|
43
|
+
# "REQUEST_URI" => "http://localhost:5000/rofl",
|
44
|
+
# "SCRIPT_NAME" => "",
|
45
|
+
# "SERVER_NAME" => "localhost",
|
46
|
+
# "SERVER_PORT" => "5000",
|
47
|
+
# "SERVER_PROTOCOL" => "HTTP/1.1",
|
48
|
+
# "SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.8.6/2008-03-03)"}
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
describe "The VALID_IP_ADDRESS regular expression" do
|
53
|
+
|
54
|
+
it "should match only valid IP addresses" do
|
55
|
+
valid_ip_addresses = ["192.168.1.98", "10.0.1.200", "255.255.255.0", "123.*.4.*"]
|
56
|
+
invalid_ip_addresses = ["10.0.1.1 foo", "bar 255.255.255.0", "0*0*0*0", "1234"]
|
57
|
+
|
58
|
+
valid_ip_addresses. each { |ip| RESTFUL_RPC::VALID_IP_ADDRESS.should =~ ip }
|
59
|
+
invalid_ip_addresses.each { |ip| RESTFUL_RPC::VALID_IP_ADDRESS.should_not =~ ip }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "The initialization block" do
|
64
|
+
|
65
|
+
it "should create a new Thread" do
|
66
|
+
mock_component_config_with :restful_rpc => {}
|
67
|
+
mock(Thread).new { nil }
|
68
|
+
RESTFUL_RPC.initialize!
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should run the Rack adapter specified in the configuration" do
|
72
|
+
mock(Thread).new.yields
|
73
|
+
mock_component_config_with :restful_rpc => {"adapter" => "Mongrel"}
|
74
|
+
mock(Rack::Handler::Mongrel).run is_a(Proc), :Port => 5000
|
75
|
+
RESTFUL_RPC.initialize!
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should wrap the RESTFUL_API_HANDLER in an Rack::Auth::Basic object if authentication is enabled" do
|
79
|
+
mock(Thread).new.yields
|
80
|
+
mock_component_config_with :restful_rpc => {"authentication" => {"foo" => "bar"}}
|
81
|
+
|
82
|
+
proper_authenticator = lambda do |obj|
|
83
|
+
request = OpenStruct.new :credentials => ["foo", "bar"]
|
84
|
+
obj.is_a?(Rack::Auth::Basic) && obj.send(:valid?, request)
|
85
|
+
end
|
86
|
+
|
87
|
+
mock(Rack::Handler::Mongrel).run(satisfy(&proper_authenticator), :Port => 5000)
|
88
|
+
RESTFUL_RPC.initialize!
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should wrap the RESTFUL_API_HANDLER in ShowStatus and ShowExceptions objects when show_exceptions is enabled' do
|
92
|
+
mock(Thread).new.yields
|
93
|
+
mock_component_config_with :restful_rpc => {"show_exceptions" => true}
|
94
|
+
|
95
|
+
mock.proxy(Rack::ShowExceptions).new(is_a(Proc))
|
96
|
+
mock.proxy(Rack::ShowStatus).new is_a(Rack::ShowExceptions)
|
97
|
+
|
98
|
+
mock(Rack::Handler::Mongrel).run is_a(Rack::ShowStatus), :Port => 5000
|
99
|
+
RESTFUL_RPC.initialize!
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'Private helper methods' do
|
105
|
+
|
106
|
+
describe "the RESTFUL_API_HANDLER lambda" do
|
107
|
+
|
108
|
+
it "should return a 200 for requests which execute a method that has been defined in the methods_for(:rpc) context" do
|
109
|
+
component_manager = Adhearsion::Components::ComponentManager.new('/path/shouldnt/matter')
|
110
|
+
|
111
|
+
mock(Adhearsion::Components).component_manager { component_manager }
|
112
|
+
component_manager.load_code <<-RUBY
|
113
|
+
methods_for(:rpc) do
|
114
|
+
def testing_123456(one,two)
|
115
|
+
[two.reverse, one.reverse]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
RUBY
|
119
|
+
|
120
|
+
input = StringIO.new %w[jay phillips].to_json
|
121
|
+
|
122
|
+
mock_component_config_with :restful_rpc => {"path_nesting" => "/"}
|
123
|
+
|
124
|
+
env = {"PATH_INFO" => "/testing_123456", "rack.input" => input}
|
125
|
+
|
126
|
+
response = RESTFUL_RPC::RESTFUL_API_HANDLER.call(env)
|
127
|
+
response.should be_kind_of(Array)
|
128
|
+
response.should have(3).items
|
129
|
+
response.first.should equal(200)
|
130
|
+
JSON.parse(response.last).should eql(%w[jay phillips].map(&:reverse).reverse)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should return a 400 when no data is POSTed" do
|
134
|
+
env = {"rack.input" => StringIO.new(""), "REQUEST_URI" => "/foobar"}
|
135
|
+
RESTFUL_RPC::RESTFUL_API_HANDLER.call(env).first.should equal(400)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should work with a high level test of a successful method invocation" do
|
139
|
+
|
140
|
+
component_manager = Adhearsion::Components::ComponentManager.new('/path/shouldnt/matter')
|
141
|
+
|
142
|
+
mock(Adhearsion::Components).component_manager { component_manager }
|
143
|
+
|
144
|
+
component_manager.load_code '
|
145
|
+
methods_for(:rpc) do
|
146
|
+
def rofl(one,two)
|
147
|
+
"Hai! #{one} #{two}"
|
148
|
+
end
|
149
|
+
end'
|
150
|
+
|
151
|
+
env = {
|
152
|
+
"CONTENT_LENGTH" => "12",
|
153
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
154
|
+
"GATEWAY_INTERFACE" => "CGI/1.1",
|
155
|
+
"HTTP_ACCEPT" => "application/xml",
|
156
|
+
"HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
157
|
+
"HTTP_AUTHORIZATION" => "Basic amlja3N0YTpyb2ZsY29wdGVyeg==",
|
158
|
+
"HTTP_HOST" => "localhost:5000",
|
159
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
160
|
+
"PATH_INFO" => "/rofl",
|
161
|
+
"QUERY_STRING" => "",
|
162
|
+
"rack.errors" => StringIO.new(""),
|
163
|
+
"rack.input" => StringIO.new('["o","hai!"]'),
|
164
|
+
"rack.multiprocess" => false,
|
165
|
+
"rack.multithread" => true,
|
166
|
+
"rack.run_once" => false,
|
167
|
+
"rack.url_scheme" => "http",
|
168
|
+
"rack.version" => [0, 1],
|
169
|
+
"REMOTE_ADDR" => "::1",
|
170
|
+
"REMOTE_HOST" => "localhost",
|
171
|
+
"REMOTE_USER" => "jicksta",
|
172
|
+
"REQUEST_METHOD" => "POST",
|
173
|
+
"REQUEST_PATH" => "/",
|
174
|
+
"REQUEST_URI" => "http://localhost:5000/rofl",
|
175
|
+
"SCRIPT_NAME" => "",
|
176
|
+
"SERVER_NAME" => "localhost",
|
177
|
+
"SERVER_PORT" => "5000",
|
178
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
179
|
+
"SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.8.6/2008-03-03)" }
|
180
|
+
|
181
|
+
response = RESTFUL_RPC::RESTFUL_API_HANDLER.call(env)
|
182
|
+
JSON.parse(response.last).should == ["Hai! o hai!"]
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should contain backtrace information when show_errors is enabled and an exception occurs" do
|
187
|
+
mock_component_config_with :restful_api => {"show_errors" => true}
|
188
|
+
pending
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'the ip_allowed?() method' do
|
194
|
+
|
195
|
+
before :each do
|
196
|
+
@method = RESTFUL_RPC.helper_method :ip_allowed?
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should raise a ConfigurationError if "access" is not one of "everyone", "whitelist" or "blacklist"' do
|
200
|
+
good_access_values = %w[everyone whitelist blacklist]
|
201
|
+
bad_access_values = %w[foo bar qaz qwerty everone blaclist whitlist]
|
202
|
+
|
203
|
+
good_access_values.each do |access_value|
|
204
|
+
mock_component_config_with :restful_rpc => {"access" => access_value, "whitelist" => [], "blacklist" => []}
|
205
|
+
lambda { @method.call("10.0.0.1") }.should_not raise_error
|
206
|
+
end
|
207
|
+
|
208
|
+
bad_access_values.each do |access_value|
|
209
|
+
mock_component_config_with :restful_rpc => {"access" => access_value, "authentication" => false}
|
210
|
+
lambda { @method.call("10.0.0.1") }.should raise_error(Adhearsion::Components::ConfigurationError)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe 'whitelists' do
|
215
|
+
|
216
|
+
it "should parse *'s as wildcards" do
|
217
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => ["10.*.*.*"]}
|
218
|
+
@method.call("10.1.2.3").should be_true
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should allow IPs which are explictly specified" do
|
222
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => ["4.3.2.1"]}
|
223
|
+
@method.call("4.3.2.1").should be_true
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should not allow IPs which are not explicitly specified" do
|
227
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => %w[ 1.2.3.4 4.3.2.1]}
|
228
|
+
@method.call("2.2.2.2").should be_false
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'blacklists' do
|
234
|
+
|
235
|
+
it "should parse *'s as wildcards" do
|
236
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["10.*.*.*"]}
|
237
|
+
@method.call("10.1.2.3").should be_false
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should not allow IPs which are explicitly specified" do
|
241
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["9.8.7.6", "9.8.7.5"]}
|
242
|
+
@method.call("9.8.7.5").should be_false
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should allow IPs which are not explicitly specified" do
|
246
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["10.20.30.40"]}
|
247
|
+
@method.call("1.1.1.1").should be_true
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe '"everyone" access' do
|
253
|
+
it "should return true for any IP given, irrespective of the configuration" do
|
254
|
+
ip_addresses = %w[100.200.100.200 0.0.0.0 *.0.0.*]
|
255
|
+
ip_addresses.each do |address|
|
256
|
+
RESTFUL_RPC.helper_method(:ip_allowed?).call(address).should equal(true)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|