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.

Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +9 -2
  5. data/README.md +232 -179
  6. data/Rakefile +13 -1
  7. data/bin/config.ru +63 -0
  8. data/bin/console +6 -0
  9. data/bin/echo +42 -32
  10. data/bin/http-hello +62 -0
  11. data/bin/http-playground +124 -0
  12. data/bin/playground +62 -0
  13. data/bin/poc/Gemfile.lock +23 -0
  14. data/bin/poc/README.md +37 -0
  15. data/bin/poc/config.ru +66 -0
  16. data/bin/poc/gemfile +1 -0
  17. data/bin/poc/www/index.html +57 -0
  18. data/bin/raw-rbhttp +35 -0
  19. data/bin/raw_broadcast +66 -0
  20. data/bin/test_with_faye +40 -0
  21. data/bin/ws-broadcast +108 -0
  22. data/bin/ws-echo +108 -0
  23. data/exe/iodine +59 -0
  24. data/ext/iodine/base64.c +264 -0
  25. data/ext/iodine/base64.h +72 -0
  26. data/ext/iodine/bscrypt-common.h +109 -0
  27. data/ext/iodine/bscrypt.h +49 -0
  28. data/ext/iodine/extconf.rb +41 -0
  29. data/ext/iodine/hex.c +123 -0
  30. data/ext/iodine/hex.h +70 -0
  31. data/ext/iodine/http.c +200 -0
  32. data/ext/iodine/http.h +128 -0
  33. data/ext/iodine/http1.c +402 -0
  34. data/ext/iodine/http1.h +56 -0
  35. data/ext/iodine/http1_simple_parser.c +473 -0
  36. data/ext/iodine/http1_simple_parser.h +59 -0
  37. data/ext/iodine/http_request.h +128 -0
  38. data/ext/iodine/http_response.c +1606 -0
  39. data/ext/iodine/http_response.h +393 -0
  40. data/ext/iodine/http_response_http1.h +374 -0
  41. data/ext/iodine/iodine_core.c +641 -0
  42. data/ext/iodine/iodine_core.h +70 -0
  43. data/ext/iodine/iodine_http.c +615 -0
  44. data/ext/iodine/iodine_http.h +19 -0
  45. data/ext/iodine/iodine_websocket.c +430 -0
  46. data/ext/iodine/iodine_websocket.h +21 -0
  47. data/ext/iodine/libasync.c +552 -0
  48. data/ext/iodine/libasync.h +117 -0
  49. data/ext/iodine/libreact.c +347 -0
  50. data/ext/iodine/libreact.h +244 -0
  51. data/ext/iodine/libserver.c +912 -0
  52. data/ext/iodine/libserver.h +435 -0
  53. data/ext/iodine/libsock.c +950 -0
  54. data/ext/iodine/libsock.h +478 -0
  55. data/ext/iodine/misc.c +181 -0
  56. data/ext/iodine/misc.h +76 -0
  57. data/ext/iodine/random.c +193 -0
  58. data/ext/iodine/random.h +48 -0
  59. data/ext/iodine/rb-call.c +127 -0
  60. data/ext/iodine/rb-call.h +60 -0
  61. data/ext/iodine/rb-libasync.h +79 -0
  62. data/ext/iodine/rb-rack-io.c +389 -0
  63. data/ext/iodine/rb-rack-io.h +17 -0
  64. data/ext/iodine/rb-registry.c +213 -0
  65. data/ext/iodine/rb-registry.h +33 -0
  66. data/ext/iodine/sha1.c +359 -0
  67. data/ext/iodine/sha1.h +85 -0
  68. data/ext/iodine/sha2.c +825 -0
  69. data/ext/iodine/sha2.h +138 -0
  70. data/ext/iodine/siphash.c +136 -0
  71. data/ext/iodine/siphash.h +15 -0
  72. data/ext/iodine/spnlock.h +235 -0
  73. data/ext/iodine/websockets.c +696 -0
  74. data/ext/iodine/websockets.h +120 -0
  75. data/ext/iodine/xor-crypt.c +189 -0
  76. data/ext/iodine/xor-crypt.h +107 -0
  77. data/iodine.gemspec +25 -18
  78. data/lib/iodine.rb +57 -58
  79. data/lib/iodine/http.rb +0 -189
  80. data/lib/iodine/protocol.rb +36 -245
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/rack/handler/iodine.rb +145 -2
  83. metadata +115 -37
  84. data/bin/core_http_test +0 -51
  85. data/bin/em playground +0 -56
  86. data/bin/hello_world +0 -75
  87. data/bin/setup +0 -7
  88. data/lib/iodine/client.rb +0 -5
  89. data/lib/iodine/core.rb +0 -102
  90. data/lib/iodine/core_init.rb +0 -143
  91. data/lib/iodine/http/hpack.rb +0 -553
  92. data/lib/iodine/http/http1.rb +0 -251
  93. data/lib/iodine/http/http2.rb +0 -507
  94. data/lib/iodine/http/rack_support.rb +0 -108
  95. data/lib/iodine/http/request.rb +0 -462
  96. data/lib/iodine/http/response.rb +0 -474
  97. data/lib/iodine/http/session.rb +0 -143
  98. data/lib/iodine/http/websocket_client.rb +0 -335
  99. data/lib/iodine/http/websocket_handler.rb +0 -101
  100. data/lib/iodine/http/websockets.rb +0 -336
  101. data/lib/iodine/io.rb +0 -56
  102. data/lib/iodine/logging.rb +0 -46
  103. data/lib/iodine/settings.rb +0 -158
  104. data/lib/iodine/ssl_connector.rb +0 -48
  105. data/lib/iodine/timers.rb +0 -95
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 52bb724586bb147b6db24a2a5c5ca7192ac27cc3
4
- data.tar.gz: 685c1496be7ec7c13181910c9748d91814345ec0
3
+ metadata.gz: dc57eb293b5e744b8e465473d80aa8b4de9cc72b
4
+ data.tar.gz: cd6912e062d3899887e82b11013479e8e51b28ec
5
5
  SHA512:
