isomorfeus-iodine 0.7.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +32 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1038 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +44 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/config.ru +56 -0
  25. data/examples/echo.ru +59 -0
  26. data/examples/hello.ru +29 -0
  27. data/examples/pubsub_engine.ru +81 -0
  28. data/examples/redis.ru +70 -0
  29. data/examples/shootout.ru +73 -0
  30. data/examples/sub-protocols.ru +90 -0
  31. data/examples/tcp_client.rb +66 -0
  32. data/examples/x-sendfile.ru +14 -0
  33. data/exe/iodine +277 -0
  34. data/ext/iodine/extconf.rb +109 -0
  35. data/ext/iodine/fio.c +11985 -0
  36. data/ext/iodine/fio.h +6373 -0
  37. data/ext/iodine/fio_cli.c +431 -0
  38. data/ext/iodine/fio_cli.h +189 -0
  39. data/ext/iodine/fio_json_parser.h +687 -0
  40. data/ext/iodine/fio_siphash.c +157 -0
  41. data/ext/iodine/fio_siphash.h +37 -0
  42. data/ext/iodine/fio_tls.h +129 -0
  43. data/ext/iodine/fio_tls_missing.c +649 -0
  44. data/ext/iodine/fio_tls_openssl.c +1056 -0
  45. data/ext/iodine/fio_tmpfile.h +50 -0
  46. data/ext/iodine/fiobj.h +44 -0
  47. data/ext/iodine/fiobj4fio.h +21 -0
  48. data/ext/iodine/fiobj_ary.c +333 -0
  49. data/ext/iodine/fiobj_ary.h +139 -0
  50. data/ext/iodine/fiobj_data.c +1185 -0
  51. data/ext/iodine/fiobj_data.h +167 -0
  52. data/ext/iodine/fiobj_hash.c +409 -0
  53. data/ext/iodine/fiobj_hash.h +176 -0
  54. data/ext/iodine/fiobj_json.c +622 -0
  55. data/ext/iodine/fiobj_json.h +68 -0
  56. data/ext/iodine/fiobj_mem.h +71 -0
  57. data/ext/iodine/fiobj_mustache.c +317 -0
  58. data/ext/iodine/fiobj_mustache.h +62 -0
  59. data/ext/iodine/fiobj_numbers.c +344 -0
  60. data/ext/iodine/fiobj_numbers.h +127 -0
  61. data/ext/iodine/fiobj_str.c +433 -0
  62. data/ext/iodine/fiobj_str.h +172 -0
  63. data/ext/iodine/fiobject.c +620 -0
  64. data/ext/iodine/fiobject.h +654 -0
  65. data/ext/iodine/hpack.h +1923 -0
  66. data/ext/iodine/http.c +2754 -0
  67. data/ext/iodine/http.h +1002 -0
  68. data/ext/iodine/http1.c +912 -0
  69. data/ext/iodine/http1.h +29 -0
  70. data/ext/iodine/http1_parser.h +873 -0
  71. data/ext/iodine/http_internal.c +1278 -0
  72. data/ext/iodine/http_internal.h +237 -0
  73. data/ext/iodine/http_mime_parser.h +350 -0
  74. data/ext/iodine/iodine.c +1430 -0
  75. data/ext/iodine/iodine.h +63 -0
  76. data/ext/iodine/iodine_caller.c +218 -0
  77. data/ext/iodine/iodine_caller.h +27 -0
  78. data/ext/iodine/iodine_connection.c +933 -0
  79. data/ext/iodine/iodine_connection.h +55 -0
  80. data/ext/iodine/iodine_defer.c +420 -0
  81. data/ext/iodine/iodine_defer.h +6 -0
  82. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  83. data/ext/iodine/iodine_helpers.c +282 -0
  84. data/ext/iodine/iodine_helpers.h +12 -0
  85. data/ext/iodine/iodine_http.c +1171 -0
  86. data/ext/iodine/iodine_http.h +23 -0
  87. data/ext/iodine/iodine_json.c +302 -0
  88. data/ext/iodine/iodine_json.h +6 -0
  89. data/ext/iodine/iodine_mustache.c +567 -0
  90. data/ext/iodine/iodine_mustache.h +6 -0
  91. data/ext/iodine/iodine_pubsub.c +580 -0
  92. data/ext/iodine/iodine_pubsub.h +26 -0
  93. data/ext/iodine/iodine_rack_io.c +281 -0
  94. data/ext/iodine/iodine_rack_io.h +20 -0
  95. data/ext/iodine/iodine_store.c +142 -0
  96. data/ext/iodine/iodine_store.h +20 -0
  97. data/ext/iodine/iodine_tcp.c +346 -0
  98. data/ext/iodine/iodine_tcp.h +13 -0
  99. data/ext/iodine/iodine_tls.c +261 -0
  100. data/ext/iodine/iodine_tls.h +13 -0
  101. data/ext/iodine/mustache_parser.h +1546 -0
  102. data/ext/iodine/redis_engine.c +957 -0
  103. data/ext/iodine/redis_engine.h +79 -0
  104. data/ext/iodine/resp_parser.h +317 -0
  105. data/ext/iodine/websocket_parser.h +505 -0
  106. data/ext/iodine/websockets.c +735 -0
  107. data/ext/iodine/websockets.h +185 -0
  108. data/isomorfeus-iodine.gemspec +42 -0
  109. data/lib/iodine/connection.rb +61 -0
  110. data/lib/iodine/json.rb +42 -0
  111. data/lib/iodine/mustache.rb +113 -0
  112. data/lib/iodine/pubsub.rb +55 -0
  113. data/lib/iodine/rack_utils.rb +43 -0
  114. data/lib/iodine/tls.rb +16 -0
  115. data/lib/iodine/version.rb +3 -0
  116. data/lib/iodine.rb +274 -0
  117. data/lib/rack/handler/iodine.rb +33 -0
  118. data/logo.png +0 -0
  119. metadata +271 -0
