MCPcopy Index your code
hub / github.com/boazsegev/iodine

github.com/boazsegev/iodine @v0.7.59

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.7.59 ↗ · + Follow
1,420 symbols 3,885 edges 92 files 721 documented · 51%

Browse by type

Functions 1,281 Types & classes 139
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

iodine - Why Settle for a fast HTTP / WebSocket Server with native Pub/Sub?

Gem Build Status Gem Version Inline docs GitHub

Logo

Iodine is a fast concurrent web application server for real-time Ruby applications, with native support for WebSockets and Pub/Sub services - but it's also so much more.

Iodine is a Ruby wrapper for much of the facil.io C framework, leveraging the speed of C for many common web application tasks. In addition, iodine abstracts away all network concerns, so you never need to worry about the transport layer, leaving you free to concentrate on your application logic.

Iodine includes native support for:

  • HTTP, WebSockets and EventSource (SSE) Services (server);
  • WebSocket connections (server / client);
  • Pub/Sub (with optional Redis Pub/Sub scaling);
  • Fast(!) builtin Mustache templating;
  • Static file service (with automatic gzip support for pre-compressed assets);
  • Optimized Logging to stderr;
  • Asynchronous event scheduling and timers;
  • HTTP/1.1 keep-alive and pipelining;
  • Heap Fragmentation Protection;
  • TLS 1.2 and above (Requiring OpenSSL >= 1.1.0);
  • TCP/IP server and client connectivity;
  • Unix Socket server and client connectivity;
  • Hot Restarts (using the USR1 signal and without hot deployment);
  • Custom protocol authoring;
  • Sequel and ActiveRecord forking protection;
  • and more!