6
- metadata.gz: 72420d26e15261d76f196f4d9df9262b675747c468e3b9119adf68ec2bc3e0e5d6a4e57c48421731fb9263c42d542dfceb66c765176cfc4566730bfb8f54f3be
7
- data.tar.gz: 535428c459b044ed1ab7ea3caccc4540e01acb32fdb7d57e910ba61f5b87162796052cd494b32ea7274a71b5f0d863a5f5ab7885faed60f32282c26d9b2b3743
6
+ metadata.gz: 4b254cf30bad1f9c58ff57308e2dab077ea0e48016ce1623374b2aeb2f37141fdbfcb04b38ad1dd946bd3b0c90202672da48aa8466d5bfd6f9ef415cc3aaba5f
7
+ data.tar.gz: b8e557f7ecf8a96a5a4cebe7d7cb6f9b3b3450c29fe2bfef9cc01f93662dabb9134cf87a43ca295b7ae77c7fdf7a6e53ec5c06f97e08d777feebb7bc92dbde3e
data/.gitignore CHANGED
@@ -7,7 +7,8 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /old_stuff/
11
+ *.bundle
12
+ *.so
10
13
 
11
14
  .ruby-version
12
-
13
- *.bundle
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.3
4
- before_install: gem install bundler -v 1.10.6
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 evented server applications easy to write.
7
+ Iodine 0.2.0 (pre-release) makes writing Object Oriented **Network Services** easy to write.
7
8
 
8
- 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.
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
- 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.
11
+ * Iodine can handle **thousands of concurrent connections** (tested with 20K connections).
11
12
 
