Iodine

Gem Version Inline docs

Iodine makes writing Object Oriented evented server applications easy to write.

In fact, it's so fun to write network protocols that mix and match together, that Iodine includes a built in Http, Http/2 (experimental) and Websocket server that act's a a great demonstration of the power behind Ruby and the Object Oriented approach.

To use Iodine, you just set up your tasks - including a single server, if you want one. Iodine will start running once your application is finished and it won't stop runing until all the scheduled tasks have completed.

Installation

Add this line to your application's Gemfile:

gem 'iodine'

And then execute:

$ bundle

Or install it yourself as:

$ gem install iodine

Simple Usage: Running tasks and shutting down

This mode of operation is effective if you have a cron-job that periodically initiates an Iodine Ruby script. It allows the script to easily initiate a task's stack and perform the tasks concurrently.

Iodine starts to work once you app is finished setting all the tasks up (upon exit).

To see how that works, open your irb terminal an try this:

require 'iodine'

# Iodine supports shutdown hooks
Iodine.on_shutdown { puts "Done!" }
# The last hook is the first scheduled for execution
Iodine.on_shutdown { puts "Finishing up :-)" }

# Setup tasks using the `run` or `callback` methods
Iodine.run do
    # tasks can create more tasks...
    Iodine.run { puts "Task 2 completed!" }
    puts "Task 1 completed!"
end

# set concurrency level (defaults to a single thread).
Iodine.threads = 5

# Iodine will start executing tasks once your script is done.
exit

In this mode, Iodine will continue running until all the tasks have completed and than it will quite. Timer based tasks will be ignored.

Simple Usage: Task polling (unreleased version)

This mode of operation is effective if want Iodine to periodically initiates new tasks, for instance if you cannot use cron.

To initiate this mode, simply set: Iodine.protocol = :timers

In example:

require 'iodine'

# set concurrency level (defaults to a single thread).
Iodine.threads = 5

# set Iodine to keep listening to TimedEvent(s).
Iodine.protocol = :timers

# perform a periodical task every ten seconds
Iodine.run_every 10 do
   Iodine.run { sleep 5; puts " * this could have been a long task..." }
   puts "I could be polling a database to schedule more tasks..."
end

# Iodine will start running once your script is done and it will never stop unless stopped.
exit

In this mode, Iodine will continue running until it receives a kill signal (i.e. ^C). Once the kill signal had been received, Iodine will start shutting down, allowing up to ~20-25 seconds to complete any pending tasks (timeout).

Server Usage: an Http and Websocket server

Using Iodine and leveraging Ruby's Object Oriented approach, is super fun to write our own network protocols and servers... This is Ioding itself includes an optional Http and websocket server. Say "Hello World":

# require the 'iodine/http' module if you want to use Iodine's Http server.
require 'iodine/http'
# returning a string will automatically append it to the response.
Iodine::Http.on_http { |request, response| "Hello World!" }

Iodine's Http server includes comes right out of the box with Websocket support as well as an experimental support for Http/2 (it's just a start, no push support just yet, but you can try it out).

Here's a quick chatroom server (use www.websocket.org to check it out):

# require the 'iodine/http' module if you want to use Iodine's Websocket server.
require 'iodine/http'
# create an object that will follow the Iodine Websocket API.
class WSChatServer < Iodine::Http::WebsocketHandler
  def on_open
     @nickname = request.params[:nickname] || "unknown"
     broadcast "#{@nickname} has joined the chat!"
     write "Welcome #{@nickname}, you have joined the chat!"
  end
  def on_message data
     broadcast "#{@nickname} >> #{data}"
     write ">> #{data}"
  end
  def on_broadcast data
     write data
  end
  def on_close
     broadcast "#{@nickname} has left the chat!"
  end
end

Iodine::Http.on_websocket WSChatServer

Server Usage: Plug in your network protocol

Iodine is designed to help write network services (Servers) where each script is intended to implement a single server.

This is not a philosophy based on any idea or preferences, but rather a response to real-world design where each Ruby script is usually assigned a single port for network access (hence, a single server).

To help you write your network service, Iodine starts you off with the Iodine::Protocol. All network protocols should inherit from this class (or implement it's essencial functionality).

Here's a quick Echo server:

require 'iodine'

# inherit from ::Iodine::Protocol
class EchoServer < Iodine::Protocol
    # The protocol class will call this withing a Mutex,
    # making sure the IO isn't accessed while being initialized.
    def on_open
        Iodine.info "Opened connection."
        set_timeout 5
    end
    # The protocol class will call this withing a Mutex, after reading the data from the IO.
    # This makes this thread-safe per connection.
    def on_message data
        write("-- Closing connection, goodbye.\n") && close if data =~ /^(bye|close|exit|stop)/i
        write(">> #{data.chomp}\n")
    end
    # Iodine makes sure this is called only once.
    def on_close
        Iodine.info "Closed connection."
    end
    # The is called whenever timeout is reached.
    # By default, ping will close the connection.
    # but we can do better...
    def ping
        # `write` will automatically close the connection if it fails.
        write "-- Are you still there?\n"
    end
end


Iodine.protocol = EchoServer

# if running this code within irb:
exit

In this mode, Iodine will continue running until it receives a kill signal (i.e. ^C). Once the kill signal had been received, Iodine will start shutting down, allowing up to ~20-25 seconds to complete any pending tasks (timeout).

Server Usage: IP address & port, SSL/TLS and other command line options

Iodine automatically respects certain command line options that make it easier to use the same script over and over again with different results and making writing a Procfile (or similar setup files) a breeze.

Let ./script.rb be an Iodine ruby script, may an easy one such as our Hello World:

#!/usr/bin/env ruby

# script.rb
require 'iodine/http'

Iodine::Http.on_http do |request, response|
  response << "Hello World!"
end

Here are different command line options that Iodine recognizes automatically when running our script:

purpose flag example
Set the server's port. -p ruby ./script.rb -p 4000
Limit the server's binding to a specific IP. -ip ruby ./script.rb -p 4000 -ip 127.0.0.1
Use SSL/TLS on a specific port. ssl ruby ./script.rb -p 3030 ssl
Try out the experimental Http2 extention. http2 ruby ./script.rb -p 3030 ssl http2

Server Usage: Running more than one server

On some machines, Iodine will allow you to run more than a single server, by forking the main process while still running the script. This is more of a hack to be used in development environments, since runnig multiple instances of the script is the prefered way to use Iodine in production.

i.e.:

require 'iodine/http'

# We'll use a simple hello world with a slight "tweek" for this example.
Iodine::Http.on_http do |request, response|
  response << "Hello World!"
  response << " We're on SSL/TLS!" if request.ssl?
end

Iodine.ssl = false

Process.fork do
   Iodine.ssl = true
   Iodine.port = 3030
   # # we can also change network behavior, so we could have used:
   # Iodine::Http.on_http { "Hello World! We're on SSL/TLS! - no `if` required ;-)" } 
end if Process.respond_to? :fork

# if using irb
exit

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine.

License

The gem is available as open source under the terms of the MIT License.