Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Documentation revamp.

Signed-off-by: Aman Gupta <aman@tmm1.net>
  • Loading branch information...
commit 38705c4418b6af33455cdfd8b6184d9cccded0fc 1 parent d326bb3
@michaelklishin michaelklishin authored tmm1 committed
Showing with 2,548 additions and 1,298 deletions.
  1. +5 −0 .gitignore
  2. +5 −1 .yardopts
  3. 0  {docs → }/GNU
  4. +1 −0  Gemfile
  5. 0  docs/COPYING → LICENSE
  6. +0 −81 README
  7. +109 −0 README.md
  8. +8 −0 Rakefile
  9. +27 −0 docs/DocumentationGuidesIndex.md
  10. +521 −0 docs/GettingStarted.md
  11. 0  docs/{ → old}/ChangeLog
  12. 0  docs/{ → old}/DEFERRABLES
  13. 0  docs/{ → old}/EPOLL
  14. 0  docs/{ → old}/INSTALL
  15. 0  docs/{ → old}/KEYBOARD
  16. 0  docs/{ → old}/LEGAL
  17. 0  docs/{ → old}/LIGHTWEIGHT_CONCURRENCY
  18. 0  docs/{ → old}/PURE_RUBY
  19. 0  docs/{ → old}/RELEASE_NOTES
  20. 0  docs/{ → old}/SMTP
  21. 0  docs/{ → old}/SPAWNED_PROCESSES
  22. 0  docs/{ → old}/TODO
  23. +4 −2 eventmachine.gemspec
  24. +18 −0 examples/guides/getting_started/01_eventmachine_echo_server.rb
  25. +22 −0 examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb
  26. +149 −0 examples/guides/getting_started/03_simple_chat_server.rb
  27. +27 −0 examples/guides/getting_started/04_simple_chat_server_step_one.rb
  28. +43 −0 examples/guides/getting_started/05_simple_chat_server_step_two.rb
  29. +98 −0 examples/guides/getting_started/06_simple_chat_server_step_three.rb
  30. +121 −0 examples/guides/getting_started/07_simple_chat_server_step_four.rb
  31. +141 −0 examples/guides/getting_started/08_simple_chat_server_step_five.rb
  32. +3 −3 examples/{ → old}/ex_channel.rb
  33. 0  examples/{ → old}/ex_queue.rb
  34. 0  examples/{ → old}/ex_tick_loop_array.rb
  35. 0  examples/{ → old}/ex_tick_loop_counter.rb
  36. 0  examples/{ → old}/helper.rb
  37. +35 −63 lib/em/buftok.rb
  38. +43 −11 lib/em/callback.rb
  39. +21 −14 lib/em/channel.rb
  40. +321 −205 lib/em/connection.rb
  41. +37 −18 lib/em/file_watch.rb
  42. +16 −11 lib/em/protocols/httpclient.rb
  43. +13 −7 lib/em/protocols/smtpclient.rb
  44. +197 −206 lib/em/pure_ruby.rb
  45. +16 −14 lib/em/queue.rb
  46. +31 −43 lib/em/streamer.rb
  47. +1 −0  lib/em/timers.rb
  48. +493 −587 lib/eventmachine.rb
  49. +10 −0 lib/jeventmachine.rb
  50. +0 −30 tasks/doc.rake
  51. +12 −2 tasks/package.rake
