iodine 0.7.60 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -21
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -148
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
@@ -1,81 +0,0 @@
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 DELETED
@@ -1,12 +0,0 @@
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 DELETED
@@ -1,70 +0,0 @@
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
data/examples/shootout.ru DELETED
@@ -1,73 +0,0 @@
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
-
@@ -1,90 +0,0 @@
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
@@ -1,66 +0,0 @@
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
@@ -1,14 +0,0 @@
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