iodine 0.0.1 → 0.0.2

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.

Potentially problematic release.


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

@@ -0,0 +1,217 @@
1
+ module Iodine
2
+ class Http < ::Iodine::Protocol
3
+ def on_open
4
+ set_timeout 1
5
+ @refuse_requests = false
6
+ @bytes_sent = 0
7
+ @parser = {}
8
+ end
9
+ def on_message data
10
+ return if @refuse_requests
11
+ @http2_pri_review ||= ( ::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
+ next if l.empty?
19
+ request[:method], request[:query], request[:version] = l.split(/[\s]+/, 3)
20
+ return (Iodine.warn('Protocol Error, closing connection.') && close) unless request[:method] =~ HTTP_METHODS_REGEXP
21
+ request[:version] = (request[:version] || '1.1'.freeze).match(/[\d\.]+/)[0]
22
+ request[:time_recieved] = Time.now
23
+ end
24
+ until request[:headers_complete] || (l = data.gets).nil?
25
+ if l.include? ':'
26
+ # n = l.slice!(0, l.index(':')); l.slice! 0
27
+ # n.strip! ; n.downcase!; n.freeze
28
+ # request[n] ? (request[n].is_a?(Array) ? (request[n] << l) : request[n] = [request[n], l ]) : (request[n] = l)
29
+ l = l.strip.split(/:[\s]?/, 2)
30
+ l[0].strip! ; l[0].downcase!;
31
+ 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])
32
+ elsif l =~ /^[\r]?\n/
33
+ request[:headers_complete] = true
34
+ else
35
+ #protocol error
36
+ Iodine.warn 'Protocol Error, closing connection.'
37
+ return close
38
+ end
39
+ end
40
+ until request[:body_complete] && request[:headers_complete]
41
+ if request['transfer-coding'.freeze] == 'chunked'.freeze
42
+ # ad mid chunk logic here
43
+ if @parser[:length].to_i == 0
44
+ chunk = data.gets
45
+ return false unless chunk
46
+ @parser[:length] = chunk.to_i(16)
47
+ return (Iodine.warn('Protocol Error, closing connection.') && close) unless @parser[:length]
48
+ request[:body_complete] = true && break if @parser[:length] == 0
49
+ @parser[:act_length] = 0
50
+ request[:body] ||= ''
51
+ end
52
+ chunk = data.read(@parser[:length] - @parser[:act_length])
53
+ return false unless chunk
54
+ request[:body] << chunk
55
+ @parser[:act_length] += chunk.bytesize
56
+ (@parser[:act_length] = @parser[:length] = 0) && (data.gets) if @parser[:act_length] >= @parser[:length]
57
+ elsif request['content-length'.freeze] && request['content-length'.freeze].to_i != 0
58
+ request[:body] ||= ''
59
+ packet = data.read(request['content-length'.freeze].to_i - request[:body].bytesize)
60
+ return false unless packet
61
+ request[:body] << packet
62
+ request[:body_complete] = true if request['content-length'.freeze].to_i - request[:body].bytesize <= 0
63
+ elsif request['content-type'.freeze]
64
+ Iodine.warn 'Body type protocol error.' unless request[:body]
65
+ line = data.gets
66
+ return false unless line
67
+ (request[:body] ||= '') << line
68
+ request[:body_complete] = true if line =~ EOHEADERS
69
+ else
70
+ request[:body_complete] = true
71
+ end
72
+ end
73
+ (@request = ::Iodine::Http::Request.new(self)) && ( ::Iodine::Http::Http2.handshake(request, self, data) || dispatch(request, data) ) if request.delete :body_complete
74
+ end
75
+ end
76
+
77
+ def send_response response
78
+ return false if response.headers.frozen?
79
+
80
+ request = response.request
81
+ headers = response.headers
82
+ body = response.extract_body
83
+
84
+ headers['content-length'.freeze] ||= body.to_s.bytesize
85
+
86
+ keep_alive = response.keep_alive
87
+ if (request[:version].to_f > 1 && request['connection'.freeze].nil?) || request['connection'.freeze].to_s =~ /ke/i || (headers['connection'.freeze] && headers['connection'.freeze] =~ /^ke/i)
88
+ keep_alive = true
89
+ headers['connection'.freeze] ||= 'Keep-Alive'.freeze
90
+ headers['keep-alive'.freeze] ||= "timeout=#{(@timeout ||= 3).to_s}"
91
+ else
92
+ headers['connection'.freeze] ||= 'close'.freeze
93
+ end
94
+
95
+ send_headers response
96
+ return log_finished(response) if request.head?
97
+ (response.bytes_written += (write(body) || 0)) && (body.frozen? || body.clear) if body
98
+ close unless keep_alive
99
+ log_finished response
100
+ end
101
+ def stream_response response, finish = false
102
+ unless response.headers.frozen?
103
+ response['transfer-encoding'.freeze] = 'chunked'
104
+ response.headers['connection'.freeze] = 'close'.freeze
105
+ send_headers response
106
+ @refuse_requests = true
107
+ end
108
+ return if response.request.head?
109
+ body = response.extract_body
110
+ response.bytes_written += stream_data(body) if body || finish
111
+ if finish
112
+ response.bytes_written += stream_data('') unless body.nil?
113
+ log_finished response
114
+ end
115
+ (body.frozen? || body.clear) if body
116
+ true
117
+ end
118
+
119
+ protected
120
+
121
+ HTTP_METHODS = %w{GET HEAD POST PUT DELETE TRACE OPTIONS CONNECT PATCH}
122
+ HTTP_METHODS_REGEXP = /\A#{HTTP_METHODS.join('|')}/i
123
+
124
+ def parse data
125
+
126
+ end
127
+
128
+
129
+ def dispatch request, data
130
+ return data.string.clear if @io.closed? || @refuse_requests
131
+ ::Iodine::Http::Request.parse request
132
+ #check for server-responses
133
+ case request[:method]
134
+ when 'TRACE'.freeze
135
+ close
136
+ data.string.clear
137
+ return false
138
+ when 'OPTIONS'.freeze
139
+ response = ::Iodine::Http::Response.new request
140
+ response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'.freeze
141
+ response['access-control-allow-origin'.freeze] = '*'
142
+ response['content-length'.freeze] = 0
143
+ send_response response
144
+ return false
145
+ end
146
+ response = ::Iodine::Http::Response.new request
147
+ begin
148
+ if request.websocket?
149
+ @refuse_requests = true
150
+ ::Iodine::Http::Websockets.handshake request, response, self.class.on_websocket.call(request, response)
151
+ else
152
+ ret = self.class.on_http.call(request, response)
153
+ if ret.is_a?(String)
154
+ response << ret
155
+ elsif ret == false
156
+ response.clear && (response.status = 404) && (response << ::Iodine::Http::Response::STATUS_CODES[404])
157
+ end
158
+ end
159
+ send_response response
160
+ rescue => e
161
+ Iodine.error e
162
+ send_response ::Iodine::Http::Response.new(request, 500, {}, ::Iodine::Http::Response::STATUS_CODES[500])
163
+ end
164
+ end
165
+
166
+ def send_headers response
167
+ return false if response.headers.frozen?
168
+ # remove old flash cookies
169
+ response.cookies.keys.each do |k|
170
+ if k.to_s.start_with? 'magic_flash_'.freeze
171
+ response.set_cookie k, nil
172
+ flash.delete k
173
+ end
174
+ end
175
+ #set new flash cookies
176
+ response.flash.each do |k,v|
177
+ response.set_cookie "magic_flash_#{k.to_s}", v
178
+ end
179
+ response.raw_cookies.freeze
180
+ # response.cookies.set_response nil
181
+ response.flash.freeze
182
+
183
+ request = response.request
184
+ headers = response.headers
185
+
186
+ # response['date'.freeze] ||= request[:time_recieved].httpdate
187
+
188
+ out = "HTTP/#{request[:version]} #{response.status} #{::Iodine::Http::Response::STATUS_CODES[response.status] || 'unknown'}\r\n"
189
+
190
+ out << request[:time_recieved].utc.strftime("Date: %a, %d %b %Y %H:%M:%S GMT\r\n".freeze) unless headers['date'.freeze]
191
+
192
+ # unless @headers['connection'] || (@request[:version].to_f <= 1 && (@request['connection'].nil? || !@request['connection'].match(/^k/i))) || (@request['connection'] && @request['connection'].match(/^c/i))
193
+ headers.each {|k,v| out << "#{k.to_s}: #{v}\r\n"}
194
+ out << "Cache-Control: max-age=0, no-cache\r\n".freeze unless headers['cache-control'.freeze]
195
+ response.raw_cookies.each {|k,v| out << "Set-Cookie: #{k.to_s}=#{v.to_s}\r\n"}
196
+ out << "\r\n"
197
+
198
+ response.bytes_written += (write(out) || 0)
199
+ out.clear
200
+ headers.freeze
201
+ response.raw_cookies.freeze
202
+ end
203
+ def stream_data data = nil
204
+ write("#{data.to_s.bytesize.to_s(16)}\r\n#{data.to_s}\r\n") || 0
205
+ end
206
+
207
+ def log_finished response
208
+ @bytes_sent = 0
209
+ request = response.request
210
+ return if Iodine.logger.nil? || request[:no_log]
211
+ t_n = Time.now
212
+ Iodine.logger << "#{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"
213
+ end
214
+ end
215
+ end
216
+
217
+
@@ -0,0 +1,465 @@
1
+ module Iodine
2
+ class Http < Iodine::Protocol
3
+ class Http2 < ::Iodine::Protocol
4
+ def initialize io, original_request = nil
5
+ super(io)
6
+ return unless original_request
7
+ original_request[:stream_id] = 1
8
+ original_request[:io] = self
9
+ process_request original_request
10
+ end
11
+ def on_open
12
+ # do stuff, i.e. related to the header:
13
+ # HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
14
+ ::Iodine.warn "HTTP/2 requested - support is still experimental."
15
+ # update the timeout to 15 seconds (ping will be sent whenever timeout is reached).
16
+ set_timeout 15
17
+
18
+ # Header compression is stateful
19
+ @hpack = ::Iodine::Http::Http2::HPACK.new
20
+
21
+ # the header-stream cache
22
+ @header_buffer = ''
23
+ @header_end_stream = false
24
+ @header_sid = nil
25
+
26
+ # frame parser starting posotion
27
+ @frame = {}
28
+
29
+ # Open stream managment
30
+ @open_streams = {}
31
+
32
+ # connection is only established after the preface was sent
33
+ @connected = false
34
+
35
+ # the last stream to be processed (For the GOAWAY frame)
36
+ @last_stream = 0
37
+ @refuese_new = false
38
+ # @complete_stream = 0
39
+
40
+ # the settings state
41
+ @settings = DEFAULT_SETTING.dup
42
+
43
+ # send connection preface (Section 3.5) consisting of a (can be empty) SETTINGS frame (Section 6.5).
44
+ #
45
+ # should prepare to accept a client connection preface which starts with:
46
+ # 0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
47
+ # == PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
48
+ # + SETTINGS frame
49
+ end
50
+ def on_message data
51
+ data = ::StringIO.new data
52
+ parse_preface data unless @connected
53
+ true while parse_frame data
54
+ data.string.clear
55
+ end
56
+
57
+ def send_response response
58
+ return false if response.headers.frozen?
59
+
60
+ request = response.request
61
+ headers = response.headers
62
+ body = response.extract_body
63
+ headers[:status] = response.status.to_s
64
+ emit_payload @hpack.encode(headers), request[:sid], 1, (request.head? ? 1 : 0)
65
+ headers.freeze
66
+ return (log_finished(response) && body.clear) if request.head?
67
+ (response.bytes_written += emit_payload(body, request[:sid], 0, 1) ) && (body.frozen? || body.clear) if body
68
+ log_finished response
69
+ end
70
+ def stream_response response, finish = false
71
+ request = response.request
72
+ headers = response.headers
73
+ unless response.headers.frozen? # send headers
74
+ headers[:status] = response.status.to_s
75
+ emit_payload @hpack.encode(headers), request[:sid], 1, (request.head? ? 1 : 0)
76
+ headers.freeze
77
+ end
78
+ return nil if request.head?
79
+ body = response.extract_body
80
+ # puts "should stream #{body}"
81
+ (response.bytes_written += emit_payload body.to_s, request[:sid], 0, (finish ? 1 : 0) ) && (body && !body.frozen? && body.clear) if body || finish
82
+ log_finished response if finish
83
+ end
84
+
85
+ def ping
86
+ emit_frame "pniodine", 0, 6
87
+ end
88
+
89
+ def push request
90
+ return false if @settings[SETTINGS_ENABLE_PUSH] == 0
91
+ @last_push ||= 0
92
+ # emit push promise
93
+ emit_payload @hpack.encode(path: request[:path], method: request[:method], scheme: request[:scheme], authority: request[:authority]), (request[:sid] = (@last_push += 2)), 5, 4
94
+ # queue for app dispatch
95
+ Iodine.run request, &::Iodine::Http::Http2.dispatch
96
+ end
97
+
98
+ def go_away error_code
99
+ return false if @io.closed?
100
+ emit_frame [@last_stream, error_code].pack('N*'), 0, 7
101
+ close
102
+ # Iodine.info "HTTP/2 connection closed with code #{error_code}"
103
+ end
104
+
105
+ # Gracefully close HTTP/2 when possible
106
+ def on_shutdown
107
+ go_away NO_ERROR
108
+ end
109
+
110
+ # clear text handshake
111
+ def self.handshake request, io, data
112
+ return false unless request['upgrade'] =~ /h2c/ && request['http2-settings']
113
+ io.write "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n"
114
+ http_2 = self.new(io, request)
115
+ unless data.eof?
116
+ http_2.on_message data.read
117
+ end
118
+ end
119
+ # preknowledge handshake
120
+ def self.pre_handshake io, data
121
+ return false unless data[0..23] == "PRI * HTTP\/2.0\r\n\r\nSM\r\n\r\n".freeze
122
+ self.new(io).on_message data
123
+ true
124
+ end
125
+ protected
126
+
127
+ # logs the sent response.
128
+ def log_finished response
129
+ request = response.request
130
+ return if Iodine.logger.nil? || request[:no_log]
131
+ t_n = Time.now
132
+ Iodine.logger << "#{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"
133
+ end
134
+
135
+ # Sends an HTTP frame with the requested payload
136
+ #
137
+ # @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').
138
+ def emit_frame payload, sid = 0, type = 0, flags = 0
139
+ # puts "Sent: #{[payload.bytesize, type, flags, sid, payload].pack('N C C N a*'.freeze)[1..-1].inspect}"
140
+ @io.write( [payload.bytesize, type, flags, sid, payload].pack('N C C N a*'.freeze)[1..-1] )
141
+ end
142
+
143
+ # Sends an HTTP frame group with the requested payload. This means the group will not be interrupted and will be sent as one unit.
144
+ #
145
+ # @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').
146
+ def emit_payload payload, sid = 0, type = 0, flags = 0
147
+ max_frame_size = @settings[SETTINGS_MAX_FRAME_SIZE]
148
+ max_frame_size = 131_072 if max_frame_size > 131_072
149
+ return emit_frame(payload, sid, type, ( type == 0x1 ? (flags | 0x4) : flags ) ) if payload.bytesize <= max_frame_size
150
+ sent = 0
151
+ payload = StringIO.new payload
152
+ if type == 0x1
153
+ sent += emit_frame(payload.read(max_frame_size), sid, 0x1, flags & 254)
154
+ sent += emit_frame(payload.read(max_frame_size), sid, 0x9, 0) while payload.size - payload.pos > max_frame_size
155
+ sent += emit_frame(payload.read(max_frame_size), sid, 0x9, (0x4 | (flags & 0x1)) )
156
+ return sent
157
+ end
158
+ sent += emit_frame(payload.read(max_frame_size), sid, type, (flags & 254)) while payload.size - payload.pos > max_frame_size
159
+ sent += emit_frame(payload.read(max_frame_size), sid, type, flags)
160
+ end
161
+
162
+ def parse_preface data
163
+ unless data.read(24) == "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
164
+ data.string.clear
165
+ data.rewind
166
+ return (connection_error(PROTOCOL_ERROR) && Iodine.warn("Preface not given"))
167
+ end
168
+ @connected = true
169
+ emit_frame '', 0, 0x4
170
+ true
171
+ end
172
+
173
+ def parse_frame data
174
+ frame = (@frame ||= {})
175
+ unless frame[:length]
176
+ tmp = (frame[:length_bytes] ||= "\x00")
177
+ tmp << data.read(4 - tmp.bytesize).to_s
178
+ return false if tmp.bytesize < 4
179
+ frame[:length] = frame.delete(:length_bytes).unpack('N*'.freeze).pop
180
+ end
181
+ # TODO: error if length is greater than max_size (16_384 is default)
182
+ if frame[:length] > @settings[SETTINGS_MAX_FRAME_SIZE]
183
+ return false unless connection_error FRAME_SIZE_ERROR
184
+ end
185
+ unless frame[:type]
186
+ tmp = data.getc
187
+ return false unless tmp
188
+ frame[:type] = tmp.ord
189
+ end
190
+ unless frame[:flags]
191
+ tmp = data.getc
192
+ return false unless tmp
193
+ frame[:flags] = tmp.ord
194
+ end
195
+ unless frame[:sid]
196
+ tmp = (frame[:sid_bytes] ||= '')
197
+ tmp << data.read(4 - tmp.bytesize).to_s
198
+ return false if tmp.bytesize < 4
199
+ tmp = frame.delete(:sid_bytes).unpack('N')[0]
200
+ frame[:sid] = tmp & 2147483647
201
+ frame[:R] = tmp & 2147483648
202
+ end
203
+ tmp = (frame[:body] ||= '')
204
+ tmp << data.read(frame[:length] - tmp.bytesize).to_s
205
+ return false if tmp.bytesize < frame[:length]
206
+ #TODO: something - Async?
207
+ process_frame frame
208
+ # reset frame buffer
209
+ @frame = {} # @frame.clear
210
+ true
211
+ end
212
+
213
+ def process_frame frame
214
+ # puts "processing HTTP/2 frame: #{frame}"
215
+ (frame[:stream] = ( @open_streams[frame[:sid]] ||= ::Iodine::Http::Request.new(self) ) ) && (frame[:stream][:sid] ||= frame[:sid]) if frame[:sid] != 0
216
+ case frame[:type]
217
+ when 0 # DATA
218
+ process_data frame
219
+ when 1, 9 # HEADERS, CONTINUATION
220
+ process_headers frame
221
+ when 2 # PRIORITY
222
+ when 3 # RST_STREAM
223
+ @open_streams.delete frame[:sid]
224
+ when 4 # SETTINGS
225
+ process_settings frame
226
+ # when 5 # PUSH_PROMISE - Should only be sent by the server
227
+ when 6 # PING
228
+ process_ping frame
229
+ when 7 # GOAWAY
230
+ go_away NO_ERROR
231
+ when 8 # WINDOW_UPDATE
232
+ when 9 #
233
+ else # Error, frame not recognized
234
+ end
235
+
236
+ # 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
237
+ # ACK flag: 0x1 - if not present, must send frame back.
238
+ # 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.
239
+ # 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
240
+ # The HEADERS frame (type=0x1)
241
+ # 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.
242
+ # The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints communicate.
243
+ # 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
244
+ # The CONTINUATION frame (type=0x9)
245
+ # The CONTINUATION frame defines the following flag:
246
+ # END_HEADERS (0x4):
247
+ # When set, bit 2 indicates that this frame ends a header block
248
+ # 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
249
+ # The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the sender intends to initiate.
250
+ # The GOAWAY frame (type=0x7) is used to initiate shutdown of a connection or to signal serious error conditions.
251
+ # The GOAWAY frame applies to the connection, not a specific stream (DIS 0x0)
252
+ # R (1 bit) LAST_STREAM_ID (31 bit) ERROR_CODE (32 bit) DEBUG_DATA(optional) (*)
253
+ # The WINDOW_UPDATE frame (type=0x8) is used to implement flow control
254
+ # 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.
255
+
256
+ end
257
+
258
+ def process_ping frame
259
+ # Iodine.info "Got HTTP/2 #{frame[:flags][0] == 1 ? 'Pong' : 'Ping'}"
260
+ return connection_error PROTOCOL_ERROR if frame[:sid].to_i > 0
261
+ return true if frame[:flags][0] == 1
262
+ emit_frame frame[:body], 0, 6, 1
263
+ # Iodine.info "Sent HTTP/2 'Pong'"
264
+ end
265
+ def process_headers frame
266
+ if @header_sid && (frame[:type] == 1 || frame[:sid] != @header_sid)
267
+ return connection_error PROTOCOL_ERROR
268
+ end
269
+ @header_end_stream = true if frame[:type] == 1 && frame[:flags][0] == 1
270
+
271
+ if frame[:flags][3] == 1 # padded
272
+ frame[:body] = frame[:body][1...(0 - frame[:body][0].ord)]
273
+ end
274
+ if frame[:flags][5] == 1 # priority
275
+ # stream_dependency = frame[:body][0..3]
276
+ # weight = frame[:body][4]
277
+ frame[:body] = frame[:body][5..-1]
278
+ end
279
+
280
+ @header_buffer << frame[:body]
281
+
282
+ return unless frame[:flags][2] == 1 # fin
283
+
284
+ frame[:stream].update @hpack.decode(@header_buffer) # this is where HPACK comes in
285
+ frame[:stream][:time_recieved] ||= Time.now
286
+ frame[:stream][:version] ||= '2'.freeze
287
+
288
+ process_request(@open_streams.delete frame[:sid]) if @header_end_stream
289
+
290
+ # TODO: manage headers and streams
291
+
292
+ @header_buffer.clear
293
+ @header_end_stream = false
294
+ @header_sid = nil
295
+
296
+ end
297
+ def process_data frame
298
+ if frame[:flags][3] == 1 # padded
299
+ frame[:body] = frame[:body][1...(0 - frame[:body][0].ord)]
300
+ end
301
+
302
+ frame[:stream][:body] ? (frame[:stream][:body] << frame[:body]) : (frame[:stream][:body] = frame[:body])
303
+
304
+ process_request(@open_streams.delete frame[:sid]) if frame[:flags][0] == 1
305
+ end
306
+
307
+ # # Settings Codes:
308
+ SETTINGS_HEADER_TABLE_SIZE = 0x1
309
+ # Allows the sender to inform the remote endpoint of the maximum size of the header compression table used to decode header blocks, in octets.
310
+ # 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]).
311
+ # The initial value is 4,096 octets.
312
+ SETTINGS_ENABLE_PUSH = 0x2
313
+ # 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.
314
+ # 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.
315
+ SETTINGS_MAX_CONCURRENT_STREAMS = 0x3
316
+ # 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.
317
+ # 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.
318
+ # A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
319
+ # A zero value does prevent the creation of new streams; however, this can also happen for any limit that is exhausted with active streams.
320
+ # 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.
321
+ SETTINGS_INITIAL_WINDOW_SIZE = 0x4
322
+ # Indicates the sender's initial window size (in octets) for stream-level flow control. The initial value is 216-1 (65,535) octets.
323
+ # This setting affects the window size of all streams (see Section 6.9.2).
324
+ # 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.
325
+ SETTINGS_MAX_FRAME_SIZE = 0x5
326
+ # Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
327
+ # The initial value is 214 (16,384) octets. The value advertised by an endpoint MUST be between this initial value and
328
+ # 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.
329
+ SETTINGS_MAX_HEADER_LIST_SIZE = 0x6
330
+ # 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.
331
+ DEFAULT_SETTING = { SETTINGS_ENABLE_PUSH => 1,
332
+ SETTINGS_INITIAL_WINDOW_SIZE => 65_535,
333
+ SETTINGS_MAX_FRAME_SIZE => 16_384
334
+ }
335
+
336
+ def process_settings frame
337
+ return if frame[:flags] == 1 # do nothing if it's only an ACK.
338
+ return connection_error PROTOCOL_ERROR unless frame[:sid] == 0 && (frame[:body].bytesize % 6) == 0
339
+ settings = StringIO.new frame[:body]
340
+ until settings.eof?
341
+ key = settings.read(2).unpack('n')[0]
342
+ value = settings.read(4).unpack('N')[0]
343
+ Iodine.info "HTTP/2 set #{key}=>#{value} for SID #{frame[:sid]}"
344
+ case frame[:body][0..1].unpack('n')[0]
345
+ when SETTINGS_HEADER_TABLE_SIZE
346
+ return connection_error ENHANCE_YOUR_CALM if value > 4096
347
+ @hpack.resize(value)
348
+ when SETTINGS_ENABLE_PUSH
349
+ @settings[SETTINGS_ENABLE_PUSH] = value
350
+ when SETTINGS_MAX_CONCURRENT_STREAMS
351
+ @settings[SETTINGS_MAX_CONCURRENT_STREAMS] = value
352
+ when SETTINGS_INITIAL_WINDOW_SIZE
353
+ @settings[SETTINGS_INITIAL_WINDOW_SIZE] = value
354
+ when SETTINGS_MAX_FRAME_SIZE
355
+ @settings[SETTINGS_MAX_FRAME_SIZE] = value
356
+ when SETTINGS_MAX_HEADER_LIST_SIZE
357
+ @settings[SETTINGS_MAX_HEADER_LIST_SIZE] = value
358
+ else
359
+ # Unsupported parameters MUST be ignored
360
+ end
361
+ end
362
+ emit_frame '', 0, 4, 1
363
+ end
364
+
365
+ def process_request request
366
+ return if @refuese_new
367
+ ::Iodine::Http::Request.parse request
368
+ # Iodine.info "Should Process request #{request.select { |k,v| k != :io } }"
369
+ @last_stream = request[:sid] if request[:sid] > @last_stream
370
+ # emit_frame [HTTP_1_1_REQUIRED].pack('N'), request[:sid], 0x3, 0
371
+ Iodine.run request, &(::Iodine::Http::Http2.dispatch)
372
+
373
+ end
374
+
375
+ # Error codes:
376
+
377
+ # 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.
378
+ NO_ERROR = 0x0
379
+ # The endpoint detected an unspecific protocol error. This error is for use when a more specific error code is not available.
380
+ PROTOCOL_ERROR = 0x1
381
+ # The endpoint encountered an unexpected internal error.
382
+ INTERNAL_ERROR = 0x2
383
+ # The endpoint detected that its peer violated the flow-control protocol.
384
+ FLOW_CONTROL_ERROR = 0x3
385
+ # The endpoint sent a SETTINGS frame but did not receive a response in a timely manner. See Section 6.5.3 ("Settings Synchronization").
386
+ SETTINGS_TIMEOUT = 0x4
387
+ # The endpoint received a frame after a stream was half-closed.
388
+ STREAM_CLOSED = 0x5
389
+ # The endpoint received a frame with an invalid size.
390
+ FRAME_SIZE_ERROR = 0x6
391
+ # The endpoint refused the stream prior to performing any application processing (see Section 8.1.4 for details).
392
+ REFUSED_STREAM = 0x7
393
+ # Used by the endpoint to indicate that the stream is no longer needed.
394
+ CANCEL = 0x8
395
+ # The endpoint is unable to maintain the header compression context for the connection.
396
+ COMPRESSION_ERROR = 0x9
397
+ # The connection established in response to a CONNECT request (Section 8.3) was reset or abnormally closed.
398
+ CONNECT_ERROR = 0xa
399
+ # The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load.
400
+ ENHANCE_YOUR_CALM = 0xb
401
+ # The underlying transport has properties that do not meet minimum security requirements (see Section 9.2).
402
+ INADEQUATE_SECURITY = 0xc
403
+ # The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
404
+ HTTP_1_1_REQUIRED = 0xd
405
+
406
+ # Process a connection error and act accordingly.
407
+ #
408
+ # @return [true, false, nil] returns true if connection handling can continue of false (or nil) for a fatal error.
409
+ def connection_error type
410
+ Iodine.warn "HTTP/2 error #{type}."
411
+ go_away type
412
+ # case type
413
+ # when NO_ERROR
414
+ # when PROTOCOL_ERROR
415
+ # when INTERNAL_ERROR
416
+ # when FLOW_CONTROL_ERROR
417
+ # when SETTINGS_TIMEOUT
418
+ # when STREAM_CLOSED
419
+ # when FRAME_SIZE_ERROR
420
+ # when REFUSED_STREAM
421
+ # when CANCEL
422
+ # when COMPRESSION_ERROR
423
+ # when CONNECT_ERROR
424
+ # when ENHANCE_YOUR_CALM
425
+ # when INADEQUATE_SECURITY
426
+ # when HTTP_1_1_REQUIRED
427
+ # else
428
+ # end
429
+ # nil
430
+ end
431
+
432
+ def self.dispatch
433
+ @dispatch ||= Proc.new do |request|
434
+ case request[:method]
435
+ when 'TRACE'.freeze
436
+ close
437
+ return false
438
+ when 'OPTIONS'.freeze
439
+ response = ::Iodine::Http::Response.new request
440
+ response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'.freeze
441
+ response['access-control-allow-origin'.freeze] = '*'
442
+ response['content-length'.freeze] = 0
443
+ response.finish
444
+ return false
445
+ end
446
+ response = ::Iodine::Http::Response.new request
447
+ begin
448
+ ret = Iodine::Http.on_http.call(request, response)
449
+ if ret.is_a?(String)
450
+ response << ret
451
+ elsif ret == false
452
+ response.clear && (response.status = 404) && (response << ::Iodine::Http::Response::STATUS_CODES[404])
453
+ end
454
+ response.finish
455
+ rescue => e
456
+ ::Iodine.error e
457
+ ::Iodine::Http::Response.new(request, 500, {}).finish
458
+ end
459
+ end
460
+ end
461
+ end
462
+ end
463
+ end
464
+
465
+