12
- Iodine is used by [the Plezi Ruby framework for real-time applications](http://www.plezi.io).
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
- ## Notice! and limitations...
15
+ This makes Iodine ideal for writing HTTP/2 and Websocket servers (which is what started this whole thing).
15
16
 
16
- Iodine 0.1.x is implemented in Ruby, using a `select` system call.
17
+ * Iodine supports only **Linux/Unix** based systems (i.e. OS X, Ubuntu, FreeBSD etc'). This allows us to:
17
18
 
18
- `select` is limited to 1024 open connections! after 1024 connection, `select` could cause "undefined behavior", including a possible "crash"... sometimes it's not important... some hosting environments only allow 1024 connections anyway... EventMachine crashes on my system after 1024 connections too...
19
+ * Optimize our code for the production environment.
19
20
 
20
- ...but websockets are connection hungry beasts.
21
+ * Have our testing and development machines behave the same as our ultimate production environment.
21
22
 
22
- Hence, a move to kqueue/epoll is required.
23
+ * Catch any issues (read: bugs) while in development - just ask AT&T about how important this is ;-)
23
24
 
24
- Iodine 0.2.x will include (hopefully), a kpoll / epoll implementation for Linux/BSD server environments, such as Heroku dynos (which run a flavor of unbuto 14).
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
- However, it is unlikely that Iodine's beautiful API could survive this shift.
27
+ ## Iodine::Rack - an HTTP and Websockets server
27
28
 
28
- The 0.1.x API should be considered deprecated while a new API is under development (assuming I will manage to link some decent C code to Ruby)... I satrted out by displiking EventMachine's API, now it seems they might not have had a choice.
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
- ## Installation
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
- gem 'iodine'
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
- And then execute:
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
- $ bundle
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
- Or install it yourself as:
44
+ ```bash
45
+ bundler exec iodine -p $PORT -t 16 - w 4
46
+ ```
44
47
 
45
- $ gem install iodine
48
+ ### Static file serving support
46
49
 
47
- ## Simple Usage: Running tasks and shutting down
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 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.
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
- Iodine starts to work once you app is finished setting all the tasks up (upon exit).
54
+ To setup native static file service, setup the public folder's address **before** starting the server.
52
55
 
53
- To see how that works, open your `irb` terminal an try this:
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
- # Iodine supports shutdown hooks
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
- # Iodine will start executing tasks once your script is done.
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
- In this mode, Iodine will continue running until all the tasks have completed and then it will quit. Timer-based tasks will be ignored.
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
- ## Simple Usage: Task polling
78
+ #### Websockets
81
79
 
82
- This mode of operation is effective if you want Iodine to periodically initiate new tasks such as when you are not able to use `cron`.
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 initiate this mode, simply set: `Iodine.protocol = :timers` OR create a TimedEvent.
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
- In example form:
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
- # set concurrency level (defaults to a single thread).
93
- Iodine.threads = 5
106
+ #### TCP/IP (raw) sockets
94
107
 
95
- # set Iodine to keep listening to TimedEvent(s).
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
- # perform a periodical task every ten seconds
99
- Iodine.run_every 10 do
100
- Iodine.run { sleep 5; puts " * this could have been a long task..." }
101
- puts "I could be polling a database to schedule more tasks..."
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
- # Iodine will start running once your script is done and it will never stop unless stopped.
105
- exit
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
- 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).
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
- ## Server Usage: an Http and Websocket server
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
- 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":
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
- # require the 'iodine/http' module if you want to use Iodine's Http server.
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
- 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).
142
+ # Our server controller and websockets handler
143
+ class My_Broadcast
122
144
 
123
- Here's a quick chatroom server (use [www.websocket.org](http://www.websocket.org/echo.html) to check it out):
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
- ```ruby
126
- # require the 'iodine/http' module if you want to use Iodine's Websocket server.
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
- broadcast "#{@nickname} >> #{data}"
137
- write ">> #{data}"
138
- end
139
- def on_broadcast data
140
- write data
141
- end
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
- Iodine::Http.on_websocket WSChatServer
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
- ### Security and limits
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
- Nobody wants their server to crash... Security measures are a fact of life as an internet entity. It is not only the theoretical malicious attacker from which a server must protect itself, but also from the unaware user or client.
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
- Mostly, it is assumed that Iodine will run behind a proxy (i.e. within a Heroku Dyno or viaduct.io process), as such it is assumed that the proxy will protect the Iodine Http server from undue stress.
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
- Having said that, Iodine is built with certain security measures in mind:
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
- - Iodine will not accept IO data (neither from new connections nor form existing ones) while still answering existing requests and performing tasks. This safeguards against task overloading and DoS attacks causing a global crash, allowing the server to resume normal operation once a DoS attack had run it's course (and potentially allowing legitimate requests to be answered while the attack is still underway).
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
- - Iodine will limit the query length, header count and header data size as well as well as react to header overloading by immediate disconnections. Iodine's limits are hardcoded to be slightly more than double those of common Proxies, so this counter-measure will only take effect should an attacker manage to bypass the Proxy.
194
+ Create a simple `config.ru` file with a hello world app:
161
195
 
162
- - Iodine limits every Http request body-size (file upload data, form data, etc') to ~0.5GB. This setting can be changed using `Iodine::Http.max_body_size`. This safeguard is meant to prevent Ruby from crashing due to insufficient memory (an error Iodine cannot, and should not, recover from).
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
- It is recommended that this number will be lowered substantially whenever possible, by using `Iodine::Http.max_body_size = new_value`
204
+ run App
205
+ ```
165
206
 
