dripdrop 0.10.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.document +5 -0
  2. data/.gitignore +31 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +20 -0
  5. data/README.md +164 -0
  6. data/Rakefile +16 -0
  7. data/dripdrop.gemspec +37 -0
  8. data/example/agent_test.rb +14 -0
  9. data/example/combined.rb +33 -0
  10. data/example/complex/README +22 -0
  11. data/example/complex/client.rb +20 -0
  12. data/example/complex/server.rb +102 -0
  13. data/example/complex/service.rb +8 -0
  14. data/example/complex/websocket.rb +442 -0
  15. data/example/http.rb +23 -0
  16. data/example/pubsub.rb +29 -0
  17. data/example/pushpull.rb +21 -0
  18. data/example/subclass.rb +54 -0
  19. data/example/xreq_xrep.rb +24 -0
  20. data/js/dripdrop.html +186 -0
  21. data/js/dripdrop.js +107 -0
  22. data/js/qunit.css +155 -0
  23. data/js/qunit.js +1261 -0
  24. data/lib/dripdrop.rb +2 -0
  25. data/lib/dripdrop/agent.rb +40 -0
  26. data/lib/dripdrop/handlers/base.rb +42 -0
  27. data/lib/dripdrop/handlers/http_client.rb +38 -0
  28. data/lib/dripdrop/handlers/http_server.rb +59 -0
  29. data/lib/dripdrop/handlers/mongrel2.rb +163 -0
  30. data/lib/dripdrop/handlers/websocket_server.rb +86 -0
  31. data/lib/dripdrop/handlers/zeromq.rb +300 -0
  32. data/lib/dripdrop/message.rb +190 -0
  33. data/lib/dripdrop/node.rb +351 -0
  34. data/lib/dripdrop/node/nodelet.rb +35 -0
  35. data/lib/dripdrop/version.rb +3 -0
  36. data/spec/gimite-websocket.rb +442 -0
  37. data/spec/message_spec.rb +94 -0
  38. data/spec/node/http_spec.rb +77 -0
  39. data/spec/node/nodelet_spec.rb +67 -0
  40. data/spec/node/routing_spec.rb +67 -0
  41. data/spec/node/websocket_spec.rb +98 -0
  42. data/spec/node/zmq_m2_spec.rb +77 -0
  43. data/spec/node/zmq_pushpull_spec.rb +54 -0
  44. data/spec/node/zmq_xrepxreq_spec.rb +108 -0
  45. data/spec/node_spec.rb +85 -0
  46. data/spec/spec_helper.rb +20 -0
  47. metadata +167 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,31 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+ *.swo
