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
data/lib/iodine.rb
CHANGED
@@ -1,71 +1,70 @@
|
|
1
|
-
require 'logger'
|
2
1
|
require 'socket'
|
3
|
-
require 'openssl'
|
4
2
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require "iodine/logging"
|
8
|
-
require "iodine/core"
|
9
|
-
require "iodine/timers"
|
10
|
-
require "iodine/protocol"
|
11
|
-
require "iodine/ssl_connector"
|
12
|
-
require "iodine/io"
|
13
|
-
require "iodine/core_init"
|
3
|
+
require 'iodine/version'
|
4
|
+
require 'iodine/iodine'
|
14
5
|
|
15
|
-
|
16
|
-
|
17
|
-
# Iodine is an easy Object-Oriented library for writing network applications (servers) with your own
|
18
|
-
# network protocol.
|
19
|
-
#
|
20
|
-
# Please read the {file:README.md} file for an introduction to Iodine.
|
6
|
+
# Iodine is both a Rack server and a platform for writing evented network services on Ruby.
|
21
7
|
#
|
22
|
-
# Here
|
23
|
-
# notice how Iodine will automatically start running once you finish setting everything up:
|
8
|
+
# Here is a sample Echo server using Iodine:
|
24
9
|
#
|
25
|
-
# require 'iodine'
|
26
10
|
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# write(">> #{data.chomp}\n")
|
38
|
-
# end
|
39
|
-
# # Iodine will call this whenever a new connection is closed.
|
40
|
-
# def on_close
|
41
|
-
# Iodine.info "Closing connection id: #{id}"
|
42
|
-
# end
|
43
|
-
# # Iodine will call this whenever a a timeout is reached.
|
44
|
-
# def ping
|
45
|
-
# # If `write` fails, it automatically closes the connection.
|
46
|
-
# write("-- Are you still there?\n")
|
47
|
-
# end
|
11
|
+
# # define the protocol for our service
|
12
|
+
# class EchoProtocol
|
13
|
+
# @timeout = 10
|
14
|
+
# # this is just one possible callback with a recyclable buffer
|
15
|
+
# def on_message buffer
|
16
|
+
# # write the data we received
|
17
|
+
# write "echo: #{buffer}"
|
18
|
+
# # close the connection when the time comes
|
19
|
+
# close if buffer =~ /^bye[\n\r]/
|
20
|
+
# end
|
48
21
|
# end
|
22
|
+
# # create the service instance
|
23
|
+
# Iodine.listen 3000, EchoProtocol
|
24
|
+
# # start the service
|
25
|
+
# Iodine.start
|
49
26
|
#
|
50
|
-
# # setting up the server is as easy as plugging in your Protocol class:
|
51
|
-
# Iodine.protocol = MyProtocol
|
52
|
-
#
|
53
|
-
# # setting up cuncurrency can be done with threads - the default is a single thread):
|
54
|
-
# Iodine.threads = 8
|
55
|
-
#
|
56
|
-
# # setting up cuncurrency can also be done with processes (forking) - the default is a single process:
|
57
|
-
# Iodine.processes = 4
|
58
|
-
#
|
59
|
-
# # if you are excecuting this script from IRB, exit IRB to start Iodine.
|
60
|
-
# exit
|
61
|
-
#
|
62
|
-
# You can test the example using `telnet`
|
63
27
|
#
|
64
|
-
#
|
28
|
+
# Please read the {file:README.md} file for an introduction to Iodine and an overview of it's API.
|
65
29
|
module Iodine
|
66
|
-
|
67
|
-
|
30
|
+
@threads = (ARGV.index('-t') && ARGV[ARGV.index('-t') + 1]) || ENV['MAX_THREADS']
|
31
|
+
@processes = (ARGV.index('-w') && ARGV[ARGV.index('-w') + 1]) || ENV['MAX_WORKERS']
|
32
|
+
@threads = @threads.to_i if @threads
|
33
|
+
@processes = @processes.to_i if @processes
|
34
|
+
|
35
|
+
# Get/Set the number of threads used in the thread pool (a static thread pool). Can be 1 (single working thread, the main thread will sleep) and can be 0 (the main thread will be used as the only active thread).
|
36
|
+
def self.threads
|
37
|
+
@threads
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get/Set the number of threads used in the thread pool (a static thread pool). Can be 1 (single working thread, the main thread will sleep) and can be 0 (the main thread will be used as the only active thread).
|
41
|
+
def self.threads=(count)
|
42
|
+
@threads = count.to_i
|
43
|
+
end
|
68
44
|
|
45
|
+
# Get/Set the number of worker processes. A value greater then 1 will cause the Iodine to "fork" any extra worker processes needed.
|
46
|
+
def self.processes
|
47
|
+
@processes
|
48
|
+
end
|
69
49
|
|
50
|
+
# Get/Set the number of worker processes. A value greater then 1 will cause the Iodine to "fork" any extra worker processes needed.
|
51
|
+
def self.processes=(count)
|
52
|
+
@processes = count.to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
# Runs the warmup sequence. {warmup} attempts to get every "autoloaded" (lazy loaded)
|
56
|
+
# file to load now instead of waiting for "first access". This allows multi-threaded safety and better memory utilization during forking.
|
57
|
+
#
|
58
|
+
# Use {warmup} when either {processes} or {threads} are set to more then 1.
|
59
|
+
def self.warmup
|
60
|
+
# load anything marked with `autoload`, since autoload isn't thread safe nor fork friendly.
|
61
|
+
Module.constants.each do |n|
|
62
|
+
begin
|
63
|
+
Object.const_get(n)
|
64
|
+
rescue Exception => _e
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
70
69
|
|
71
|
-
|
70
|
+
require 'rack/handler/iodine' unless defined? ::Iodine::Rack::IODINE_RACK_LOADED
|
data/lib/iodine/http.rb
CHANGED
@@ -1,193 +1,4 @@
|
|
1
1
|
require 'iodine'
|
2
|
-
require 'stringio'
|
3
|
-
require 'time'
|
4
|
-
require 'json'
|
5
|
-
require 'yaml'
|
6
|
-
require 'uri'
|
7
|
-
require 'tmpdir'
|
8
|
-
require 'zlib'
|
9
|
-
require 'securerandom'
|
10
|
-
require 'tempfile.rb'
|
11
|
-
|
12
|
-
require 'iodine/http/request'
|
13
|
-
require 'iodine/http/response'
|
14
|
-
require 'iodine/http/session'
|
15
|
-
|
16
|
-
require 'iodine/http/http1'
|
17
|
-
|
18
|
-
require 'iodine/http/hpack'
|
19
|
-
require 'iodine/http/http2'
|
20
|
-
|
21
|
-
require 'iodine/http/websockets'
|
22
|
-
require 'iodine/http/websocket_handler'
|
23
|
-
require 'iodine/http/websocket_client'
|
24
|
-
|
25
|
-
require 'iodine/http/rack_support'
|
26
|
-
|
27
2
|
|
28
3
|
module Iodine
|
29
|
-
|
30
|
-
# The {Iodine::Http} class allows the creation of Http and Websocket servers using Iodine.
|
31
|
-
#
|
32
|
-
# To start an Http server, simply require `iodine/http` (which isn't required by default) and set up
|
33
|
-
# your Http callback. i.e.:
|
34
|
-
#
|
35
|
-
# require 'iodine/http'
|
36
|
-
# Iodine::Http.on_http { |request, response| 'Hello World!' }
|
37
|
-
# exit # only if running from irb
|
38
|
-
#
|
39
|
-
# To start a Websocket server, require `iodine/http` (which isn't required by default), create a Websocket handling Class and set up
|
40
|
-
# your Websocket callback. i.e.:
|
41
|
-
#
|
42
|
-
# require 'iodine/http'
|
43
|
-
# class WSChatServer
|
44
|
-
# def initialize nickname, response
|
45
|
-
# @nickname = nickname || "unknown"
|
46
|
-
# @response = response
|
47
|
-
# # @response.io # => Http Protocol
|
48
|
-
# end
|
49
|
-
# def on_open
|
50
|
-
# # only now is the response.io pointing at the Websocket Protocol
|
51
|
-
# @io = @response.io
|
52
|
-
# @io.broadcast "#{@nickname} has joined the chat!"
|
53
|
-
# @io << "Welcome #{@nickname}, you have joined the chat!"
|
54
|
-
# end
|
55
|
-
# def on_message data
|
56
|
-
# @io.broadcast "#{@nickname} >> #{data}"
|
57
|
-
# @io << ">> #{data}"
|
58
|
-
# end
|
59
|
-
# def on_broadcast data
|
60
|
-
# # the http response can also be used to send websocket data.
|
61
|
-
# @response << data
|
62
|
-
# end
|
63
|
-
# def on_close
|
64
|
-
# @io.broadcast "#{@nickname} has left the chat!"
|
65
|
-
# end
|
66
|
-
# end
|
67
|
-
#
|
68
|
-
# Iodine::Http.on_websocket { |request, response| WSChatServer.new request.params[:name], response}
|
69
|
-
#
|
70
|
-
# See {Iodine::Http::WebsocketHandler} for a good starting point or inherit {Iodine::Http::WebsocketHandler} in your handler.
|
71
|
-
#
|
72
|
-
module Http
|
73
|
-
public
|
74
|
-
# Sets or gets the Http callback.
|
75
|
-
#
|
76
|
-
# An Http callback is a Proc like object that answers to `call(request, response)` and returns either:
|
77
|
-
# `true`:: the response has been set by the callback and can be managed (including any streaming) by the server.
|
78
|
-
# `false`:: the request shouldn't be answered or resource not found (error 404 will be sent as a response).
|
79
|
-
# String:: the String will be appended to the response and the response sent.
|
80
|
-
def on_http handler = nil, &block
|
81
|
-
@http_app = handler || block if handler || block
|
82
|
-
@http_app
|
83
|
-
end
|
84
|
-
# Sets or gets the Websockets callback.
|
85
|
-
#
|
86
|
-
# A Websockets callback is a Proc like object that answers to `call(request)` and returns either:
|
87
|
-
# `false`:: the request shouldn't be answered or resource not found (error 404 will be sent as a response).
|
88
|
-
# Websocket Handler:: a Websocket handler is an object that is expected to answer `on_message(data)` and `on_close`. See {} for more data.
|
89
|
-
def on_websocket handler = nil, &block
|
90
|
-
@websocket_app = handler || block if handler || block
|
91
|
-
@websocket_app
|
92
|
-
end
|
93
|
-
|
94
|
-
# Sets the session token for the Http server (String). Defaults to the name of the script + '_id'.
|
95
|
-
def session_token= token
|
96
|
-
@session_token = token
|
97
|
-
end
|
98
|
-
# Gets the session token for the Http server (String). Defaults to the name of the script.
|
99
|
-
def session_token
|
100
|
-
@session_token
|
101
|
-
end
|
102
|
-
|
103
|
-
# `max_http_buffer` is deprecated. Use `max_body_size` instead.
|
104
|
-
def max_http_buffer= size
|
105
|
-
Iodine.warn "`max_http_buffer` is deprecated. Use `max_body_size` instead."
|
106
|
-
max_body_size = size
|
107
|
-
end
|
108
|
-
# `max_http_buffer` is deprecated. Use `max_body_size` instead.
|
109
|
-
def max_http_buffer
|
110
|
-
Iodine.warn "`max_http_buffer` is deprecated. Use `max_body_size` instead."
|
111
|
-
@max_body_size
|
112
|
-
end
|
113
|
-
# Sets the maximum bytes allowed for an HTTP's body request (file upload data). Defaults to the command line `-limit` argument, or ~0.5GB.
|
114
|
-
def max_body_size= size
|
115
|
-
@max_body_size = size
|
116
|
-
end
|
117
|
-
# Gets the maximum bytes allowed for an HTTP's body request (file upload data). Defaults to the command line `-limit` argument, or ~0.5GB.
|
118
|
-
def max_body_size
|
119
|
-
@max_body_size
|
120
|
-
end
|
121
|
-
|
122
|
-
# Sets whether Iodine will allow connections to the experiemntal Http2 protocol. Defaults to false unless the `http2` command line flag is present.
|
123
|
-
def http2= allow
|
124
|
-
@http2 = allow && true
|
125
|
-
end
|
126
|
-
# Returns true if Iodine will require that new connection be encrypted.
|
127
|
-
def http2
|
128
|
-
@http2
|
129
|
-
end
|
130
|
-
|
131
|
-
# # Sets whether Iodine will allow connections to the experiemntal Http2 protocol. Defaults to false unless the `http2` command line flag is present.
|
132
|
-
# def self.message_buffer_size= size
|
133
|
-
# @message_buffer_size = size
|
134
|
-
# end
|
135
|
-
# # Returns true if Iodine will require that new connection be encrypted.
|
136
|
-
# def self.message_buffer_size
|
137
|
-
# @message_buffer_size
|
138
|
-
# end
|
139
|
-
|
140
|
-
# Creates a websocket client within a new task (non-blocking).
|
141
|
-
#
|
142
|
-
# Make sure to setup all the callbacks (as needed) prior to starting the connection. See {::Iodine::Http::WebsocketClient.connect}
|
143
|
-
#
|
144
|
-
# i.e.:
|
145
|
-
#
|
146
|
-
# require 'iodine/http'
|
147
|
-
# # don't start the server
|
148
|
-
# Iodine.protocol = :timer
|
149
|
-
# options = {}
|
150
|
-
# options[:on_open] = Proc.new { write "Hello there!"}
|
151
|
-
# options[:on_message] = Proc.new do |data|
|
152
|
-
# puts ">> #{data}";
|
153
|
-
# write "Bye!";
|
154
|
-
# # It's possible to update the callback midstream.
|
155
|
-
# on_message {|data| puts "-- Goodbye message: #{data}"; close;}
|
156
|
-
# end
|
157
|
-
# # After closing we will call `Iodine.signal_exit` to signal Iodine to finish up.
|
158
|
-
# options[:on_close] = Proc.new { puts "disconnected"; Iodine.signal_exit }
|
159
|
-
#
|
160
|
-
# Iodine::Http.ws_connect "ws://echo.websocket.org", options
|
161
|
-
#
|
162
|
-
# #if running from irb:
|
163
|
-
# exit
|
164
|
-
#
|
165
|
-
def ws_connect url, options={}, &block
|
166
|
-
::Iodine.run { ::Iodine::Http::WebsocketClient.connect url, options, &block }
|
167
|
-
end
|
168
|
-
|
169
|
-
protected
|
170
|
-
|
171
|
-
@http2 = (ARGV.index('http2') && true)
|
172
|
-
@max_body_size = ((ARGV.index('-limit') && ARGV[ARGV.index('-limit') + 1]) || 536_870_912).to_i
|
173
|
-
|
174
|
-
@websocket_app = @http_app = NOT_IMPLEMENTED = Proc.new { |i,o| false }
|
175
|
-
@session_token = "#{File.basename($0, '.*')}_uuid"
|
176
|
-
extend self
|
177
|
-
end
|
178
|
-
|
179
|
-
@queue.tap do |q|
|
180
|
-
arr =[];
|
181
|
-
arr << q.pop until q.empty?;
|
182
|
-
run { ::Iodine.ssl_protocols = { 'h2' => ::Iodine::Http::Http2, 'http/1.1' => ::Iodine::Http::Http1 } if @ssl && @ssl_protocols.empty? && ::Iodine::Http.http2 }
|
183
|
-
run do
|
184
|
-
if Iodine.protocol == ::Iodine::Http::Http1 && ::Iodine::Http.on_http == ::Iodine::Http::NOT_IMPLEMENTED && ::Iodine::Http.on_websocket == ::Iodine::Http::NOT_IMPLEMENTED
|
185
|
-
::Iodine.protocol = :http_not_initialized
|
186
|
-
q << arr.shift until arr.empty?
|
187
|
-
run { Process.kill("INT", 0) }
|
188
|
-
end
|
189
|
-
end
|
190
|
-
q << arr.shift until arr.empty?
|
191
|
-
end
|
192
4
|
end
|
193
|
-
Iodine.protocol = ::Iodine::Http::Http1 unless Iodine.protocol
|
data/lib/iodine/protocol.rb
CHANGED
@@ -1,247 +1,38 @@
|
|
1
1
|
module Iodine
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# # tell Iodine
|
39
|
-
# Iodine.protocol = MyProtocol
|
40
|
-
#
|
41
|
-
class Protocol
|
42
|
-
|
43
|
-
# returns the IO object. If the connection uses SSL/TLS, this will return the SSLSocket (not a native IO object).
|
44
|
-
#
|
45
|
-
# Using one of the Protocol methods {#write}, {#read}, {#close} is prefered over direct access.
|
46
|
-
attr_reader :io
|
47
|
-
# the argument or options Hash passed to the initializer as a second argument (the first argument MUST be the IO object).
|
48
|
-
# the value is usually `nil` unless the protocol instance was created by a different protocol while "upgrading" from one protocol to the next.
|
49
|
-
attr_reader :options
|
50
|
-
# The protocol's Mutex locker. It should be locked whenever your code is runing, unless you are
|
51
|
-
# writing asynchronous code.
|
52
|
-
#
|
53
|
-
# Use with care (or, better yet, don't use).
|
54
|
-
attr_reader :locker
|
55
|
-
|
56
|
-
# Sets the timeout in seconds for IO activity (set timeout within {#on_open}).
|
57
|
-
#
|
58
|
-
# After timeout is reached, {#ping} will be called. The connection will be closed if {#ping} returns `false` or `nil`.
|
59
|
-
def set_timeout seconds
|
60
|
-
@timeout = seconds
|
61
|
-
end
|
62
|
-
|
63
|
-
# This method is called whenever the Protocol is initialized - i.e.:
|
64
|
-
# a new connection is established or an old connection switches to this protocol.
|
65
|
-
def on_open
|
66
|
-
end
|
67
|
-
# This method is called whenever data is received from the IO.
|
68
|
-
#
|
69
|
-
# The `data` received is a reference to the socket's buffer and it will be cleared and replaced whenever `read`
|
70
|
-
# is called or when new IO data comes in (after `on_message` had completed). To presist
|
71
|
-
# the data, make sure to duplicate the data String using `data.dup`.
|
72
|
-
def on_message data
|
73
|
-
end
|
74
|
-
|
75
|
-
# This method is called AFTER the Protocol's IO is closed - it will only be called once.
|
76
|
-
def on_close
|
77
|
-
end
|
78
|
-
|
79
|
-
# This method is called when the server's shutdown process had started and BEFORE the Protocol's IO is closed. It allows graceful shutdown for network protocols.
|
80
|
-
def on_shutdown
|
81
|
-
end
|
82
|
-
|
83
|
-
# This method is called whenever a timeout has occurred. Either implement a ping or close the connection.
|
84
|
-
# The default implementation closes the connection unless the protocol is still processing information received before timeout occurred.
|
85
|
-
def ping
|
86
|
-
close unless @locker.locked?
|
87
|
-
end
|
88
|
-
|
89
|
-
#############
|
90
|
-
## functionality and helpers
|
91
|
-
|
92
|
-
|
93
|
-
# returns true if the protocol is using an encrypted connection (the IO is an OpenSSL::SSL::SSLSocket).
|
94
|
-
def ssl?
|
95
|
-
@io.is_a?(OpenSSL::SSL::SSLSocket) # io.npn_protocol
|
96
|
-
end
|
97
|
-
|
98
|
-
|
99
|
-
# Closes the IO object.
|
100
|
-
# @return [nil]
|
101
|
-
def close
|
102
|
-
@io.close unless @io.closed?
|
103
|
-
nil
|
104
|
-
end
|
105
|
-
alias :disconnect :close
|
106
|
-
def closed?
|
107
|
-
@io.closed?
|
108
|
-
end
|
109
|
-
|
110
|
-
# reads from the IO up to the specified number of bytes (defaults to ~2Mb).
|
111
|
-
def read size = 2_097_152
|
112
|
-
touch
|
113
|
-
ssl? ? read_ssl(size) : @io.read_nonblock( size , Thread.current[:buffer] )
|
114
|
-
# @io.read_nonblock( size ) # this one is a bit slower...
|
115
|
-
rescue OpenSSL::SSL::SSLErrorWaitReadable, IO::WaitReadable, IO::WaitWritable
|
116
|
-
nil
|
117
|
-
rescue IOError, Errno::ECONNRESET
|
118
|
-
close
|
119
|
-
rescue => e
|
120
|
-
Iodine.warn "Protocol read error: #{e.class.name} #{e.message} (closing connection)"
|
121
|
-
close
|
122
|
-
end
|
123
|
-
|
124
|
-
# this method, writes data to the socket / io object.
|
125
|
-
def write data
|
126
|
-
begin
|
127
|
-
@send_locker.synchronize do
|
128
|
-
r = @io.write data
|
129
|
-
touch
|
130
|
-
r
|
131
|
-
end
|
132
|
-
rescue # => e
|
133
|
-
# Iodine.info e.message
|
134
|
-
close
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# returns the connection's unique local ID as a Hex string.
|
139
|
-
#
|
140
|
-
# This can be used locally but not across processes.
|
141
|
-
def id
|
142
|
-
@id ||= @io.to_io.object_id.to_s(16)
|
143
|
-
end
|
144
|
-
|
145
|
-
# @return [Enumerable] returns an Enumerable with all the active connections (instances of THIS Protocol or it's children).
|
146
|
-
#
|
147
|
-
# if a block is passed, than this method exceutes the block.
|
148
|
-
def self.each
|
149
|
-
if block_given?
|
150
|
-
Iodine.to_a.each {|p| yield(p) if p.is_a?(self) }
|
151
|
-
else
|
152
|
-
( Iodine.to_a.select {|p| p.is_a?(self) } ).each
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
|
157
|
-
#################
|
158
|
-
## the following are Iodine's "system" methods, used internally. Don't override.
|
159
|
-
|
160
|
-
|
161
|
-
# This method is used by Iodine to initialized the Protocol.
|
162
|
-
#
|
163
|
-
# A new Protocol instance set itself up as the IO's protocol (replacing any previous protocol).
|
164
|
-
#
|
165
|
-
# Normally you won't need to override this method. Override {#on_open} instead.
|
166
|
-
def initialize io, options = nil
|
167
|
-
@timeout ||= nil
|
168
|
-
@send_locker = Mutex.new
|
169
|
-
@ping_locker = Mutex.new
|
170
|
-
@locker = Mutex.new
|
171
|
-
@io = io
|
172
|
-
@options = options
|
173
|
-
touch
|
174
|
-
@locker.synchronize do
|
175
|
-
Iodine.switch_protocol @io.to_io, self
|
176
|
-
on_open
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
# Called by Iodine whenever there is data in the IO's read buffer.
|
181
|
-
#
|
182
|
-
# Normally you won't need to override this method. Override {#on_message} instead.
|
183
|
-
def call
|
184
|
-
return unless @locker.try_lock
|
185
|
-
begin
|
186
|
-
data = read
|
187
|
-
if data
|
188
|
-
on_message(data)
|
189
|
-
# data.clear
|
190
|
-
end
|
191
|
-
ensure
|
192
|
-
@locker.unlock
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
|
197
|
-
# This method is used by Iodine invoke a timeout review.
|
198
|
-
#
|
199
|
-
# Normally you won't need to override this method. See {#ping}
|
200
|
-
def timeout? time
|
201
|
-
return unless @ping_locker.try_lock
|
202
|
-
begin
|
203
|
-
touch && ping if @timeout && !@send_locker.locked? && ( (time - @last_active) > @timeout )
|
204
|
-
ensure
|
205
|
-
@ping_locker.unlock
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
protected
|
210
|
-
# This method is used by Iodine to create the IO handler whenever a new connection is established.
|
211
|
-
#
|
212
|
-
# Normally you won't need to override this method.
|
213
|
-
def self.accept io, ssl
|
214
|
-
ssl ? SSLConnector.new(io, self) : self.new(io)
|
215
|
-
rescue
|
216
|
-
io.close unless io.closed?
|
217
|
-
raise
|
218
|
-
end
|
219
|
-
# This methos updates the timeout "watch", signifying the IO was active.
|
220
|
-
def touch
|
221
|
-
@last_active = Iodine.time
|
222
|
-
end
|
223
|
-
|
224
|
-
# reads from the IO up to the specified number of bytes (defaults to ~1Mb).
|
225
|
-
def read_ssl size
|
226
|
-
@send_locker.synchronize do
|
227
|
-
data = String.new
|
228
|
-
begin
|
229
|
-
(data << @io.read_nonblock(size, Thread.current[:buffer]).to_s) until data.bytesize >= size
|
230
|
-
rescue OpenSSL::SSL::SSLErrorWaitReadable, IO::WaitReadable, IO::WaitWritable
|
231
|
-
|
232
|
-
rescue IOError
|
233
|
-
close
|
234
|
-
rescue => e
|
235
|
-
Iodine.warn "SSL Protocol read error: #{e.class.name} #{e.message} (closing connection)"
|
236
|
-
close
|
237
|
-
end
|
238
|
-
return false if data.to_s.empty?
|
239
|
-
touch
|
240
|
-
data
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
|
245
|
-
end
|
246
|
-
|
2
|
+
# The Protocol class is used only for documenting the Protocol API, it will not be included when requiring `iodine`.
|
3
|
+
#
|
4
|
+
# A dynamic (stateful) protocol is defined as a Ruby class instance which is in control of one single connection.
|
5
|
+
#
|
6
|
+
# It is called dynamic because it is dynamically allocated for each connection and then discarded,
|
7
|
+
# also it sounded better then calling it "the stateful protocol", even though that's what it actually is.
|
8
|
+
#
|
9
|
+
# It is (mostly) thread-safe as long as it's operations are limited to the scope
|
10
|
+
# of the object.
|
11
|
+
#
|
12
|
+
# <b>The Callbacks</b>
|
13
|
+
#
|
14
|
+
# A protocol class <b>MUST</b> contain ONE of the following callbacks:
|
15
|
+
#
|
16
|
+
# on_data:: called whened there's data available to be read, but no data was read just yet. `on_data` will not be called again untill all the existing network buffer was read (edge triggered event).
|
17
|
+
# on_message(buffer):: the default `on_data` implementation creates a 1Kb buffer and reads data while recycling the same String memory space. The buffer is forwarded to the `on_message` callback before being recycled. The buffer object will be over-written once `on_message` returns, so creating a persistent copy requires `buffer.dup`.
|
18
|
+
#
|
19
|
+
# A protocol class <b>MAY</b> contain any of the following optional callbacks:
|
20
|
+
#
|
21
|
+
# on_open:: called after a new connection was accepted and the protocol was linked with Iodine's Protocol API. Initialization should be performed here.
|
22
|
+
# ping:: called whenever timeout was reached. The default implementation will close the connection unless a protocol task ({Protocol#defer}, `on_data` or `on_message`) are busy in the background.
|
23
|
+
# on_shutdown:: called if the connection is still open while the server is shutting down. This allows the protocol to send a "going away" frame before the connection is closed and `on_close` is called.
|
24
|
+
# on_close:: called after a connection was closed, for any cleanup (if any).
|
25
|
+
#
|
26
|
+
# WARNING: for thread safety and connection management, `on_open`, `on_shutdown`, `on_close` and `ping` will all be performed within the reactor's main thread.
|
27
|
+
# Do not run long running tasks within these callbacks, or the server might block while you do.
|
28
|
+
# Use {#defer} to run protocol related tasks (this locks the connection, preventing it from running more then one task at a time and offering thread safety),
|
29
|
+
# or {#run} to run asynchronous tasks that aren't protocol related.
|
30
|
+
#
|
31
|
+
# <b>The API:</b>
|
32
|
+
#
|
33
|
+
# After a new connection is accepted and a new protocol object is created, the protocol will be linked with Iodine's Protocol API.
|
34
|
+
# Only the main protocol will be able to access the API within `initialize`, so it's best to use `on_open` for any Initialization required.
|
35
|
+
#
|
36
|
+
module Protocol
|
37
|
+
end
|
247
38
|
end
|