rage-iodine 1.7.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.github/workflows/ruby.yml +42 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +1098 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/LIMITS.md +41 -0
- data/README.md +782 -0
- data/Rakefile +23 -0
- data/SPEC-PubSub-Draft.md +159 -0
- data/SPEC-WebSocket-Draft.md +239 -0
- data/bin/console +22 -0
- data/bin/info.md +353 -0
- data/bin/mustache_bench.rb +100 -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/examples/async_task.ru +92 -0
- data/examples/bates/README.md +3 -0
- data/examples/bates/config.ru +342 -0
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +600 -0
- data/examples/config.ru +59 -0
- data/examples/echo.ru +59 -0
- data/examples/etag.ru +16 -0
- data/examples/hello.ru +29 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/rack3.ru +12 -0
- data/examples/redis.ru +70 -0
- data/examples/shootout.ru +73 -0
- data/examples/sub-protocols.ru +90 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +280 -0
- data/ext/iodine/extconf.rb +110 -0
- data/ext/iodine/fio.c +12096 -0
- data/ext/iodine/fio.h +6390 -0
- data/ext/iodine/fio_cli.c +431 -0
- data/ext/iodine/fio_cli.h +189 -0
- data/ext/iodine/fio_json_parser.h +687 -0
- data/ext/iodine/fio_siphash.c +157 -0
- data/ext/iodine/fio_siphash.h +37 -0
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +649 -0
- data/ext/iodine/fio_tls_openssl.c +1056 -0
- data/ext/iodine/fio_tmpfile.h +50 -0
- data/ext/iodine/fiobj.h +44 -0
- data/ext/iodine/fiobj4fio.h +21 -0
- data/ext/iodine/fiobj_ary.c +333 -0
- data/ext/iodine/fiobj_ary.h +139 -0
- data/ext/iodine/fiobj_data.c +1185 -0
- data/ext/iodine/fiobj_data.h +167 -0
- data/ext/iodine/fiobj_hash.c +409 -0
- data/ext/iodine/fiobj_hash.h +176 -0
- data/ext/iodine/fiobj_json.c +622 -0
- data/ext/iodine/fiobj_json.h +68 -0
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +317 -0
- data/ext/iodine/fiobj_mustache.h +62 -0
- data/ext/iodine/fiobj_numbers.c +344 -0
- data/ext/iodine/fiobj_numbers.h +127 -0
- data/ext/iodine/fiobj_str.c +433 -0
- data/ext/iodine/fiobj_str.h +172 -0
- data/ext/iodine/fiobject.c +620 -0
- data/ext/iodine/fiobject.h +654 -0
- data/ext/iodine/hpack.h +1923 -0
- data/ext/iodine/http.c +2736 -0
- data/ext/iodine/http.h +1019 -0
- data/ext/iodine/http1.c +825 -0
- data/ext/iodine/http1.h +29 -0
- data/ext/iodine/http1_parser.h +1835 -0
- data/ext/iodine/http_internal.c +1279 -0
- data/ext/iodine/http_internal.h +248 -0
- data/ext/iodine/http_mime_parser.h +350 -0
- data/ext/iodine/iodine.c +1433 -0
- data/ext/iodine/iodine.h +64 -0
- data/ext/iodine/iodine_caller.c +218 -0
- data/ext/iodine/iodine_caller.h +27 -0
- data/ext/iodine/iodine_connection.c +941 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +420 -0
- data/ext/iodine/iodine_defer.h +6 -0
- data/ext/iodine/iodine_fiobj2rb.h +120 -0
- data/ext/iodine/iodine_helpers.c +282 -0
- data/ext/iodine/iodine_helpers.h +12 -0
- data/ext/iodine/iodine_http.c +1280 -0
- data/ext/iodine/iodine_http.h +23 -0
- data/ext/iodine/iodine_json.c +302 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_mustache.c +567 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +580 -0
- data/ext/iodine/iodine_pubsub.h +26 -0
- data/ext/iodine/iodine_rack_io.c +273 -0
- data/ext/iodine/iodine_rack_io.h +20 -0
- data/ext/iodine/iodine_store.c +142 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +346 -0
- data/ext/iodine/iodine_tcp.h +13 -0
- data/ext/iodine/iodine_tls.c +261 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1546 -0
- data/ext/iodine/redis_engine.c +957 -0
- data/ext/iodine/redis_engine.h +79 -0
- data/ext/iodine/resp_parser.h +317 -0
- data/ext/iodine/scheduler.c +173 -0
- data/ext/iodine/scheduler.h +6 -0
- data/ext/iodine/websocket_parser.h +506 -0
- data/ext/iodine/websockets.c +752 -0
- data/ext/iodine/websockets.h +185 -0
- data/iodine.gemspec +50 -0
- data/lib/iodine/connection.rb +61 -0
- data/lib/iodine/json.rb +42 -0
- data/lib/iodine/mustache.rb +113 -0
- data/lib/iodine/pubsub.rb +55 -0
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +3 -0
- data/lib/iodine.rb +274 -0
- data/lib/rack/handler/iodine.rb +33 -0
- data/logo.png +0 -0
- metadata +284 -0
data/examples/echo.ru
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# This is a Websocket echo application.
|
2
|
+
#
|
3
|
+
# Running this application from the command line is easy with:
|
4
|
+
#
|
5
|
+
# iodine echo.ru
|
6
|
+
#
|
7
|
+
# Or, in single thread and single process:
|
8
|
+
#
|
9
|
+
# iodine -t 1 -w 1 echo.ru
|
10
|
+
#
|
11
|
+
# Benchmark with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients):
|
12
|
+
#
|
13
|
+
# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/
|
14
|
+
# wrk -c2000 -d5 -t12 http://localhost:3000/
|
15
|
+
#
|
16
|
+
# Test websocket echo using the browser. For example:
|
17
|
+
# ws = new WebSocket("ws://localhost:3000"); ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; ws.onclose = function(e) {console.log("closed")}; ws.onopen = function(e) {ws.send("hi");};
|
18
|
+
|
19
|
+
|
20
|
+
# A simple router - Checks for Websocket Upgrade and answers HTTP.
|
21
|
+
module MyHTTPRouter
|
22
|
+
# This is the HTTP response object according to the Rack specification.
|
23
|
+
HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html',
|
24
|
+
'Content-Length' => '32' },
|
25
|
+
['Please connect using websockets.']]
|
26
|
+
|
27
|
+
WS_RESPONSE = [0, {}, []].freeze
|
28
|
+
|
29
|
+
# this is function will be called by the Rack server (iodine) for every request.
|
30
|
+
def self.call env
|
31
|
+
# check if this is an upgrade request.
|
32
|
+
if(env['rack.upgrade?'.freeze] == :websocket)
|
33
|
+
env['rack.upgrade'.freeze] = WebsocketEcho
|
34
|
+
return WS_RESPONSE
|
35
|
+
end
|
36
|
+
# simply return the RESPONSE object, no matter what request was received.
|
37
|
+
HTTP_RESPONSE
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# A simple Websocket Callback Object.
|
42
|
+
module WebsocketEcho
|
43
|
+
# seng a message to new clients.
|
44
|
+
def on_open client
|
45
|
+
client.write "Welcome to our echo service!"
|
46
|
+
end
|
47
|
+
# send a message, letting the client know the server is suggunt down.
|
48
|
+
def on_shutdown client
|
49
|
+
client.write "Server shutting down. Goodbye."
|
50
|
+
end
|
51
|
+
# perform the echo
|
52
|
+
def on_message client, data
|
53
|
+
client.write data
|
54
|
+
end
|
55
|
+
extend self
|
56
|
+
end
|
57
|
+
|
58
|
+
# this function call links our HelloWorld application with Rack
|
59
|
+
run MyHTTPRouter
|
data/examples/etag.ru
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# This example uses Rack::ETag to allow for response caching.
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'iodine'
|
5
|
+
|
6
|
+
App = Proc.new do |env|
|
7
|
+
[200,
|
8
|
+
{ "Content-Type" => "text/html".freeze,
|
9
|
+
"Content-Length" => "16".freeze },
|
10
|
+
['Hello from Rack!'.freeze] ]
|
11
|
+
end
|
12
|
+
|
13
|
+
use Rack::ConditionalGet
|
14
|
+
use Rack::ETag, 'public'
|
15
|
+
|
16
|
+
run App
|
data/examples/hello.ru
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# This is a simple Hello World Rack application
|
2
|
+
#
|
3
|
+
# Running this application from the command line is easy with:
|
4
|
+
#
|
5
|
+
# iodine hello.ru
|
6
|
+
#
|
7
|
+
# Or, in single thread and single process:
|
8
|
+
#
|
9
|
+
# iodine -t 1 -w 1 hello.ru
|
10
|
+
#
|
11
|
+
# Benchmark with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients):
|
12
|
+
#
|
13
|
+
# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/
|
14
|
+
# wrk -c2000 -d5 -t12 http://localhost:3000/
|
15
|
+
|
16
|
+
module HelloWorld
|
17
|
+
# This is the HTTP response object according to the Rack specification.
|
18
|
+
RESPONSE = [200, { 'Content-Type' => 'text/html',
|
19
|
+
'Content-Length' => '12' }, [ 'Hello World!' ] ]
|
20
|
+
|
21
|
+
# this is function will be called by the Rack server (iodine) for every request.
|
22
|
+
def self.call env
|
23
|
+
# simply return the RESPONSE object, no matter what request was received.
|
24
|
+
RESPONSE
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# this function call links our HelloWorld application with Rack
|
29
|
+
run HelloWorld
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# This example implements a custom (noop) pub/sub engine according to the Iodine::PubSub::Engine specifications.
|
2
|
+
#
|
3
|
+
require 'uri'
|
4
|
+
require 'iodine'
|
5
|
+
|
6
|
+
# creates an example Pub/Sub Engine that merely reports any pub/sub events to the system's terminal
|
7
|
+
class PubSubReporter < Iodine::PubSub::Engine
|
8
|
+
def initialize
|
9
|
+
# make sure engine setup is complete
|
10
|
+
super
|
11
|
+
# register engine and make it into the new default
|
12
|
+
@target = Iodine::PubSub.default
|
13
|
+
Iodine::PubSub.default = self
|
14
|
+
Iodine::PubSub.attach self
|
15
|
+
end
|
16
|
+
def subscribe to, as = nil
|
17
|
+
puts "* Subscribing to \"#{to}\" (#{as || "exact match"})"
|
18
|
+
end
|
19
|
+
def unsubscribe to, as = nil
|
20
|
+
puts "* Unsubscribing to \"#{to}\" (#{as || "exact match"})"
|
21
|
+
end
|
22
|
+
def publish to, msg
|
23
|
+
puts "* Publishing to \"#{to}\": #{msg.to_s[0..12]}..."
|
24
|
+
# we need to forward the message to the actual engine (the previous default engine),
|
25
|
+
# or it will never be received by any Pub/Sub client.
|
26
|
+
@target.publish to, msg
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
PubSubReporter.new
|
31
|
+
|
32
|
+
# A simple router - Checks for Websocket Upgrade and answers HTTP.
|
33
|
+
module MyHTTPRouter
|
34
|
+
# This is the HTTP response object according to the Rack specification.
|
35
|
+
HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html',
|
36
|
+
'Content-Length' => '32' },
|
37
|
+
['Please connect using websockets.']]
|
38
|
+
|
39
|
+
WS_RESPONSE = [0, {}, []]
|
40
|
+
|
41
|
+
# this is function will be called by the Rack server (iodine) for every request.
|
42
|
+
def self.call env
|
43
|
+
# check if this is an upgrade request.
|
44
|
+
if(env['rack.upgrade?'.freeze])
|
45
|
+
puts "SSE connections will not be able te send data, just listen." if(env['rack.upgrade?'.freeze] == :sse)
|
46
|
+
env['rack.upgrade'.freeze] = PubSubClient.new(env['PATH_INFO'] && env['PATH_INFO'].length > 1 ? env['PATH_INFO'][1..-1] : "guest")
|
47
|
+
return WS_RESPONSE
|
48
|
+
end
|
49
|
+
# simply return the RESPONSE object, no matter what request was received.
|
50
|
+
HTTP_RESPONSE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# A simple Websocket Callback Object.
|
55
|
+
class PubSubClient
|
56
|
+
def initialize name
|
57
|
+
@name = name
|
58
|
+
end
|
59
|
+
# seng a message to new clients.
|
60
|
+
def on_open(client)
|
61
|
+
client.subscribe "chat"
|
62
|
+
# let everyone know we arrived
|
63
|
+
client.publish "chat", "#{@name} entered the chat."
|
64
|
+
end
|
65
|
+
# send a message, letting the client know the server is suggunt down.
|
66
|
+
def on_shutdown(client)
|
67
|
+
client.write "Server shutting down. Goodbye."
|
68
|
+
end
|
69
|
+
# perform the echo
|
70
|
+
def on_message(client, data)
|
71
|
+
client.publish "chat", "#{@name}: #{data}"
|
72
|
+
end
|
73
|
+
def on_close(client)
|
74
|
+
# let everyone know we left
|
75
|
+
client.publish "chat", "#{@name} left the chat."
|
76
|
+
# we don't need to unsubscribe, subscriptions are cleared automatically once the connection is closed.
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# this function call links our HelloWorld application with Rack
|
81
|
+
run MyHTTPRouter
|
data/examples/rack3.ru
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
# adjusted from the code suggested by @taku0 (GitHub issue #131)
|
4
|
+
require 'rack'
|
5
|
+
require 'iodine'
|
6
|
+
|
7
|
+
run { |env|
|
8
|
+
response = Rack::Response.new(nil, 204)
|
9
|
+
response.set_cookie('aaa', 'aaa')
|
10
|
+
response.set_cookie('bbb', 'bbb')
|
11
|
+
response.finish
|
12
|
+
}
|
data/examples/redis.ru
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# This example implements a Redis pub/sub engine according to the Iodine::PubSub::Engine specifications.
|
2
|
+
#
|
3
|
+
# Run this applications on two ports, in two terminals to see the synchronization is action:
|
4
|
+
#
|
5
|
+
# IODINE_REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3000 redis.ru
|
6
|
+
# IODINE_REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3030 redis.ru
|
7
|
+
#
|
8
|
+
# Or:
|
9
|
+
#
|
10
|
+
# iodine -t 1 -p 3000 redis.ru -redis redis://localhost:6379/0
|
11
|
+
# iodine -t 1 -p 3030 redis.ru -redis redis://localhost:6379/0
|
12
|
+
#
|
13
|
+
require 'iodine'
|
14
|
+
# initialize the Redis engine for each Iodine process.
|
15
|
+
if Iodine::DEFAULT_HTTP_ARGS[:redis_]
|
16
|
+
puts "* Redis support automatically detected."
|
17
|
+
else
|
18
|
+
puts "* No Redis, it's okay, pub/sub will support the process cluster."
|
19
|
+
end
|
20
|
+
|
21
|
+
# A simple router - Checks for Websocket Upgrade and answers HTTP.
|
22
|
+
module MyHTTPRouter
|
23
|
+
# This is the HTTP response object according to the Rack specification.
|
24
|
+
HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html',
|
25
|
+
'Content-Length' => '32' },
|
26
|
+
['Please connect using websockets.']]
|
27
|
+
|
28
|
+
WS_RESPONSE = [0, {}, []]
|
29
|
+
|
30
|
+
# this is function will be called by the Rack server (iodine) for every request.
|
31
|
+
def self.call env
|
32
|
+
# check if this is an upgrade request.
|
33
|
+
if(env['rack.upgrade?'.freeze])
|
34
|
+
puts "SSE connections will not be able te send data, just listen." if(env['rack.upgrade?'.freeze] == :sse)
|
35
|
+
env['rack.upgrade'.freeze] = WS_RedisPubSub.new(env['PATH_INFO'] && env['PATH_INFO'].length > 1 ? env['PATH_INFO'][1..-1] : "guest")
|
36
|
+
return WS_RESPONSE
|
37
|
+
end
|
38
|
+
# simply return the RESPONSE object, no matter what request was received.
|
39
|
+
HTTP_RESPONSE
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# A simple Websocket Callback Object.
|
44
|
+
class WS_RedisPubSub
|
45
|
+
def initialize name
|
46
|
+
@name = name
|
47
|
+
end
|
48
|
+
# seng a message to new clients.
|
49
|
+
def on_open client
|
50
|
+
client.subscribe "chat"
|
51
|
+
# let everyone know we arrived
|
52
|
+
client.publish "chat", "#{@name} entered the chat."
|
53
|
+
end
|
54
|
+
# send a message, letting the client know the server is suggunt down.
|
55
|
+
def on_shutdown client
|
56
|
+
client.write "Server shutting down. Goodbye."
|
57
|
+
end
|
58
|
+
# perform the echo
|
59
|
+
def on_message client, data
|
60
|
+
client.publish "chat", "#{@name}: #{data}"
|
61
|
+
end
|
62
|
+
def on_close client
|
63
|
+
# let everyone know we left
|
64
|
+
client.publish "chat", "#{@name} left the chat."
|
65
|
+
# we don't need to unsubscribe, subscriptions are cleared automatically once the connection is closed.
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# this function call links our HelloWorld application with Rack
|
70
|
+
run MyHTTPRouter
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'iodine'
|
2
|
+
require 'json'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module ShootoutApp
|
6
|
+
# the default HTTP response
|
7
|
+
def self.call(env)
|
8
|
+
if(env['rack.upgrade?'.freeze] == :websocket)
|
9
|
+
env['rack.upgrade'.freeze] = ShootoutApp
|
10
|
+
return [0, {}, []]
|
11
|
+
end
|
12
|
+
out = []
|
13
|
+
len = 0
|
14
|
+
out << "ENV:\n"
|
15
|
+
len += 5
|
16
|
+
env.each { |k, v| out << "#{k}: #{v}\n" ; len += out[-1].length }
|
17
|
+
request = Rack::Request.new(env)
|
18
|
+
out << "\nRequest Path: #{request.path_info}\n"
|
19
|
+
len += out[-1].length
|
20
|
+
unless request.params.empty?
|
21
|
+
out << "Params:\n"
|
22
|
+
len += out[-1].length
|
23
|
+
request.params.each { |k,v| out << "#{k}: #{v}\n" ; len += out[-1].length }
|
24
|
+
end
|
25
|
+
if(env['rack.input'])
|
26
|
+
env['rack.input'].rewind
|
27
|
+
out << "Body\n\n"
|
28
|
+
out << env['rack.input'].read
|
29
|
+
len += out[-1].length + 6
|
30
|
+
out << "\n\nBody Length: #{out[-1].length}\n\n"
|
31
|
+
len += out[-1].length
|
32
|
+
end
|
33
|
+
[200, { 'Content-Length' => len.to_s, 'Content-Type' => 'text/plain; charset=UTF-8;' }, out]
|
34
|
+
end
|
35
|
+
# We'll base the shootout on the internal Pub/Sub service.
|
36
|
+
# It's slower than writing to every socket a pre-parsed message, but it's closer
|
37
|
+
# to real-life implementations.
|
38
|
+
def self.on_open client
|
39
|
+
client.subscribe(:shootout_b, as: :binary) # { |ch, msg| client.write(msg)}
|
40
|
+
client.subscribe(:shootout) # { |ch, msg| client.write(msg)}
|
41
|
+
end
|
42
|
+
def self.on_message client, data
|
43
|
+
if data[0] == 'b' # binary
|
44
|
+
client.publish :shootout_b, data
|
45
|
+
data[0] = 'r'
|
46
|
+
client.write data
|
47
|
+
return
|
48
|
+
end
|
49
|
+
cmd, payload = JSON(data).values_at('type', 'payload')
|
50
|
+
if cmd == 'echo'
|
51
|
+
client.write({type: 'echo', payload: payload}.to_json)
|
52
|
+
else
|
53
|
+
client.publish :shootout, {type: 'broadcast', payload: payload}.to_json
|
54
|
+
client.write({type: "broadcastResult", payload: payload}.to_json)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
run ShootoutApp
|
60
|
+
#
|
61
|
+
# def cycle
|
62
|
+
# puts `websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --server-type binary --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000`
|
63
|
+
# sleep(4)
|
64
|
+
# puts `wrk -c4000 -d15 -t2 http://localhost:3000/`
|
65
|
+
# true
|
66
|
+
# end
|
67
|
+
# sleep(10) while cycle
|
68
|
+
|
69
|
+
# # Used when debugging:
|
70
|
+
# ON_IDLE = proc { Iodine::Base.db_print_protected_objects ; Iodine.on_idle(&ON_IDLE) }
|
71
|
+
# ON_IDLE.call
|
72
|
+
# Iodine.on_shutdown { Iodine::Base.db_print_protected_objects }
|
73
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This is a WebSocket / SSE notification example application.
|
4
|
+
#
|
5
|
+
# In this example, WebSocket sub-protocols are explored.
|
6
|
+
#
|
7
|
+
# Running this application from the command line is easy with:
|
8
|
+
#
|
9
|
+
# iodine
|
10
|
+
#
|
11
|
+
# Or, in a single thread and a single process:
|
12
|
+
#
|
13
|
+
# iodine -t 1 -w 1
|
14
|
+
#
|
15
|
+
# Test using:
|
16
|
+
#
|
17
|
+
# var subprotocol = "echo"; // or "chat"
|
18
|
+
# ws = new WebSocket("ws://localhost:3000/Mitchel", subprotocol);
|
19
|
+
# ws.onmessage = function(e) { console.log(e.data); };
|
20
|
+
# ws.onclose = function(e) { console.log("Closed"); };
|
21
|
+
# ws.onopen = function(e) { e.target.send("Yo!"); };
|
22
|
+
|
23
|
+
|
24
|
+
# Chat clients connect with the "chat" sub-protocol.
|
25
|
+
class ChatClient
|
26
|
+
def on_open client
|
27
|
+
@nickname = client.env['PATH_INFO'].to_s.split('/')[1] || "Guest"
|
28
|
+
client.subscribe :chat
|
29
|
+
client.publish :chat , "#{@nickname} joined the chat."
|
30
|
+
end
|
31
|
+
def on_close client
|
32
|
+
client.publish :chat , "#{@nickname} left the chat."
|
33
|
+
end
|
34
|
+
def on_shutdown client
|
35
|
+
client.write "Server is shutting down... disconnecting all clients. Goodbye."
|
36
|
+
end
|
37
|
+
def on_message client, message
|
38
|
+
client.publish :chat , "#{@nickname}: #{message}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Echo clients connect with the "echo" sub-protocol.
|
43
|
+
class EchoClient
|
44
|
+
def on_open client
|
45
|
+
client.write "You established an echo connection."
|
46
|
+
end
|
47
|
+
def on_shutdown client
|
48
|
+
client.write "Server is shutting down... goodbye."
|
49
|
+
end
|
50
|
+
def on_message client, message
|
51
|
+
client.write message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Rack application module
|
56
|
+
module APP
|
57
|
+
# the allowed protocols
|
58
|
+
CHAT_PROTOCOL_NAME = "chat"
|
59
|
+
ECHO_PROTOCOL_NAME = "echo"
|
60
|
+
PROTOCOLS =[CHAT_PROTOCOL_NAME, ECHO_PROTOCOL_NAME]
|
61
|
+
|
62
|
+
# the Rack application
|
63
|
+
def call env
|
64
|
+
return [200, {}, ["Hello World"]] unless env["rack.upgrade?"]
|
65
|
+
protocol = select_protocol(env)
|
66
|
+
case(protocol)
|
67
|
+
when CHAT_PROTOCOL_NAME
|
68
|
+
env["rack.upgrade"] = ChatClient.new
|
69
|
+
[101, { "Sec-Websocket-Protocol" => protocol }, []]
|
70
|
+
when ECHO_PROTOCOL_NAME
|
71
|
+
env["rack.upgrade"] = EchoClient.new
|
72
|
+
[101, { "Sec-Websocket-Protocol" => protocol }, []]
|
73
|
+
else
|
74
|
+
[400, {}, ["Unsupported protocol specified"]]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def select_protocol(env)
|
79
|
+
request_protocols = env["HTTP_SEC_WEBSOCKET_PROTOCOL"]
|
80
|
+
unless request_protocols.nil?
|
81
|
+
request_protocols = request_protocols.split(/,\s?/) if request_protocols.is_a?(String)
|
82
|
+
request_protocols.detect { |request_protocol| PROTOCOLS.include? request_protocol }
|
83
|
+
end # either `nil` or the result of `request_protocols.detect` are returned
|
84
|
+
end
|
85
|
+
|
86
|
+
# make functions availble as singleton module
|
87
|
+
extend self
|
88
|
+
end
|
89
|
+
|
90
|
+
run APP
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#! ruby
|
2
|
+
|
3
|
+
# A raw TCP/IP client example using iodine.
|
4
|
+
#
|
5
|
+
# The client will connect to a remote server and send a simple HTTP/1.1 GET request.
|
6
|
+
#
|
7
|
+
# Once some data was recieved, a delayed closure and shutdown signal will be sent to iodine.
|
8
|
+
|
9
|
+
# use a secure connection?
|
10
|
+
USE_TLS = true
|
11
|
+
|
12
|
+
# remote server details
|
13
|
+
$port = USE_TLS ? 443 : 80
|
14
|
+
$address = "google.com"
|
15
|
+
|
16
|
+
|
17
|
+
# require iodine
|
18
|
+
require 'iodine'
|
19
|
+
|
20
|
+
# Iodine runtime settings
|
21
|
+
Iodine.threads = 1
|
22
|
+
Iodine.workers = 1
|
23
|
+
Iodine.verbosity = 3 # warnings only
|
24
|
+
|
25
|
+
|
26
|
+
# a client callback handler
|
27
|
+
module Client
|
28
|
+
|
29
|
+
def self.on_open(client)
|
30
|
+
# Set a connection timeout
|
31
|
+
client.timeout = 10
|
32
|
+
# subscribe to the chat channel.
|
33
|
+
puts "* Sending request..."
|
34
|
+
client.write "GET / HTTP/1.1\r\nHost: #{$address}\r\n\r\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.on_message(client, data)
|
38
|
+
# publish the data we received
|
39
|
+
STDOUT.write data
|
40
|
+
# close the client after a second... we're not really parsing anything, so it's a guess.
|
41
|
+
Iodine.run_after(1000) { client.close }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.on_close(client)
|
45
|
+
# stop iodine
|
46
|
+
Iodine.stop
|
47
|
+
puts "Done."
|
48
|
+
end
|
49
|
+
|
50
|
+
# returns the callback object (self).
|
51
|
+
def self.call
|
52
|
+
self
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
if(USE_TLS)
|
59
|
+
tls = Iodine::TLS.new
|
60
|
+
tls.on_protocol("http/1.1") { Client }
|
61
|
+
end
|
62
|
+
# we use can both the `handler` keyword or a block, anything that answers #call.
|
63
|
+
Iodine.connect(address: $address, port: $port, handler: Client, tls: tls)
|
64
|
+
|
65
|
+
# start the iodine reactor
|
66
|
+
Iodine.start
|
@@ -0,0 +1,14 @@
|
|
1
|
+
app = proc do |env|
|
2
|
+
request = Rack::Request.new(env)
|
3
|
+
if request.path_info == '/source'.freeze
|
4
|
+
[200, { 'X-Sendfile' => File.expand_path(__FILE__), 'Content-Type' => 'text/plain'}, []]
|
5
|
+
elsif request.path_info == '/file'.freeze
|
6
|
+
[200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text.' }, File.open(__FILE__)]
|
7
|
+
else
|
8
|
+
[200, { 'Content-Type' => 'text/html',
|
9
|
+
'Content-Length' => request.path_info.length.to_s },
|
10
|
+
[request.path_info]]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
run app
|