iodine 0.1.21 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +9 -2
  5. data/README.md +232 -179
  6. data/Rakefile +13 -1
  7. data/bin/config.ru +63 -0
  8. data/bin/console +6 -0
  9. data/bin/echo +42 -32
  10. data/bin/http-hello +62 -0
  11. data/bin/http-playground +124 -0
  12. data/bin/playground +62 -0
  13. data/bin/poc/Gemfile.lock +23 -0
  14. data/bin/poc/README.md +37 -0
  15. data/bin/poc/config.ru +66 -0
  16. data/bin/poc/gemfile +1 -0
  17. data/bin/poc/www/index.html +57 -0
  18. data/bin/raw-rbhttp +35 -0
  19. data/bin/raw_broadcast +66 -0
  20. data/bin/test_with_faye +40 -0
  21. data/bin/ws-broadcast +108 -0
  22. data/bin/ws-echo +108 -0
  23. data/exe/iodine +59 -0
  24. data/ext/iodine/base64.c +264 -0
  25. data/ext/iodine/base64.h +72 -0
  26. data/ext/iodine/bscrypt-common.h +109 -0
  27. data/ext/iodine/bscrypt.h +49 -0
  28. data/ext/iodine/extconf.rb +41 -0
  29. data/ext/iodine/hex.c +123 -0
  30. data/ext/iodine/hex.h +70 -0
  31. data/ext/iodine/http.c +200 -0
  32. data/ext/iodine/http.h +128 -0
  33. data/ext/iodine/http1.c +402 -0
  34. data/ext/iodine/http1.h +56 -0
  35. data/ext/iodine/http1_simple_parser.c +473 -0
  36. data/ext/iodine/http1_simple_parser.h +59 -0
  37. data/ext/iodine/http_request.h +128 -0
  38. data/ext/iodine/http_response.c +1606 -0
  39. data/ext/iodine/http_response.h +393 -0
  40. data/ext/iodine/http_response_http1.h +374 -0
  41. data/ext/iodine/iodine_core.c +641 -0
  42. data/ext/iodine/iodine_core.h +70 -0
  43. data/ext/iodine/iodine_http.c +615 -0
  44. data/ext/iodine/iodine_http.h +19 -0
  45. data/ext/iodine/iodine_websocket.c +430 -0
  46. data/ext/iodine/iodine_websocket.h +21 -0
  47. data/ext/iodine/libasync.c +552 -0
  48. data/ext/iodine/libasync.h +117 -0
  49. data/ext/iodine/libreact.c +347 -0
  50. data/ext/iodine/libreact.h +244 -0
  51. data/ext/iodine/libserver.c +912 -0
  52. data/ext/iodine/libserver.h +435 -0
  53. data/ext/iodine/libsock.c +950 -0
  54. data/ext/iodine/libsock.h +478 -0
  55. data/ext/iodine/misc.c +181 -0
  56. data/ext/iodine/misc.h +76 -0
  57. data/ext/iodine/random.c +193 -0
  58. data/ext/iodine/random.h +48 -0
  59. data/ext/iodine/rb-call.c +127 -0
  60. data/ext/iodine/rb-call.h +60 -0
  61. data/ext/iodine/rb-libasync.h +79 -0
  62. data/ext/iodine/rb-rack-io.c +389 -0
  63. data/ext/iodine/rb-rack-io.h +17 -0
  64. data/ext/iodine/rb-registry.c +213 -0
  65. data/ext/iodine/rb-registry.h +33 -0
  66. data/ext/iodine/sha1.c +359 -0
  67. data/ext/iodine/sha1.h +85 -0
  68. data/ext/iodine/sha2.c +825 -0
  69. data/ext/iodine/sha2.h +138 -0
  70. data/ext/iodine/siphash.c +136 -0
  71. data/ext/iodine/siphash.h +15 -0
  72. data/ext/iodine/spnlock.h +235 -0
  73. data/ext/iodine/websockets.c +696 -0
  74. data/ext/iodine/websockets.h +120 -0
  75. data/ext/iodine/xor-crypt.c +189 -0
  76. data/ext/iodine/xor-crypt.h +107 -0
  77. data/iodine.gemspec +25 -18
  78. data/lib/iodine.rb +57 -58
  79. data/lib/iodine/http.rb +0 -189
  80. data/lib/iodine/protocol.rb +36 -245
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/rack/handler/iodine.rb +145 -2
  83. metadata +115 -37
  84. data/bin/core_http_test +0 -51
  85. data/bin/em playground +0 -56
  86. data/bin/hello_world +0 -75
  87. data/bin/setup +0 -7
  88. data/lib/iodine/client.rb +0 -5
  89. data/lib/iodine/core.rb +0 -102
  90. data/lib/iodine/core_init.rb +0 -143
  91. data/lib/iodine/http/hpack.rb +0 -553
  92. data/lib/iodine/http/http1.rb +0 -251
  93. data/lib/iodine/http/http2.rb +0 -507
  94. data/lib/iodine/http/rack_support.rb +0 -108
  95. data/lib/iodine/http/request.rb +0 -462
  96. data/lib/iodine/http/response.rb +0 -474
  97. data/lib/iodine/http/session.rb +0 -143
  98. data/lib/iodine/http/websocket_client.rb +0 -335
  99. data/lib/iodine/http/websocket_handler.rb +0 -101
  100. data/lib/iodine/http/websockets.rb +0 -336
  101. data/lib/iodine/io.rb +0 -56
  102. data/lib/iodine/logging.rb +0 -46
  103. data/lib/iodine/settings.rb +0 -158
  104. data/lib/iodine/ssl_connector.rb +0 -48
  105. data/lib/iodine/timers.rb +0 -95