View
5 .gitignore
@@ -14,3 +14,8 @@ Makefile
*.dSYM
java/src/.project
*.rbc
+Gemfile.lock
+
+.yardoc/*
+doc/*
+
View
6 .yardopts
@@ -1,3 +1,7 @@
+--no-private
+--protected
+--markup="markdown" lib/**/*.rb
+--main README.md
--exclude jeventmachine --exclude pure_ruby
-
-docs/DEFERRABLES docs/EPOLL docs/KEYBOARD
+docs/*.md
View
0  docs/GNU → GNU
File renamed without changes
View
1  Gemfile
@@ -1,2 +1,3 @@
source :rubygems
gemspec
+
View
0  docs/COPYING → LICENSE
File renamed without changes
View
81 README
@@ -1,81 +0,0 @@
-= RUBY/EventMachine
-
-Homepage:: http://rubyeventmachine.com
-Rubyforge Page:: http://rubyforge.org/projects/eventmachine
-Google Group:: http://groups.google.com/group/eventmachine
-RDoc:: http://eventmachine.rubyforge.org
-IRC:: #eventmachine on irc.freenode.net
-Copyright:: (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
-Email:: gmail address: garbagecat10
-
-EventMachine is copyrighted free software made available under the terms
-of either the GPL or Ruby's License. See the file COPYING for full licensing
-information.
-See EventMachine and EventMachine::Connection for documentation and
-usage examples.
-
-EventMachine implements a fast, single-threaded engine for arbitrary network
-communications. It's extremely easy to use in Ruby. EventMachine wraps all
-interactions with IP sockets, allowing programs to concentrate on the
-implementation of network protocols. It can be used to create both network
-servers and clients. To create a server or client, a Ruby program only needs
-to specify the IP address and port, and provide a Module that implements the
-communications protocol. Implementations of several standard network protocols
-are provided with the package, primarily to serve as examples. The real goal
-of EventMachine is to enable programs to easily interface with other programs
-using TCP/IP, especially if custom protocols are required.
-
-A Ruby program uses EventMachine by registering the addresses and ports of
-network servers and clients, and then entering an event-handling loop.
-EventMachine contains glue code in Ruby which will execute callbacks to
-user-supplied code for all significant events occurring in the clients
-and servers. These events include connection acceptance, startup, data-receipt,
-shutdown, and timer events. Arbitrary processing can be performed by user code
-during event callbacks, including sending data to one or more remote network
-peers, startup and shutdown of network connections, and installation of new
-event handlers.
-
-The EventMachine implements a very familiar model for network programming.
-It emphasizes: 1) the maximum possible isolation of user code from network
-objects like sockets; 2) maximum performance and scalability; and 3) extreme
-ease-of-use for user code. It attempts to provide a higher-level interface
-than similar projects which expose a variety of low-level event-handling
-and networking objects to Ruby programs.
-
-The design and implementation of EventMachine grows out of nearly ten years
-of experience writing high-performance, high-scaling network server applications.
-We have taken particular account of the challenges and lessons described as
-the "C10K problem" by Dan Kegel and others.
-
-EventMachine consists of an extension library written in C++ (which can be
-accessed from languages other than Ruby), and a Ruby module which can be dropped
-into user programs. On most platforms, EventMachine uses the
-<tt>select(2)</tt> system call,
-so it will run on a large range of Unix-like systems and on Microsoft
-Windows with good performance and scalability. On Linux 2.6 kernels, EventMachine
-automatically configures itself to use <tt>epoll(4)</tt> instead of
-<tt>select(2),</tt> so scalability on that platform can be significantly
-improved.
-
-Here's a fully-functional echo server written with EventMachine:
-
- require 'eventmachine'
-
- module EchoServer
- def post_init
- puts "-- someone connected to the echo server!"
- end
-
- def receive_data data
- send_data ">>>you sent: #{data}"
- close_connection if data =~ /quit/i
- end
-
- def unbind
- puts "-- someone disconnected from the echo server!"
- end
- end
-
- EventMachine::run {
- EventMachine::start_server "127.0.0.1", 8081, EchoServer
- }
View
109 README.md
@@ -0,0 +1,109 @@
+# About EventMachine #
+
+
+## What is EventMachine ##
+
+EventMachine is an event-driven I/O and lightweight concurrency library for Ruby.
+It provides event-driven I/O using the [Reactor pattern](http://en.wikipedia.org/wiki/Reactor_pattern),
+much like [JBoss Netty](http://www.jboss.org/netty), [Apache MINA](http://mina.apache.org/),
+Python's [Twisted](http://twistedmatrix.com), [Node.js](http://nodejs.org), libevent and libev.
+
+EventMachine is designed to simultaneously meet two key needs:
+
+ * Extremely high scalability, performance and stability for the most demanding production environments.
+ * An API that eliminates the complexities of high-performance threaded network programming,
+ allowing engineers to concentrate on their application logic.
+
+This unique combination makes EventMachine a premier choice for designers of critical networked
+applications, including Web servers and proxies, email and IM production systems, authentication/authorization
+processors, and many more.
+
+EventMachine has been around since yearly 2000s and is a mature and battle tested library.
+
+
+## What EventMachine is good for? ##
+
+ * Scalable event-driven servers. Examples: [Thin](http://code.macournoyer.com/thin/) or [Goliath](https://github.com/postrank-labs/goliath/).
+ * Scalable asynchronous clients for various protocols, RESTful APIs and so on. Examples: [em-http-request](https://github.com/igrigorik/em-http-request) or [amqp gem](https://github.com/ruby-amqp/amqp).
+ * Efficient network proxies with custom logic. Examples: [Proxymachine](https://github.com/mojombo/proxymachine/).
+ * File and network monitoring tools. Examples: [eventmachine-tail](https://github.com/jordansissel/eventmachine-tail) and [logstash](https://github.com/logstash/logstash).
+
+
+
+## What platforms are supported by EventMachine? ##
+
+EventMachine supports Ruby 1.8.7, 1.9.2, REE, JRuby and **works well on Windows** as well
+as many operating systems from the Unix family (Linux, Mac OS X, BSD flavors).
+
+
+
+## Install the gem ##
+
+Install it with [RubyGems](https://rubygems.org/)
+
+ gem install eventmachine
+
+or add this to your Gemfile if you use [Bundler](http://gembundler.com/):
+
+ gem "eventmachine"
+
+
+
+## Getting started ##
+
+For an introduction to EventMachine, check out:
+
+ * [blog post about EventMachine by Ilya Grigorik](http://www.igvita.com/2008/05/27/ruby-eventmachine-the-speed-demon/).
+ * [EventMachine Introductions by Dan Sinclair](http://everburning.com/news/eventmachine-introductions/).
+
+
+### Server example: Echo server ###
+
+Here's a fully-functional echo server written with EventMachine:
+
+ require 'eventmachine'
+
+ module EchoServer
+ def post_init
+ puts "-- someone connected to the echo server!"
+ end
+
+ def receive_data data
+ send_data ">>>you sent: #{data}"
+ close_connection if data =~ /quit/i
+ end
+
+ def unbind
+ puts "-- someone disconnected from the echo server!"
+ end
+ end
+
+ # Note that this will block current thread.
+ EventMachine.run {
+ EventMachine.start_server "127.0.0.1", 8081, EchoServer
+ }
+
+
+## EventMachine documentation ##
+
+Currently we only have [reference documentation](http://eventmachine.rubyforge.org) and a [wiki](https://github.com/eventmachine/eventmachine/wiki).
+
+
+## Community and where to get help ##
+
+ * Join the [mailing list](http://groups.google.com/group/eventmachine) (Google Group)
+ * Join IRC channel #eventmachine on irc.freenode.net
+
+
+## License and copyright ##
+
+EventMachine is copyrighted free software made available under the terms
+of either the GPL or Ruby's License.
+
+Copyright: (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
+
+
+## Alternatives ##
+
+If you are unhappy with EventMachine and want to use Ruby, check out [Cool.io](http://coolio.github.com/).
+One caveat: by May 2011, it did not support JRuby and Windows.
View
8 Rakefile
@@ -4,8 +4,16 @@ import *Dir['tasks/*.rake']
GEMSPEC = eval(File.read(File.expand_path('../eventmachine.gemspec', __FILE__)))
+require 'yard'
require 'rake/clean'
task :clobber => :clean
desc "Build eventmachine, then run tests."
task :default => [:compile, :test]
+
+desc 'Generate documentation'
+YARD::Rake::YardocTask.new do |t|
+ t.files = ['lib/**/*.rb', '-', 'docs/*.md']
+ t.options = ['--main', 'README.md', '--no-private']
+ t.options = ['--exclude', 'lib/jeventmachine', '--exclude', 'lib/pr_eventmachine']
+end
View
27 docs/DocumentationGuidesIndex.md
@@ -0,0 +1,27 @@
+# EventMachine documentation guides #
+
+Welcome to the documentation guides for [EventMachine](http://github.com/eventmachine/eventmachine),
+a fast and simple event-processing library for Ruby programs (à la JBoss Netty, Twisted, Node.js
+and so on).
+
+## Guide list ##
+
+ * {file:docs/GettingStarted.md Getting started with EventMachine}
+ * {file:docs/EventDrivenServers.md Writing event-driven servers}
+ * {file:docs/EventDrivenClients.md Writing event-driven clients}
+ * {file:docs/ConnectionFailureAndRecovery.md Connection Failure and Recovery}
+ * {file:docs/TLS.md TLS (aka SSL)}
+ * {file:docs/Ecosystem.md EventMachine ecosystem}: Thin, Goliath, em-http-request, em-websockets, Proxymachine and beyond
+ * {file:docs/BlockingEventLoop.md On blocking the event loop: why it is harmful for performance and how to avoid it}
+ * {file:docs/LightweightConcurrency.md Lightweight concurrency with EventMachine}
+ * {file:docs/Deferrables.md Deferrables}
+ * {file:docs/ModernKernelInputOutputAPIs.md Brief introduction to epoll, kqueue, select}
+ * {file:docs/WorkingWithOtherIOSources.md Working with other IO sources such as the keyboard}
+
+
+## Tell us what you think! ##
+
+Please take a moment and tell us what you think about this guide on the [EventMachine mailing list](http://bit.ly/jW3cR3)
+or in the #eventmachine channel on irc.freenode.net: what was unclear? What wasn't covered?
+Maybe you don't like the guide style or the grammar and spelling are incorrect? Reader feedback is
+key to making documentation better.
View
521 docs/GettingStarted.md
@@ -0,0 +1,521 @@
+# @title Getting Started with Ruby EventMachine
+# @markup markdown
+# @author Michael S. Klishin, Dan Sinclair
+
+# Getting started with Ruby EventMachine #
+
+
+## About this guide ##
+
+This guide is a quick tutorial that helps you to get started with EventMachine for writing event-driven
+servers, clients and using it as a lightweight concurrency library.
+It should take about 20 minutes to read and study the provided code examples. This guide covers
+
+ * Installing EventMachine via [Rubygems](http://rubygems.org) and [Bundler](http://gembundler.com).
+ * Building an Echo server, the "Hello, world"-like code example of network servers.
+ * Building a simple chat, both server and client.
+ * Building a very small asynchronous Websockets client.
+
+
+## Covered versions ##
+
+This guide covers EventMachine v0.12.10 and 1.0 (including betas).
+
+
+## Level ##
+
+This guide assumes you are comfortable (but not necessary a guru) with the command line. On Microsoft Windows™,
+we recommend you to use [JRuby](http://jruby.org) when running these examples.
+
+
+## Installing EventMachine ##
+
+### Make sure you have Ruby installed ###
+
+This guide assumes you have one of the supported Ruby implementations installed:
+
+ * Ruby 1.8.7
+ * Ruby 1.9.2
+ * [JRuby](http://jruby.org) (we recommend 1.6)
+ * [Rubinius](http://rubini.us) 1.2 or higher
+ * [Ruby Enterprise Edition](http://www.rubyenterpriseedition.com)
+
+EventMachine works on Microsoft Windows™.
+
+
+### With Rubygems ###
+
+To install the EventMachine gem do
+
+ gem install eventmachine
+
+
+### With Bundler ###
+
+ gem "eventmachine"
+
+
+### Verifying your installation ###
+
+Lets verify your installation with this quick IRB session:
+
+ irb -rubygems
+
+ ruby-1.9.2-p180 :001 > require "eventmachine"
+ => true
+ ruby-1.9.2-p180 :002 > EventMachine::VERSION
+ => "1.0.0.beta.3"
+
+
+## An Echo Server Example ##
+
+Lets begin with the classic "Hello, world"-like example, an echo server. The echo server responds clients with the
+same data that was provided. First, here's the code:
+
+{include:file:examples/guides/getting\_started/01\_eventmachine\_echo_server.rb}
+
+
+When run, the server binds to port 10000. We can connect using Telnet and verify it's working:
+
+ telnet localhost 10000
+
+On my machine the output looks like:
+
+ ~ telnet localhost 10000
+ Trying 127.0.0.1...
+ Connected to localhost.
+ Escape character is '^]'.
+
+Let's send something to our server. Type in "Hello, EventMachine" and hit Enter. The server will respond with
+the same string:
+
+ ~ telnet localhost 10000
+ Trying 127.0.0.1...
+ Connected to localhost.
+ Escape character is '^]'.
+ Hello, EventMachine
+ # (here we hit Enter)
+ Hello, EventMachine
+ # (this ^^^ is our echo server reply)
+
+It works! Congratulations, you now can tell your Node.js-loving friends that you "have done some event-driven programming, too".
+Oh, and to stop Telnet, hit Control + Shift + ] and then Control + C.
+
+Lets walk this example line by line and see what's going on. These lines
+
+ require 'rubygems' # or use Bundler.setup
+ require 'eventmachine'
+
+probably look familiar: you use [RubyGems](http://rubygems.org) (or [Bundler](http://gembundler.com/)) for dependencies and then require EventMachine gem. Boring.
+
+Next:
+
+ class EchoServer < EventMachine::Connection
+ def receive_data(data)
+ send_data(data)
+ end
+ end
+
+Is the implementation of our echo server. We define a class that inherits from {EventMachine::Connection}
+and a handler (aka callback) for one event: when we receive data from a client.
+
+EventMachine handles the connection setup, receiving data and passing it to our handler, {EventMachine::Connection#receive_data}.
+
+Then we implement our protocol logic, which in the case of Echo is pretty trivial: we send back whatever we receive.
+To do so, we're using {EventMachine::Connection#send_data}.
+
+Lets modify the example to recognize `exit` command:
+
+{include:file:examples/guides/getting\_started/02\_eventmachine\_echo_server\_that\_recognizes\_exit\_command.rb}
+
+Our `receive\_data` changed slightly and now looks like this:
+
+ def receive_data(data)
+ if data.strip =~ /exit$/i
+ EventMachine.stop_event_loop
+ else
+ send_data(data)
+ end
+ end
+
+Because incoming data has trailing newline character, we strip it off before matching it against a simple regular
+expression. If the data ends in `exit`, we stop EventMachine event loop with {EventMachine.stop_event_loop}. This unblocks
+main thread and it finishes execution, and our little program exits as the result.
+
+To summarize this first example:
+
+ * Subclass {EventMachine::Connection} and override {EventMachine::Connection#send_data} to handle incoming data.
+ * Use {EventMachine.run} to start EventMachine event loop and then bind echo server with {EventMachine.start_server}.
+ * To stop the event loop, use {EventMachine.stop_event_loop} (aliased as {EventMachine.stop})
+
+Lets move on to a slightly more sophisticated example that will introduce several more features and methods
+EventMachine has to offer.
+
+
+## A Simple Chat Server Example ##
+
+Next we will write a simple chat. Initially clients will still use telnet to connect, but then we will add little
+client application that will serve as a proxy between telnet and the chat server. This example is certainly longer
+(~ 150 lines with whitespace and comments) so instead of looking at the final version and going through it line by line,
+we will instead begin with a very simple version that only keeps track of connected clients and then add features
+as we go.
+
+To set some expectations about our example:
+
+ * It will keep track of connected clients
+ * It will support a couple of commands, à la IRC
+ * It will support direct messages using Twitter-like @usernames
+ * It won't use MongoDB, fibers or distributed map/reduce for anything but will be totally [Web Scale™](http://bit.ly/webscaletm) nonetheless. Maybe even [ROFLscale](http://bit.ly/roflscalevideo).
+
+### Step one: detecting connections and disconnectons ###
+
+First step looks like this:
+
+{include:file:examples/guides/getting\_started/04\_simple\_chat\_server\_step\_one.rb}
+
+We see familiar {EventMachine.run} and {EventMachine.start_server}, but also {EventMachine::Connection#post_init} and {EventMachine::Connection#unbind} we haven't
+met yet. We don't use them in this code, so when are they run? Like {EventMachine::Connection#receive_data}, these methods are callbacks. EventMachine calls them
+when certain events happen:
+
+ * {EventMachine#post_init} is called by the event loop immediately after the network connection has been established.
+ In the chat server example case, this is when a new client connects.
+ * {EventMachine#unbind} is called when client disconnects, connection is closed or is lost (because of a network issue, for example).
+
+All our chat server does so far is logging connections or disconnections. What we want it to do next is to keep track of connected clients.
+
+
+### Step two: keep track of connected clients ###
+
+Next iteration of the code looks like this:
+
+{include:file:examples/guides/getting\_started/05\_simple\_chat\_server\_step\_two.rb}
+
+While the code we added is very straightforward, we have to clarify one this first: subclasses of {EventMachine::Connection} are instantiated by
+EventMachine for every new connected peer. So for 10 connected chat clients, there will be 10 separate `SimpleChatServer` instances in our
+server process. Like any other objects, they can be stored in a collection, can provide public API other objects use, can instantiate or inject
+dependencies and in general live a happy life all Ruby objects live until garbage collection happens.
+
+In the example above we use a @@class_variable to keep track of connected clients. In Ruby, @@class variables are accessible from instance
+methods so we can add new connections to the list from `SimpleChatServer#post_init` and remove them in `SimpleChatServer#unbind`. We can also
+filter connections by some criteria, as `SimpleChatServer#other_peers demonstrates`.
+
+So, we keep track of connections but how do we identify them? For a chat app, it's pretty common to use usernames for that. Lets ask our clients
+to enter usernames when they connect.
+
+
+### Step three: adding usernames ##
+
+To add usernames, we need to add a few things:
+
+ * We need to invite newly connected clients to enter their username.
+ * A reader (getter) method on our {EventMachine::Connection} subclass.
+ * An idea of connection state (keeping track of whether a particular participant had entered username before).
+
+Here is one way to do it:
+
+{include:file:examples/guides/getting\_started/06\_simple\_chat\_server\_step\_three.rb}
+
+This is quite an update so lets take a look at each method individually. First, `SimpleChatServer#post_init`:
+
+ def post_init
+ @username = nil
+ puts "A client has connected..."
+ ask_username
+ end
+
+To keep track of username we ask chat participants for, we add @username instance variable to our connection class. Connection
+instances are just Ruby objects associated with a particular connected peer, so using @ivars is very natural. To make username
+value accessible to other objects, we added a reader method that was not shown on the snippet above.
+
+Lets dig into `SimpleChatServer#ask_username`:
+
+ def ask_username
+ self.send_line("[info] Enter your username:")
+ end # ask_username
+
+ # ...
+
+ def send_line(line)
+ self.send_data("#{line}\n")
+ end # send_line(line)
+
+Nothing new here, we are using {EventMachine::Connection#send_data} which we have seen before.
+
+
+In `SimpleChatServer#receive_data` we now have to check if the username was entered or we need
+to ask for it:
+
+ def receive_data(data)
+ if entered_username?
+ handle_chat_message(data.strip)
+ else
+ handle_username(data.strip)
+ end
+ end
+
+ # ...
+
+ def entered_username?
+ !@username.nil? && !@username.empty?
+ end # entered_username?
+
+Finally, handler of chat messages is not yet implemented:
+
+ def handle_chat_message(msg)
+ raise NotImplementedError
+ end
+
+Lets try this example out using Telnet:
+
+ ~ telnet localhost 10000
+ Trying 127.0.0.1...
+ Connected to localhost.
+ Escape character is '^]'.
+ [info] Enter your username:
+ antares_
+ [info] Ohai, antares_
+
+and the server output:
+
+ A client has connected...
+ antares_ has joined
+
+This version requires you to remember how to terminate your Telnet session (Ctrl + Shift + ], then Ctrl + C).
+It is annoying, so why don't we add the same `exit` command to our chat server?
+
+
+### Step four: adding exit command and delivering chat messages ####
+
+{include:file:examples/guides/getting\_started/07\_simple\_chat\_server\_step\_four.rb}
+
+TBD
+
+Lets test-drive this version. Client A:
+
+ ~ telnet localhost 10000
+ Trying 127.0.0.1...
+ Connected to localhost.
+ Escape character is '^]'.
+ [info] Enter your username:
+ michael
+ [info] Ohai, michael
+ Hi everyone
+ michael: Hi everyone
+ joe has joined the room
+ # here ^^^ client B connects, lets greet him
+ hi joe
+ michael: hi joe
+ joe: hey michael
+ # ^^^ client B replies
+ exit
+ # ^^^ out command in action
+ Connection closed by foreign host.
+
+Client B:
+
+ ~ telnet localhost 10000
+ Trying 127.0.0.1...
+ Connected to localhost.
+ Escape character is '^]'.
+ [info] Enter your username:
+ joe
+ [info] Ohai, joe
+ michael: hi joe
+ # ^^^ client A greets us, lets reply
+ hey michael
+ joe: hey michael
+ exit
+ # ^^^ out command in action
+ Connection closed by foreign host.
+
+And finally, the server output:
+
+ A client has connected...
+ michael has joined
+ A client has connected...
+ _antares has joined
+ [info] _antares has left
+ [info] michael has left
+
+Our little char server now supports usernames, sending messages and the `exit` command. Next up, private (aka direct) messages.
+
+
+### Step five: adding direct messages and one more command ###
+
+To add direct messages, we come up with a simple convention: private messages begin with @username and may have optional colon before
+message text, like this:
+
+ @joe: hey, how do you like eventmachine?
+
+This convention makes parsing of messages simple so that we can concentrate on delivering them to a particular client connection.
+Remember when we added `username` reader on our connection class? That tiny change makes this step possible: when a new direct
+message comes in, we extract username and message text and then find then connection for @username in question:
+
+ #
+ # Message handling
+ #
+
+ def handle_chat_message(msg)
+ if command?(msg)
+ self.handle_command(msg)
+ else
+ if direct_message?(msg)
+ self.handle_direct_message(msg)
+ else
+ self.announce(msg, "#{@username}:")
+ end
+ end
+ end # handle_chat_message(msg)
+
+ def direct_message?(input)
+ input =~ DM_REGEXP
+ end # direct_message?(input)
+
+ def handle_direct_message(input)
+ username, message = parse_direct_message(input)
+
+ if connection = @@connected_clients.find { |c| c.username == username }
+ puts "[dm] @#{@username} => @#{username}"
+ connection.send_line("[dm] @#{@username}: #{message}")
+ else
+ send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}"
+ end
+ end # handle_direct_message(input)
+
+ def parse_direct_message(input)
+ return [$1, $2] if input =~ DM_REGEXP
+ end # parse_direct_message(input)
+
+This snippet demonstrates how one connection instance can obtain another connection instance and send data to it.
+This is a very powerful feature, consider just a few use cases:
+
+ * Peer-to-peer protocols
+ * Content-aware routing
+ * Efficient streaming with optional filtering
+
+Less common use cases include extending C++ core of EventMachine to provide access to hardware that streams events that
+can be re-broadcasted to any interested parties connected via TCP, UDP or something like AMQP or WebSockets. With this,
+sky is the limit. Actually, EventMachine has several features for efficient proxying data between connections.
+We will not cover them in this guide.
+
+One last feature that we are going to add to our chat server is the `status` command that tells you current server time and how many people
+are there in the chat room:
+
+ #
+ # Commands handling
+ #
+
+ def command?(input)
+ input =~ /(exit|status)$/i
+ end # command?(input)
+
+ def handle_command(cmd)
+ case cmd
+ when /exit$/i then self.close_connection
+ when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room")
+ end
+ end # handle_command(cmd)
+
+Hopefully this piece of code is easy to follow. Try adding a few more commands, for example, the `whoishere` command that lists people
+currently in the chat room.
+
+In the end, our chat server looks like this:
+
+{include:file:examples/guides/getting\_started/08\_simple\_chat\_server\_step\_five.rb}
+
+We are almost done with the server but there are some closing thoughts.
+
+
+### Step six: final version ###
+
+Just in case, here is the final version of the chat server code we have built:
+
+{include:file:examples/guides/getting\_started/03\_simple\_chat\_server.rb}
+
+
+### Step seven: future directions and some closing thoughts ###
+
+The chat server is just about 150 lines of Ruby including empty lines and comments, yet it has a few features most of chat server
+examples never add. We did not, however, implement many other features that popular IRC clients like [Colloquy](http://colloquy.info) have:
+
+ * Chat moderation
+ * Multiple rooms
+ * Connection timeout detection
+
+How would one go about implementing them? We thought it is worth discussing what else EventMachine has to offer and what ecosystem projects
+one can use to build a really feature-rich Web-based IRC chat client.
+
+With multiple rooms it's more or less straightforward, just add one more hash and a bunch of commands and use the information about which rooms participant
+is in when you are delivering messages. There is nothing in EventMachine itself that can make the job much easier for developer.
+
+To implement chat moderation feature you may want to do a few things:
+
+ * Work with client IP addresses. Maybe we want to consider everyone who connects from certain IPs a moderator.
+ * Access persistent data about usernames of moderators and their credentials.
+
+Does EventMachine have anything to offer here? It does. To obtain peer IP address, take a look at {EventMachine::Connection#get_peername}. The name of this method is
+a little bit misleading and originates from low-level socket programming APIs.
+
+#### A whirlwind tour of the EventMachine ecosystem ####
+
+To work with data stores you can use several database drivers that ship with EventMachine itself, however, quite often there are some 3rd party projects in
+the EventMachine ecosystem that have more features, are faster or just better maintained. So we figured it will be helpful to provide a few pointers
+to some of those projects:
+
+ * For MySQL, check out [em-mysql](https://github.com/eventmachine/em-mysql) project.
+ * For PostgreSQL, have a look at Mike Perham's [EventMachine-based PostgreSQL driver](https://github.com/mperham/em_postgresql).
+ * For Redis, there is a young but already popular [em-hiredis](https://github.com/mloughran/em-hiredis) library that combines EventMachine's non-blocking I/O with
+ extreme performance of the official Redis C client, [hiredis](https://github.com/antirez/hiredis).
+ * For MongoDB, see [em-mongo](https://github.com/bcg/em-mongo)
+ * For Cassandra, Mike Perham [added transport agnosticism feature](http://www.mikeperham.com/2010/02/09/cassandra-and-eventmachine/) to the [cassandra gem](https://rubygems.org/gems/cassandra).
+
+[Riak](http://www.basho.com/products_riak_overview.php) and CouchDB talk HTTP so it's possible to use [em-http-request](https://github.com/igrigorik/em-http-request).
+If you are aware of EventMachine-based non-blocking drivers for these databases, as well as for HBase, let us know on the [EventMachine mailing list](http://groups.google.com/group/eventmachine).
+Also, EventMachine supports TLS (aka SSL) and works well on [JRuby](http://jruby.org) and Windows.
+
+Learn more in our {file:docs/Ecosystem.md EventMachine ecosystem} and {file:docs/TLS.md TLS (aka SSL)} guides.
+
+
+#### Connection loss detection ####
+
+Finally, connection loss detection. When our chat participant closes her laptop lid, how do we know that she is no longer active? The answer is, when EventMachine
+detects TCP connectin closure, it calls {EventMachine::Connection#unbind}. Version 1.0.beta3 and later also pass an optional argument to that method. The argument
+indicates what error (if any) caused the connection to be closed.
+
+Learn more in our {file:docs/ConnectionFailureAndRecovery.md Connection Failure and Recovery} guide.
+
+
+#### What the Chat Server Example doesn't demonstrate ####
+
+This chat server also leaves out something production quality clients and servers must take care of: buffering. We intentionally did not include any buffering in
+our chat server example: it would only distract you from learning what you really came here to learn: how to use EventMachine to build blazing fast asynchronous
+networking programs quickly. However, {EventMachine::Connection#receive_data} does not offer any guarantees that you will be receiving "whole messages" all the time,
+largely because the underlying transport (UDP or TCP) does not offer such guarantees. Many protocols, for example, AMQP, mandate that large content chunks are
+split into smaller _frames_ of certain size. This means that [amq-client](https://github.com/ruby-amqp/amq-client) library, for instance, that has EventMachine-based driver,
+has to deal with figuring out when exactly we received "the whole message". To do so, it uses buffering and employs various checks to detect _frame boundaries_.
+So **don't be deceived by the simplicity of this chat example**: it intentionally leaves framing out, but real world protocols usually require it.
+
+
+
+## A (Proxying) Chat Client Example ##
+
+TBD
+
+
+## Wrapping up ##
+
+This tutorial ends here. Congratulations! You have learned quite a bit about EventMachine.
+
+
+## What to read next ##
+
+The documentation is organized as a {file:docs/DocumentationGuidesIndex.md number of guides}, covering all kinds of
+topics. TBD
+
+
+## Tell us what you think! ##
+
+Please take a moment and tell us what you think about this guide on the [EventMachine mailing list](http://bit.ly/jW3cR3)
+or in the #eventmachine channel on irc.freenode.net: what was unclear? What wasn't covered?
+Maybe you don't like the guide style or the grammar and spelling are incorrect? Reader feedback is
+key to making documentation better.
View
0  docs/ChangeLog → docs/old/ChangeLog
File renamed without changes
View
0  docs/DEFERRABLES → docs/old/DEFERRABLES
File renamed without changes
View
0  docs/EPOLL → docs/old/EPOLL
File renamed without changes
View
0  docs/INSTALL → docs/old/INSTALL
File renamed without changes
View
0  docs/KEYBOARD → docs/old/KEYBOARD
File renamed without changes
View
0  docs/LEGAL → docs/old/LEGAL
File renamed without changes
View
0  docs/LIGHTWEIGHT_CONCURRENCY → docs/old/LIGHTWEIGHT_CONCURRENCY
File renamed without changes
View
0  docs/PURE_RUBY → docs/old/PURE_RUBY
File renamed without changes
View
0  docs/RELEASE_NOTES → docs/old/RELEASE_NOTES
File renamed without changes
View
0  docs/SMTP → docs/old/SMTP
File renamed without changes
View
0  docs/SPAWNED_PROCESSES → docs/old/SPAWNED_PROCESSES
File renamed without changes
View
0  docs/TODO → docs/old/TODO
File renamed without changes
View
6 eventmachine.gemspec
@@ -14,6 +14,8 @@ Gem::Specification.new do |s|
s.extensions = ["ext/extconf.rb", "ext/fastfilereader/extconf.rb"]
s.add_development_dependency 'rake-compiler', '0.7.6'
+ s.add_development_dependency 'yard', ">= 0.7.2"
+ s.add_development_dependency 'bluecloth'
s.summary = 'Ruby/EventMachine library'
s.description = "EventMachine implements a fast, single-threaded engine for arbitrary network
@@ -27,6 +29,6 @@ are provided with the package, primarily to serve as examples. The real goal
of EventMachine is to enable programs to easily interface with other programs
using TCP/IP, especially if custom protocols are required."
- s.rdoc_options = ["--title", "EventMachine", "--main", "README", "-x", "lib/em/version", "-x", "lib/jeventmachine"]
- s.extra_rdoc_files = ["README"] + `git ls-files -- docs/*`.split("\n")
+ s.rdoc_options = ["--title", "EventMachine", "--main", "README.md", "-x", "lib/em/version", "-x", "lib/jeventmachine"]
+ s.extra_rdoc_files = ["README.md"] + `git ls-files -- docs/*`.split("\n")
end
View
18 examples/guides/getting_started/01_eventmachine_echo_server.rb
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class EchoServer < EM::Connection
+ def receive_data(data)
+ send_data(data)
+ end
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, EchoServer)
+end
View
22 examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb
@@ -0,0 +1,22 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class EchoServer < EM::Connection
+ def receive_data(data)
+ if data.strip =~ /exit$/i
+ EventMachine.stop
+ else
+ send_data(data)
+ end
+ end
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, EchoServer)
+end
View
149 examples/guides/getting_started/03_simple_chat_server.rb
@@ -0,0 +1,149 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class SimpleChatServer < EM::Connection
+
+ @@connected_clients = Array.new
+ DM_REGEXP = /^@([a-zA-Z0-9]+)\s*:?\s*(.+)/.freeze
+
+ attr_reader :username
+
+
+ #
+ # EventMachine handlers
+ #
+
+ def post_init
+ @username = nil
+
+ puts "A client has connected..."
+ ask_username
+ end
+
+ def unbind
+ @@connected_clients.delete(self)
+ puts "[info] #{@username} has left" if entered_username?
+ end
+
+ def receive_data(data)
+ if entered_username?
+ handle_chat_message(data.strip)
+ else
+ handle_username(data.strip)
+ end
+ end
+
+
+ #
+ # Username handling
+ #
+
+ def entered_username?
+ !@username.nil? && !@username.empty?
+ end # entered_username?
+
+ def handle_username(input)
+ if input.empty?
+ send_line("Blank usernames are not allowed. Try again.")
+ ask_username
+ else
+ @username = input
+ @@connected_clients.push(self)
+ self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
+ puts "#{@username} has joined"
+
+ self.send_line("[info] Ohai, #{@username}")
+ end
+ end # handle_username(input)
+
+ def ask_username
+ self.send_line("[info] Enter your username:")
+ end # ask_username
+
+
+ #
+ # Message handling
+ #
+
+ def handle_chat_message(msg)
+ if command?(msg)
+ self.handle_command(msg)
+ else
+ if direct_message?(msg)
+ self.handle_direct_message(msg)
+ else
+ self.announce(msg, "#{@username}:")
+ end
+ end
+ end # handle_chat_message(msg)
+
+ def direct_message?(input)
+ input =~ DM_REGEXP
+ end # direct_message?(input)
+
+ def handle_direct_message(input)
+ username, message = parse_direct_message(input)
+
+ if connection = @@connected_clients.find { |c| c.username == username }
+ puts "[dm] @#{@username} => @#{username}"
+ connection.send_line("[dm] @#{@username}: #{message}")
+ else
+ send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}"
+ end
+ end # handle_direct_message(input)
+
+ def parse_direct_message(input)
+ return [$1, $2] if input =~ DM_REGEXP
+ end # parse_direct_message(input)
+
+
+ #
+ # Commands handling
+ #
+
+ def command?(input)
+ input =~ /(exit|status)$/i
+ end # command?(input)
+
+ def handle_command(cmd)
+ case cmd
+ when /exit$/i then self.close_connection
+ when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room")
+ end
+ end # handle_command(cmd)
+
+
+ #
+ # Helpers
+ #
+
+ def announce(msg = nil, prefix = "[chat server]")
+ @@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty?
+ end # announce(msg)
+
+ def number_of_connected_clients
+ @@connected_clients.size
+ end # number_of_connected_clients
+
+ def other_peers
+ @@connected_clients.reject { |c| self == c }
+ end # other_peers
+
+ def send_line(line)
+ self.send_data("#{line}\n")
+ end # send_line(line)
+
+ def usernames
+ @@connected_clients.map { |c| c.username }
+ end # usernames
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
+end
View
27 examples/guides/getting_started/04_simple_chat_server_step_one.rb
@@ -0,0 +1,27 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class SimpleChatServer < EM::Connection
+
+ #
+ # EventMachine handlers
+ #
+
+ def post_init
+ puts "A client has connected..."
+ end
+
+ def unbind
+ puts "A client has left..."
+ end
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
+end
View
43 examples/guides/getting_started/05_simple_chat_server_step_two.rb
@@ -0,0 +1,43 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class SimpleChatServer < EM::Connection
+
+ @@connected_clients = Array.new
+
+
+ #
+ # EventMachine handlers
+ #
+
+ def post_init
+ @@connected_clients.push(self)
+ puts "A client has connected..."
+ end
+
+ def unbind
+ @@connected_clients.delete(self)
+ puts "A client has left..."
+ end
+
+
+
+
+ #
+ # Helpers
+ #
+
+ def other_peers
+ @@connected_clients.reject { |c| self == c }
+ end # other_peers
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
+end
View
98 examples/guides/getting_started/06_simple_chat_server_step_three.rb
@@ -0,0 +1,98 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class SimpleChatServer < EM::Connection
+
+ @@connected_clients = Array.new
+
+
+ attr_reader :username
+
+
+ #
+ # EventMachine handlers
+ #
+
+ def post_init
+ @username = nil
+
+ puts "A client has connected..."
+ ask_username
+ end
+
+ def unbind
+ @@connected_clients.delete(self)
+ puts "A client has left..."
+ end
+
+ def receive_data(data)
+ if entered_username?
+ handle_chat_message(data.strip)
+ else
+ handle_username(data.strip)
+ end
+ end
+
+
+
+
+ #
+ # Username handling
+ #
+
+ def entered_username?
+ !@username.nil? && !@username.empty?
+ end # entered_username?
+
+ def handle_username(input)
+ if input.empty?
+ send_line("Blank usernames are not allowed. Try again.")
+ ask_username
+ else
+ @username = input
+ @@connected_clients.push(self)
+ self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
+ puts "#{@username} has joined"
+
+ self.send_line("[info] Ohai, #{@username}")
+ end
+ end # handle_username(input)
+
+ def ask_username
+ self.send_line("[info] Enter your username:")
+ end # ask_username
+
+
+
+ #
+ # Message handling
+ #
+
+ def handle_chat_message(msg)
+ raise NotImplementedError
+ end
+
+
+
+ #
+ # Helpers
+ #
+
+ def other_peers
+ @@connected_clients.reject { |c| self == c }
+ end # other_peers
+
+ def send_line(line)
+ self.send_data("#{line}\n")
+ end # send_line(line)
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
+end
View
121 examples/guides/getting_started/07_simple_chat_server_step_four.rb
@@ -0,0 +1,121 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class SimpleChatServer < EM::Connection
+
+ @@connected_clients = Array.new
+
+
+ attr_reader :username
+
+
+ #
+ # EventMachine handlers
+ #
+
+ def post_init
+ @username = nil
+
+ puts "A client has connected..."
+ ask_username
+ end
+
+ def unbind
+ @@connected_clients.delete(self)
+ puts "[info] #{@username} has left" if entered_username?
+ end
+
+ def receive_data(data)
+ if entered_username?
+ handle_chat_message(data.strip)
+ else
+ handle_username(data.strip)
+ end
+ end
+
+
+
+
+ #
+ # Username handling
+ #
+
+ def entered_username?
+ !@username.nil? && !@username.empty?
+ end # entered_username?
+
+ def handle_username(input)
+ if input.empty?
+ send_line("Blank usernames are not allowed. Try again.")
+ ask_username
+ else
+ @username = input
+ @@connected_clients.push(self)
+ self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
+ puts "#{@username} has joined"
+
+ self.send_line("[info] Ohai, #{@username}")
+ end
+ end # handle_username(input)
+
+ def ask_username
+ self.send_line("[info] Enter your username:")
+ end # ask_username
+
+
+
+ #
+ # Message handling
+ #
+
+ def handle_chat_message(msg)
+ if command?(msg)
+ self.handle_command(msg)
+ else
+ self.announce(msg, "#{@username}:")
+ end
+ end
+
+
+ #
+ # Commands handling
+ #
+
+ def command?(input)
+ input =~ /exit$/i
+ end # command?(input)
+
+ def handle_command(cmd)
+ case cmd
+ when /exit$/i then self.close_connection
+ end
+ end # handle_command(cmd)
+
+
+
+ #
+ # Helpers
+ #
+
+ def announce(msg = nil, prefix = "[chat server]")
+ @@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty?
+ end # announce(msg)
+
+ def other_peers
+ @@connected_clients.reject { |c| self == c }
+ end # other_peers
+
+ def send_line(line)
+ self.send_data("#{line}\n")
+ end # send_line(line)
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
+end
View
141 examples/guides/getting_started/08_simple_chat_server_step_five.rb
@@ -0,0 +1,141 @@
+#!/usr/bin/env ruby
+
+require 'rubygems' # or use Bundler.setup
+require 'eventmachine'
+
+class SimpleChatServer < EM::Connection
+
+ @@connected_clients = Array.new
+ DM_REGEXP = /^@([a-zA-Z0-9]+)\s*:?\s+(.+)/.freeze
+
+ attr_reader :username
+
+
+ #
+ # EventMachine handlers
+ #
+
+ def post_init
+ @username = nil
+
+ puts "A client has connected..."
+ ask_username
+ end
+
+ def unbind
+ @@connected_clients.delete(self)
+ puts "[info] #{@username} has left" if entered_username?
+ end
+
+ def receive_data(data)
+ if entered_username?
+ handle_chat_message(data.strip)
+ else
+ handle_username(data.strip)
+ end
+ end
+
+
+ #
+ # Username handling
+ #
+
+ def entered_username?
+ !@username.nil? && !@username.empty?
+ end # entered_username?
+
+ def handle_username(input)
+ if input.empty?
+ send_line("Blank usernames are not allowed. Try again.")
+ ask_username
+ else
+ @username = input
+ @@connected_clients.push(self)
+ self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") }
+ puts "#{@username} has joined"
+
+ self.send_line("[info] Ohai, #{@username}")
+ end
+ end # handle_username(input)
+
+ def ask_username
+ self.send_line("[info] Enter your username:")
+ end # ask_username
+
+
+ #
+ # Message handling
+ #
+
+ def handle_chat_message(msg)
+ if command?(msg)
+ self.handle_command(msg)
+ else
+ if direct_message?(msg)
+ self.handle_direct_message(msg)
+ else
+ self.announce(msg, "#{@username}:")
+ end
+ end
+ end # handle_chat_message(msg)
+
+ def direct_message?(input)
+ input =~ DM_REGEXP
+ end # direct_message?(input)
+
+ def handle_direct_message(input)
+ username, message = parse_direct_message(input)
+
+ if connection = @@connected_clients.find { |c| c.username == username }
+ puts "[dm] @#{@username} => @#{username}"
+ connection.send_line("[dm] @#{@username}: #{message}")
+ else
+ send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}"
+ end
+ end # handle_direct_message(input)
+
+ def parse_direct_message(input)
+ return [$1, $2] if input =~ DM_REGEXP
+ end # parse_direct_message(input)
+
+
+ #
+ # Commands handling
+ #
+
+ def command?(input)
+ input =~ /(exit|status)$/i
+ end # command?(input)
+
+ def handle_command(cmd)
+ case cmd
+ when /exit$/i then self.close_connection
+ when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room")
+ end
+ end # handle_command(cmd)
+
+
+ #
+ # Helpers
+ #
+
+ def announce(msg = nil, prefix = "[chat server]")
+ @@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty?
+ end # announce(msg)
+
+ def other_peers
+ @@connected_clients.reject { |c| self == c }
+ end # other_peers
+
+ def send_line(line)
+ self.send_data("#{line}\n")
+ end # send_line(line)
+end
+
+EventMachine.run do
+ # hit Control + C to stop
+ Signal.trap("INT") { EventMachine.stop }
+ Signal.trap("TERM") { EventMachine.stop }
+
+ EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer)
+end
View
6 examples/ex_channel.rb → examples/old/ex_channel.rb
@@ -33,11 +33,11 @@ def c.receive_line(line)
end
end
- # This part of the example is more fake, but imagine sleep was in fact a
+ # This part of the example is more fake, but imagine sleep was in fact a
# long running calculation to achieve the value.
40.times do
EM.defer lambda { v = sleep(rand * 2); RandChannel << [Time.now, v] }
end
-
+
EM.add_timer(5) { EM.stop }
-end
+end
View
0  examples/ex_queue.rb → examples/old/ex_queue.rb
File renamed without changes
View
0  examples/ex_tick_loop_array.rb → examples/old/ex_tick_loop_array.rb
File renamed without changes
View
0  examples/ex_tick_loop_counter.rb → examples/old/ex_tick_loop_counter.rb
File renamed without changes
View
0  examples/helper.rb → examples/old/helper.rb
File renamed without changes
View
98 lib/em/buftok.rb
@@ -1,43 +1,29 @@
-# BufferedTokenizer - Statefully split input data by a specifiable token
-#
-# Authors:: Tony Arcieri, Martin Emde
-#
-#----------------------------------------------------------------------------
-#
-# Copyright (C) 2006-07 by Tony Arcieri and Martin Emde
-#
-# Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
-#
-#---------------------------------------------------------------------------
-#
-
-# (C)2006 Tony Arcieri, Martin Emde
-# Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
-
# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
# by default. It allows input to be spoon-fed from some outside source which
# receives arbitrary length datagrams which may-or-may-not contain the token
# by which entities are delimited.
#
-# Commonly used to parse lines out of incoming data:
+# By default, new BufferedTokenizers will operate on lines delimited by "\n" by default
+# or allow you to specify any delimiter token you so choose, which will then
+# be used by String#split to tokenize the input data
#
-# module LineBufferedConnection
-# def receive_data(data)
-# (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
-# receive_line(line)
-# end
-# end
-# end
-
+# @example Using BufferedTokernizer to parse lines out of incoming data
+#
+# module LineBufferedConnection
+# def receive_data(data)
+# (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
+# receive_line(line)
+# end
+# end
+# end
+#
+# @author Tony Arcieri
+# @author Martin Emde
class BufferedTokenizer
- # New BufferedTokenizers will operate on lines delimited by "\n" by default
- # or allow you to specify any delimiter token you so choose, which will then
- # be used by String#split to tokenize the input data
+ # @param [String] delimiter
+ # @param [Integer] size_limit
def initialize(delimiter = "\n", size_limit = nil)
- # Store the specified delimiter
- @delimiter = delimiter
-
- # Store the specified size limitation
+ @delimiter = delimiter
@size_limit = size_limit
# The input buffer is stored as an array. This is by far the most efficient
@@ -52,14 +38,18 @@ def initialize(delimiter = "\n", size_limit = nil)
end
# Extract takes an arbitrary string of input data and returns an array of
- # tokenized entities, provided there were any available to extract. This
- # makes for easy processing of datagrams using a pattern like:
+ # tokenized entities, provided there were any available to extract.
#
- # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
+ # @example
+ #
+ # tokenizer.extract(data).
+ # map { |entity| Decode(entity) }.each { ... }
+ #
+ # @param [String] data
def extract(data)
# Extract token-delimited entities from the input string with the split command.
# There's a bit of craftiness here with the -1 parameter. Normally split would
- # behave no differently regardless of if the token lies at the very end of the
+ # behave no differently regardless of if the token lies at the very end of the
# input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
# return "" in this case, meaning that the last entry in the list represents a
# new segment of data where the token has not been encountered
@@ -70,7 +60,7 @@ def extract(data)
raise 'input buffer full' if @input_size + entities.first.size > @size_limit
@input_size += entities.first.size
end
-
+
# Move the first entry in the resulting array into the input buffer. It represents
# the last segment of a token-delimited entity unless it's the only entry in the list.
@input << entities.shift
@@ -85,36 +75,16 @@ def extract(data)
# and add it to our list of discovered entities.
entities.unshift @input.join
-=begin
- # Note added by FC, 10Jul07. This paragraph contains a regression. It breaks
- # empty tokens. Think of the empty line that delimits an HTTP header. It will have
- # two "\n" delimiters in a row, and this code mishandles the resulting empty token.
- # It someone figures out how to fix the problem, we can re-enable this code branch.
- # Multi-character token support.
- # Split any tokens that were incomplete on the last iteration buf complete now.
- entities.map! do |e|
- e.split @delimiter, -1
- end
- # Flatten the resulting array. This has the side effect of removing the empty
- # entry at the end that was produced by passing -1 to split. Add it again if
- # necessary.
- if (entities[-1] == [])
- entities.flatten! << []
- else
- entities.flatten!
- end
-=end
-
# Now that we've hit a token, joined the input buffer and added it to the entities
# list, we can go ahead and clear the input buffer. All of the segments that were
# stored before the join can now be garbage collected.
@input.clear
-
+
# The last entity in the list is not token delimited, however, thanks to the -1
- # passed to split. It represents the beginning of a new list of as-yet-untokenized
+ # passed to split. It represents the beginning of a new list of as-yet-untokenized
# data, so we add it to the start of the list.
@input << entities.pop
-
+
# Set the new input buffer size, provided we're keeping track
@input_size = @input.first.size if @size_limit
@@ -122,16 +92,18 @@ def extract(data)
# in the first place. Hooray!
entities
end
-
+
# Flush the contents of the input buffer, i.e. return the input buffer even though
- # a token has not yet been encountered
+ # a token has not yet been encountered.
+ #
+ # @return [String]
def flush
buffer = @input.join
@input.clear
buffer
end
- # Is the buffer empty?
+ # @return [Boolean]
def empty?
@input.empty?
end
View
54 lib/em/callback.rb
@@ -1,26 +1,58 @@
module EventMachine
- # Utility method for coercing arguments to an object that responds to #call
+ # Utility method for coercing arguments to an object that responds to :call.
# Accepts an object and a method name to send to, or a block, or an object
- # that responds to call.
+ # that responds to :call.
#
- # cb = EM.Callback{ |msg| puts(msg) }
+ # @example EventMachine.Callback used with a block. Returns that block.
+ #
+ # cb = EventMachine.Callback do |msg|
+ # puts(msg)
+ # end
+ # # returned object is a callable
# cb.call('hello world')
#
- # cb = EM.Callback(Object, :puts)
+ #
+ # @example EventMachine.Callback used with an object (to be more specific, class object) and a method name, returns an object that responds to #call
+ #
+ # cb = EventMachine.Callback(Object, :puts)
+ # # returned object is a callable that delegates to Kernel#puts (in this case Object.puts)
# cb.call('hello world')
#
- # cb = EM.Callback(proc{ |msg| puts(msg) })
+ #
+ # @example EventMachine.Callback used with an object that responds to #call. Returns the argument.
+ #
+ # cb = EventMachine.Callback(proc{ |msg| puts(msg) })
+ # # returned object is a callable
# cb.call('hello world')
#
+ #
+ # @overload Callback(object, method)
+ # Wraps `method` invocation on `object` into an object that responds to #call that proxies all the arguments to that method
+ # @param [Object] Object to invoke method on
+ # @param [Symbol] Method name
+ # @return [<#call>] An object that responds to #call that takes any number of arguments and invokes method on object with those arguments
+ #
+ # @overload Callback(object)
+ # Returns callable object as is, without any coercion
+ # @param [<#call>] An object that responds to #call
+ # @return [<#call>] Its argument
+ #
+ # @overload Callback(&block)
+ # Returns block passed to it without any coercion
+ # @return [<#call>] Block passed to this method
+ #
+ # @raise [ArgumentError] When argument doesn't respond to #call, method name is missing or when invoked without arguments and block isn't given
+ #
+ # @return [<#call>]
def self.Callback(object = nil, method = nil, &blk)
if object && method
- lambda { |*args| object.send method, *args }
+ lambda { |*args| object.__send__ method, *args }
else
if object.respond_to? :call
object
- else
+ else
blk || raise(ArgumentError)
- end
- end
- end
-end
+ end # if
+ end # if
+ end # self.Callback
+end # EventMachine
View
35 lib/em/channel.rb
@@ -1,33 +1,38 @@
module EventMachine
- # Provides a simple interface to push items to a number of subscribers. The
- # channel will schedule all operations on the main reactor thread for thread
- # safe reactor operations.
+ # Provides a simple thread-safe way to transfer data between (typically) long running
+ # tasks in {EventMachine.defer} and event loop thread.
#
- # This provides a convenient way for connections to consume messages from
- # long running code in defer, without threading issues.
+ # @example
+ #
+ # channel = EventMachine::Channel.new
+ # sid = channel.subscribe { |msg| p [:got, msg] }
#
- # channel = EM::Channel.new
- # sid = channel.subscribe{ |msg| p [:got, msg] }
# channel.push('hello world')
# channel.unsubscribe(sid)
#
- # See examples/ex_channel.rb for a detailed example.
+ #
class Channel
- # Create a new channel
def initialize
@subs = {}
- @uid = 0
+ @uid = 0
end
# Takes any arguments suitable for EM::Callback() and returns a subscriber
# id for use when unsubscribing.
+ #
+ # @return [Integer] Subscribe identifier
+ # @see #unsubscribe
def subscribe(*a, &b)
name = gen_id
EM.schedule { @subs[name] = EM::Callback(*a, &b) }
+
name
end
- # Removes this subscriber from the list.
+ # Removes subscriber from the list.
+ #
+ # @param [Integer] Subscriber identifier
+ # @see #subscribe
def unsubscribe(name)
EM.schedule { @subs.delete name }
end
@@ -39,7 +44,7 @@ def push(*items)
end
alias << push
- # Receive exactly one message from the channel.
+ # Fetches one message from the channel.
def pop(*a, &b)
EM.schedule {
name = subscribe do |*args|
@@ -50,8 +55,10 @@ def pop(*a, &b)
end
private
- def gen_id # :nodoc:
+
+ # @private
+ def gen_id
@uid += 1
end
end
-end
+end
View
526 lib/em/connection.rb
@@ -1,5 +1,5 @@
module EventMachine
- class FileNotFoundException < Exception # :nodoc:
+ class FileNotFoundException < Exception
end
# EventMachine::Connection is a class that is instantiated
@@ -8,7 +8,7 @@ class FileNotFoundException < Exception # :nodoc:
# to a remote server or accepted locally from a remote client.)
# When a Connection object is instantiated, it <i>mixes in</i>
# the functionality contained in the user-defined module
- # specified in calls to EventMachine#connect or EventMachine#start_server.
+ # specified in calls to {EventMachine.connect} or {EventMachine.start_server}.
# User-defined handler modules may redefine any or all of the standard
# methods defined here, as well as add arbitrary additional code
# that will also be mixed in.
@@ -22,17 +22,27 @@ class FileNotFoundException < Exception # :nodoc:
#
# This class is never instantiated by user code, and does not publish an
# initialize method. The instance methods of EventMachine::Connection
- # which may be called by the event loop are: post_init, receive_data,
- # and unbind. All of the other instance methods defined here are called
- # only by user code.
+ # which may be called by the event loop are:
#
+ # * {#post_init}
+ # * {#connection_completed}
+ # * {#receive_data}
+ # * {#unbind}
+ # * {#ssl_verify_peer} (if TLS is used)
+ # * {#ssl_handshake_completed}
+ #
+ # All of the other instance methods defined here are called only by user code.
+ #
+ # @see file:docs/GettingStarted.md EventMachine tutorial
class Connection
- attr_accessor :signature # :nodoc:
+ # @private
+ attr_accessor :signature
# Override .new so subclasses don't have to call super and can ignore
# connection-specific arguments
#
- def self.new(sig, *args) #:nodoc:
+ # @private
+ def self.new(sig, *args)
allocate.instance_eval do
# Store signature
@signature = sig
@@ -50,28 +60,26 @@ def self.new(sig, *args) #:nodoc:
# Stubbed initialize so legacy superclasses can safely call super
#
- def initialize(*args) #:nodoc:
+ # @private
+ def initialize(*args)
end
- # def associate_callback_target(sig) #:nodoc:
- # # no-op for the time being, to match similar no-op in rubymain.cpp
- # end
-
- # EventMachine::Connection#post_init is called by the event loop
- # immediately after the network connection has been established,
+ # Called by the event loop immediately after the network connection has been established,
# and before resumption of the network loop.
# This method is generally not called by user code, but is called automatically
# by the event loop. The base-class implementation is a no-op.
# This is a very good place to initialize instance variables that will
# be used throughout the lifetime of the network connection.
#
+ # @see #connection_completed
+ # @see #unbind
+ # @see #send_data
+ # @see #receive_data
def post_init
end
- # EventMachine::Connection#receive_data is called by the event loop
- # whenever data has been received by the network connection.
- # It is never called by user code.
- # receive_data is called with a single parameter, a String containing
+ # Called by the event loop whenever data has been received by the network connection.
+ # It is never called by user code. {#receive_data} is called with a single parameter, a String containing
# the network protocol data, which may of course be binary. You will
# generally redefine this method to perform your own processing of the incoming data.
#
@@ -89,52 +97,126 @@ def post_init
# in your redefined implementation of receive_data. For a better understanding
# of this, read through the examples of specific protocol handlers in EventMachine::Protocols
#
- # The base-class implementation of receive_data (which will be invoked if
- # you don't redefine it) simply prints the size of each incoming data packet
- # to stdout.
+ # The base-class implementation (which will be invoked only if you didn't override it in your protocol handler)
+ # simply prints incoming data packet size to stdout.
#
+ # @param [String] data Opaque incoming data.
+ # @note Depending on the protocol, buffer sizes and OS networking stack configuration, incoming data may or may not be "a complete message".
+ # It is up to this handler to detect content boundaries to determine whether all the content (for example, full HTTP request)
+ # has been received and can be processed.
+ #
+ # @see #post_init
+ # @see #connection_completed
+ # @see #unbind
+ # @see #send_data
+ # @see file:docs/GettingStarted.md EventMachine tutorial
def receive_data data
puts "............>>>#{data.length}"
end
- # #ssl_handshake_completed is called by EventMachine when the SSL/TLS handshake has
+ # Called by EventMachine when the SSL/TLS handshake has
# been completed, as a result of calling #start_tls to initiate SSL/TLS on the connection.
#
- # This callback exists because #post_init and #connection_completed are <b>not</b> reliable
+ # This callback exists because {#post_init} and {#connection_completed} are **not** reliable
# for indicating when an SSL/TLS connection is ready to have it's certificate queried for.
#
- # See #get_peer_cert for application and example.
+ # @see #get_peer_cert
def ssl_handshake_completed
end
- # #ssl_verify_peer is called by EventMachine when :verify_peer => true has been passed to #start_tls.
+ # Called by EventMachine when :verify_peer => true has been passed to {#start_tls}.
# It will be called with each certificate in the certificate chain provided by the remote peer.
- # The cert will be passed as a String in PEM format, the same as in #get_peer_cert. It is up to user defined
+ #
+ # The cert will be passed as a String in PEM format, the same as in {#get_peer_cert}. It is up to user defined
# code to perform a check on the certificates. The return value from this callback is used to accept or deny the peer.
# A return value that is not nil or false triggers acceptance. If the peer is not accepted, the connection
- # will be subsequently closed. See 'tests/test_ssl_verify.rb' for a simple example.
+ # will be subsequently closed.
+ #
+ # @example This server always accepts all peers
+ #
+ # module AcceptServer
+ # def post_init
+ # start_tls(:verify_peer => true)
+ # end
+ #
+ # def ssl_verify_peer(cert)
+ # true
+ # end
+ #
+ # def ssl_handshake_completed
+ # $server_handshake_completed = true
+ # end
+ # end
+ #
+ #
+ # @example This server never accepts any peers
+ #
+ # module DenyServer
+ # def post_init
+ # start_tls(:verify_peer => true)
+ # end
+ #
+ # def ssl_verify_peer(cert)
+ # # Do not accept the peer. This should now cause the connection to shut down
+ # # without the SSL handshake being completed.
+ # false
+ # end
+ #
+ # def ssl_handshake_completed
+ # $server_handshake_completed = true
+ # end
+ # end
+ #
+ # @see #start_tls
def ssl_verify_peer(cert)
end
- # EventMachine::Connection#unbind is called by the framework whenever a connection
- # (either a server or client connection) is closed. The close can occur because
- # your code intentionally closes it (see close_connection and close_connection_after_writing),
+ # called by the framework whenever a connection (either a server or client connection) is closed.
+ # The close can occur because your code intentionally closes it (using {#close_connection} and {#close_connection_after_writing}),
# because the remote peer closed the connection, or because of a network error.
# You may not assume that the network connection is still open and able to send or
# receive data when the callback to unbind is made. This is intended only to give
# you a chance to clean up associations your code may have made to the connection
# object while it was open.
#
+ # If you want to detect which peer has closed the connection, you can override {#close_connection} in your protocol handler
+ # and set an @ivar.
+ #
+ # @example Overriding Connection#close_connection to distinguish connections closed on our side
+ #
+ # class MyProtocolHandler < EventMachine::Connection
+ #
+ # # ...
+ #
+ # def close_connection(*args)
+ # @intentionally_closed_connection = true
+ # super(*args)
+ # end
+ #
+ # def unbind
+ # if @intentionally_closed_connection
+ # # ...
+ # end
+ # end
+ #
+ # # ...
+ #
+ # end
+ #
+ # @see #post_init
+ # @see #connection_completed
+ # @see file:docs/GettingStarted.md EventMachine tutorial
def unbind
end
- # EventMachine::Connection#proxy_target_unbound is called by the reactor after attempting
- # to relay incoming data to a descriptor (set as a proxy target descriptor with
- # EventMachine::enable_proxy) that has already been closed.
+ # Called by the reactor after attempting to relay incoming data to a descriptor (set as a proxy target descriptor with
+ # {EventMachine.enable_proxy}) that has already been closed.
+ #
+ # @see EventMachine.enable_proxy
def proxy_target_unbound
end
- # EventMachine::Connection#proxy_completed is called when the reactor finished proxying all
+ # called when the reactor finished proxying all
# of the requested bytes.
def proxy_completed
end
@@ -142,12 +224,13 @@ def proxy_completed
# EventMachine::Connection#proxy_incoming_to is called only by user code. It sets up
# a low-level proxy relay for all data inbound for this connection, to the connection given
# as the argument. This is essentially just a helper method for enable_proxy.
- # See EventMachine::enable_proxy documentation for details.
+ #
+ # @see EventMachine.enable_proxy
def proxy_incoming_to(conn,bufsize=0)
EventMachine::enable_proxy(self, conn, bufsize)
end
- # Helper method for EventMachine::disable_proxy(self)
+ # A helper method for {EventMachine.disable_proxy}
def stop_proxying
EventMachine::disable_proxy(self)
end
@@ -165,17 +248,17 @@ def stop_proxying
# However, it's not guaranteed that a future version of EventMachine will not change
# this behavior.
#
- # close_connection will <i>silently discard</i> any outbound data which you have
- # sent to the connection using EventMachine::Connection#send_data but which has not
+ # {#close_connection} will *silently discard* any outbound data which you have
+ # sent to the connection using {EventMachine::Connection#send_data} but which has not
# yet been sent across the network. If you want to avoid this behavior, use
- # EventMachine::Connection#close_connection_after_writing.
+ # {EventMachine::Connection#close_connection_after_writing}.
#
def close_connection after_writing = false
EventMachine::close_connection @signature, after_writing
end
- # EventMachine::Connection#detach will remove the given connection from the event loop.
- # The connection's socket remains open and its file descriptor number is returned
+ # Removes given connection from the event loop.
+ # The connection's socket remains open and its file descriptor number is returned.
def detach
EventMachine::detach_fd @signature
end
@@ -184,42 +267,47 @@ def get_sock_opt level, option
EventMachine::get_sock_opt @signature, level, option
end
- # EventMachine::Connection#close_connection_after_writing is a variant of close_connection.
+ # A variant of {#close_connection}.
# All of the descriptive comments given for close_connection also apply to
- # close_connection_after_writing, <i>with one exception:</i> If the connection has
+ # close_connection_after_writing, *with one exception*: if the connection has
# outbound data sent using send_dat but which has not yet been sent across the network,
- # close_connection_after_writing will schedule the connection to be closed <i>after</i>
+ # close_connection_after_writing will schedule the connection to be closed *after*
# all of the outbound data has been safely written to the remote peer.
#
# Depending on the amount of outgoing data and the speed of the network,
# considerable time may elapse between your call to close_connection_after_writing
# and the actual closing of the socket (at which time the unbind callback will be called
- # by the event loop). During this time, you <i>may not</i> call send_data to transmit
+ # by the event loop). During this time, you *may not* call send_data to transmit
# additional data (that is, the connection is closed for further writes). In very
- # rare cases, you may experience a receive_data callback after your call to close_connection_after_writing,
+ # rare cases, you may experience a receive_data callback after your call to {#close_connection_after_writing},
# depending on whether incoming data was in the process of being received on the connection
- # at the moment when you called close_connection_after_writing. Your protocol handler must
+ # at the moment when you called {#close_connection_after_writing}. Your protocol handler must
# be prepared to properly deal with such data (probably by ignoring it).
#
+ # @see #close_connection
+ # @see #send_data
def close_connection_after_writing
close_connection true
end
- # EventMachine::Connection#send_data is only called by user code, never by
- # the event loop. You call this method to send data to the remote end of the
- # network connection. send_data is called with a single String argument, which
- # may of course contain binary data. You can call send_data any number of times.
- # send_data is an instance method of an object derived from EventMachine::Connection
- # and containing your mixed-in handler code), so if you call it without qualification
- # within a callback function, the data will be sent to the same network connection
- # that generated the callback. Calling self.send_data is exactly equivalent.
+ # Call this method to send data to the remote end of the network connection. It takes a single String argument,
+ # which may contain binary data. Data is buffered to be sent at the end of this event loop tick (cycle).
+ #
+ # When used in a method that is event handler (for example, {#post_init} or {#connection_completed}, it will send
+ # data to the other end of the connection that generated the event.
+ # You can also call {#send_data} to write to other connections. For more information see The Chat Server Example in the
+ # {file:docs/GettingStarted.md EventMachine tutorial}.
#
- # You can also call send_data to write to a connection <i>other than the one
- # whose callback you are calling send_data from.</i> This is done by recording
- # the value of the connection in any callback function (the value self), in any
- # variable visible to other callback invocations on the same or different
- # connection objects. (Need an example to make that clear.)
+ # If you want to send some data and then immediately close the connection, make sure to use {#close_connection_after_writing}
+ # instead of {#close_connection}.
#
+ #
+ # @param [String] data Data to send asynchronously
+ #
+ # @see file:docs/GettingStarted.md EventMachine tutorial
+ # @see Connection#receive_data
+ # @see Connection#post_init
+ # @see Connection#unbind
def send_data data
data = data.to_s
size = data.bytesize if data.respond_to?(:bytesize)
@@ -228,11 +316,13 @@ def send_data data
end
# Returns true if the connection is in an error state, false otherwise.
+ #
# In general, you can detect the occurrence of communication errors or unexpected
- # disconnection by the remote peer by handing the #unbind method. In some cases, however,
- # it's useful to check the status of the connection using #error? before attempting to send data.
- # This function is synchronous: it will return immediately without blocking.
+ # disconnection by the remote peer by handing the {#unbind} method. In some cases, however,
+ # it's useful to check the status of the connection using {#error?} before attempting to send data.
+ # This function is synchronous but it will return immediately without blocking.
#
+ # @return [Boolean] true if the connection is in an error state, false otherwise
def error?
errno = EventMachine::report_connection_error_status(@signature)
case errno
@@ -245,46 +335,39 @@ def error?
end
end
- # #connection_completed is called by the event loop when a remote TCP connection
- # attempt completes successfully. You can expect to get this notification after calls
- # to EventMachine#connect. Remember that EventMachine makes remote connections
- # asynchronously, just as with any other kind of network event. #connection_completed
+ # Called by the event loop when a remote TCP connection attempt completes successfully.
+ # You can expect to get this notification after calls to {EventMachine.connect}. Remember that EventMachine makes remote connections
+ # asynchronously, just as with any other kind of network event. This method
# is intended primarily to assist with network diagnostics. For normal protocol
- # handling, use #post_init to perform initial work on a new connection (such as
- # send an initial set of data).
- # #post_init will always be called. #connection_completed will only be called in case
- # of a successful completion. A connection-attempt which fails will receive a call
- # to #unbind after the failure.
+ # handling, use #post_init to perform initial work on a new connection (such as sending initial set of data).
+ # {Connection#post_init} will always be called. This method will only be called in case of a successful completion.
+ # A connection attempt which fails will result a call to {Connection#unbind} after the failure.
+ #
+ # @see Connection#post_init
+ # @see Connection#unbind
+ # @see file:docs/GettingStarted.md EventMachine tutorial
def connection_completed
end
- # Call #start_tls at any point to initiate TLS encryption on connected streams.
+ # Call {#start_tls} at any point to initiate TLS encryption on connected streams.
# The method is smart enough to know whether it should perform a server-side
- # or a client-side handshake. An appropriate place to call #start_tls is in
- # your redefined #post_init method, or in the #connection_completed handler for
+ # or a client-side handshake. An appropriate place to call {#start_tls} is in
+ # your redefined {#post_init} method, or in the {#connection_completed} handler for
# an outbound connection.
#
- # #start_tls takes an optional parameter hash that allows you to specify certificate
- # and other options to be used with this Connection object. Here are the currently-supported
- # options:
#
- # * :cert_chain_file :
- # takes a String, which is interpreted as the name of a readable file in the
- # local filesystem. The file is expected to contain a chain of X509 certificates in
- # PEM format, with the most-resolved certificate at the top of the file, successive
- # intermediate certs in the middle, and the root (or CA) cert at the bottom.
+ # @option args [String] :cert_chain_file (nil) local path of a readable file that contants a chain of X509 certificates in
+ # the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail),
+ # with the most-resolved certificate at the top of the file, successive intermediate
+ # certs in the middle, and the root (or CA) cert at the bottom.
#
- # * :private_key_file :
- # takes a String, which is interpreted as the name of a readable file in the
- # local filesystem. The file must contain a private key in PEM format.
+ # @option args [String] :private_key_file (nil) local path of a readable file that must contain a private key in the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail).
#
- # * :verify_peer :
- # takes either true or false. Default is false. This indicates whether a server should request a
- # certificate from a peer, to be verified by user code. If true, the #ssl_verify_peer callback
- # on the Connection object is called with each certificate in the certificate chain provided by
- # the peer. See documentation on #ssl_verify_peer for how to use this.
+ # @option args [String] :verify_peer (false) indicates whether a server should request a certificate from a peer, to be verified by user code.
+ # If true, the {#ssl_verify_peer} callback on the {EventMachine::Connection} object is called with each certificate
+ # in the certificate chain provided by the peer. See documentation on {#ssl_verify_peer} for how to use this.
#
- # === Usage example:
+ # @example Using TLS with EventMachine
#
# require 'rubygems'
# require 'eventmachine'
@@ -295,101 +378,106 @@ def connection_completed
# end
# end
#
- # EM.run {
- # EM.start_server("127.0.0.1", 9999, Handler)
- # }
+ # EventMachine.run do
+ # EventMachine.start_server("127.0.0.1", 9999, Handler)
+ # end
#
- #--
- # TODO: support passing an encryption parameter, which can be string or Proc, to get a passphrase
+ # @param [Hash] args
+ #
+ # @todo support passing an encryption parameter, which can be string or Proc, to get a passphrase
# for encrypted private keys.
- # TODO: support passing key material via raw strings or Procs that return strings instead of
+ # @todo support passing key material via raw strings or Procs that return strings instead of
# just filenames.
- # What will get nasty is whether we have to define a location for storing this stuff as files.
- # In general, the OpenSSL interfaces for dealing with certs and keys in files are much better
- # behaved than the ones for raw chunks of memory.
#
+ # @see #ssl_verify_peer
def start_tls args={}
priv_key, cert_chain, verify_peer = args.values_at(:private_key_file, :cert_chain_file, :verify_peer)
[priv_key, cert_chain].each do |file|
next if file.nil? or file.empty?
raise FileNotFoundException,
- "Could not find #{file} for start_tls" unless File.exists? file
+ "Could not find #{file} for start_tls" unless File.exists? file
end
EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer)
EventMachine::start_tls @signature
end
- # If SSL/TLS is active on the connection, #get_peer_cert returns the remote X509 certificate
- # as a String, in the popular PEM format. This can then be used for arbitrary validation
+ # If [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is active on the connection, returns the remote [X509 certificate](http://en.wikipedia.org/wiki/X.509)
+ # as a string, in the popular [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail). This can then be used for arbitrary validation
# of a peer's certificate in your code.
#
- # This should be called in/after the #ssl_handshake_completed callback, which indicates
+ # This should be called in/after the {#ssl_handshake_completed} callback, which indicates
# that SSL/TLS is active. Using this callback is important, because the certificate may not
# be available until the time it is executed. Using #post_init or #connection_completed is
# not adequate, because the SSL handshake may still be taking place.
#
- # #get_peer_cert will return <b>nil</b> if:
+ # This method will return `nil` if:
#
- # * EventMachine is not built with OpenSSL support
- # * SSL/TLS is not active on the connection
- # * SSL/TLS handshake is not yet complete
+ # * EventMachine is not built with [OpenSSL](http://www.openssl.org) support
+ # * [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is not active on the connection
+ # * TLS handshake is not yet complete
# * Re