Since iodine wraps much of the C facil.io framework for Ruby:

  • Iodine can handle thousands of concurrent connections (tested with more than 20K connections on Linux)!

  • Iodine is ideal for Linux/Unix based systems (i.e. macOS, Ubuntu, FreeBSD etc'), which are ideal for evented IO (while Windows and Solaris are better at IO completion events, which are very different).

Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.3 and up... it should support the whole Ruby 2.x and 3.x MRI family, but CI tests start at Ruby 2.3.

Note: iodine does not support streaming when using Rack. It's recommended to avoid blocking the server when using body.each since the each loop will block iodine's thread until it's finished and iodine won't send any data before the loop is done.

Iodine - a fast & powerful HTTP + WebSockets server with native Pub/Sub

Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the Rack interface specifications and the Websocket draft extension.

With Iodine.listen service: :http it's possible to run multiple HTTP applications (but please remember not to set more than a single application on a single TCP/IP port).

Iodine also supports native process cluster Pub/Sub and a native RedisEngine to easily scale iodine's Pub/Sub horizontally.

Known Issues and Reporting Issues

See the GitHub Open Issues list for known issues and to report new issues.

Installing and Running Iodine

Install iodine on any Linux / BSD / macOS system using:

gem install iodine

Using the iodine server is easy, simply add iodine as a gem to your Rails / Sinatra / Rack application's Gemfile:

gem 'iodine', '~>0.7'

Then start your application from the command-line / terminal using iodine:

bundler exec iodine

Installing with SSL/TLS

Note: iodine has known issues with the TLS/SSL support. TLS/SSL should NOT be used in production (see issues #95 and #94).

Make sure to update OpenSSL to the latest version before installing Ruby (rbenv should do this automatically).

To avoid name resolution conflicts, iodine will bind to the same OpenSSL version Ruby is bound to. To use SSL/TLS this should be OpenSSL >= 1.1.0 or LibreSSL >= 2.7.4.

Verbose installation should provide a confirmation message, such as:

$ gem install iodine -f -V
...
checking for -lcrypto... yes
checking for -lssl... yes
Detected OpenSSL library, testing for version.
Confirmed OpenSSL to be version 1.1.0 or above (OpenSSL 1.1.0j  20 Nov 2018)...
* Compiling with HAVE_OPENSSL.
...

The installation script tests for OpenSSL 1.1.0 and above. However, this testing approach sometimes provides false positives. If TLS isn't required, install with NO_SSL=1. i.e.:

NO_SSL=1 bundler exec iodine

Running with Rails

On Rails:

  1. Replace the puma gem with the iodine gem.

  2. Remove the config/puma.rb file (or comment out the code).

  3. Optionally, it's possible to add a config/initializers/iodine.rb file. For example:

    ```ruby

    Iodine setup - use conditional setup to allow command-line arguments to override these:

    if(defined?(Iodine)) Iodine.threads = ENV.fetch("RAILS_MAX_THREADS", 5).to_i if Iodine.threads.zero? Iodine.workers = ENV.fetch("WEB_CONCURRENCY", 2).to_i if Iodine.workers.zero? Iodine::DEFAULT_SETTINGS[:port] ||= ENV.fetch("PORT") if ENV.fetch("PORT") end ```

When using native WebSockets with Rails, middle-ware is probably the best approach. A guide for this approach will, hopefully, get published in the future.

Note: command-line instructions (CLI) should be the preferred way for configuring iodine, allowing for code-less configuration updates.

Optimizing Iodine's Concurrency

To get the most out of iodine, consider the amount of CPU cores available and the concurrency level the application requires.

Iodine will calculate, when possible, a good enough default concurrency model for fast applications. See if this works for your application or customize according to the application's needs.

Command line arguments allow easy access to different options, including concurrency levels. i.e., to set up 16 threads and 4 processes:

bundler exec iodine -p $PORT -t 16 -w 4

The environment variables THREADS and WORKERS are automatically recognized when iodine is first required, allowing environment specific customization. i.e.:

export THREADS=16
export WORKERS=-1 # negative values are fractions of CPU cores.
bundler exec iodine -p $PORT

Negative values are evaluated as "CPU Cores / abs(Value)". i.e., on an 8 core CPU machine, this will produce 4 worker processes with 2 threads per worker:

bundler exec iodine -p $PORT -t 2 -w -2

Heap Fragmentation Protection

Iodine includes a fast, network oriented, custom memory allocator, optimizing away some of the work usually placed on the Ruby Garbage Collector (GC).

This approach helps to minimize heap fragmentation for long running processes, by grouping many short-lived objects into a common memory space.

It is still recommended to consider jemalloc or other allocators that also help mitigate heap fragmentation issues.

Static file serving support

Iodine supports an internal static file service that bypasses the Ruby layer and serves static files directly from "C-land".

This means that iodine won't lock Ruby's GVL when sending static files. The files will be sent directly, allowing for true native concurrency.

Since the Ruby layer is unaware of these requests, logging can be performed by turning iodine's logger on.

To use native static file service, setup the public folder's address before starting the server.

This can be done when starting the server from the command line:

bundler exec iodine -p $PORT -t 16 -w 4 -www /my/public/folder

Or using a simple Ruby script. i.e. (a my_server.rb example):

require 'iodine'
# static file service
Iodine.listen, service: :http, public: '/my/public/folder'
# for static file service, we only need a single thread and a single worker.
Iodine.threads = 1
Iodine.start

To enable logging from the command line, use the -v (verbose) option:

bundler exec iodine -p $PORT -t 16 -w 4 -www /my/public/folder -v

X-Sendfile

When a public folder is assigned (the static file server is active), iodine automatically adds support for the X-Sendfile header in any Ruby application response.

This allows Ruby to send very large files using a very small memory footprint and usually leverages the sendfile system call.

i.e. (example config.ru for iodine):

app = proc do |env|
  request = Rack::Request.new(env)
  if request.path_info == '/source'.freeze
    [200, { 'X-Sendfile' => File.expand_path(__FILE__), 'Content-Type' => 'text/plain'}, []]
  elsif request.path_info == '/file'.freeze
    [200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text.' }, File.open(__FILE__)]
  else
    [200, { 'Content-Type' => 'text/html',
            'Content-Length' => request.path_info.length.to_s },
     [request.path_info]]
 end
end
# # optional:
# use Rack::Sendfile
run app

Benchmark localhost:3000/source to experience the X-Sendfile extension at work.

Pre-Compressed assets / files

Rails does this automatically when compiling assets, which is: gzip your static files.

Iodine will automatically recognize and send the gz version if the client (browser) supports the gzip transfer-encoding.

For example, to offer a compressed version of style.css, run (in the terminal):

$  gzip -k -9 style.css

This results in both files, style.css (the original) and style.css.gz (the compressed).

When a browser that supports compressed encoding (which is most browsers) requests the file, iodine will recognize that a pre-compressed option exists and will prefer the gzip compressed version.

It's as easy as that. No extra code required.

Special HTTP Upgrade and SSE support

Iodine's HTTP server implements the WebSocket/SSE Rack Specification Draft, supporting native WebSocket/SSE connections using Rack's env Hash.

This promotes separation of concerns, where iodine handles all the Network related logic and the application can focus on the API and data it provides.

Upgrading an HTTP connection can be performed either using iodine's native WebSocket / EventSource (SSE) support with env['rack.upgrade?'] or by implementing your own protocol directly over the TCP/IP layer - be it a WebSocket flavor or something completely different - using env['upgrade.tcp'].

EventSource / SSE

Iodine treats EventSource / SSE connections as if they were a half-duplex WebSocket connection, using the exact same API and callbacks as WebSockets.

When an EventSource / SSE request is received, iodine will set the Rack Hash's upgrade property to :sse, so that: env['rack.upgrade?'] == :sse.

The rest is detailed in the WebSocket support section.

WebSockets

When a WebSocket connection request is received, iodine will set the Rack Hash's upgrade property to :websocket, so that: env['rack.upgrade?'] == :websocket

To "upgrade" the HTTP request to the WebSockets protocol (or SSE), simply provide iodine with a WebSocket Callback Object instance or class: env['rack.upgrade'] = MyWebsocketClass or env['rack.upgrade'] = MyWebsocketClass.new(args)

Iodine will adopt the object, providing it with network functionality (methods such as write, defer and close will become available) and invoke it's callbacks on network events.

Here is a simple chat-room example we can run in the terminal (irb) or easily paste into a config.ru file:

require 'iodine'
module WebsocketChat
  def on_open client
    # Pub/Sub directly to the client (or use a block to process the messages)
    client.subscribe :chat
    # Writing directly to the socket
    client.write "You're now in the chatroom."
  end
  def on_message client, data
    # Strings and symbol channel names are equivalent.
    client.publish "chat", data
  end
  extend self
end
APP = Proc.new do |env|
  if env['rack.upgrade?'.freeze] == :websocket
    env['rack.upgrade'.freeze] = WebsocketChat
    [0,{}, []] # It's possible to set cookies for the response.
  elsif env['rack.upgrade?'.freeze] == :sse
    puts "SSE connections can only receive data from the server, the can't write."
    env['rack.upgrade'.freeze] = WebsocketChat
    [0,{}, []] # It's possible to set cookies for the response.
  else
    [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Welcome Home"] ]
  end
end
# Pus/Sub can be server oriented as well as connection bound
Iodine.subscribe(:chat) {|ch, msg| puts msg if Iodine.master? }
# By default, Pub/Sub performs in process cluster mode.
Iodine.workers = 4
# # in irb:
Iodine.listen service: :http, public: "www/public", handler: APP
Iodine.start
# # or in config.ru
run APP

Native Pub/Sub with optional Redis scaling

Iodine's core, facil.io offers a native Pub/Sub implementation that can be scaled across machine boundaries using Redis.

The default implementation covers the whole process cluster, so a single cluster doesn't need Redis

Once a single iodine process cluster isn't enough, horizontal scaling for the Pub/Sub layer is as simple as connecting iodine to Redis using the -r <url> from the command line. i.e.:

$ iodine -w -1 -t 8 -r redis://localhost

It's also possible to initialize the iodine<=>Redis link using Ruby, directly from the application's code:

``

Core symbols most depended-on inside this repo

Shape

Function 1,257
Class 130
Method 24
Enum 9

Languages

C81%
C++15%
Ruby4%

Modules by API surface

ext/iodine/fio.c372 symbols
ext/iodine/http.c103 symbols
ext/iodine/fio.h84 symbols
ext/iodine/redis_engine.c47 symbols
ext/iodine/http1.c47 symbols
ext/iodine/websockets.c44 symbols
ext/iodine/fio_tls_openssl.c42 symbols
ext/iodine/fiobj_data.c40 symbols
ext/iodine/iodine_http.c35 symbols
ext/iodine/fio_tls_missing.c35 symbols
ext/iodine/iodine_connection.c30 symbols
ext/iodine/fiobj_str.c27 symbols

For agents

$ claude mcp add iodine \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page