iodine 0.1.21 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc57eb293b5e744b8e465473d80aa8b4de9cc72b
|
4
|
+
data.tar.gz: cd6912e062d3899887e82b11013479e8e51b28ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b254cf30bad1f9c58ff57308e2dab077ea0e48016ce1623374b2aeb2f37141fdbfcb04b38ad1dd946bd3b0c90202672da48aa8466d5bfd6f9ef415cc3aaba5f
|
7
|
+
data.tar.gz: b8e557f7ecf8a96a5a4cebe7d7cb6f9b3b3450c29fe2bfef9cc01f93662dabb9134cf87a43ca295b7ae77c7fdf7a6e53ec5c06f97e08d777feebb7bc92dbde3e
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,4 +1,25 @@
|
|
1
1
|
language: ruby
|
2
|
+
os:
|
3
|
+
- linux
|
4
|
+
# - osx
|
5
|
+
before_install:
|
6
|
+
- gem install bundler -v 1.10.6
|
2
7
|
rvm:
|
3
|
-
- 2.2.
|
4
|
-
|
8
|
+
- 2.2.4
|
9
|
+
- 2.3.0
|
10
|
+
- 2.3.1
|
11
|
+
- 2.2.2
|
12
|
+
notifications:
|
13
|
+
email: false
|
14
|
+
sudo: required
|
15
|
+
dist: trusty
|
16
|
+
addons:
|
17
|
+
apt:
|
18
|
+
sources:
|
19
|
+
- ubuntu-toolchain-r-test
|
20
|
+
packages:
|
21
|
+
- gcc-4.9
|
22
|
+
- gcc-5
|
23
|
+
# compiler:
|
24
|
+
# - clang
|
25
|
+
# - gcc
|
data/CHANGELOG.md
CHANGED
@@ -6,8 +6,16 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
6
6
|
|
7
7
|
## Changes:
|
8
8
|
|
9
|
+
Change log v.0.2.0
|
10
|
+
|
11
|
+
This version is a total rewrite. The API is totally changed, nothing stayed.
|
12
|
+
|
13
|
+
Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby code written is just the fluff and feathers.
|
14
|
+
|
9
15
|
***
|
10
16
|
|
17
|
+
### deprecation of the 0.1.x version line
|
18
|
+
|
11
19
|
Change log v.0.1.21
|
12
20
|
|
13
21
|
**Optimization**: Minor optimizations. i.e. - creates 1 less Time object per request (The logging still creates a Time object unless disabled using `Iodine.logger = nil`).
|
@@ -90,7 +98,7 @@ Change log v.0.1.13
|
|
90
98
|
|
91
99
|
Change log v.0.1.12
|
92
100
|
|
93
|
-
**Update**: Passing a hash as the cookie value will allow to set cookie parameters using the {Response#set_cookie} options. i.e.: `cookies['key']= {value: "lock", max_age: 20}`.
|
101
|
+
**Update**: Passing a hash as the cookie value will allow to set cookie parameters using the {Response#set_cookie} options. i.e.: `cookies['key']= {value: "lock", max_age: 20}`.
|
94
102
|
|
95
103
|
**Security**: set the HttpOnly flag for session id cookies.
|
96
104
|
|
@@ -223,4 +231,3 @@ I tested this new gem during the 0.0.x version releases, and I feel that version
|
|
223
231
|
## License
|
224
232
|
|
225
233
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
226
|
-
|
data/README.md
CHANGED
@@ -1,296 +1,349 @@
|
|
1
|
-
# Iodine
|
1
|
+
# Iodine - a C kqueue/epoll EventMachine alternative (pre-release)
|
2
|
+
[![Build Status](https://travis-ci.org/boazsegev/iodine.svg?branch=master)](https://travis-ci.org/boazsegev/iodine)
|
2
3
|
[![Gem Version](https://badge.fury.io/rb/iodine.svg)](https://badge.fury.io/rb/iodine)
|
3
4
|
[![Inline docs](http://inch-ci.org/github/boazsegev/iodine.svg?branch=master)](http://www.rubydoc.info/github/boazsegev/iodine/master/frames)
|
4
5
|
[![GitHub](https://img.shields.io/badge/GitHub-Open%20Source-blue.svg)](https://github.com/boazsegev/iodine)
|
5
6
|
|
6
|
-
Iodine makes writing Object Oriented
|
7
|
+
Iodine 0.2.0 (pre-release) makes writing Object Oriented **Network Services** easy to write.
|
7
8
|
|
8
|
-
|
9
|
+
Iodine is an **evented** framework with a simple API that builds off a low level [C code library](https://github.com/boazsegev/c-server-tools) with support for **epoll** and **kqueue** - this means that:
|
9
10
|
|
10
|
-
|
11
|
+
* Iodine can handle **thousands of concurrent connections** (tested with 20K connections).
|
11
12
|
|
12
|
-
Iodine
|
13
|
+
That's right, Iodine isn't subject to the 1024 connection limit imposed by native Ruby and `select`/`poll` based applications.
|
13
14
|
|
14
|
-
|
15
|
+
This makes Iodine ideal for writing HTTP/2 and Websocket servers (which is what started this whole thing).
|
15
16
|
|
16
|
-
Iodine
|
17
|
+
* Iodine supports only **Linux/Unix** based systems (i.e. OS X, Ubuntu, FreeBSD etc'). This allows us to:
|
17
18
|
|
18
|
-
|
19
|
+
* Optimize our code for the production environment.
|
19
20
|
|
20
|
-
|
21
|
+
* Have our testing and development machines behave the same as our ultimate production environment.
|
21
22
|
|
22
|
-
|
23
|
+
* Catch any issues (read: bugs) while in development - just ask AT&T about how important this is ;-)
|
23
24
|
|
24
|
-
Iodine
|
25
|
+
Iodine is a C extension for Ruby, developed for Ruby MRI 2.2.2 and up... it should support the whole Ruby 2.0 family, but Rack requires Ruby 2.2.2, and so Iodine matches this requirement.
|
25
26
|
|
26
|
-
|
27
|
+
## Iodine::Rack - an HTTP and Websockets server
|
27
28
|
|
28
|
-
|
29
|
+
Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC).
|
29
30
|
|
31
|
+
### Running the web server
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
Add this line to your application's Gemfile:
|
33
|
+
Using the Iodine server is easy, simply add Iodine as a gem to your Rack application:
|
34
34
|
|
35
35
|
```ruby
|
36
|
-
|
36
|
+
# notice that the `git` is required since Iodine 2.0 hadn't been released just yet.
|
37
|
+
gem 'iodine', :git => 'https://github.com/boazsegev/iodine.git'
|
37
38
|
```
|
38
39
|
|
39
|
-
|
40
|
+
To get the most out of Iodine, consider the amount of CPU cores available and the concurrency level the application requires.
|
40
41
|
|
41
|
-
|
42
|
+
Puma's model of 16 threads and 4 processes is easily adopted and proved to provide a good enough balance for most use-cases. Use:
|
42
43
|
|
43
|
-
|
44
|
+
```bash
|
45
|
+
bundler exec iodine -p $PORT -t 16 - w 4
|
46
|
+
```
|
44
47
|
|
45
|
-
|
48
|
+
### Static file serving support
|
46
49
|
|
47
|
-
|
50
|
+
Iodine supports static file serving that allows the server to serve static files directly, with no Ruby layer (all from C-land).
|
48
51
|
|
49
|
-
This
|
52
|
+
This means that Iodine won't lock Ruby's GVL when sending static files (nor log these requests). The files will be sent directly, allowing for true native concurrency.
|
50
53
|
|
51
|
-
|
54
|
+
To setup native static file service, setup the public folder's address **before** starting the server.
|
52
55
|
|
53
|
-
|
56
|
+
This can be done when starting the server from the command line:
|
54
57
|
|
58
|
+
```bash
|
59
|
+
bundler exec iodine -p $PORT -t 16 - w 4 -www /my/public/folder
|
60
|
+
```
|
61
|
+
|
62
|
+
Or by adding a single line to the application. i.e. (a `config.ru` example):
|
55
63
|
|
56
64
|
```ruby
|
57
65
|
require 'iodine'
|
66
|
+
Iodine::Rack.public = '/my/public/folder'
|
67
|
+
out = [404, {"Content-Length" => "10".freeze}.freeze, ["Not Found.".freeze].freeze].freeze
|
68
|
+
app = Proc.new { out }
|
69
|
+
run app
|
70
|
+
```
|
58
71
|
|
59
|
-
|
60
|
-
Iodine.on_shutdown { puts "Done!" }
|
61
|
-
# The last hook is the first scheduled for execution
|
62
|
-
Iodine.on_shutdown { puts "Finishing up :-)" }
|
63
|
-
|
64
|
-
# Setup tasks using the `run` or `callback` methods
|
65
|
-
Iodine.run do
|
66
|
-
# tasks can create more tasks...
|
67
|
-
Iodine.run { puts "Task 2 completed!" }
|
68
|
-
puts "Task 1 completed!"
|
69
|
-
end
|
70
|
-
|
71
|
-
# set concurrency level (defaults to a single thread).
|
72
|
-
Iodine.threads = 5
|
72
|
+
### Special HTTP `Upgrade` support
|
73
73
|
|
74
|
-
|
75
|
-
exit
|
76
|
-
```
|
74
|
+
Iodine's HTTP server includes special support for the Upgrade directive using Rack's `env` Hash, allowing the application to focus on services and data while Iodine takes care of the network layer.
|
77
75
|
|
78
|
-
|
76
|
+
Upgrading an HTTP connection can be performed either using Iodine's Websocket Protocol support with `env['upgrade.websocket']` 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']`.
|
79
77
|
|
80
|
-
|
78
|
+
#### Websockets
|
81
79
|
|
82
|
-
|
80
|
+
When an HTTP Upgrade request is received, Iodine will set the Rack Hash's upgrade property to `true`, so that: `env[upgrade.websocket?] == true`
|
83
81
|
|
84
|
-
To
|
82
|
+
To "upgrade" the HTTP request to the Websockets protocol, simply provide Iodine with a Websocket Callback Object instance or class: `env['upgrade.websocket'] = MyWebsocketClass` or `env['upgrade.websocket'] = MyWebsocketClass.new(args)`
|
85
83
|
|
86
|
-
|
84
|
+
Iodine will adopt the object, providing it with network functionality (methods such as `write`, `each`, `defer` and `close` will become available) and invoke it's callbacks on network events.
|
87
85
|
|
86
|
+
Here is a simple example we can run in the terminal (`irb`) or easily paste into a `config.ru` file:
|
88
87
|
|
89
88
|
```ruby
|
90
89
|
require 'iodine'
|
90
|
+
class WebsocketEcho
|
91
|
+
def on_message data
|
92
|
+
write data
|
93
|
+
end
|
94
|
+
end
|
95
|
+
Iodine::Rack.app= Proc.new do |env|
|
96
|
+
if env['upgrade.websocket?'.freeze] && env["HTTP_UPGRADE".freeze] =~ /websocket/i.freeze
|
97
|
+
env['iodine.websocket'.freeze] = WebsocketEcho # or: WebsocketEcho.new
|
98
|
+
[100,{}, []] # It's possible to set cookies for the response.
|
99
|
+
else
|
100
|
+
[200, {"Content-Length" => "12"}, ["Welcome Home"] ]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
Iodine.start
|
104
|
+
```
|
91
105
|
|
92
|
-
|
93
|
-
Iodine.threads = 5
|
106
|
+
#### TCP/IP (raw) sockets
|
94
107
|
|
95
|
-
|
96
|
-
Iodine.protocol = :timers
|
108
|
+
Upgrading to a custom protocol (i.e., in order to implement your own Websocket protocol with special extensions) is performed almost the ame way, using `env['upgrade.tcp']`. In the following (terminal) example, we'll use an echo server without (direct socket echo):
|
97
109
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
110
|
+
```ruby
|
111
|
+
require 'iodine'
|
112
|
+
class MyProtocol
|
113
|
+
def on_message data
|
114
|
+
# regular socket echo - NOT websockets - notice the upgrade code
|
115
|
+
write data
|
116
|
+
end
|
102
117
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
118
|
+
Iodine::Rack.app = Proc.new do |env|
|
119
|
+
if env['upgrade.tcp?'.freeze] && env["HTTP_UPGRADE".freeze] =~ /echo/i.freeze
|
120
|
+
env['upgrade.tcp'.freeze] = MyProtocol
|
121
|
+
# no HTTP response will be sent when the status code is 0 (or less).
|
122
|
+
# to upgrade AFTER a response, set a valid response status code.
|
123
|
+
[1000,{}, []]
|
124
|
+
else
|
125
|
+
[200, {"Content-Length" => "12"}, ["Welcome Home"] ]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
Iodine.start
|
106
129
|
```
|
107
130
|
|
108
|
-
|
131
|
+
#### A few notes
|
132
|
+
|
133
|
+
This design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems) etc'. This also allows us to use middleware without interfering with connection upgrades and provides up with backwards compatibility.
|
109
134
|
|
110
|
-
|
135
|
+
Iodine::Rack imposes a few restrictions for performance and security reasons, such as that the headers (both sending and receiving) must be less then 8Kb in size. These restrictions shouldn't be an issue and are similar to limitations imposed by Apache.
|
111
136
|
|
112
|
-
|
137
|
+
Here's a small HTTP and Websocket broadcast server with Iodine::Rack, which can be used directly from `irb`:
|
113
138
|
|
114
139
|
```ruby
|
115
|
-
|
116
|
-
require 'iodine/http'
|
117
|
-
# returning a string will automatically append it to the response.
|
118
|
-
Iodine::Http.on_http { |request, response| "Hello World!" }
|
119
|
-
```
|
140
|
+
require 'iodine'
|
120
141
|
|
121
|
-
|
142
|
+
# Our server controller and websockets handler
|
143
|
+
class My_Broadcast
|
122
144
|
|
123
|
-
|
145
|
+
# handle HTTP requests (a class callback, emulating a Proc)
|
146
|
+
def self.call env
|
147
|
+
if env["HTTP_UPGRADE".freeze] =~ /websocket/i.freeze
|
148
|
+
env['upgrade.websocket'.freeze] = self.new(env)
|
149
|
+
[0,{}, []]
|
150
|
+
end
|
151
|
+
[200, {"Content-Length" => "12".freeze}, ["Hello World!".freeze]]
|
152
|
+
end
|
124
153
|
|
125
|
-
|
126
|
-
#
|
127
|
-
require 'iodine/http'
|
128
|
-
# create an object that will follow the Iodine Websocket API.
|
129
|
-
class WSChatServer < Iodine::Http::WebsocketHandler
|
130
|
-
def on_open
|
131
|
-
@nickname = request.params[:nickname] || "unknown"
|
132
|
-
broadcast "#{@nickname} has joined the chat!"
|
133
|
-
write "Welcome #{@nickname}, you have joined the chat!"
|
154
|
+
def initialize env
|
155
|
+
@env = env # allows us to access the HTTP request data during the Websocket session
|
134
156
|
end
|
157
|
+
|
158
|
+
# handles websocket data (an instance callback)
|
135
159
|
def on_message data
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
def on_close
|
143
|
-
broadcast "#{@nickname} has left the chat!"
|
160
|
+
# data is the direct buffer and will be recycled once we leave this scope.
|
161
|
+
# we'll copy it to prevent corruption when broadcasting the data asynchronously.
|
162
|
+
data_copy = data.dup
|
163
|
+
# We'll broadcast the data asynchronously to all open websocket connections.
|
164
|
+
each {|ws| ws.write data_copy } # (limited to current process)
|
165
|
+
close if data =~ /^bye[\r\n]/i
|
144
166
|
end
|
145
167
|
end
|
146
168
|
|
147
|
-
|
169
|
+
# static file serving is as easy as (also supports simple byte serving):
|
170
|
+
Iodine::Rack.public = "www/public"
|
171
|
+
|
172
|
+
# start the server while setting the app at the same time
|
173
|
+
Iodine::Rack.run My_Broadcast
|
148
174
|
```
|
149
175
|
|
150
|
-
|
176
|
+
Of course, if you still want to use Rack's `hijack` API, Iodine will support you - but be aware that you will need to implement your own reactor and thread pool for any sockets you hijack, as well as a socket buffer for non-blocking `write` operations (why do that when you can write a protocol object and have the main reactor manage the socket?).
|
177
|
+
|
178
|
+
### How does it compare to other servers?
|
179
|
+
|
180
|
+
Since the HTTP and Websocket parsers are written in C (with no RegExp), they're fairly fast.
|
151
181
|
|
152
|
-
|
182
|
+
Also, Iodine's core and parsers are running outside of Ruby's global lock, meaning that they enjoy true concurrency before entering the Ruby layer (your application) - this offers Iodine a big advantage over other servers.
|
153
183
|
|
154
|
-
|
184
|
+
Another assumption Iodine makes is that it is behind a load balancer / proxy (which is the normal way Ruby applications are deployed) - this allows Iodine to disregard header validity checks (we're not checking for invalid characters) which speeds up the parsing process even more.
|
155
185
|
|
156
|
-
|
186
|
+
I'm not posting any data because Iodine is still under development and things are somewhat dynamic - but you can compare the performance for yourself using `wrk` or `ab`:
|
157
187
|
|
158
|
-
|
188
|
+
```bash
|
189
|
+
$ wrk -c200 -d4 -t12 http://localhost:3000/
|
190
|
+
# or
|
191
|
+
$ ab -n 100000 -c 200 -k http://127.0.0.1:3000/
|
192
|
+
```
|
159
193
|
|
160
|
-
|
194
|
+
Create a simple `config.ru` file with a hello world app:
|
161
195
|
|
162
|
-
|
196
|
+
```ruby
|
197
|
+
App = Proc.new do |env|
|
198
|
+
[200,
|
199
|
+
{ "Content-Type" => "text/html".freeze,
|
200
|
+
"Content-Length" => "16".freeze },
|
201
|
+
['Hello from Rack!'.freeze] ]
|
202
|
+
end
|
163
203
|
|
164
|
-
|
204
|
+
run App
|
205
|
+
```
|
165
206
|
|
166
|
-
|
207
|
+
Then start comparing servers:
|
167
208
|
|
168
|
-
|
209
|
+
```bash
|
210
|
+
$ rackup -p 3000 -E production -s iodine
|
211
|
+
```
|
169
212
|
|
170
|
-
|
213
|
+
vs.
|
171
214
|
|
172
|
-
|
215
|
+
```bash
|
216
|
+
$ rackup -p 3000 -E production -s <Other_Server_Here>
|
217
|
+
```
|
173
218
|
|
174
|
-
|
219
|
+
Puma has ~16 threads by default, so when comparing against Puma, consider using an equal number of threads:
|
175
220
|
|
176
|
-
|
221
|
+
```bash
|
222
|
+
# (t - threads, w - worker processes)
|
223
|
+
$ RACK_ENV=production iodine -p 3000 -t 16 -w 4
|
224
|
+
```
|
177
225
|
|
178
|
-
|
179
|
-
require 'iodine'
|
226
|
+
vs.
|
180
227
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
# The protocol class will call this withing a Mutex, after reading the data from the IO.
|
190
|
-
# This makes this thread-safe per connection.
|
191
|
-
def on_message data
|
192
|
-
write("-- Closing connection, goodbye.\n") && close if data =~ /^(bye|close|exit|stop)/i
|
193
|
-
write(">> #{data.chomp}\n")
|
194
|
-
end
|
195
|
-
# Iodine makes sure this is called only once.
|
196
|
-
def on_close
|
197
|
-
Iodine.info "Closed connection."
|
198
|
-
end
|
199
|
-
# The is called whenever timeout is reached.
|
200
|
-
# By default, ping will close the connection.
|
201
|
-
# but we can do better...
|
202
|
-
def ping
|
203
|
-
# `write` will automatically close the connection if it fails.
|
204
|
-
write "-- Are you still there?\n"
|
205
|
-
end
|
206
|
-
end
|
228
|
+
```bash
|
229
|
+
# (t - threads, w - worker processes)
|
230
|
+
$ RACK_ENV=production puma -p 3000 -w 4 -q
|
231
|
+
```
|
232
|
+
|
233
|
+
Review the `iodine -?` help for more data.
|
234
|
+
|
235
|
+
Remember to compare the memory footprint after running some requests - it's not just speed that C is helping with, it's also memory management and object pooling (i.e., Iodine uses a buffer packet pool management).
|
207
236
|
|
237
|
+
## Can I try before before I buy?
|
208
238
|
|
209
|
-
|
239
|
+
Well, it is **free** and **open source**, no need to buy.. and of course you can try it out.
|
210
240
|
|
211
|
-
|
212
|
-
|
241
|
+
It's installable just like any other gem on MRI, run:
|
242
|
+
|
243
|
+
```
|
244
|
+
$ gem install iodine
|
213
245
|
```
|
214
246
|
|
215
|
-
|
247
|
+
If building the native C extension fails, please notice that some Ruby installations, such as on Ubuntu, require that you separately install the development headers (`ruby.h` and friends). I have no idea why they do that, as you will need the development headers for any native gems you want to install - so hurry up and get it.
|
216
248
|
|
217
|
-
|
249
|
+
If you have the development headers but still can't compile the Iodine extension, [open an issue](https://github.com/boazsegev/iodine/issues) with any messages you're getting and I be happy to look into it.
|
218
250
|
|
219
|
-
|
251
|
+
## Mr. Sandman, write me a server
|
220
252
|
|
221
|
-
|
253
|
+
Girls love flowers, or so my ex used to keep telling me... but I think code is the way to really show that something is hot!
|
254
|
+
|
255
|
+
I mean, look at this short and sweet echo server - No HTTP, just use `telnet`... but it's so elegant I could cry:
|
222
256
|
|
223
257
|
```ruby
|
224
|
-
#!/usr/bin/env ruby
|
225
258
|
|
226
|
-
|
227
|
-
require 'iodine/http'
|
259
|
+
require 'iodine'
|
228
260
|
|
229
|
-
|
230
|
-
|
261
|
+
# an echo protocol with asynchronous notifications.
|
262
|
+
class EchoProtocol
|
263
|
+
# `on_message` is an optional alternative to the `on_data` callback.
|
264
|
+
# `on_message` has a 1Kb buffer that recycles itself for memory optimization.
|
265
|
+
def on_message buffer
|
266
|
+
# writing will never block and will use a buffer written in C when needed.
|
267
|
+
write buffer
|
268
|
+
# close will be performed only once all the data in the write buffer
|
269
|
+
# was sent. use `force_close` to close early.
|
270
|
+
close if buffer =~ /^bye[\r\n]/i
|
271
|
+
# use buffer.dup to save the data from being recycled once we return.
|
272
|
+
data = buffer.dup
|
273
|
+
# run asynchronous tasks with ease
|
274
|
+
run do
|
275
|
+
sleep 1
|
276
|
+
puts "Echoed data: #{data}"
|
277
|
+
end
|
278
|
+
end
|
231
279
|
end
|
232
280
|
|
281
|
+
# listen on port 3000 for the echo protocol.
|
282
|
+
Iodine.listen 3000, EchoProtocol
|
283
|
+
Iodine.threads = 1
|
284
|
+
Iodine.processes = 1
|
285
|
+
Iodine.start
|
286
|
+
|
233
287
|
```
|
234
288
|
|
235
|
-
|
289
|
+
## I loved Iodine 0.1.x - is this an upgrade?
|
236
290
|
|
237
|
-
|
238
|
-
--------------------------------------------------|:------:|------------------------------------------|
|
239
|
-
| Set the server's port. | `-p` | `ruby ./script.rb -p 4000` |
|
240
|
-
| Limit the server's binding to a specific IP. | `-ip` | `ruby ./script.rb -p 4000 -ip 127.0.0.1` |
|
241
|
-
| Use SSL/TLS on a specific port. | `ssl` | `ruby ./script.rb -p 3030 ssl` |
|
242
|
-
| Try out the experimental Http2 extention. | `http2`| `ruby ./script.rb -p 3030 ssl http2` |
|
291
|
+
This is **not** an upgrade, this is a **full rewrite**.
|
243
292
|
|
244
|
-
|
293
|
+
Iodine 0.1.x was written in Ruby and had tons of bells and whistles and a somewhat different API. It also inherited the `IO.select` limit of 1024 concurrent connections.
|
245
294
|
|
246
|
-
|
295
|
+
Iodine 0.2.x is written in C, doesn't have as many bells and whistles (i.e., no Websocket Client) and has a stripped down API (simpler to learn). The connection limit is calculated on startup, according to the system's limits. Connection overflows are terminated with an optional busy message, so the system won't crash.
|
247
296
|
|
248
|
-
|
297
|
+
## Why not EventMachine?
|
249
298
|
|
250
|
-
|
251
|
-
require 'iodine/http'
|
299
|
+
You can go ahead and use EventMachine if you like. They're doing amazing work on that one and it's been used a lot in Ruby-land... really, tons of good developers and people on that project, I'm sure...
|
252
300
|
|
253
|
-
|
254
|
-
Iodine::Http.on_http do |request, response|
|
255
|
-
response << "Hello World!"
|
256
|
-
response << " We're on SSL/TLS!" if request.ssl?
|
257
|
-
end
|
301
|
+
But me, I prefer to make sure my development software runs the exact same code as my production software. So here we are.
|
258
302
|
|
259
|
-
|
303
|
+
Also, I don't really understand all the minute details of EventMachine's API, it kept crashing my system every time I reached ~1024 active connections... I'm sure I just don't know how to use EventMachine, but that's just that.
|
260
304
|
|
261
|
-
|
262
|
-
Iodine.ssl = true
|
263
|
-
Iodine.port = 3030
|
264
|
-
# # we can also change network behavior, so we could have used:
|
265
|
-
# Iodine::Http.on_http { "Hello World! We're on SSL/TLS! - no `if` required ;-)" }
|
266
|
-
end if Process.respond_to? :fork
|
305
|
+
Besides, you're here - why not take Iodine out for a spin and see for yourself?
|
267
306
|
|
268
|
-
|
269
|
-
exit
|
270
|
-
```
|
307
|
+
## Can I contribute?
|
271
308
|
|
272
|
-
|
309
|
+
Yes, please, here are some thoughts:
|
273
310
|
|
274
|
-
|
311
|
+
* I'm really not good at writing automated tests and benchmarks, any help would be appreciated. I keep testing manually and that's less then ideal (and it's mistake prone).
|
275
312
|
|
276
|
-
|
313
|
+
* If we can write a Java wrapper for [the C libraries](https://github.com/boazsegev/c-server-tools), it would be nice... but it could be as big a project as the whole gem, as a lot of minor details are implemented within the bridge between these two languages.
|
277
314
|
|
278
|
-
|
315
|
+
* Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine.
|
279
316
|
|
280
|
-
|
317
|
+
* If you love the project or thought the code was nice, maybe helped you in your own project, drop me a line. I'd love to know.
|
281
318
|
|
282
|
-
|
319
|
+
## License
|
283
320
|
|
284
|
-
|
321
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
285
322
|
|
286
|
-
|
323
|
+
---
|
287
324
|
|
288
|
-
##
|
325
|
+
## "I'm also writing a Ruby extension in C"
|
289
326
|
|
290
|
-
|
327
|
+
Really?! That's great!
|
291
328
|
|
329
|
+
We could all use some more documentation around the subject and having an eco-system for extension tidbits would be nice.
|
292
330
|
|
293
|
-
|
331
|
+
Here's a few things you can use from this project and they seem to be handy to have (and easy to port):
|
294
332
|
|
295
|
-
|
333
|
+
* Iodine is using a [Registry](https://github.com/boazsegev/iodine/blob/0.2.0/ext/core/rb-registry.h) to keep dynamic Ruby objects that are owned by C-land from being collected by the garbage collector in Ruby-land...
|
334
|
+
|
335
|
+
Some people use global Ruby arrays, adding and removing Ruby objects to the array, but that sounds like a performance hog to me.
|
336
|
+
|
337
|
+
This one is a simple binary tree with a Ruby GC callback. Remember to initialize the Registry (`Registry.init(owner)`) so it's "owned" by some Roby-land object, this allows it to bridge the two worlds for the GC's mark and sweep.
|
338
|
+
|
339
|
+
I'm attaching it to one of Iodine's library classes, just in-case someone adopts my code and decides the registry should be owned by the global Object class.
|
340
|
+
|
341
|
+
* I was using a POSIX thread pool library ([`libasync.h`](https://github.com/boazsegev/c-server-tools/blob/master/lib/libasync.c)) until I realized how many issues Ruby has with non-Ruby threads... So now there's a Ruby-thread port for this library at ([`rb-libasync.h`](https://github.com/boazsegev/iodine/blob/master/ext/iodine/rb-libasync.h)).
|
342
|
+
|
343
|
+
Notice that all the new threads are free from the GVL - this allows true concurrency... but, you can't make Ruby API calls in that state.
|
344
|
+
|
345
|
+
To perform Ruby API calls you need to re-enter the global lock (GVL), albeit temporarily, using `rb_thread_call_with_gvl` and `rv_protect` (gotta watch out from Ruby `longjmp` exceptions).
|
346
|
+
|
347
|
+
* Since I needed to call Ruby methods while multi-threading and running outside the GVL, I wrote [`RubyCaller`](https://github.com/boazsegev/iodine/blob/0.2.0/ext/core/rb-call.h) which let's me call an object's method and wraps all the `rb_thread_call_with_gvl` and `rb_protect` details in a secret hidden place I never have to see again. It also keeps track of the thread's state, so if we're already within the GVL, we won't enter it "twice" (which will crash Ruby sporadically).
|
296
348
|
|
349
|
+
These are nice code snippets that can be easily used in other extensions. They're easy enough to write, I guess, but I already did the legwork, so enjoy.
|