166
- Do be aware that, at the moment, file upload data must passed through the memory on it's way to the temporary file. The parser's memory consumption will hopefully decrese in future releases, however, it is always recomended that large data be avoided when possible or handled using download/upload management protocols and services.
207
+ Then start comparing servers:
167
208
 
168
- ## Server Usage: Plug in your network protocol
209
+ ```bash
210
+ $ rackup -p 3000 -E production -s iodine
211
+ ```
169
212
 
170
- Iodine is designed to help write network services (Servers) where each script is intended to implement a single server.
213
+ vs.
171
214
 
172
- 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).
215
+ ```bash
216
+ $ rackup -p 3000 -E production -s <Other_Server_Here>
217
+ ```
173
218
 
174
- 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).
219
+ Puma has ~16 threads by default, so when comparing against Puma, consider using an equal number of threads:
175
220
 
176
- Here's a quick Echo server:
221
+ ```bash
222
+ # (t - threads, w - worker processes)
223
+ $ RACK_ENV=production iodine -p 3000 -t 16 -w 4
224
+ ```
177
225
 
178
- ```ruby
179
- require 'iodine'
226
+ vs.
180
227
 
181
- # inherit from ::Iodine::Protocol
182
- class EchoServer < Iodine::Protocol
183
- # The protocol class will call this withing a Mutex,
184
- # making sure the IO isn't accessed while being initialized.
185
- def on_open
186
- Iodine.info "Opened connection."
187
- set_timeout 5
188
- end
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
- Iodine.protocol = EchoServer
239
+ Well, it is **free** and **open source**, no need to buy.. and of course you can try it out.
210
240
 
211
- # if running this code within irb:
212
- exit
241
+ It's installable just like any other gem on MRI, run:
242
+
243
+ ```
244
+ $ gem install iodine
213
245
  ```
214
246
 
215
- 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).
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
- ## Server Usage: IP address & port, SSL/TLS and other command line options
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
- 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.
251
+ ## Mr. Sandman, write me a server
220
252
 
221
- Let `./script.rb` be an Iodine ruby script, may an easy one such as our Hello World:
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
- # script.rb
227
- require 'iodine/http'
259
+ require 'iodine'
228
260
 
229
- Iodine::Http.on_http do |request, response|
230
- response << "Hello World!"
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
- Here are different command line options that Iodine recognizes automatically when running our script:
289
+ ## I loved Iodine 0.1.x - is this an upgrade?
236
290
 
237
- | purpose | flag | example |
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
- ## Server Usage: Running more than one server
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
- 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.
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
- i.e.:
297
+ ## Why not EventMachine?
249
298
 
250
- ```ruby
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
- # We'll use a simple hello world with a slight "tweek" for this example.
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
- Iodine.ssl = false
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
- Process.fork do
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
- # if using irb
269
- exit
270
- ```
307
+ ## Can I contribute?
271
308
 
272
- ## Cuncurrency?
309
+ Yes, please, here are some thoughts:
273
310
 
274
- Iodine maintains the idea of: "yes" to concurrency between objects but "no" to concurrency within objects.
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
- Iodine applies this concept when running in Task mode (or timer mode), by defaulting to single threaded mode, preventing multi-threading race conditions in an unknown environment. Also, each task runs in a single thread from the thread-pool, so unless it tries to set or manipulate global data, it's safe from race conditions.
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
- Iodine applies this concept when running in Server mode by locking the Protocol instance whenever Iodine calls for actions related to that Protocol.
315
+ * Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine.
279
316
 
280
- For instance, in Iodine's implementation for the Websocket protocol: Websocket messages to different connections can run concurrently, however multiple messages to the same connection are only executed one at a time, maintaining their order (lately a fix in version 0.1.8 made sure that also websocket broadcasting will be executed within the Protocol lock, preventing concurrency within the same connection).
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
- The exception to the rule is the `ping` implementation. Your protocol's `ping` method will execute in parallel with other parts of your protocol's code. Pinging is a machanism that is often time sensitive and is required to maintain an open connection. For this reason, if your code is working hard on a long task, a ping will still occure automatically in the background and offset any connection timeout.
319
+ ## License
283
320
 
284
- If your code is short and efficient (no blocking tasks), it is best to run Iodine in a single threaded mode (you get better performance AND safer code, as long as you don't block):
321
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
285
322
 
286
- Iodine.threads = 1
323
+ ---
287
324
 
288
- ## Contributing
325
+ ## "I'm also writing a Ruby extension in C"
289
326
 
290
- Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine.
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
- ## License
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
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
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.