@@ -1,251 +0,0 @@
1
- module Iodine
2
- module Http
3
- class Http1 < ::Iodine::Protocol
4
- def on_open
5
- set_timeout 1
6
- @refuse_requests = false
7
- @parser = {}
8
- end
9
- def on_message data
10
- return if @refuse_requests
11
- @http2_pri_review ||= ( ::Iodine::Http.http2 && ::Iodine::Http::Http2.pre_handshake(self, data) && (return true) ) || true
12
-
13
- data = ::StringIO.new data
14
- until data.eof?
15
- request = (@request ||= ::Iodine::Http::Request.new(self))
16
- unless request[:method]
17
- l = data.gets.strip
18
- if l.bytesize > 16_384
19
- write "HTTP/1.0 414 Request-URI Too Long\r\ncontent-length: 20\r\n\r\nRequest URI too Long".freeze
20
- Iodine.warn "Http/1 URI too long, closing connection.".freeze
21
- return close
22
- end
23
- next if l.empty?
24
- request[:method], request[:query], request[:version] = l.split(/[\s]+/.freeze, 3)
25
- return (Iodine.warn('Http1 Protocol Error, closing connection.'.freeze, l, request) && close) unless request[:method] =~ HTTP_METHODS_REGEXP
26
- request[:version] = (request[:version] || '1.1'.freeze).match(/[\d\.]+/.freeze)[0]
27
- request[:time_recieved] = Iodine.time
28
- end
29
- until request[:headers_complete] || (l = data.gets).nil?
30
- # if l.bytesize > 16_384
31
- # write "HTTP/1.0 413 Entity Too Large\r\ncontent-length: 16\r\n\r\nEntity Too Large".freeze
32
- # Iodine.warn "Http/1 Header data too large, closing connection.".freeze
33
- # return close
34
- # end
35
- if l.include? ':'.freeze
36
- # n = l.slice!(0, l.index(':')); l.slice! 0
37
- # n.strip! ; n.downcase!; n.freeze
38
- # request[n] ? (request[n].is_a?(Array) ? (request[n] << l) : request[n] = [request[n], l ]) : (request[n] = l)
39
- request[:headers_size] ||= 0
40
- request[:headers_size] += l.bytesize
41
- if request.length > 2096 || request[:headers_size] > 262_144
42
- write "HTTP/1.0 431 Request Header Fields Too Large\r\ncontent-length: 31\r\n\r\nRequest Header Fields Too Large".freeze
43
- return (Iodine.warn('Http1 header overloading, closing connection.'.freeze) && close)
44
- end
45
- l = l.strip.split(/:[\s]?/.freeze, 2)
46
- l[0].strip! ; l[0].downcase!;
47
- request[l[0]] ? (request[l[0]].is_a?(Array) ? (request[l[0]] << l[1]) : request[l[0]] = [request[l[0]], l[1] ]) : (request[l[0]] = l[1])
48
- elsif l =~ /^[\r]?\n/.freeze
49
- request[:headers_complete] = true
50
- else
51
- #protocol error
52
- Iodine.warn 'Protocol Error, closing connection.'.freeze
53
- return close
54
- end
55
- end
56
- next unless request[:headers_complete]
57
- if request['transfer-coding'.freeze] == 'chunked'.freeze
58
- until request[:body_complete]
59
- # add mid chunk logic here
60
- if @parser[:length].to_i == 0
61
- chunk = data.gets
62
- return false unless chunk
63
- @parser[:length] = chunk.to_i(16)
64
- return (Iodine.warn('Protocol Error, closing connection.'.freeze) && close) unless @parser[:length]
65
- request[:body_complete] = true && break if @parser[:length] == 0
66
- @parser[:act_length] = 0
67
- request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze)
68
- end
69
- chunk = data.read(@parser[:length] - @parser[:act_length])
70
- return false unless chunk
71
- request[:body] << chunk
72
- @parser[:act_length] += chunk.bytesize
73
- (@parser[:act_length] = @parser[:length] = 0) && (data.gets) if @parser[:act_length] >= @parser[:length]
74
- return if bad_body_size?
75
- end
76
- elsif request['content-length'.freeze] && request['content-length'.freeze].to_i != 0
77
- until request[:body_complete]
78
- request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze)
79
- packet = data.read(request['content-length'.freeze].to_i - request[:body].size)
80
- return false unless packet
81
- request[:body] << packet
82
- return if bad_body_size?
83
- request[:body_complete] = true if request['content-length'.freeze].to_i - request[:body].size <= 0
84
- end
85
- elsif request['content-type'.freeze]
86
- until request[:body_complete]
87
- Iodine.warn 'Body type protocol error.'.freeze unless request[:body]
88
- line = data.gets
89
- return false unless line
90
- (request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze) ) << line
91
- return if bad_body_size?
92
- request[:body_complete] = true if line =~ EOHEADERS
93
- end
94
- else
95
- request[:body_complete] = true
96
- end
97
- (@request = ::Iodine::Http::Request.new(self)) && ( (::Iodine::Http.http2 && ::Iodine::Http::Http2.handshake(request, self, data)) || dispatch(request, data) ) if request.delete :body_complete
98
- end
99
- end
100
-
101
- def send_response response
102
- return false if response.headers.frozen?
103
-
104
- body = response.extract_body
105
- request = response.request
106
- headers = response.headers
107
-
108
- headers['content-length'.freeze] ||= body.size if body
109
-
110
- keep_alive = response.keep_alive
111
- if (request[:version].to_f > 1 && request['connection'.freeze].nil?) || request['connection'.freeze].to_s =~ /ke/i.freeze || (headers['connection'.freeze] && headers['connection'.freeze] =~ /^ke/i.freeze)
112
- keep_alive = true
113
- headers['connection'.freeze] ||= 'keep-alive'.freeze
114
- headers['keep-alive'.freeze] ||= "timeout=#{(@timeout ||= 3).to_s}".freeze
115
- else
116
- headers['connection'.freeze] ||= 'close'.freeze
117
- end
118
- send_headers response
119
- return log_finished(response) && (body && body.close) if request.head? || body.nil?
120
-
121
- until body.eof?
122
- written = write(body.read 65_536, Thread.current[:write_buffer])
123
- return Iodine.warn("Http/1 couldn't send response because connection was lost.".freeze) && body.close unless written
124
- response.bytes_written += written
125
- end
126
- body.close
127
- close unless keep_alive
128
- log_finished response
129
- end
130
- def stream_response response, finish = false
131
- set_timeout 15
132
- unless response.headers.frozen?
133
- response['transfer-encoding'.freeze] = 'chunked'.freeze
134
- response.headers['connection'.freeze] = 'close'.freeze
135
- send_headers response
136
- @refuse_requests = true
137
- end
138
- return if response.request.head?
139
- body = response.extract_body
140
- until body.eof?
141
- written = stream_data(body.read 65_536, Thread.current[:write_buffer])
142
- return Iodine.warn("Http/1 couldn't send response because connection was lost.".freeze) && body.close unless written
143
- response.bytes_written += written
144
- end if body
145
- if finish
146
- response.bytes_written += stream_data(''.freeze)
147
- log_finished response
148
- close unless response.keep_alive
149
- end
150
- body.close if body
151
- true
152
- end
153
-
154
- def go_away error_code
155
- return false if @io.closed?
156
- close
157
- end
158
-
159
- protected
160
-
161
- HTTP_METHODS = %w{GET HEAD POST PUT DELETE TRACE OPTIONS CONNECT PATCH}
162
- HTTP_METHODS_REGEXP = /\A#{HTTP_METHODS.join('|')}/i
163
-
164
- def dispatch request, data
165
- return data.string.clear if @io.closed? || @refuse_requests
166
- ::Iodine::Http::Request.parse request
167
- #check for server-responses
168
- case request[:method]
169
- when 'TRACE'.freeze
170
- close
171
- data.string.clear
172
- return false
173
- when 'OPTIONS'.freeze
174
- response = ::Iodine::Http::Response.new request
175
- response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'.freeze
176
- response['access-control-allow-origin'.freeze] = '*'.freeze
177
- response['content-length'.freeze] = 0
178
- send_response response
179
- return false
180
- end
181
- response = ::Iodine::Http::Response.new request
182
- begin
183
- if request.websocket?
184
- @refuse_requests = true
185
- ::Iodine::Http::Websockets.handshake request, response, ::Iodine::Http.on_websocket.call(request, response)
186
- else
187
- ret = ::Iodine::Http.on_http.call(request, response)
188
- if ret.is_a?(String)
189
- response << ret
190
- elsif ret == false
191
- response.clear && (response.status = 404) && (response << ::Iodine::Http::Response::STATUS_CODES[404])
192
- end
193
- end
194
- send_response response
195
- rescue => e
196
- Iodine.error e
197
- send_response ::Iodine::Http::Response.new(request, 500, {}, ::Iodine::Http::Response::STATUS_CODES[500])
198
- end
199
- end
200
-
201
- def send_headers response
202
- return false if response.headers.frozen?
203
- request = response.request
204
- headers = response.headers
205
-
206
- # response['date'.freeze] ||= request[:time_recieved].httpdate
207
- (out = (Thread.current[:headers_buffer] ||= String.new)).clear
208
-
209
- out << "HTTP/#{request[:version]} #{response.status} #{::Iodine::Http::Response::STATUS_CODES[response.status] || 'unknown'}\r\n"
210
-
211
- out << request[:time_recieved].utc.strftime("Date: %a, %d %b %Y %H:%M:%S GMT\r\n".freeze) unless headers['date'.freeze]
212
-
213
- # unless @headers['connection'] || (@request[:version].to_f <= 1 && (@request['connection'].nil? || !@request['connection'].match(/^k/i))) || (@request['connection'] && @request['connection'].match(/^c/i))
214
- headers.each {|k,v| out << "#{k.to_s}: #{v}\r\n"}
215
- out << "cache-control: max-age=0, no-cache\r\n".freeze unless headers['cache-control'.freeze]
216
- response.extract_cookies.each {|cookie| out << "set-cookie: #{cookie}\r\n"}
217
- out << "\r\n"
218
-
219
- response.bytes_written += (write(out) || 0)
220
- headers.freeze
221
- response.raw_cookies.freeze
222
- end
223
- def stream_data data = nil
224
- write("#{data.to_s.bytesize.to_s(16)}\r\n#{data.to_s}\r\n")
225
- end
226
-
227
- def log_finished response
228
- request = response.request
229
- return if Iodine.logger.nil? || request[:no_log]
230
- t_n = Time.now
231
- # (Thread.current[:log_buffer] ||= String.new).clear
232
- # Thread.current[:log_buffer] << "#{request[:client_ip]} [#{t_n.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:scheme]}\/#{request[:version]}\" #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n"
233
- # Iodine.log(Thread.current[:log_buffer])
234
- Iodine.log("#{request[:client_ip]} [#{t_n.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:scheme]}\/#{request[:version]}\" #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n").clear
235
- end
236
-
237
- def bad_body_size?
238
- request = @request
239
- if request[:body] && request[:body].size > ::Iodine::Http.max_body_size
240
- Iodine.warn("Http1 message body too big, closing connection (Iodine::Http.max_body_size == #{::Iodine::Http.max_body_size} bytes) - #{request[:body].size} bytes.")
241
- request.delete(:body).tap {|f| f.close unless f.closed? } rescue false
242
- write "HTTP/1.0 413 Payload Too Large\r\ncontent-length: 17\r\n\r\nPayload Too Large".freeze
243
- return true
244
- end
245
- false
246
- end
247
- end
248
- end
249
- end
250
-
251
-
@@ -1,507 +0,0 @@
1
- module Iodine
2
- module Http
3
- class Http2 < ::Iodine::Protocol
4
- def on_open
5
- # not fully fanctional.
6
- ::Iodine.warn "Http/2 requested - support is still experimental."
7
-
8
- # update the timeout to 15 seconds (ping will be sent whenever timeout is reached).
9
- set_timeout 15
10
-
11
- # Header compression is stateful
12
- @hpack = ::Iodine::Http::Http2::HPACK.new
13
-
14
- # the header-stream cache
15
- @header_buffer = String.new
16
- @header_end_stream = false
17
- @header_sid = nil
18
- @frame_locker = Mutex.new
19
-
20
- # frame parser starting posotion
21
- @frame = {}
22
-
23
- # Open stream managment
24
- @open_streams = {}
25
-
26
- # connection is only established after the preface was sent
27
- @connected = false
28
-
29
- # the last stream to be processed (For the GOAWAY frame)
30
- @last_stream = 0
31
- @refuese_new = false
32
- # @complete_stream = 0
33
-
34
- # the settings state
35
- @settings = DEFAULT_SETTING.dup
36
-
37
- # send connection preface (Section 3.5) consisting of a (can be empty) SETTINGS frame (Section 6.5).
38
- #
39
- # should prepare to accept a client connection preface which starts with:
40
- # 0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
41
- # == PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
42
- # + SETTINGS frame
43
-
44
- # The @options variable contains the original Http1 request, if exists.
45
- return unless @options
46
- ::Iodine.warn "Http/2: upgrade handshake settings not implemented. upgrade request:\n#{@options}"
47
- @last_stream = @options[:stream_id] = 1
48
- @options[:io] = self
49
- # deal with the request['http2-settings'] - NO ACK
50
- # HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
51
-
52
- # dispatch the original request
53
- ::Iodine.run @options, &(::Iodine::Http::Http2.dispatch)
54
- @options = nil
55
- end
56
- def on_message data
57
- data = ::StringIO.new data
58
- parse_preface data unless @connected
59
- true while parse_frame data
60
- end
61
-
62
- def send_response response
63
- return false if response.headers.frozen?
64
- body = response.extract_body
65
- request = response.request
66
- return body && body.close unless send_headers response, request
67
- return log_finished(response) && body && body.close if request.head?
68
- if body
69
- until body.eof?
70
- response.bytes_written += emit_payload(body.read(@settings[SETTINGS_MAX_FRAME_SIZE], Thread.current[:write_buffer]), request[:sid], 0, (body.eof? ? 1 : 0))
71
- end
72
- body.close
73
- else
74
- emit_payload(''.freeze, request[:sid], 0, 1)
75
- end
76
- log_finished response
77
- end
78
-
79
- def stream_response response, finish = false
80
- request = response.request
81
- body = response.extract_body
82
- send_headers response, request
83
- return body && body.close if request.head?
84
- if body
85
- response.bytes_written += emit_payload body, request[:sid], 0,(finish ? 1 : 0)
86
- body.close
87
- elsif finish
88
- emit_payload(''.freeze, request[:sid], 0, 1)
89
- end
90
- log_finished response if finish
91
- end
92
-
93
- def ping
94
- @frame_locker.synchronize { emit_frame "pniodine".freeze, 0, 6 }
95
- end
96
-
97
- def push request
98
- return false if @settings[SETTINGS_ENABLE_PUSH] == 0
99
- @last_push ||= 0
100
- # emit push promise
101
- emit_payload @hpack.encode(path: request[:path], method: request[:method], scheme: request[:scheme], authority: request[:authority]), (request[:sid] = (@last_push += 2)), 5, 4
102
- # queue for app dispatch
103
- Iodine.run( request, &::Iodine::Http::Http2.dispatch)
104
- end
105
-
106
- def go_away error_code
107
- return false if @io.closed?
108
- @frame_locker.synchronize { emit_frame [@last_stream, error_code].pack('N*'), 0, 7 }
109
- close
110
- # Iodine.info "HTTP/2 connection closed with code #{error_code}"
111
- end
112
-
113
- # Gracefully close HTTP/2 when possible
114
- def on_shutdown
115
- go_away NO_ERROR
116
- end
117
-
118
- # clear text handshake
119
- def self.handshake request, io, data
120
- return false unless request['upgrade'.freeze] =~ /h2c/.freeze && request['http2-settings'.freeze]
121
- io.write "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n".freeze
122
- http_2 = self.new(io, request)
123
- unless data.eof?
124
- http_2.on_message data.read
125
- end
126
- end
127
- # preknowledge handshake
128
- def self.pre_handshake io, data
129
- return false unless data[0..23] == "PRI * HTTP\/2.0\r\n\r\nSM\r\n\r\n".freeze
130
- self.new(io).on_message data
131
- true
132
- end
133
-
134
-
135
- protected
136
-
137
- # logs the sent response.
138
- def log_finished response
139
- request = response.request
140
- return if Iodine.logger.nil? || request[:no_log]
141
- t_n = Time.now
142
- # (Thread.current[:log_buffer] ||= String.new).clear
143
- # Thread.current[:log_buffer] << "#{request[:client_ip]} [#{t_n.utc}] #{request[:method]} #{request[:original_path]} #{request[:scheme]}\/2 #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n"
144
- # Iodine.log Thread.current[:log_buffer]
145
- Iodine.log("#{request[:client_ip]} [#{t_n.utc}] #{request[:method]} #{request[:original_path]} #{request[:scheme]}\/2 #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n").clear
146
- end
147
-
148
- def send_headers response, request
149
- return false if response.headers.frozen?
150
- headers = response.headers
151
- # headers[:status] = response.status.to_s
152
- headers['set-cookie'.freeze] = response.extract_cookies
153
- headers.freeze
154
- (Thread.current[:headers_buffer] ||= String.new).clear
155
- Thread.current[:headers_buffer] << @hpack.encode(status: response.status)
156
- Thread.current[:headers_buffer] << @hpack.encode(headers)
157
- emit_payload Thread.current[:headers_buffer], request[:sid], 1, (request.head? ? 1 : 0)
158
- return true
159
- end
160
-
161
- # Sends an HTTP frame with the requested payload
162
- #
163
- # @return [true, false] returns true if the frame was sent and false if the frame couldn't be sent (i.e. payload too big, connection closed etc').
164
- def emit_frame payload, sid = 0, type = 0, flags = 0
165
- # puts "Sent: #{[payload.bytesize, type, flags, sid, payload].pack('N C C N a*'.freeze)[1..-1].inspect}"
166
- @io.write( [payload.bytesize, type, flags, sid, payload].pack('N C C N a*'.freeze)[1..-1] ) #.tap {|s| next if type !=1 ;puts "Frame: #{s.class.name} - #{s.encoding}"; puts s.inspect } )
167
- end
168
-
169
- # Sends an HTTP frame group with the requested payload. This means the group will not be interrupted and will be sent as one unit.
170
- #
171
- # @return [true, false] returns true if the frame was sent and false if the frame couldn't be sent (i.e. payload too big, connection closed etc').
172
- def emit_payload payload, sid = 0, type = 0, flags = 0
173
- max_frame_size = @settings[SETTINGS_MAX_FRAME_SIZE]
174
- max_frame_size = 131_072 if max_frame_size > 131_072
175
- return @frame_locker.synchronize { emit_frame(payload, sid, type, ( (type == 0x1 || type == 0x5) ? (flags | 0x4) : flags ) ) } if payload.bytesize <= max_frame_size
176
- sent = 0
177
- payload = StringIO.new payload unless payload.respond_to? :read
178
- if type == 0x1 || type == 0x5
179
- @frame_locker.synchronize do
180
- sent += emit_frame(payload.read(max_frame_size, Thread.current[:write_buffer]), sid, 0x1, flags & 254)
181
- sent += emit_frame(payload.read(max_frame_size, Thread.current[:write_buffer]), sid, 0x9, 0) while payload.size - payload.pos > max_frame_size
182
- sent += emit_frame(payload.read(max_frame_size, Thread.current[:write_buffer]), sid, 0x9, (0x4 | (flags & 0x1)) )
183
- end
184
- return sent
185
- end
186
- sent += @frame_locker.synchronize { emit_frame(payload.read(max_frame_size, Thread.current[:write_buffer]), sid, type, (flags & 254)) } while payload.size - payload.pos > max_frame_size
187
- sent += @frame_locker.synchronize { emit_frame(payload.read(max_frame_size, Thread.current[:write_buffer]), sid, type, flags) }
188
- end
189
-
190
- def parse_preface data
191
- unless data.read(24) == "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
192
- data.string.clear
193
- data.rewind
194
- return (connection_error(PROTOCOL_ERROR) && Iodine.warn("Preface not given"))
195
- end
196
- @connected = true
197
- emit_frame ''.freeze, 0, 0x4
198
- true
199
- end
200
-
201
- def parse_frame data
202
- frame = (@frame ||= {})
203
- unless frame[:length]
204
- tmp = (frame[:length_bytes] ||= "\x00")
205
- tmp << data.read(4 - tmp.bytesize).to_s
206
- return false if tmp.bytesize < 4
207
- frame[:length] = frame.delete(:length_bytes).unpack('N*'.freeze).pop
208
- end
209
- # TODO: error if length is greater than max_size (16_384 is default)
210
- if frame[:length] > @settings[SETTINGS_MAX_FRAME_SIZE]
211
- return false unless connection_error FRAME_SIZE_ERROR
212
- end
213
- unless frame[:type]
214
- tmp = data.getc
215
- return false unless tmp
216
- frame[:type] = tmp.ord
217
- end
218
- unless frame[:flags]
219
- tmp = data.getc
220
- return false unless tmp
221
- frame[:flags] = tmp.ord
222
- end
223
- unless frame[:sid]
224
- tmp = (frame[:sid_bytes] ||= String.new)
225
- tmp << data.read(4 - tmp.bytesize).to_s
226
- return false if tmp.bytesize < 4
227
- tmp = frame.delete(:sid_bytes).unpack('N')[0]
228
- frame[:sid] = tmp & 2147483647
229
- frame[:R] = tmp & 2147483648
230
- end
231
- tmp = (frame[:body] ||= String.new)
232
- tmp << data.read(frame[:length] - tmp.bytesize).to_s
233
- return false if tmp.bytesize < frame[:length]
234
- #TODO: something - Async?
235
- process_frame frame
236
- # reset frame buffer
237
- @frame = {} # @frame.clear
238
- true
239
- end
240
-
241
- def process_frame frame
242
- # puts "processing HTTP/2 frame: #{frame}"
243
- (frame[:stream] = ( @open_streams[frame[:sid]] ||= ::Iodine::Http::Request.new(self) ) ) && (frame[:stream][:sid] ||= frame[:sid]) if frame[:sid] != 0
244
- case frame[:type]
245
- when 0 # DATA
246
- process_data frame
247
- when 1, 9 # HEADERS, CONTINUATION
248
- process_headers frame
249
- when 2 # PRIORITY
250
- when 3 # RST_STREAM
251
- @open_streams.delete frame[:sid]
252
- when 4 # SETTINGS
253
- process_settings frame
254
- # when 5 # PUSH_PROMISE - Should only be sent by the server
255
- when 6 # PING
256
- process_ping frame
257
- when 7 # GOAWAY
258
- go_away NO_ERROR
259
- Iodine.error "Http2 Disconnection with error (#{frame[:flags].to_s}): #{frame[:body].strip}" unless frame[:flags] == 0 && frame[:body] == ''.freeze
260
- when 8 # WINDOW_UPDATE
261
- else # Error, frame not recognized
262
- end
263
-
264
- # The PING frame (type=0x6) (most important!!!) is a mechanism for measuring a minimal round-trip time from the sender, as well as determining whether an idle connection is still functional
265
- # ACK flag: 0x1 - if not present, must send frame back.
266
- # PING frames are not associated with any individual stream. If a PING frame is received with a stream identifier field value other than 0x0, the recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
267
- # DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a stream. One or more DATA frames are used, for instance, to carry HTTP request or response payloads
268
- # The HEADERS frame (type=0x1)
269
- # The RST_STREAM frame (type=0x3 - 32bit error code) allows for immediate termination of a stream. RST_STREAM is sent to request cancellation of a stream or to indicate that an error condition has occurred.
270
- # The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints communicate.
271
- # The payload of a SETTINGS frame consists of zero or more parameters, each consisting of an unsigned 16-bit setting identifier and an unsigned 32-bit value
272
- # The CONTINUATION frame (type=0x9)
273
- # The CONTINUATION frame defines the following flag:
274
- # END_HEADERS (0x4):
275
- # When set, bit 2 indicates that this frame ends a header block
276
- # The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream (Section 5.3). It can be sent in any stream state, including idle or closed streams
277
- # The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the sender intends to initiate.
278
- # The GOAWAY frame (type=0x7) is used to initiate shutdown of a connection or to signal serious error conditions.
279
- # The GOAWAY frame applies to the connection, not a specific stream (DIS 0x0)
280
- # R (1 bit) LAST_STREAM_ID (31 bit) ERROR_CODE (32 bit) DEBUG_DATA(optional) (*)
281
- # The WINDOW_UPDATE frame (type=0x8) is used to implement flow control
282
- # A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR.
283
-
284
- end
285
-
286
- def process_ping frame
287
- # Iodine.info "Got HTTP/2 #{frame[:flags][0] == 1 ? 'Pong' : 'Ping'}"
288
- return connection_error PROTOCOL_ERROR if frame[:sid].to_i > 0
289
- return true if frame[:flags][0] == 1
290
- emit_frame frame[:body], 0, 6, 1
291
- # Iodine.info "Sent HTTP/2 'Pong'"
292
- end
293
- def process_headers frame
294
- if @header_sid && (frame[:type] == 1 || frame[:sid] != @header_sid)
295
- return connection_error PROTOCOL_ERROR
296
- end
297
- @header_end_stream = true if frame[:type] == 1 && frame[:flags][0] == 1
298
-
299
- if frame[:flags][3] == 1 # padded
300
- frame[:body] = frame[:body][1...(0 - frame[:body][0].ord)]
301
- end
302
- if frame[:flags][5] == 1 # priority
303
- # stream_dependency = frame[:body][0..3]
304
- # weight = frame[:body][4]
305
- frame[:body] = frame[:body][5..-1]
306
- end
307
-
308
- @header_buffer << frame[:body]
309
-
310
- frame[:stream][:headers_size] ||= 0
311
- frame[:stream][:headers_size] += frame[:body].bytesize
312
-
313
- return (Iodine.warn('Http2 header overloading, closing connection.') && connection_error( ENHANCE_YOUR_CALM ) ) if frame[:stream][:headers_size] > 262_144
314
-
315
- return unless frame[:flags][2] == 1 # fin
316
-
317
- frame[:stream].update @hpack.decode(@header_buffer) # this is where HPACK comes in
318
- return (Iodine.warn('Http2 header overloading, closing connection.') && connection_error( ENHANCE_YOUR_CALM ) ) if frame[:stream].length > 2096
319
- frame[:stream][:time_recieved] ||= Iodine.time
320
- frame[:stream][:version] ||= '2'.freeze
321
-
322
- process_request(@open_streams.delete frame[:sid]) if @header_end_stream
323
-
324
- # TODO: manage headers and streams
325
-
326
- @header_buffer.clear
327
- @header_end_stream = false
328
- @header_sid = nil
329
- rescue => e
330
- connection_error 5
331
- Iodine.warn e
332
- end
333
- def process_data frame
334
- if frame[:flags][3] == 1 # padded
335
- frame[:body] = frame[:body][1...(0 - frame[:body][0].ord)]
336
- end
337
-
338
- (frame[:stream][:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze) ) << frame[:body]
339
-
340
- # check request size
341
- if frame[:stream][:body].size > ::Iodine::Http.max_body_size
342
- Iodine.warn("Http2 payload (message size) too big (Iodine::Http.max_body_size == #{::Iodine::Http.max_body_size} bytes) - #{frame[:stream][:body].size} bytes.")
343
- return connection_error( ENHANCE_YOUR_CALM )
344
- end
345
-
346
- process_request(@open_streams.delete frame[:sid]) if frame[:flags][0] == 1
347
- end
348
-
349
- # # Settings Codes:
350
- SETTINGS_HEADER_TABLE_SIZE = 0x1
351
- # Allows the sender to inform the remote endpoint of the maximum size of the header compression table used to decode header blocks, in octets.
352
- # The encoder can select any size equal to or less than this value by using signaling specific to the header compression format inside a header block (see [COMPRESSION]).
353
- # The initial value is 4,096 octets.
354
- SETTINGS_ENABLE_PUSH = 0x2
355
- # This setting can be used to disable server push (Section 8.2). An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0. An endpoint that has both set this parameter to 0 and had it acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
356
- # The initial value is 1, which indicates that server push is permitted. Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
357
- SETTINGS_MAX_CONCURRENT_STREAMS = 0x3
358
- # Indicates the maximum number of concurrent streams that the sender will allow. This limit is directional: it applies to the number of streams that the sender permits the receiver to create.
359
- # Initially, there is no limit to this value. It is recommended that this value be no smaller than 100, so as to not unnecessarily limit parallelism.
360
- # A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
361
- # A zero value does prevent the creation of new streams; however, this can also happen for any limit that is exhausted with active streams.
362
- # Servers SHOULD only set a zero value for short durations; if a server does not wish to accept requests, closing the connection is more appropriate.
363
- SETTINGS_INITIAL_WINDOW_SIZE = 0x4
364
- # Indicates the sender's initial window size (in octets) for stream-level flow control. The initial value is 216-1 (65,535) octets.
365
- # This setting affects the window size of all streams (see Section 6.9.2).
366
- # Values above the maximum flow-control window size of 231-1 MUST be treated as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR.
367
- SETTINGS_MAX_FRAME_SIZE = 0x5
368
- # Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
369
- # The initial value is 214 (16,384) octets. The value advertised by an endpoint MUST be between this initial value and
370
- # the maximum allowed frame size (224-1 or 16,777,215 octets), inclusive. Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
371
- SETTINGS_MAX_HEADER_LIST_SIZE = 0x6
372
- # This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets. The value is based on the uncompressed size of header fields, including the length of the name and value in octets plus an overhead of 32 octets for each header field.
373
- DEFAULT_SETTING = { SETTINGS_ENABLE_PUSH => 1,
374
- SETTINGS_INITIAL_WINDOW_SIZE => 65_535,
375
- SETTINGS_MAX_FRAME_SIZE => 16_384
376
- }
377
-
378
- def process_settings frame
379
- return if frame[:flags] == 1 # do nothing if it's only an ACK.
380
- return connection_error PROTOCOL_ERROR unless frame[:sid] == 0 && (frame[:body].bytesize % 6) == 0
381
- settings = StringIO.new frame[:body]
382
- until settings.eof?
383
- key = settings.read(2).unpack('n'.freeze)[0]
384
- value = settings.read(4).unpack('N'.freeze)[0]
385
- Iodine.info "HTTP/2 set #{key}=>#{value} for SID #{frame[:sid]}"
386
- case frame[:body][0..1].unpack('n'.freeze)[0]
387
- when SETTINGS_HEADER_TABLE_SIZE
388
- return connection_error ENHANCE_YOUR_CALM if value > 4096
389
- @hpack.resize(value)
390
- when SETTINGS_ENABLE_PUSH
391
- @settings[SETTINGS_ENABLE_PUSH] = value
392
- when SETTINGS_MAX_CONCURRENT_STREAMS
393
- @settings[SETTINGS_MAX_CONCURRENT_STREAMS] = value
394
- when SETTINGS_INITIAL_WINDOW_SIZE
395
- @settings[SETTINGS_INITIAL_WINDOW_SIZE] = value
396
- when SETTINGS_MAX_FRAME_SIZE
397
- @settings[SETTINGS_MAX_FRAME_SIZE] = value
398
- when SETTINGS_MAX_HEADER_LIST_SIZE
399
- @settings[SETTINGS_MAX_HEADER_LIST_SIZE] = value
400
- else
401
- # Unsupported parameters MUST be ignored
402
- end
403
- end
404
- emit_frame ''.freeze, 0, 4, 1
405
- end
406
-
407
- def process_request request
408
- return if @refuese_new
409
- ::Iodine::Http::Request.parse request
410
- # Iodine.info "Should Process request #{request.select { |k,v| k != :io } }"
411
- @last_stream = request[:sid] if request[:sid] > @last_stream
412
- # emit_frame [HTTP_1_1_REQUIRED].pack('N'), request[:sid], 0x3, 0
413
- ::Iodine.run request, &(::Iodine::Http::Http2.dispatch)
414
-
415
- end
416
-
417
- # Error codes:
418
-
419
- # The associated condition is not a result of an error. For example, a GOAWAY might include this code to indicate graceful shutdown of a connection.
420
- NO_ERROR = 0x0
421
- # The endpoint detected an unspecific protocol error. This error is for use when a more specific error code is not available.
422
- PROTOCOL_ERROR = 0x1
423
- # The endpoint encountered an unexpected internal error.
424
- INTERNAL_ERROR = 0x2
425
- # The endpoint detected that its peer violated the flow-control protocol.
426
- FLOW_CONTROL_ERROR = 0x3
427
- # The endpoint sent a SETTINGS frame but did not receive a response in a timely manner. See Section 6.5.3 ("Settings Synchronization").
428
- SETTINGS_TIMEOUT = 0x4
429
- # The endpoint received a frame after a stream was half-closed.
430
- STREAM_CLOSED = 0x5
431
- # The endpoint received a frame with an invalid size.
432
- FRAME_SIZE_ERROR = 0x6
433
- # The endpoint refused the stream prior to performing any application processing (see Section 8.1.4 for details).
434
- REFUSED_STREAM = 0x7
435
- # Used by the endpoint to indicate that the stream is no longer needed.
436
- CANCEL = 0x8
437
- # The endpoint is unable to maintain the header compression context for the connection.
438
- COMPRESSION_ERROR = 0x9
439
- # The connection established in response to a CONNECT request (Section 8.3) was reset or abnormally closed.
440
- CONNECT_ERROR = 0xa
441
- # The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load.
442
- ENHANCE_YOUR_CALM = 0xb
443
- # The underlying transport has properties that do not meet minimum security requirements (see Section 9.2).
444
- INADEQUATE_SECURITY = 0xc
445
- # The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
446
- HTTP_1_1_REQUIRED = 0xd
447
-
448
- # Process a connection error and act accordingly.
449
- #
450
- # @return [true, false, nil] returns true if connection handling can continue of false (or nil) for a fatal error.
451
- def connection_error type
452
- ::Iodine.warn "HTTP/2 error #{type}."
453
- go_away type
454
- # case type
455
- # when NO_ERROR
456
- # when PROTOCOL_ERROR
457
- # when INTERNAL_ERROR
458
- # when FLOW_CONTROL_ERROR
459
- # when SETTINGS_TIMEOUT
460
- # when STREAM_CLOSED
461
- # when FRAME_SIZE_ERROR
462
- # when REFUSED_STREAM
463
- # when CANCEL
464
- # when COMPRESSION_ERROR
465
- # when CONNECT_ERROR
466
- # when ENHANCE_YOUR_CALM
467
- # when INADEQUATE_SECURITY
468
- # when HTTP_1_1_REQUIRED
469
- # else
470
- # end
471
- # nil
472
- end
473
-
474
- def self.dispatch
475
- @dispatch ||= Proc.new do |request|
476
- case request[:method]
477
- when 'TRACE'.freeze
478
- close
479
- return false
480
- when 'OPTIONS'.freeze
481
- response = ::Iodine::Http::Response.new request
482
- response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'.freeze
483
- response['access-control-allow-origin'.freeze] = '*'
484
- response.finish
485
- return false
486
- end
487
- response = ::Iodine::Http::Response.new request
488
- begin
489
- ret = Iodine::Http.on_http.call(request, response)
490
- if ret.is_a?(String)
491
- response << ret
492
- elsif ret == false
493
- response.clear && (response.status = 404) && (response << ::Iodine::Http::Response::STATUS_CODES[404])
494
- end
495
- response.finish
496
- rescue => e
497
- ::Iodine.error e
498
- # request[:io].emit_frame [INTERNAL_ERROR].pack('N'.freeze), request[:sid], 3
499
- ::Iodine::Http::Response.new(request, 500, {}, ::Iodine::Http::Response::STATUS_CODES[500]).finish
500
- end
501
- end
502
- end
503
- end
504
- end
505
- end
506
-
507
-