dripdrop 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -6,29 +6,49 @@ DripDrop is ZeroMQ(using zmqmachine) + Event Machine simplified for the general
6
6
 
7
7
  Here's an example of the kind of thing DripDrop makes easy, from [examples/pubsub.rb](http://github.com/andrewvc/dripdrop/blob/master/example/pubsub.rb)
8
8
 
9
- DripDrop::Node.new do |node|
9
+ require 'dripdrop/node'
10
+ Thread.abort_on_exception = true
11
+
12
+ #Define our handlers
13
+ DripDrop::Node.new do
10
14
  z_addr = 'tcp://127.0.0.1:2200'
15
+
16
+ #Create a publisher
17
+ pub = zmq_publish(z_addr,:bind)
11
18
 
12
- pub = node.zmq_publish(z_addr,:bind)
13
- sub = node.zmq_subscribe(z_addr,:connect).on_recv do |message|
19
+ #Create two subscribers
20
+ zmq_subscribe(z_addr,:connect).on_recv do |message|
14
21
  puts "Receiver 1 #{message.inspect}"
15
22
  end
16
- sub = node.zmq_subscribe(z_addr, :connect).on_recv do |message|
23
+ zmq_subscribe(z_addr, :connect).on_recv do |message|
17
24
  puts "Receiver 2 #{message.inspect}"
18
25
  end
26
+
27
+ zm_reactor.periodical_timer(5) do
28
+ #Sending a hash as a message implicitly transforms it into a DripDrop::Message
29
+ pub.send_message(:name => 'test', :body => 'Test Payload')
30
+ end
31
+
32
+ http_server(addr).on_recv do |response,msg|
33
+ i += 1
34
+ response.send_message(msg)
35
+ end
19
36
 
20
- node.zm_reactor.periodical_timer(5) do
21
- pub.send_message(DripDrop::Message.new('test', :body => 'Test Payload'))
37
+ EM::PeriodicTimer.new(1) do
38
+ client = http_client(addr)
39
+ msg = DripDrop::Message.new('http/status', :body => "Success #{i}")
40
+ client.send_message(msg) do |resp_msg|
41
+ puts resp_msg.inspect
42
+ end
22
43
  end
23
- end
24
-
44
+ end.start! #Start the reactor and block until complete
25
45
 