16
+ *.swn
17
+
18
+ ## PROJECT::GENERAL
19
+ coverage
20
+ rdoc
21
+ doc
22
+ pkg
23
+
24
+ #RBX
25
+ *.rbc
26
+
27
+ ## PROJECT::SPECIFIC
28
+ .rvmrc
29
+ Gemfile.lock
30
+ .bundle
31
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dripdrop.gemspec
4
+ gemspec
5
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Andrew Cholakian
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # DripDrop
2
+
3
+ ## INSTALLATION NOTES:
4
+
5
+ 1. This does NOT work with 1.8.7. 1.9.2/RBX/jRuby only.
6
+ 2. Build zeromq2 from [git master](https://github.com/zeromq/zeromq2) if you haven't already.
7
+ 3. Install with 'gem install dripdrop'.
8
+ 4. If on jRuby, http servers will be unavailable unless you have a jruby with cext support, and manually `gem install eventmachine_httpserver`
9
+ 5. If on 1.9.x `gem install ffi`
10
+
11
+ ## About
12
+
13
+ DripDrop is a library aiming to help you write better message passing apps. It's a rich toolchest, currently based on EventMachine, which provides async IO.
14
+
15
+ DripDrop helps in these ways:
16
+
17
+ 1. Normalized Communication Interfaces. All protocols, regardless of their using HTTP, ZeroMQ, or WebSockets, have a unified API, with minor differences only to accomodate the reality of the underlying protocol.
18
+ 2. A set of tools to break your app into composable parts--we call them nodelets in dripdrop--ideally communicating with each other with the aforementioned interfaces..
19
+ 3. A simple, yet powerful messaging class, that lets you control the structure, formatting, and serialization of messages sent between nodelets.
20
+ 4. Tools to break your nodelets off when it comes time to deploy, letting you scale your app by delegating roles and scaling out resources
21
+
22
+ ## Normalized Interfaces
23
+
24
+ Let's start by looking at the normalized communication interface in a simple app.
25
+
26
+ class MyApp < DripDrop::Node
27
+ def action #Special method that gets executed on Node#start
28
+ # Define some sockets, here we create an HTTP server, and
29
+ # a client to it. :my_hts and :my_htc are custom names
30
+ # that will be available after definition
31
+ route :my_server, :http_server, 'http://127.0.0.1:2201'
32
+ route :my_client, :http_client, 'http://127.0.0.1:2201'
33
+
34
+ # Our http server is a simple time server
35
+ my_server.on_recv do |message,response|
36
+ response.send_message(:name => 'time', :body => {'time' => Time.now.to_s})
37
+ end
38
+
39
+ # Here, we setup a timer, and periodically poll the http server
40
+ EM::PeriodicTimer.new(1) do
41
+ # Messages must have a :name. They can optionally have a :body.
42
+ # Additionally, they can set custom :head properties.
43
+ my_client.send_message(:name => 'time_request') do |response_message|
44
+ puts "The time is: #{response_message.body['time']}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ #Start the app and block
51
+ MyApp.new.start!
52
+
53
+ What we've done here is use HTTP as a simple messaging protocol. Yes, we've thrown out a good chunk of what HTTP does, but consider this, that exact same code would work if we replaced the top two lines with:
54
+
55
+ route :my_server, :zmq_xrep, 'http://127.0.0.1:2201', :bind
56
+ route :my_client, :zmq_xreq, 'http://127.0.0.1:2201', :connect
57
+
58
+ That replaces the HTTP server and client with ultra-high performance zeromq sockets. Now, protocols have varying strengths and weaknesses, and ZeroMQ is not HTTP necessarily, for instance, given a :zmq_pub socket, you can only send_messages, but there is no response message, because :zmq_pub is the publishing end of a request/reply pattern. The messaging API attempts to reduce all methods on sockets to the following set:
59
+
60
+ * on_recv (sometimes takes a block with |message,response| if it can send a response)
61
+ * send_message
62
+ * on_open (Websockets only)
63
+ * on_close (Websockets only)
64
+
65
+
66
+ ## Composable Parts
67
+
68
+ The tools mentioned above are useful, but if you try and build a larger app you'll quickly find them lacking. The callbacks get tricky, and mixing your logic up in a single #action method becomes messy. That's why we have nodelets in DripDrop. Here's a trivial example.
69
+
70
+ class MyApp < DripDrop::Node
71
+ # This will instantiate a new StatsCollector object, and define the
72
+ # stats_raw and stats_filtered methods inside it.
73
+ nodelet :stats_collector, StatsCollector do |nodelet|
74
+ nodelet.route :stats_raw, :zmq_pull, 'tcp://127.0.0.1:2301', :bind
75
+ nodelet.route :stats_filtered, :zmq_push, 'tcp://127.0.0.1:2302', :bind
76
+ end
77
+
78
+ nodelet :stats_processor, StatsProcessor do |nodelet|
79
+ nodelet.route :stats_ingress, :zmq_pull, 'tcp://127.0.0.1:2302', :bind
80
+ end
81
+
82
+ # The nodelets method gives you access to all defined nodelets
83
+ # We created a #run method on each nodelet we call here.
84
+ nodelets.each {|n| n.run}
85
+ end
86
+
87
+ # You must subclass Nodelet
88
+ # The method #run here is merely a convention
89
+ class StatsCollector < DripDrop::Node::Nodelet
90
+ def run
91
+ stats_raw.on_recv do |raw_stat_msg|
92
+ # Custom filtering code could go here...
93
+ stats_filtered.send_message(raw_stat_msg)
94
+ end
95
+ end
96
+ end
97
+
98
+ class StatsProcessor < DripDrop::Node::Nodelet
99
+ # Initialize shouldn't be subclassed on a Nodelet, this gets called
100
+ # After the nodelet is instantiated
101
+ def configure
102
+ @name_counts = Hash.new(0)
103
+ end
104
+
105
+ def run
106
+ stats_ingress.on_recv do |message|
107
+ @name_counts[message.name] += 1
108
+ puts @name_counts.inspect
109
+ end
110
+ end
111
+ end
112
+
113
+ MyApp.new.start!
114
+
115
+ # Custom Messages
116
+
117
+ DripDrop::Message is the parent class of all messages in dripdrop, it's a flexible and freeform way to send data. In more complex apps you'll want to both define custom behaviour on messages, and restrict the data they carry. This is possible by subclassing DripDrop::Message. Before we look at that though, lets see what makes a DripDrop::Message.
118
+
119
+ The simplest DripDrop::Message you could create would look something like this if dumped into JSON:
120
+
121
+ {name: 'msgname', head: {}, body: null}
122
+
123
+ In other words, a dripdrop message *must* provide a name, it must also be able to store arbitrary, nested, keys and values in its head, and it may use the body for any data it wishes.
124
+
125
+ If you'd like to create your own Message format, simply Subclass DripDrop::Message. If you want to restrict your handlers to using a specific message type, it's easily done by passing in the :message_class option. For instance
126
+
127
+ class MyMessageClass < DripDrop::Message
128
+ # Custom code
129
+ end
130
+ class MyApp < DripDrop::Node
131
+ def action
132
+ route :myhandler, :zmq_publish, 'tcp://127.0.0.1:2200', :bind, :message_class => MyMessageClass
133
+ route :myhandler, :zmq_subscribe, 'tcp://127.0.0.1:2200', :connect, :message_class => MyMessageClass
134
+ end
135
+ end
136
+
137
+ # Breaking out your nodelets
138
+
139
+ One of the core ideas behind dripdrop, is that if your application is composed of a bunch of separate parts, that in production deployment, will run on separate physical servers, it should still be possible for you to develop and test with ease. If you structure your app into separate nodelets, and *only* communicate between them via message passing, you can accomplish this easily.
140
+
141
+ While you will have to write your own executable wrappers suitable for your own deployment, one convenenience feature built in is the notion of a +run_list+. By setting the #run_list you can restrict which nodelets actually get initialized. For example:
142
+
143
+ class MyApp < DripDrop::Node
144
+ nodelet :service_one, ServiceOneClass do
145
+ #nodelet setup
146
+ end
147
+ nodelet :service_two, ServiceTwoClass do
148
+ #nodelet setup
149
+ end
150
+ end
151
+
152
+ # Only starts :service_two, the setup for :service_one
153
+ # is skipped as well
154
+ MyApp.new(:run_list => [:service_two]).start!
155
+
156
+ #RDocs
157
+
158
+ RDocs can be found [here](http://www.rdoc.info/github/andrewvc/dripdrop/master/frames). Most of the interesting stuff is in the [Node](http://www.rdoc.info/github/andrewvc/dripdrop/master/DripDrop/Node) and [Message](http://www.rdoc.info/github/andrewvc/dripdrop/master/DripDrop/Message) classes.
159
+
160
+ #Contributors
161
+
162
+ * Andrew Cholakian: [andrewvc](http://github.com/andrewvc)
163
+ * John W Higgins: [wishdev](http://github.com/wishdev)
164
+ * Nick Recobra: [oruen](https://github.com/oruen)
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/rdoctask'
5
+ require 'dripdrop/version'
6
+ Rake::RDocTask.new do |rdoc|
7
+ rdoc.rdoc_dir = 'rdoc'
8
+ rdoc.title = "dripdrop #{DripDrop::VERSION}"
9
+ rdoc.rdoc_files.include('README*')
10
+ rdoc.rdoc_files.include('lib/**/*.rb')
11
+ end
12
+
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec) do |t|
15
+ end
16
+ task :default => :spec
data/dripdrop.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dripdrop/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dripdrop"
7
+ s.version = DripDrop::VERSION
8
+ s.platform = RUBY_PLATFORM[/java/] || Gem::Platform::RUBY
9
+ s.authors = ["Andrew Cholakian"]
10
+ s.email = ["andrew@andrewvc.com"]
11
+ s.homepage = "https://github.com/andrewvc/dripdrop"
12
+ s.summary = %q{Evented framework for ZeroMQ and EventMachine Apps.}
13
+ s.description = %q{Evented framework for ZeroMQ and EventMachine Apps.}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.md"
17
+ ]
18
+
19
+ s.rubyforge_project = "dripdrop"
20
+
21
+ s.add_dependency "eventmachine", ">= 0.12.10"
22
+ s.add_dependency "em-websocket", ">= 0"
23
+ s.add_dependency "em-zeromq", ">= 0.2.1"
24
+ if s.platform.to_s == "java"
25
+ s.add_dependency "json", ">= 1.5.1"
26
+ else
27
+ s.add_dependency "yajl-ruby", ">= 0.8.1"
28
+ s.add_dependency "eventmachine_httpserver", ">= 0.2.1"
29
+ end
30
+ s.add_development_dependency "rspec", ">= 2.4.0"
31
+
32
+ s.files = `git ls-files`.split("\n")
33
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
34
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
35
+ s.require_paths = ["lib"]
36
+ end
37
+
@@ -0,0 +1,14 @@
1
+ ##
2
+ ##TODO: This badly needs to be rewritten
3
+ ##
4
+
5
+ require 'rubygems'
6
+ require 'dripdrop/agent'
7
+
8
+ agent = DripDrop::Agent.new(ZMQ::PUB,'tcp://127.0.0.1:2900',:connect)
9
+
10
+ loop do
11
+ agent.send_message('test', :body => 'hello', :head => {:key => 'value'})
12
+ puts "SEND"
13
+ sleep 1
14
+ end
@@ -0,0 +1,33 @@
1
+ require 'dripdrop'
2
+ Thread.abort_on_exception = true #Always a good idea in multithreaded apps.
3
+
4
+ # Encapsulates our EM and ZMQ reactors
5
+ DripDrop::Node.new do
6
+ # Define all our sockets
7
+ route :stats_pub, :zmq_publish, 'tcp://127.0.0.1:2200', :bind
8
+ route :stats_sub1, :zmq_subscribe, stats_pub.address, :connect
9
+ route :stats_sub2, :zmq_subscribe, stats_pub.address, :connect
10
+ route :http_collector, :http_server, 'http://127.0.0.1:8080'
11
+ route :http_agent, :http_client, http_collector.address
12
+
13
+ stats_sub1.on_recv do |message|
14
+ puts "Receiver 1: #{message.body}"
15
+ end
16
+ stats_sub2.on_recv do |message|
17
+ puts "Receiver 2: #{message.body}"
18
+ end
19
+
20
+ i = 0
21
+ http_collector.on_recv do |message,response|
22
+ i += 1
23
+ stats_pub.send_message(message)
24
+ response.send_message(:name => 'ack', :body => {:seq => i})
25
+ end
26
+
27
+ EM::PeriodicTimer.new(1) do
28
+ msg = DripDrop::Message.new('http/status', :body => "Success #{i}")
29
+ http_agent.send_message(msg) do |resp_msg|
30
+ puts "RESP: #{resp_msg.body['seq']}"
31
+ end
32
+ end
33
+ end.start! #Start the reactor and block until complete
@@ -0,0 +1,22 @@
1
+ This example creates an async, evented app that can do the following.
2
+
3
+ * Broadcast messages to all connected websockets originating from a
4
+ master control server.
5
+ * Proxy requests sent via websocket to HTTP through a master
6
+ control server
7
+
8
+ It's broken into three parts
9
+ * service.rb: The async core, written in dripdrop/eventmachine/zeromq
10
+ * client.rb: A Test websocket client
11
+ * service.rb: A test HTTP web-service that could beb used to control messages
12
+
13
+ To run.
14
+
15
+ In one terminal (in dripdrop root)
16
+ ruby -I lib/ example/complex/server.rb
17
+
18
+ In another terminal (Websocket client)
19
+ cd example/complex && ruby client.rb
20
+
21
+ In a third terminal (Minimal webapp in sinatra)
22
+ cd example/complex && ruby service.rb
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'websocket'
3
+ require 'dripdrop/message'
4
+
5
+ Thread.abort_on_exception = true
6
+
7
+ client = WebSocket.new('ws://127.0.0.1:8080')
8
+
9
+ Thread.new do
10
+ while data = client.receive
11
+ puts data
12
+ end
13
+ end
14
+
15
+ i = 0
16
+ while sleep 1
17
+ i += 1
18
+ puts '.'
19
+ client.send(DripDrop::Message.new('Client Broadcast', :body => i).json_encoded)
20
+ end
@@ -0,0 +1,102 @@
1
+ require 'dripdrop'
2
+ Thread.abort_on_exception = true
3
+
4
+ class ComplexExample < DripDrop::Node
5
+ def initialize(mode=:all)
6
+ super()
7
+ @mode = mode
8
+ end
9
+
10
+ def action
11
+ nodelet :ws_listener, WSListener do |n|
12
+ n.route :ws_listener, :websocket, 'ws://127.0.0.1:8080'
13
+ n.route :broadcast_in, :zmq_subscribe, 'tcp://127.0.0.1:2200', :connect
14
+ n.route :reqs_out, :zmq_xreq, 'tcp://127.0.0.1:2201', :connect
15
+ end
16
+
17
+ nodelet :coordinator, Coordinator do |n|
18
+ n.route :broadcast_out, :zmq_publish, 'tcp://127.0.0.1:2200', :bind
19
+ n.route :reqs_in, :zmq_xrep, 'tcp://127.0.0.1:2201', :bind
20
+ n.route :reqs_htout, :http_client, 'tcp://127.0.0.1:3000/endpoint'
21
+ end
22
+ end
23
+ end
24
+
25
+ class Coordinator < DripDrop::Node::Nodelet
26
+ def run
27
+ proxy_reqs
28
+ heartbeat
29
+ end
30
+
31
+ def proxy_reqs
32
+ reqs_in.on_recv do |message, response|
33
+ puts "Proxying #{message.inspect} to htout"
34
+ reqs_htout.send_message(message) do |http_response|
35
+ puts "Received http response #{http_response.inspect} sending back"
36
+ response.send_message(http_response)
37
+ end
38
+ end
39
+ end
40
+
41
+ def heartbeat
42
+ EM::PeriodicTimer.new(1) do
43
+ broadcast_out.send_message :name => 'tick', :body => Time.now.to_s
44
+ end
45
+ end
46
+ end
47
+
48
+ class WSListener < DripDrop::Node::Nodelet
49
+ def initialize(*args)
50
+ super
51
+ @client_channel = EM::Channel.new
52
+ end
53
+
54
+ def run
55
+ proxy_websockets
56
+ broadcast_to_websockets
57
+ end
58
+
59
+ def broadcast_to_websockets
60
+ # Receives messages from Broadcast Out
61
+ broadcast_in.on_recv do |message|
62
+ puts "Broadcast In recv: #{message.inspect}"
63
+ @client_channel.push(message)
64
+ end
65
+ end
66
+
67
+ def proxy_websockets
68
+ sigs_sids = {} #Map connection signatures to subscriber IDs
69
+
70
+ ws.on_open do |conn|
71
+ puts "WS Connected"
72
+ conn.send_message(DripDrop::Message.new('test'))
73
+
74
+ sid = @client_channel.subscribe do |message|
75
+ puts message.inspect
76
+ conn.send_message(message)
77
+ end
78
+
79
+ sigs_sids[conn.signature] = sid
80
+ end
81
+ ws.on_close do |conn|
82
+ puts "Closed #{conn.signature}"
83
+ @client_channel.unsubscribe sigs_sids[conn.signature]
84
+ end
85
+ ws.on_error do |reason,conn|
86
+ puts "Errored #{reason.inspect}, #{conn.signature}"
87
+ @client_channel.unsubscribe sigs_sids[conn.signature]
88
+ end
89
+
90
+ ws.on_recv do |message,conn|
91
+ puts "WS Recv #{message.name}"
92
+ reqs_out.send_message(message) do |resp_message|
93
+ puts "Recvd resp_message #{resp_message.inspect}, sending back to client"
94
+ conn.send_message(resp_message)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+ puts "Starting..."
102
+ ComplexExample.new.start!