revactor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +3 -0
- data/LICENSE +58 -0
- data/README +338 -0
- data/Rakefile +48 -0
- data/examples/echo_server.rb +39 -0
- data/examples/google.rb +24 -0
- data/examples/mongrel.rb +14 -0
- data/lib/revactor/actor.rb +316 -0
- data/lib/revactor/behaviors/server.rb +87 -0
- data/lib/revactor/filters/line.rb +53 -0
- data/lib/revactor/filters/packet.rb +59 -0
- data/lib/revactor/mongrel.rb +62 -0
- data/lib/revactor/server.rb +153 -0
- data/lib/revactor/tcp.rb +397 -0
- data/lib/revactor.rb +29 -0
- data/revactor.gemspec +28 -0
- data/spec/actor_spec.rb +127 -0
- data/spec/line_filter_spec.rb +36 -0
- data/spec/packet_filter_spec.rb +59 -0
- data/spec/tcp_spec.rb +84 -0
- data/tools/messaging_throughput.rb +33 -0
- metadata +92 -0
data/CHANGES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
(see COPYING.txt file), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) rename any non-standard executables so the names do not conflict
|
21
|
+
with standard executables, which must also be provided.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or executable
|
26
|
+
form, provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the executables and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard executables non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under this terms.
|
43
|
+
|
44
|
+
They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
|
45
|
+
files under the ./missing directory. See each file for the copying
|
46
|
+
condition.
|
47
|
+
|
48
|
+
5. The scripts and library files supplied as input to or produced as
|
49
|
+
output from the software do not automatically fall under the
|
50
|
+
copyright of the software, but belong to whomever generated them,
|
51
|
+
and may be sold commercially, and may be aggregated with this
|
52
|
+
software.
|
53
|
+
|
54
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
55
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
56
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
57
|
+
PURPOSE.
|
58
|
+
|
data/README
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
= Revactor
|
2
|
+
|
3
|
+
Revactor is an Actor model implementation for Ruby 1.9 built on top of the
|
4
|
+
Rev high performance event library. Revactor is primarily designed for
|
5
|
+
writing Erlang-like network services and tools.
|
6
|
+
|
7
|
+
You can load Revactor into your Ruby 1.9 application with:
|
8
|
+
|
9
|
+
require 'revactor'
|
10
|
+
|
11
|
+
If you'd like to learn more about the Actor model, more information is
|
12
|
+
available on the Revactor web site:
|
13
|
+
|
14
|
+
http://revactor.org/philosophy
|
15
|
+
|
16
|
+
More information about Rev is available on Rubyforge:
|
17
|
+
|
18
|
+
http://rev.rubyforge.org/rdoc
|
19
|
+
|
20
|
+
== Anatomy
|
21
|
+
|
22
|
+
Revactor is built out of several parts which interoperate to let you build
|
23
|
+
network servers painlessly while still guaranteeing correct operation:
|
24
|
+
|
25
|
+
* Actors - Actors are the main concurrency primitive used by Revactor.
|
26
|
+
|
27
|
+
* Revactor::TCP - This module provides an API duck typed to the Ruby
|
28
|
+
Sockets API. However, rather than blocking calls actually blocking,
|
29
|
+
they defer to the underlying event loop. Actor-focused means of
|
30
|
+
receiving data are also provided.
|
31
|
+
|
32
|
+
* Filters - Applied to all incoming data directly when it's received, Filters
|
33
|
+
can preprocess or postprocess data before it's even delivered to an Actor.
|
34
|
+
This is useful for handling protocol framing or other streaming transforms.
|
35
|
+
|
36
|
+
* Behaviors - These are patterns for implementing Actors which accomplish
|
37
|
+
specific tasks. Right now only the Server behavior is supported.
|
38
|
+
|
39
|
+
== Actors
|
40
|
+
|
41
|
+
Actors add mailboxes to Ruby's Fiber mechanism. They multitask cooperatively,
|
42
|
+
meaning that many of the worries surrounding threaded programming disappear.
|
43
|
+
Any sequence of operations you do in an Actor are executed in the order
|
44
|
+
you specify. You don't (generally) have to worry about another Actor doing
|
45
|
+
something in the background as you frob a particular data structure.
|
46
|
+
|
47
|
+
Unfortunately, in Ruby 1.9, Actors are not first-class citizens. This means
|
48
|
+
you will need to jump from the non-Actor world to the Actor world before you
|
49
|
+
can do anything with Actors. This is accomplished by running Actor.start:
|
50
|
+
|
51
|
+
# Not in Actor world
|
52
|
+
Actor.start do
|
53
|
+
# In Actor world, yay
|
54
|
+
...
|
55
|
+
end
|
56
|
+
# Won't get called until all Actors have processes their entire mailbox
|
57
|
+
# and aren't waiting for any events.
|
58
|
+
|
59
|
+
Ideally, what you place in Actor.start is a small startup routine which
|
60
|
+
spawns all the Actors your application needs to get started and nothing else.
|
61
|
+
|
62
|
+
Once you're in Actor world, you can begin making Actors and sending them
|
63
|
+
events. You create Actors with Actor.spawn:
|
64
|
+
|
65
|
+
myactor = Actor.spawn { puts "I'm an Actor!" }
|
66
|
+
|
67
|
+
When you spawn an Actor it's scheduled to run after the current Actor either
|
68
|
+
completes or calls Actor.receive. Speaking of which, Actor.receive is used
|
69
|
+
to receive messages:
|
70
|
+
|
71
|
+
myactor = Actor.spawn do
|
72
|
+
Actor.receive do |filter|
|
73
|
+
filter.when(:dog) { puts "I got a dog!" }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
You can send a message to an actor using its #send method or <<
|
78
|
+
|
79
|
+
Calling:
|
80
|
+
|
81
|
+
myactor << :dog
|
82
|
+
|
83
|
+
prints:
|
84
|
+
|
85
|
+
"Yay, I got a dog!"
|
86
|
+
|
87
|
+
== Mailboxes
|
88
|
+
|
89
|
+
So, Actors can receive messages. But where do those messages go? The answer
|
90
|
+
is every Actor has a mailbox. The mailbox is sort of like a message queue,
|
91
|
+
but you don't have to read it sequentially. You can apply filters to it, and
|
92
|
+
change the filterset at any time.
|
93
|
+
|
94
|
+
When you call Actor.receive, it yields a filter object and lets you register
|
95
|
+
message patterns you're interested in, then it sleeps and waits for messages.
|
96
|
+
Each time the current actor receives a message, it's scanned by the filter,
|
97
|
+
and if a match occurs the appropriate action is given.
|
98
|
+
|
99
|
+
Matching is performed by the Filter#when method, which takes a pattern to match
|
100
|
+
against a message and a block to call if the message matches. The pattern is
|
101
|
+
compared to the message using ===, the same thing Ruby uses for case statements.
|
102
|
+
You can think of the filter as a big case statement.
|
103
|
+
|
104
|
+
Understanding the === statement is a bit strange, so here's a short guide:
|
105
|
+
|
106
|
+
You can pass #when a class to match against the message. Obviously passing it
|
107
|
+
Object will match all messages.
|
108
|
+
|
109
|
+
You can pass #when a regexp to match against the message.
|
110
|
+
|
111
|
+
Revactor installs the Case gem by default. This is useful for matching against
|
112
|
+
messages stored in Arrays, or in fixed-size arrays called Tuples. Case can
|
113
|
+
be used as follows:
|
114
|
+
|
115
|
+
filter.when(Case[:foobar, Object, Object]) { ... }
|
116
|
+
|
117
|
+
This will look for messages which are Arrays or Tuples with three members,
|
118
|
+
whose first member is the symbol :foobar. As you can probably guess, Case[]
|
119
|
+
matches against an Array or Tuple with the same number of members, and
|
120
|
+
matches each member of the given tuple with ===. Once again, Object is a
|
121
|
+
wildcard, so the other members of the message can be anything.
|
122
|
+
|
123
|
+
Want more complex pattern matching? Case lets you use a block to match any
|
124
|
+
member by using a guard, ala:
|
125
|
+
|
126
|
+
filter.when(Case[:foobar, Case.guard { |n| n > 100 }, Object]) { ... }
|
127
|
+
|
128
|
+
This will look for an Array / Tuple with three members, whose first member is
|
129
|
+
the symbol :foobar and whose second member is greater than 100.
|
130
|
+
|
131
|
+
You can also specify how long you wish to wait for a message before timing out.
|
132
|
+
This is accomplished with Filter#after:
|
133
|
+
|
134
|
+
filter.after(0.5) { raise 'it timed out ;_;' }
|
135
|
+
|
136
|
+
The #after method takes a duration in seconds to wait (in the above example it
|
137
|
+
waits a half second) before the receive operation times out.
|
138
|
+
|
139
|
+
Actor.receive returns whatever value the evaluated action returned. This means
|
140
|
+
you don't have to depend on side effects to extract values from receive,
|
141
|
+
instead you can just interpret its return value.
|
142
|
+
|
143
|
+
== Revactor::TCP
|
144
|
+
|
145
|
+
The TCP module lets you perform TCP operations on top of the Actor model. For
|
146
|
+
those of you familiar with Erlang, it implements something akin to gen_tcp.
|
147
|
+
Everyone else, read on!
|
148
|
+
|
149
|
+
Perhaps the best part of Revactor::TCP is you don't really need to know
|
150
|
+
anything about the Actor model to use it. For the most part it's duck typed
|
151
|
+
to the Ruby Socket API and can operate as a drop-in replacement.
|
152
|
+
|
153
|
+
To make an outgoing connection, call:
|
154
|
+
|
155
|
+
sock = Revactor::TCP.connect(host, port)
|
156
|
+
|
157
|
+
This will resolve the hostname for host (if it's not an IPv4 or IPv6 address),
|
158
|
+
make the connection, and return a socket to it. The best part is: this call
|
159
|
+
will "block" until the connection is established, and raise exceptions if the
|
160
|
+
connection fails. It works just like the Sockets API.
|
161
|
+
|
162
|
+
However, it's not actually blocking. Underneath this call is using the
|
163
|
+
Actor.receive method to wait for events. This means other Actors can run in
|
164
|
+
the background while the current one is waiting for a connection.
|
165
|
+
|
166
|
+
Furthermore, the Actor making this call can receive other events and they
|
167
|
+
will remain undisturbed in the mailbox. The connect method filters for
|
168
|
+
messages specifically related to making an outgoing connection.
|
169
|
+
|
170
|
+
To listen for incoming connections, there's a complimentary method:
|
171
|
+
|
172
|
+
listener = Revactor::TCP.listen(addr, port)
|
173
|
+
|
174
|
+
This will listen for incoming connections on the given address and port. It
|
175
|
+
returns a listen socket with a #accept method:
|
176
|
+
|
177
|
+
sock = listener.accept
|
178
|
+
|
179
|
+
Like TCP.connect, this method will block waiting for connections, but in
|
180
|
+
actuality is calling Actor.receive waiting for messages related to incoming
|
181
|
+
connections.
|
182
|
+
|
183
|
+
Now that you have a handle on a Revactor TCP socket, there's several ways you
|
184
|
+
can begin using it. The first is using a standard imperative sockets API:
|
185
|
+
|
186
|
+
data = sock.read(1024)
|
187
|
+
|
188
|
+
This call will "block" until it reads a kilobyte from the socket. However,
|
189
|
+
you may not be interested in a specific amount of data, just whenever data
|
190
|
+
is available on the socket. In that case, you can just call the #read method
|
191
|
+
without any argument:
|
192
|
+
|
193
|
+
data = sock.read
|
194
|
+
|
195
|
+
There's also a corresponding command to write to the socket. Like read this
|
196
|
+
command will also "block" until all data has been written out to the socket:
|
197
|
+
|
198
|
+
sock.write data
|
199
|
+
|
200
|
+
For Actors that want to deal with both incoming TCP data and messages from
|
201
|
+
other Actors, Revactor's TCP sockets also support an approach called
|
202
|
+
active mode. Active mode automatically delivers incoming data as a message
|
203
|
+
to what's known as the Socket's controller. You can assign the Socket's
|
204
|
+
controller whenever you want:
|
205
|
+
|
206
|
+
sock.controller = Actor.current
|
207
|
+
|
208
|
+
Once you've done this, you can turn on active mode to begin receiving messages:
|
209
|
+
|
210
|
+
sock.active = true
|
211
|
+
Actor.receive do |filter|
|
212
|
+
filter.when(Case[:tcp, sock, Object]) do |_, _, data|
|
213
|
+
...
|
214
|
+
end
|
215
|
+
|
216
|
+
filter.when(Case[:somethingelse, Object]) do |_, message|
|
217
|
+
...
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
(note: _ is an idiom which means ignore/discard a variable)
|
222
|
+
|
223
|
+
With active mode, the controller will receive all data as quickly as it can be
|
224
|
+
read off the socket. If the Actor processing incoming message can't process
|
225
|
+
them as quickly as they're being read, then they'll begin piling up in the
|
226
|
+
mailbox until the controller is able to catch up (if ever).
|
227
|
+
|
228
|
+
In order to prevent this from happening, sockets can be set active once:
|
229
|
+
|
230
|
+
sock.active = :once
|
231
|
+
|
232
|
+
This means read the next incoming message, then fall back to active = false.
|
233
|
+
The underlying system will stop monitoring the socket for incoming data,
|
234
|
+
and you're free to spend as much time as you'd like handling it. Once
|
235
|
+
you're ready for the next message, just set active to :once again.
|
236
|
+
|
237
|
+
== Filters
|
238
|
+
|
239
|
+
Not to be confused with Mailbox filters, Revactor's TCP sockets can each have
|
240
|
+
a filter chain. Filters are specified when a connection is created:
|
241
|
+
|
242
|
+
sock = Revactor::TCP.connect('irc.efnet.org', 6667, :filter => :line)
|
243
|
+
|
244
|
+
Filters transform data as it's read or written off the wire. In this case
|
245
|
+
we're connecting to an IRC server, and the IRC protocol is framed using a
|
246
|
+
newline delimiter.
|
247
|
+
|
248
|
+
The line filter will scan incoming messages for a newline, and buffer until
|
249
|
+
it encounters one. When it finds one, it will reassemble the entire message
|
250
|
+
from the buffer and deliver it to you in one fell swoop.
|
251
|
+
|
252
|
+
With the line filter on, receiving messages off IRC is easy:
|
253
|
+
|
254
|
+
message = sock.read
|
255
|
+
|
256
|
+
This will provide you with the entire next message, with the newline delimiter
|
257
|
+
already removed.
|
258
|
+
|
259
|
+
If the filter name is a symbol, Revactor will look under its filters directory
|
260
|
+
for a class of the cooresponding name. Alternatively you can pass the name of
|
261
|
+
a class you created yourself which responds to the methods encode and decode:
|
262
|
+
|
263
|
+
sock = Revactor::TCP.connect(host, port, :filter => MyFilter)
|
264
|
+
|
265
|
+
Filter chains can be specified by passing an array:
|
266
|
+
|
267
|
+
sock = Revactor::TCP.connect(host, port, :filter => [MyFilter, :line])
|
268
|
+
|
269
|
+
You can pass arguments to your filter's initialize method by passing an array
|
270
|
+
with a class name as a member of a filter chain:
|
271
|
+
|
272
|
+
sock = Revactor::TCP.connect(host, port, :filter => [[Myfilter, 42], :line])
|
273
|
+
|
274
|
+
In addition to the line filter, Revactor bundles a :packet filter. This filter
|
275
|
+
constructs messages with a length prefix that specifies the size of the
|
276
|
+
remaining message. This is a simple and straightforward way to frame
|
277
|
+
discrete messages on top of a streaming protocol like TCP, and is used for,
|
278
|
+
among other things, DRb.
|
279
|
+
|
280
|
+
== Behaviors
|
281
|
+
|
282
|
+
Behaviors are kind of like design patterns for Actors. Often you just want
|
283
|
+
an Actor to hold some state and service calls which query or mutate that state.
|
284
|
+
This behavior is wrapped up as a Server.
|
285
|
+
|
286
|
+
To begin implementing a server, look at Revactor::Behavior::Server, which is
|
287
|
+
a module demonstrating the API. Servers are implemented using a set of
|
288
|
+
callbacks which are called in response to certain events.
|
289
|
+
|
290
|
+
Servers should implement a number of principles, including transactional
|
291
|
+
semantics, however due to the side effect potential of mutable state in
|
292
|
+
Ruby this isn't possible to achieve.
|
293
|
+
|
294
|
+
Future versions of Revactor may attempt to address this, and also change the
|
295
|
+
present implementation (which is effectively a carbon copy of Erlang's
|
296
|
+
gen_server) to be more Ruby-like.
|
297
|
+
|
298
|
+
== Mongrel
|
299
|
+
|
300
|
+
Revactor includes complete support for running Mongrel on top of Actors and
|
301
|
+
Revactor::TCP. The implementation monkeypatches two methods in the
|
302
|
+
Mongrel::HttpServer class. Initial benchmarks show better throughput
|
303
|
+
and concurrency than threaded Mongrel running on Ruby 1.8 or 1.9.
|
304
|
+
|
305
|
+
To use Mongrel on top of Revactor, just:
|
306
|
+
|
307
|
+
require 'revactor/mongrel'
|
308
|
+
|
309
|
+
Then call Mongrel within an Actor.start block. The semantics are the same.
|
310
|
+
The only difference is that Actors, not Threads, are being used for
|
311
|
+
concurrency.
|
312
|
+
|
313
|
+
== Roadmap
|
314
|
+
|
315
|
+
Revactor is still in its infancy. Erlang and its Open Telcom Platform are an
|
316
|
+
extremely feature rich platform, and many features can be borrowed and
|
317
|
+
incorporated into Revactor.
|
318
|
+
|
319
|
+
The first and foremost feature to be added is the concept of linked Actors.
|
320
|
+
This idea is somewhat difficult to explain for the uninitiated, but the general
|
321
|
+
concept is that interdependent Actors are linked in a graph. When one Actor
|
322
|
+
dies, it kills all linked Actors is well. However, special Actors trap the
|
323
|
+
exit messages from the Actors they're linked with. These Actors are generally
|
324
|
+
supervisors and restart any linked Actor graphs which crash.
|
325
|
+
|
326
|
+
Next on the agenda is implementing DRb. This should be possible simply by
|
327
|
+
monkeypatching the existing DRb implementation to run on top of Revactor::TCP.
|
328
|
+
Once DRb has been implemented it should be fairly trivial to implement
|
329
|
+
distributed Actor networks over TCP using DRb as the underlying message
|
330
|
+
passing protocol.
|
331
|
+
|
332
|
+
Long term items include implementation of more Filters, Behaviors, and protocol
|
333
|
+
modules. These include an HTTP client (subclassed from the client in Rev),
|
334
|
+
an HTTP server adapter (using the Mongrel parser), and the gen_fsm behavior from
|
335
|
+
Erlang. Additional areas of concern are addressing the problems of mutable
|
336
|
+
state in conjunction with Server and FSM behaviors. A possible solution is
|
337
|
+
to implement a tuple which stores immutable collections of primitive types
|
338
|
+
like numbers, strings, and symbols, but this approach is likely to be slow.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
load 'revactor.gemspec'
|
5
|
+
|
6
|
+
# Default Rake task
|
7
|
+
task :default => :rdoc
|
8
|
+
|
9
|
+
# RDoc
|
10
|
+
Rake::RDocTask.new(:rdoc) do |task|
|
11
|
+
task.rdoc_dir = 'doc'
|
12
|
+
task.title = 'Revactor'
|
13
|
+
task.options = %w(--title Revactor --main README --line-numbers)
|
14
|
+
task.rdoc_files.include('bin/**/*.rb')
|
15
|
+
task.rdoc_files.include('lib/**/*.rb')
|
16
|
+
task.rdoc_files.include('README')
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gem
|
20
|
+
Rake::GemPackageTask.new(GEMSPEC) do |pkg|
|
21
|
+
pkg.need_tar = true
|
22
|
+
end
|
23
|
+
|
24
|
+
# RSpec
|
25
|
+
begin
|
26
|
+
require 'spec/rake/spectask'
|
27
|
+
|
28
|
+
SPECS = FileList['spec/**/*_spec.rb']
|
29
|
+
|
30
|
+
Spec::Rake::SpecTask.new(:spec) do |task|
|
31
|
+
task.spec_files = SPECS
|
32
|
+
end
|
33
|
+
|
34
|
+
namespace :spec do
|
35
|
+
Spec::Rake::SpecTask.new(:print) do |task|
|
36
|
+
task.spec_files = SPECS
|
37
|
+
task.spec_opts="-f s".split
|
38
|
+
end
|
39
|
+
|
40
|
+
Spec::Rake::SpecTask.new(:rcov) do |task|
|
41
|
+
task.spec_files = SPECS
|
42
|
+
task.rcov = true
|
43
|
+
task.rcov_opts = ['--exclude', 'spec']
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
rescue LoadError
|
48
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# An example echo server, written using Revactor::TCP
|
2
|
+
# This implementation creates a new actor for each
|
3
|
+
# incoming connection.
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/../lib/revactor'
|
6
|
+
|
7
|
+
HOST = 'localhost'
|
8
|
+
PORT = 4321
|
9
|
+
|
10
|
+
# Before we can begin using actors we have to call Actor.start
|
11
|
+
# Future versions of Revactor will hopefully eliminate this
|
12
|
+
Actor.start do
|
13
|
+
# Create a new listener socket on the given host and port
|
14
|
+
listener = Revactor::TCP.listen(HOST, PORT)
|
15
|
+
puts "Listening on #{HOST}:#{PORT}"
|
16
|
+
|
17
|
+
# Begin receiving connections
|
18
|
+
loop do
|
19
|
+
# Accept an incoming connection and start a new Actor
|
20
|
+
# to handle it
|
21
|
+
Actor.spawn(listener.accept) do |sock|
|
22
|
+
puts "#{sock.remote_addr}:#{sock.remote_port} connected"
|
23
|
+
|
24
|
+
# Begin echoing received data
|
25
|
+
loop do
|
26
|
+
begin
|
27
|
+
# Write everything we read
|
28
|
+
sock.write sock.read
|
29
|
+
rescue EOFError
|
30
|
+
puts "#{sock.remote_addr}:#{sock.remote_port} disconnected"
|
31
|
+
|
32
|
+
# Break (and exit the current actor) if the connection
|
33
|
+
# is closed, just like with a normal Ruby socket
|
34
|
+
break
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/examples/google.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/revactor'
|
5
|
+
|
6
|
+
Actor.start do
|
7
|
+
term = ARGV[0] || 'foobar'
|
8
|
+
sock = Revactor::TCP.connect("www.google.com", 80)
|
9
|
+
|
10
|
+
sock.write [
|
11
|
+
"GET /search?q=#{CGI.escape(term)} HTTP/1.0",
|
12
|
+
"Host: www.google.com",
|
13
|
+
"\r\n"
|
14
|
+
].join("\r\n")
|
15
|
+
|
16
|
+
loop do
|
17
|
+
begin
|
18
|
+
STDOUT.write sock.read
|
19
|
+
STDOUT.flush
|
20
|
+
rescue EOFError
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/examples/mongrel.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/../lib/revactor/mongrel'
|
4
|
+
|
5
|
+
ADDR = '127.0.0.1'
|
6
|
+
PORT = 8080
|
7
|
+
|
8
|
+
Actor.start do
|
9
|
+
server = Mongrel::HttpServer.new(ADDR, PORT)
|
10
|
+
server.register '/', Mongrel::DirHandler.new(".")
|
11
|
+
server.run
|
12
|
+
|
13
|
+
puts "Running on #{ADDR}:#{PORT}"
|
14
|
+
end
|