26
- Want to see a longer example encapsulating both zmqmachine and eventmachine functionality? Check out [this file](http://github.com/andrewvc/dripdrop-webstats/blob/master/lib/dripdrop-webstats.rb), which encapsulates all the functionality of the diagram below:
46
+ Note that these aren't regular ZMQ sockets, and that the HTTP server isn't a regular server. They only speak and respond using DripDrop::Message formatted messages. For HTTP/WebSockets it's JSON that looks like {name: 'name', head: {}, body: anything}, for ZeroMQ it means BERT. There is a raw made that you can use for other message formats, but using DripDrop::Messages makes things easier, and for some socket types (like XREQ/XREP) the predefined format is very useful in matching requests to replies.
27
47
 
28
- ![topology](http://github.com/andrewvc/dripdrop/raw/master/doc_img/topology.png "Topology")
48
+ Want to see a longer example encapsulating both zmqmachine and eventmachine functionality? Check out [this file](http://github.com/andrewvc/dripdrop-webstats/blob/master/lib/dripdrop-webstats.rb), which encapsulates all the functionality of the diagram below:
29
49
 
30
50
  #How It Works
31
51
 
32
- DripDrop encapsulates both zmqmachine, and eventmachine. It provides some sane default messaging choices, using [BERT](http://github.com/blog/531-introducing-bert-and-bert-rpc) (A binary, JSON, like serialization format) and JSON to automatically. zmqmachine and eventmachine have some good APIs, some convoluted ones, the goal here is to smooth over the bumps, and make writing highly concurrent programs both as terse and beautiful as possible.
52
+ DripDrop encapsulates both zmqmachine, and eventmachine. It provides some sane default messaging choices, using [BERT](http://github.com/blog/531-introducing-bert-and-bert-rpc) (A binary, JSON, like serialization format) and JSON for serialization. While zmqmachine and eventmachine APIs, some convoluted ones, the goal here is to smooth over the bumps, and make them play together nicely.
33
53
 
34
54
  Copyright (c) 2010 Andrew Cholakian. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/dripdrop.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dripdrop}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andrew Cholakian"]
12
- s.date = %q{2010-09-05}
12
+ s.date = %q{2010-10-10}
13
13
  s.description = %q{0MQ App stats}
14
14
  s.email = %q{andrew@andrewvc.com}
15
15
  s.extra_rdoc_files = [
@@ -26,20 +26,38 @@ Gem::Specification.new do |s|
26
26
  "doc_img/topology.png",
27
27
  "dripdrop.gemspec",
28
28
  "example/agent_test.rb",
29
+ "example/http.rb",
29
30
  "example/pubsub.rb",
30
31
  "example/pushpull.rb",
32
+ "example/xreq_xrep.rb",
33
+ "js/dripdrop.html",
34
+ "js/dripdrop.js",
35
+ "js/jack.js",
36
+ "js/qunit.css",
37
+ "js/qunit.js",
31
38
  "lib/dripdrop.rb",
32
39
  "lib/dripdrop/agent.rb",
40
+ "lib/dripdrop/handlers/http.rb",
33
41
  "lib/dripdrop/handlers/websockets.rb",
34
42
  "lib/dripdrop/handlers/zeromq.rb",
35
43
  "lib/dripdrop/message.rb",
36
- "lib/dripdrop/node.rb"
44
+ "lib/dripdrop/node.rb",
45
+ "spec/node/zmq_pushpull.rb",
46
+ "spec/node/zmq_xrepxreq.rb",
47
+ "spec/node_spec.rb",
48
+ "spec/spec_helper.rb"
37
49
  ]
38
50
  s.homepage = %q{http://github.com/andrewvc/dripdrop}
39
51
  s.rdoc_options = ["--charset=UTF-8"]
40
52
  s.require_paths = ["lib"]
41
53
  s.rubygems_version = %q{1.3.7}
42
54
  s.summary = %q{0MQ App Stats}
55
+ s.test_files = [
56
+ "spec/node/zmq_pushpull.rb",
57
+ "spec/node/zmq_xrepxreq.rb",
58
+ "spec/node_spec.rb",
59
+ "spec/spec_helper.rb"
60
+ ]
43
61
 
44
62
  if s.respond_to? :specification_version then
45
63
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
data/example/http.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'dripdrop/node'
2
+ Thread.abort_on_exception = true
3
+
4
+ DripDrop::Node.new do
5
+ addr = 'http://127.0.0.1:2200'
6
+
7
+ i = 0
8
+ http_server(addr).on_recv do |response,msg|
9
+ i += 1
10
+ response.send_message(msg)
11
+ end
12
+
13
+ EM::PeriodicTimer.new(1) do
14
+ client = http_client(addr)
15
+ msg = DripDrop::Message.new('http/status', :body => "Success #{i}")
16
+ client.send_message(msg) do |resp_msg|
17
+ puts resp_msg.inspect
18
+ end
19
+ end
20
+
21
+ #Keep zmqmachine from spinning around using up all our CPU by creating a socket
22
+ req = zmq_xreq('tcp://127.0.0.1:2091', :connect)
23
+ end.start!
data/example/pubsub.rb CHANGED
@@ -1,18 +1,36 @@
1
1
  require 'dripdrop/node'
2
2
  Thread.abort_on_exception = true
3
3
 
4
- DripDrop::Node.new do |node|
4
+ #Define our handlers
5
+ DripDrop::Node.new do
5
6
  z_addr = 'tcp://127.0.0.1:2200'
6
7
 
7
- pub = node.zmq_publish(z_addr,:bind)
8
- sub = node.zmq_subscribe(z_addr,:connect).on_recv do |message|
8
+ #Create a publisher
9
+ pub = zmq_publish(z_addr,:bind)
10
+
11
+ #Create three subscribers
12
+ sub1 = zmq_subscribe(z_addr,:connect)
13
+
14
+ sub1.on_recv do |message|
9
15
  puts "Receiver 1 #{message.inspect}"
10
16
  end
11
- sub = node.zmq_subscribe(z_addr, :connect).on_recv do |message|
17
+
18
+ sub1.topic_filter = /[13579]$/
19
+
20
+ sub2 = zmq_subscribe(z_addr,:connect)
21
+
22
+ sub2.on_recv do |message|
12
23
  puts "Receiver 2 #{message.inspect}"
13
24
  end
25
+
26
+ sub2.topic_filter = /[02468]$/
27
+
28
+ zmq_subscribe(z_addr, :connect).on_recv do |message|
29
+ puts "Receiver 3 #{message.inspect}"
30
+ end
14
31
 
15
- node.zm_reactor.periodical_timer(5) do
16
- pub.send_message(DripDrop::Message.new('test', :body => 'Test Payload'))
32
+ zm_reactor.periodical_timer(5) do
33
+ #Sending a hash as a message implicitly transforms it into a DripDrop::Message
34
+ pub.send_message(:name => Time.now.to_i.to_s, :body => 'Test Payload')
17
35
  end
18
- end
36
+ end.start! #Start the reactor and block until complete
data/example/pushpull.rb CHANGED
@@ -1,21 +1,21 @@
1
1
  require 'dripdrop/node'
2
2
  Thread.abort_on_exception = true
3
3
 
4
- DripDrop::Node.new do |node|
4
+ DripDrop::Node.new do
5
5
  z_addr = 'tcp://127.0.0.1:2200'
6
6
 
7
- node.zmq_pull(z_addr, :connect).on_recv do |message|
7
+ zmq_pull(z_addr, :connect).on_recv do |message|
8
8
  puts "Receiver 2 #{message.body}"
9
9
  end
10
- node.zmq_pull(z_addr, :connect).on_recv do |message|
10
+ zmq_pull(z_addr, :connect).on_recv do |message|
11
11
  puts "Receiver 1 #{message.body}"
12
12
  end
13
- push = node.zmq_push(z_addr, :bind)
13
+ push = zmq_push(z_addr, :bind)
14
14
 
15
15
  i = 0
16
- node.zm_reactor.periodical_timer(800) do
16
+ zm_reactor.periodical_timer(800) do
17
17
  i += 1
18
18
  puts i
19
- push.send_message(DripDrop::Message.new('test', :body => "Test Payload #{i}"))
19
+ push.send_message(:name => 'test', :body => "Test Payload #{i}")
20
20
  end
21
- end
21
+ end.start!
@@ -0,0 +1,26 @@
1
+ require 'dripdrop/node'
2
+ Thread.abort_on_exception = true
3
+
4
+ DripDrop::Node.new do
5
+ z_addr = 'tcp://127.0.0.1:2200'
6
+
7
+ rep = zmq_xrep(z_addr, :bind)
8
+ rep.on_recv do |identities,seq,message|
9
+ puts "REP #{message.body}"
10
+ rep.send_message(identities,seq,message)
11
+ end
12
+
13
+ req = zmq_xreq(z_addr, :connect)
14
+
15
+ i = 0
16
+ k = 0
17
+
18
+ zm_reactor.periodical_timer(1000) do
19
+ req.send_message(:name => 'test', :body => "Test Payload i#{i}") do |message|
20
+ puts "RECV I RESP #{message.inspect}"
21
+ end
22
+ req.send_message(:name => 'test', :body => "Test Payload k#{i}") do |message|
23
+ puts "RECV K RESP #{message.inspect}"
24
+ end
25
+ end
26
+ end.start!
data/js/dripdrop.html ADDED
@@ -0,0 +1,186 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2
+ "http://www.w3.org/TR/html4/loose.dtd">
3
+ <html>
4
+ <head>
5
+ <script src="../jquery.js"></script>
6
+ <script src="../jquery.json.js"></script>
7
+ <script src="../dripdrop.js"></script>
8
+
9
+ <link rel="stylesheet" href="qunit.css" type="text/css" media="screen" />
10
+ <script type="text/javascript" src="qunit.js"></script>
11
+ <script type="text/javascript" src="jack.js"></script>
12
+
13
+ <script>
14
+ $(document).ready(function(){
15
+
16
+ /* Test Helpers */
17
+ function TSock() {return new DD.WebSocket('ws://localhost:2702')};
18
+ function THTTP() {return new DD.HTTP('http://localhost:2703')};
19
+
20
+ //Faked message to for a websocket
21
+ function FakeWSMessage(ddMessage) {
22
+ var msg = (ddMessage === undefined) ? new DD.Message('test') : ddMessage;
23
+ this.data = msg.jsonEncoded();
24
+ }
25
+
26
+ /* Tests */
27
+
28
+ test("The DD Singleton should be constructed from DripDrop", function() {
29
+ equals( DripDrop, DD.constructor, "Match");
30
+ });
31
+
32
+ module("DD.Message");
33
+
34
+ var msgVals = {
35
+ name: 'foo',
36
+ body: 'bar',
37
+ head: {key: 'baz'}
38
+ }
39
+ test("Creation: Name Only", function() {
40
+ var msg = new DD.Message(msgVals.name);
41
+ equals(msg.name, msgVals.name, "name");
42
+ });
43
+ test("Creation: Name + Body", function() {
44
+ var msg = new DD.Message(msgVals.name, {body : msgVals.body});
45
+ equals(msg.name, msgVals.name, "name");
46
+ equals(msg.body, msgVals.body, "body");
47
+ });
48
+ test("Creation: Name + Head + Body", function() {
49
+ var msg = new DD.Message(msgVals.name,
50
+ {body : msgVals.body, head : msgVals.head});
51
+ expect(3);
52
+ equals(msg.name, msgVals.name, "name");
53
+ equals(msg.head, msgVals.head, "head");
54
+ equals(msg.body, msgVals.body, "body");
55
+ });
56
+ test("Creation: No Head should still have head as an object", function() {
57
+ var msg = new DD.Message(msgVals.name);
58
+ equals(msg.head.constructor, Object, "head");
59
+ });
60
+
61
+ test("JSON Export", function() {
62
+ var msg = new DD.Message(msgVals.name,
63
+ {body : msgVals.body, head : msgVals.head});
64
+ equals(msg.jsonEncoded().constructor, String, "jsonEncoded");
65
+ });
66
+
67
+ module("DD.WebSocket");
68
+
69
+ test("Creating a websocket should make available the raw socket", function() {
70
+ var tSock = new TSock;
71
+ equals(tSock.socket.constructor, WebSocket,'raw socket');
72
+ });
73
+
74
+ $([['onOpen','onopen'],
75
+ ['onClose','onclose'],
76
+ ['onError','onerror']]).each(function(i,pair) {
77
+ var dd_func = pair[0];
78
+ var ws_func = pair[1];
79
+
80
+ test("DD function " + dd_func + " should set WS func " + ws_func, function() {
81
+ var tSock = new TSock;
82
+ var tFunc = function() {};
83
+ tSock[dd_func](tFunc);
84
+ equals(tSock.socket[ws_func], tFunc, "Func Set");
85
+ });
86
+ });
87
+
88
+ test("onRecv should trigger the passed in callback", function() {
89
+ expect(3);
90
+
91
+ var expectedMsg = new DD.Message('foo');
92
+
93
+ var tSock = new TSock;
94
+ tSock.onRecv(function(msg) {
95
+ ok(true, "Function executed");
96
+ equals(msg.constructor,DD.Message, "Is a DD.Message");
97
+ equals(msg.name, expectedMsg.name);
98
+ });
99
+
100
+ tSock.socket.onmessage(new FakeWSMessage(expectedMsg));
101
+ });
102
+
103
+ test("sendMessage should send a string to the websocket", function() {
104
+ expect(1);
105
+
106
+ var tSock = new TSock;
107
+ var tMsg = new DD.Message('foo');
108
+ tSock.socket.send = function(message) {
109
+ equals(tMsg.jsonEncoded(), message);
110
+ }
111
+ console.log(tSock.sendMessage)
112
+ tSock.sendMessage(tMsg);
113
+ });
114
+
115
+ module("DD.HTTP");
116
+
117
+ test("Creation", function() {
118
+ var dht = new DD.HTTP;
119
+ equals(dht.constructor, DD.HTTP, "Constructor Match");
120
+ });
121
+
122
+ test("Sending a message should call a get posting a JSON representation of the data",function() {
123
+ expect(1);
124
+ var tHTTP = new THTTP;
125
+ tHTTP.sendMessage();
126
+ });
127
+
128
+
129
+ module("DD.Pipeline");
130
+
131
+ test("Creation", function() {
132
+ var pl = new DD.Pipeline;
133
+ equals(pl.constructor, DD.Pipeline, "Constructor Match");
134
+ });
135
+
136
+ test("Should expose the pipeline stages as an array", function() {
137
+ var pl = new DD.Pipeline;
138
+ equals(pl.stages.constructor, Array, "Constructor Match");
139
+ });
140
+
141
+ test("Should return null when no stages", function() {
142
+ equals((new DD.Pipeline).execute(new DD.Message('test')), null);
143
+ });
144
+
145
+ test("Should return null when no stages", function() {
146
+ equals((new DD.Pipeline).execute(new DD.Message('test')), null);
147
+ });
148
+
149
+ //This tests too many things at once, should be broken apart
150
+ test("Execution, Order, Message Transformation", function() {
151
+ expect(5);
152
+
153
+ var pl = new DD.Pipeline;
154
+
155
+ var s1 = new DD.PipelineStage('t1','Test1',function(message) {
156
+ ok(true,"Test1 Executed");
157
+ equals(message.name,0,"Executed first job first");
158
+ message.name = 1;
159
+ return message;
160
+ });
161
+ var s2 = new DD.PipelineStage('t2','Test2',function(message) {
162
+ ok(true,"Test2 Executed");
163
+ equals(message.name,1,"Executed second job after first");
164
+ message.name = 2;
165
+
166
+ return message;
167
+ });
168
+ pl.stages = [s1,s2];
169
+
170
+ var resMsg = pl.execute(new DD.Message('0'));
171
+ equals(resMsg.name,2);
172
+ });
173
+
174
+ }); //End of tests
175
+
176
+ </script>
177
+
178
+ </head>
179
+ <body>
180
+ <h1 id="qunit-header">DripDrop Tests</h1>
181
+ <h2 id="qunit-banner"></h2>
182
+ <h2 id="qunit-userAgent"></h2>
183
+ <ol id="qunit-tests"></ol>
184
+ <div id="qunit-fixture">test markup, will be hidden</div>
185
+ </body>
186
+ </html>
data/js/dripdrop.js ADDED
@@ -0,0 +1,103 @@
1
+ function DripDrop() {
2
+ /* JavaScript object for DripDrop Messages */
3
+ this.Message = function(name,opts) {
4
+ this.name = name;
5
+ if (opts && opts.body) {
6
+ this.body = opts.body;
7
+ };
8
+
9
+ this.head = (opts && opts.head !== undefined) ? opts.head : {empty:''};
10
+
11
+ this.jsonEncoded = function() {
12
+ return $.JSON.encode({name: this.name, head: this.head, body: this.body});
13
+ };
14
+ };
15
+
16
+ /* A DripDrop friendly WebSocket Object.
17
+ This automatically converts messages to DD.Message objects.
18
+ Additionally, this uses friendlier callback methods, closer to the DripDrop
19
+ server-side API, like onOpen, onRecv, onError, and onClose. */
20
+ this.WebSocket = function(url) {
21
+ this.socket = new WebSocket(url);
22
+
23
+ this.onOpen = function(callback) {
24
+ this.socket.onopen = callback;
25
+ };
26
+
27
+ this.onRecv = function(callback) {
28
+ this.socket.onmessage = function(wsMessage) {
29
+ var json = $.parseJSON(wsMessage.data)
30
+ var message = new DD.Message(json.name, {head: json.head, body: json.body});
31
+
32
+ callback(message);
33
+ return this;
34
+ }
35
+ };
36
+
37
+ this.onClose = function(callback) {
38
+ this.socket.onclose = callback;
39
+ };
40
+
41
+ this.onError = function(callback) {
42
+ this.socket.onerror = callback;
43
+ };
44
+
45
+ this.sendMessage = function(message) {
46
+ this.socket.send(message.jsonEncoded());
47
+ };
48
+ };
49
+
50
+ this.HTTPResponse = function() {
51
+
52
+ };
53
+
54
+ /* A DripDrop friendly HTTP Request. */
55
+ this.HTTP = function(url) {
56
+ this.url = url;
57
+
58
+ this.onRecv = function(data) {};
59
+ this.sendMessage = function() {
60
+ var response = new this.HTTPResponse;
61
+ $.post(this.url, function(json) {
62
+ this.onRecv(new DD.Message(json.name, {head: json.head, body: json.body}));
63
+ });
64
+ };
65
+ };
66
+
67
+ /* An Object for reperesenting pipeline processing.
68
+ Ex:
69
+ var mypl = new DD.Pipeline;
70
+ mypl.stages.push(new DD.PipelineStage{'namecapper','Name Capper',
71
+ function(message) { message.name = message.name.toUpperCase() });
72
+ mypl.execute(message); //Message must be a valid DD.Message
73
+
74
+
75
+ All functions must either return a message, or false.
76
+ If false is returned the pipeline short-circuits and returns false, not running
77
+ subsequent stages */
78
+ this.PipelineStage = function(id,name,action) {
79
+ this.id = id;
80
+ this.name = name;
81
+ this.action = action;
82
+ };
83
+
84
+ this.Pipeline = function() {
85
+ this.stages = [];
86
+
87
+ this.execute = function(message) {
88
+ if (this.stages.length == 0) {
89
+ return null;
90
+ };
91
+
92
+ for (var i=0,l=this.stages.length; i < l; i++) {
93
+ var stage = this.stages[i];
94
+ message = stage.action(message);
95
+ };
96
+
97
+ return message;
98
+ };
99
+ };
100
+ };
101
+
102
+ //Use this as shorthand
103
+ DD = new DripDrop;