@@ -0,0 +1,92 @@
1
+ # This is a task scheduling WebSocket push example application.
2
+ #
3
+ # Benchmark HTTPP with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients):
4
+ #
5
+ # ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/
6
+ # wrk -c2000 -d5 -t12 http://localhost:3000/
7
+ #
8
+ # Test websocket tasks using the browser. For example:
9
+ # ws = new WebSocket("ws://localhost:3000/userID"); ws.onmessage = function(e) {console.log(e.data);}; ws.onclose = function(e) {console.log("closed")};
10
+ # ws.onopen = function(e) {ws.send(JSON.stringify({'task': 'echo', 'data': 'Hello!'}));};
11
+ require 'iodine'
12
+ require 'json'
13
+
14
+ TASK_PUBLISHING_ENGINE = Iodine::PubSub::PROCESS
15
+
16
+ # This module handles tasks and send them back to the frontend
17
+ module TaskHandler
18
+ def echo msg
19
+ msg = Iodine::JSON.parse(msg, symbolize_names: true)
20
+ publish_to = msg.delete(:from)
21
+ Iodine.publish(publish_to, msg.to_json, TASK_PUBLISHING_ENGINE) if publish_to
22
+ puts "performed 'echo' task"
23
+ rescue => e
24
+ puts "JSON task message error? #{e.message} - under attack?"
25
+ end
26
+
27
+ def add msg
28
+ msg = Iodine::JSON.parse(msg, symbolize_names: true)
29
+ raise "addition task requires an array of numbers" unless msg[:data].is_a?(Array)
30
+ msg[:data] = msg[:data].inject(0){|sum,x| sum + x }
31
+ publish_to = msg.delete(:from)
32
+ Iodine.publish(publish_to, msg.to_json, TASK_PUBLISHING_ENGINE) if publish_to
33
+ puts "performed 'add' task"
34
+ rescue => e
35
+ puts
36
+ "JSON task message error? #{e.message} - under attack?"
37
+ end
38
+
39
+ def listen2tasks
40
+ Iodine.subscribe(:echo) {|ch,msg| TaskHandler.echo(msg) }
41
+ Iodine.subscribe(:add) {|ch,msg| TaskHandler.add(msg) }
42
+ end
43
+
44
+ extend self
45
+ end
46
+
47
+ module WebsocketClient
48
+ def on_open client
49
+ # Pub/Sub directly to the client (or use a block to process the messages)
50
+ client.subscribe client.env['PATH_INFO'.freeze]
51
+ end
52
+ def on_message client, data
53
+ # Strings and symbol channel names are equivalent.
54
+ msg = Iodine::JSON.parse(data, symbolize_names: true)
55
+ raise "no valid task" unless ["echo".freeze, "add".freeze].include? msg[:task]
56
+ msg[:from] = client.env['PATH_INFO'.freeze]
57
+ client.publish msg[:task], msg.to_json, TASK_PUBLISHING_ENGINE
58
+ rescue => e
59
+ puts "JSON message error? #{e.message}\n\t#{data}\n\t#{msg}"
60
+ end
61
+ extend self
62
+ end
63
+
64
+ APP = Proc.new do |env|
65
+ if env['rack.upgrade?'.freeze] == :websocket
66
+ env['rack.upgrade'.freeze] = WebsocketClient
67
+ [0,{}, []] # It's possible to set cookies for the response.
68
+ elsif env['rack.upgrade?'.freeze] == :sse
69
+ puts "SSE connections can only receive data from the server, the can't write."
70
+ env['rack.upgrade'.freeze] = WebsocketClient
71
+ [0,{}, []] # It's possible to set cookies for the response.
72
+ else
73
+ [200, {"Content-Type" => "text/plain"}, ["Send messages with WebSockets using JSON.\ni.e.: {\"task\":\"add\", \"data\":[1,2]}"]]
74
+ end
75
+ end
76
+
77
+ # test automatically for Redis extensions.
78
+ if(Iodine::PubSub.default.is_a? Iodine::PubSub::Redis)
79
+ TASK_PUBLISHING_ENGINE = Iodine::PubSub.default
80
+ if(ARGV.include? "worker")
81
+ TaskHandler.listen2tasks
82
+ Iodine.workers = 1
83
+ Iodine.threads = 16 if Iodine.threads == 0
84
+ Iodine.start
85
+ exit(0)
86
+ end
87
+ else
88
+ TaskHandler.listen2tasks
89
+ end
90
+
91
+ # # or in config.ru
92
+ run APP
@@ -0,0 +1,56 @@
1
+ # This is a WebSocket / SSE notification broadcasting application.
2
+ #
3
+ # Running this application from the command line is easy with:
4
+ #
5
+ # iodine
6
+ #
7
+ # Or, in single thread and single process:
8
+ #
9
+ # iodine -t 1 -w 1
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
+
17
+ # A simple router - Checks for Websocket Upgrade and answers HTTP.
18
+ module MyHTTPRouter
19
+ # This is the HTTP response object according to the Rack specification.
20
+ HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html',
21
+ 'Content-Length' => '77' },
22
+ ['Please connect using WebSockets or SSE (send messages only using WebSockets).']]
23
+
24
+ WS_RESPONSE = [0, {}, []].freeze
25
+
26
+ # this is function will be called by the Rack server (iodine) for every request.
27
+ def self.call env
28
+ # check if this is an upgrade request (WebsSocket / SSE).
29
+ if(env['rack.upgrade?'.freeze])
30
+ env['rack.upgrade'.freeze] = BroadcastClient
31
+ return WS_RESPONSE
32
+ end
33
+ # simply return the RESPONSE object, no matter what request was received.
34
+ HTTP_RESPONSE
35
+ end
36
+ end
37
+
38
+ # A simple Websocket Callback Object.
39
+ module BroadcastClient
40
+ # seng a message to new clients.
41
+ def on_open client
42
+ client.subscribe :broadcast
43
+ end
44
+ # send a message, letting the client know the server is suggunt down.
45
+ def on_shutdown client
46
+ client.write "Server shutting down. Goodbye."
47
+ end
48
+ # perform the echo
49
+ def on_message client, data
50
+ client.publish :broadcast, data
51
+ end
52
+ extend self
53
+ end
54
+
55
+ # this function call links our HelloWorld application with Rack
56
+ run MyHTTPRouter
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/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/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