source: EcnlProtoTool/trunk/mrbgems/mruby-tls-openssl/http2.rb@ 331

Last change on this file since 331 was 331, checked in by coas-nagasima, 6 years ago

prototoolに関連するプロジェクトをnewlibからmuslを使うよう変更・更新
ntshellをnewlibの下位の実装から、muslのsyscallの実装に変更・更新
以下のOSSをアップデート
・mruby-1.3.0
・musl-1.1.18
・onigmo-6.1.3
・tcc-0.9.27
以下のOSSを追加
・openssl-1.1.0e
・curl-7.57.0
・zlib-1.2.11
以下のmrbgemsを追加
・iij/mruby-digest
・iij/mruby-env
・iij/mruby-errno
・iij/mruby-iijson
・iij/mruby-ipaddr
・iij/mruby-mock
・iij/mruby-require
・iij/mruby-tls-openssl

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-ruby
File size: 7.8 KB
Line 
1# Required mrbgems: mruby-pack, mruby-regexp-pcre(or some regexp mrbgem)
2
3# TODO:
4# - connect via proxy
5
6module HTTP2
7 FRAME_TYPE_DATA = 0
8 FRAME_TYPE_HEADERS = 1
9 FRAME_TYPE_SETTINGS = 4
10 FRAME_TYPE_GOAWAY = 7
11 FRAME_TYPE_BLOCK = 11
12
13 class Client
14 FRAME_FLAG_SETTINGS_ACK = 0x1
15
16 def initialize(host, port=443)
17 @tls = TLS.new host, {
18 :version => "TLSv1.2",
19 :port => port,
20 :alpn => "h2",
21 :certs => "nghttp2.crt", :identity => "nghttp2"
22 }
23 @recvbuf = ""
24 @my_next_stream_id = 1
25 @window = 0
26 @streams = {}
27
28 @settings = {
29 :header_table_size => 4096,
30 :enable_push => 1,
31 :max_concurrent_streams => -1, # no limit
32 :enable_push => 1,
33 :initial_window_size => 65535,
34 :compress_data => 0
35 }
36
37 self.connect
38 end
39
40 def close
41 # send goaway
42 @tls.close
43 end
44
45 def connect
46 self.send_magic
47 self.send_settings_frame
48 self.wait_for :settings_ack
49 end
50
51 def get path, &block
52 stream = self.new_stream
53 self.send_headers_frame path
54 self.wait_for :data_end
55 $stdout.write stream.response_body
56 end
57
58 def make_frame(type, flags, stream, payload)
59 len = [payload.size/65536, payload.size/256, payload.size].map {
60 |x| x % 256 }.pack("C3")
61 len + [type, flags, stream].pack("CCN") + payload
62 end
63
64 def new_stream
65 id = @my_next_stream_id
66 @my_next_stream_id += 2
67 stream = Stream.new(self, id, @settings)
68 @streams[id] = stream
69 stream
70 end
71
72 def send_frame f
73 puts "send_frame: #{f.inspect}" if $debug
74 @tls.write f.to_bytes
75 end
76
77 def send_magic
78 @tls.write "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
79 end
80
81 def send_settings_frame
82 payload = ""
83 frame = make_frame(4, 0, 0, "")
84 puts "send_settings_frame: #{frame.inspect}" if $debug
85 @tls.write frame
86 end
87
88 def send_headers_frame path
89 def make_a_header(key, val)
90 "\x00" + [key.length].pack("C") + key + [val.length].pack("C") + val
91 end
92
93 payload = "\x00\x00"
94 payload = ""
95 payload += make_a_header(":method", "GET")
96 payload += make_a_header(":scheme", "https")
97 payload += make_a_header(":authority", "1.2.3.4:80")
98 payload += make_a_header(":path", path)
99
100 frame = make_frame(FRAME_TYPE_HEADERS, 5, 1, payload)
101 @tls.write frame
102 end
103
104 def send_window_update(stream_id, inc)
105 self.send_frame WindowUpdateFrame.make_update(stream_id, inc)
106 self.send_frame WindowUpdateFrame.make_update(0, inc)
107 end
108
109 def recv_frame
110 loop do
111 f = Frame.parse @recvbuf
112 if f
113 @recvbuf[0, f.bytelen] = ""
114 return f
115 end
116 bytes = @tls.read(1000)
117 @recvbuf += bytes
118 end
119 end
120
121 def stream id
122 @streams[id]
123 end
124
125 def wait_for cond
126 while frame = self.recv_frame
127 if frame.is_a? SettingsFrame
128 if frame.ack?
129 break if cond == :settings_ack
130 else
131 @settings = frame.settings
132 self.send_frame SettingsFrame.make_ack_frame
133 end
134 elsif frame.is_a? DataFrame
135 stream = @streams[frame.stream_id]
136 stream.recv_data_frame(frame)
137 break if cond == :data_end and frame.end_stream?
138 end
139 puts "wait_for receive: #{frame.inspect}" if $debug
140 end
141 end
142 end
143
144 class Stream
145 def initialize(client, id, settings)
146 @client = client
147 @id = id
148 @window = settings[:initial_window_size]
149
150 @response_body = ""
151 end
152
153 attr_reader :response_body
154
155 def close
156 end
157
158 def recv_data_frame dframe
159 @response_body += dframe.payload
160 @client.send_window_update(@id, dframe.len) if dframe.len > 0
161 end
162 end
163
164 class Frame
165 HEADERLEN = 9
166
167 def initialize(len, type, flags, stream_id, payload)
168 @len = len
169 @type = type
170 @flags = flags
171 @stream_id = stream_id
172 @payload = payload
173 end
174
175 attr_reader :len, :type, :flags, :stream_id, :payload
176
177 def self.parse buf
178 return nil if buf.size < HEADERLEN
179 len0, len1, len2, type, flags, stream_id = buf.unpack("C3CCN")
180 len = len0*65536 + len1*256 + len2
181 return nil if buf.size < HEADERLEN + len
182 payload = buf[HEADERLEN, len]
183
184 args = [ len, type, flags, stream_id, payload ]
185 case type
186 when HTTP2::FRAME_TYPE_DATA
187 f = DataFrame.parse(*args)
188 when HTTP2::FRAME_TYPE_HEADERS
189 f = HeadersFrame.parse(*args)
190 when HTTP2::FRAME_TYPE_SETTINGS
191 f = SettingsFrame.parse(*args)
192 when HTTP2::FRAME_TYPE_GOAWAY
193 f = GoawayFrame.parse(*args)
194 when HTTP2::FRAME_TYPE_BLOCK
195 f = BlockFrame.parse(*args)
196 else
197 raise "unsupported frame type: #{type}"
198 end
199 f
200 end
201
202 def bytelen
203 HEADERLEN + @len
204 end
205
206 def to_bytes
207 lens = [payload.size/65536, payload.size/256, payload.size].map {
208 |x| x % 256 }.pack("C3")
209 lens + [ @type, @flags, @stream_id ].pack("CCN") + @payload
210 end
211
212 def inspect
213 format "<%s len=%d flags=0x%02x stream-id=%d%s>", self.class, @len, @flags, @stream_id, inspect_payload
214 end
215
216 def inspect_payload
217 ""
218 end
219 end
220
221 class DataFrame < Frame
222 def self.parse(len, type, flags, stream_id, payload)
223 f = self.new(len, type, flags, stream_id, payload)
224 end
225
226 def end_stream?
227 (@flags & 1) > 0
228 end
229 end
230
231 class HeadersFrame < Frame
232 def self.parse(len, type, flags, stream_id, payload)
233 f = self.new(len, type, flags, stream_id, payload)
234 end
235 end
236
237 class SettingsFrame < Frame
238 def self.make_ack_frame
239 self.new(0, 4, 1, 0, "")
240 end
241
242 def self.parse(len, type, flags, stream_id, payload)
243 if (flags & 1) == 0
244 s = payload.dup
245 h = {}
246 while s.length > 0
247 t, v = s.unpack("nN")
248 case t
249 when 1
250 h[:header_table_size] = v
251 when 2
252 h[:enable_push] = v
253 when 3
254 h[:max_concurrent_streams] = v
255 when 4
256 h[:initial_window_size] = v
257 when 5
258 h[:compress_data] = v
259 else
260 raise "unknown settings parameter: #{t} = #{v}"
261 end
262 s = s[6..-1]
263 end
264 f = self.new len, type, flags, stream_id, payload
265 f.settings = h
266 else
267 # SETTINGS ACK frame
268 f = self.new len, type, flags, stream_id, payload
269 end
270 f
271 end
272
273 attr_accessor :settings
274
275 def ack?
276 (flags & 1) == 1
277 end
278 end
279
280 class WindowUpdateFrame < Frame
281 attr_accessor :inc
282
283 def self.parse(len, type, flags, stream_id, payload)
284 f = self.new(len, type, flags, stream_id, payload)
285 end
286
287 def self.make_update(stream_id, inc)
288 payload = [ inc ].pack("N")
289 f = self.new(payload.size, 8, 0, stream_id, payload)
290 f.inc = inc
291 f
292 end
293
294 def inspect_payload
295 " inc=#{@inc}"
296 end
297 end
298
299 class GoawayFrame < Frame
300 attr_accessor :laststream, :ecode, :debugdata
301
302 def self.parse(len, type, flags, stream_id, payload)
303 f = self.new(len, type, flags, stream_id, payload)
304 f.laststream, f.ecode, f.debugdata = payload.unpack("NNa*")
305 f
306 end
307
308 def inspect_payload
309 " last_stream_id=#{@laststream} error_code=#{@ecode} additional_debug_data=\"#{@debugdata}\""
310 end
311 end
312
313 class BlockFrame < Frame
314 def self.parse(len, type, flags, stream_id, payload)
315 f = self.new(len, type, flags, stream_id, payload)
316 end
317 end
318end
319
320
321$debug = true
322if ARGV.size != 1
323 puts "usage: mruby http2.rb <url>"
324 exit
325end
326
327unless ARGV[0] =~ Regexp.new('https://([^:/]+)(:\d+)?(/.*)')
328 puts "unsupported url: #{ARGV[0]}"
329 exit 1
330end
331host = $1
332port = ($2) ? $2[1..-1].to_i : 443
333path = $3 || ""
334
335http2 = HTTP2::Client.new host, port
336http2.get path
337http2.close
Note: See TracBrowser for help on using the repository browser.