http2 0.0.24 → 0.0.25

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.
@@ -0,0 +1,58 @@
1
+ class Http2::UrlBuilder
2
+ attr_accessor :host, :port, :protocol, :path, :params
3
+
4
+ def initialize args = {}
5
+ @params = {}
6
+ end
7
+
8
+ def build_params
9
+ url_params = ""
10
+
11
+ if !params.empty?
12
+ first = true
13
+
14
+ params.each do |key, val|
15
+ if first
16
+ first = false
17
+ else
18
+ url_params << "&"
19
+ end
20
+
21
+ url_params << Http2::Utils.urlenc(key)
22
+ url_params << "="
23
+ url_params << Http2::Utils.urlenc(val)
24
+ end
25
+ end
26
+
27
+ return url_params
28
+ end
29
+
30
+ def build_path_and_params
31
+ url = "#{path}"
32
+
33
+ if params?
34
+ url << "?"
35
+ url << build_params
36
+ end
37
+
38
+ return url
39
+ end
40
+
41
+ def build
42
+ url = ""
43
+ url << "#{protocol}://" if protocol
44
+
45
+ if host
46
+ url << host
47
+ url << ":#{port}/" if port
48
+ end
49
+
50
+ url << build_path_and_params
51
+
52
+ return url
53
+ end
54
+
55
+ def params?
56
+ @params.any?
57
+ end
58
+ end
data/lib/http2.rb CHANGED
@@ -29,15 +29,17 @@ class Http2
29
29
  end
30
30
  end
31
31
 
32
- attr_reader :cookies, :args, :resp, :raise_errors, :nl
32
+ attr_reader :autostate, :connection, :cookies, :args, :debug, :mutex, :resp, :raise_errors, :nl
33
+ attr_accessor :keepalive_max, :keepalive_timeout
33
34
 
34
- VALID_ARGUMENTS_INITIALIZE = [:host, :port, :ssl, :nl, :user_agent, :raise_errors, :follow_redirects, :debug, :encoding_gzip, :autostate, :basic_auth, :extra_headers, :proxy]
35
+ VALID_ARGUMENTS_INITIALIZE = [:host, :port, :skip_port_in_host_header, :ssl, :nl, :user_agent, :raise_errors, :follow_redirects, :debug, :encoding_gzip, :autostate, :basic_auth, :extra_headers, :proxy]
35
36
  def initialize(args = {})
36
37
  @args = parse_init_args(args)
37
38
  set_default_values
38
39
  @cookies = {}
39
40
  @mutex = Monitor.new
40
- self.reconnect
41
+
42
+ @connection = ::Http2::Connection.new(self)
41
43
 
42
44
  if block_given?
43
45
  begin
@@ -48,35 +50,11 @@ class Http2
48
50
  end
49
51
  end
50
52
 
51
- #Closes current connection if any, changes the arguments on the object and reconnects keeping all cookies and other stuff intact.
53
+ # Closes current connection if any, changes the arguments on the object and reconnects keeping all cookies and other stuff intact.
52
54
  def change(args)
53
- self.close
54
55
  @args.merge!(args)
55
- self.reconnect
56
- end
57
-
58
- #Closes the current connection if any.
59
- def close
60
- @sock.close if @sock and !@sock.closed?
61
- @sock_ssl.close if @sock_ssl and !@sock_ssl.closed?
62
- @sock_plain.close if @sock_plain and !@sock_plain.closed?
63
- end
64
-
65
- #Returns boolean based on the if the object is connected and the socket is working.
66
- #===Examples
67
- # puts "Socket is working." if http.socket_working?
68
- def socket_working?
69
- return false if !@sock or @sock.closed?
70
-
71
- if @keepalive_timeout and @request_last
72
- between = Time.now.to_i - @request_last.to_i
73
- if between >= @keepalive_timeout
74
- puts "Http2: We are over the keepalive-wait - returning false for socket_working?." if @debug
75
- return false
76
- end
77
- end
78
-
79
- return true
56
+ @connection.destroy
57
+ @connection = ::Http2::Connection.new(self)
80
58
  end
81
59
 
82
60
  #Destroys the object unsetting all variables and closing all sockets.
@@ -91,53 +69,24 @@ class Http2
91
69
  @keepalive_timeout = nil
92
70
  @request_last = nil
93
71
 
94
- @sock.close if @sock and !@sock.closed?
95
- @sock = nil
96
-
97
- @sock_plain.close if @sock_plain and !@sock_plain.closed?
98
- @sock_plain = nil
99
-
100
- @sock_ssl.close if @sock_ssl and !@sock_ssl.closed?
101
- @sock_ssl = nil
102
- end
103
-
104
- #Reconnects to the host.
105
- def reconnect
106
- puts "Http2: Reconnect." if @debug
107
-
108
- #Open connection.
109
- if @args[:proxy] && @args[:ssl]
110
- connect_proxy_ssl
111
- elsif @args[:proxy]
112
- connect_proxy
113
- else
114
- print "Http2: Opening socket connection to '#{@args[:host]}:#{@args[:port]}'.\n" if @debug
115
- @sock_plain = TCPSocket.new(@args[:host], @args[:port].to_i)
116
- end
117
-
118
- if @args[:ssl]
119
- apply_ssl
120
- else
121
- @sock = @sock_plain
122
- end
72
+ @connection.destroy
73
+ @connection = nil
123
74
  end
124
75
 
125
76
  #Forces various stuff into arguments-hash like URL from original arguments and enables single-string-shortcuts and more.
126
77
  def parse_args(*args)
127
- if args.length == 1 and args.first.is_a?(String)
78
+ if args.length == 1 && args.first.is_a?(String)
128
79
  args = {:url => args.first}
129
80
  elsif args.length >= 2
130
81
  raise "Couldnt parse arguments."
131
- elsif args.is_a?(Array) and args.length == 1
82
+ elsif args.is_a?(Array) && args.length == 1
132
83
  args = args.first
133
84
  else
134
85
  raise "Invalid arguments: '#{args.class.name}'."
135
86
  end
136
87
 
137
- if !args.key?(:url) or !args[:url]
138
- raise "No URL given: '#{args[:url]}'."
139
- elsif args[:url].to_s.split("\n").length > 1
140
- raise "Multiple lines given in URL: '#{args[:url]}'."
88
+ if args[:url].to_s.split("\n").length != 1
89
+ raise "Invalid URL: '#{args[:url]}'."
141
90
  end
142
91
 
143
92
  return args
@@ -148,58 +97,18 @@ class Http2
148
97
  # res = http.get("somepage.html")
149
98
  # print res.body #=> <String>-object containing the HTML gotten.
150
99
  def get(args)
151
- args = self.parse_args(args)
152
-
153
- if args.key?(:method) && args[:method]
154
- method = args[:method].to_s.upcase
155
- else
156
- method = "GET"
157
- end
158
-
159
- header_str = "#{method} /#{args[:url]} HTTP/1.1#{@nl}"
160
- header_str << self.header_str(self.default_headers(args), args)
161
- header_str << @nl
162
-
163
- @mutex.synchronize do
164
- print "Http2: Writing headers.\n" if @debug
165
- print "Header str: #{header_str}\n" if @debug
166
- self.write(header_str)
167
-
168
- print "Http2: Reading response.\n" if @debug
169
- resp = self.read_response(args)
170
-
171
- print "Http2: Done with get request.\n" if @debug
172
- return resp
173
- end
100
+ ::Http2::GetRequest.new(self, args).execute
174
101
  end
175
102
 
176
103
  # Proxies the request to another method but forces the method to be "DELETE".
177
104
  def delete(args)
178
105
  if args[:json]
179
- return self.post(args.merge(:method => :delete))
106
+ return post(args.merge(:method => :delete))
180
107
  else
181
- return self.get(args.merge(:method => :delete))
108
+ return get(args.merge(:method => :delete))
182
109
  end
183
110
  end
184
111
 
185
- #Tries to write a string to the socket. If it fails it reconnects and tries again.
186
- def write(str)
187
- #Reset variables.
188
- @length = nil
189
- @encoding = nil
190
- self.reconnect if !self.socket_working?
191
-
192
- begin
193
- raise Errno::EPIPE, "The socket is closed." if !@sock or @sock.closed?
194
- self.sock_write(str)
195
- rescue Errno::EPIPE #this can also be thrown by puts.
196
- self.reconnect
197
- self.sock_write(str)
198
- end
199
-
200
- @request_last = Time.now
201
- end
202
-
203
112
  #Returns the default headers for a request.
204
113
  #===Examples
205
114
  # headers_hash = http.default_headers
@@ -213,192 +122,40 @@ class Http2
213
122
  }
214
123
 
215
124
  #Possible to give custom host-argument.
216
- _args = args[:host] ? args : @args
217
- headers["Host"] = _args[:host]
218
- headers["Host"] += ":#{_args[:port]}" unless _args[:port] && [80,443].include?(_args[:port].to_i)
125
+ host = args[:host] || @args[:host]
126
+ port = args[:port] || @args[:port]
219
127
 
220
- if !@args.key?(:encoding_gzip) or @args[:encoding_gzip]
221
- headers["Accept-Encoding"] = "gzip"
222
- end
128
+ headers["Host"] = host
129
+ headers["Host"] << ":#{port}" if port && ![80, 443].include?(port.to_i) && !@args[:skip_port_in_host_header]
130
+ headers["Accept-Encoding"] = "gzip" if @args[:encoding_gzip]
223
131
 
224
132
  if @args[:basic_auth]
225
133
  require "base64" unless ::Kernel.const_defined?(:Base64)
226
134
  headers["Authorization"] = "Basic #{Base64.encode64("#{@args[:basic_auth][:user]}:#{@args[:basic_auth][:passwd]}").strip}"
227
135
  end
228
136
 
229
- if @args[:extra_headers]
230
- headers.merge!(@args[:extra_headers])
231
- end
232
-
233
- if args[:headers]
234
- headers.merge!(args[:headers])
235
- end
236
-
137
+ headers.merge!(@args[:extra_headers]) if @args[:extra_headers]
138
+ headers.merge!(args[:headers]) if args[:headers]
237
139
  return headers
238
140
  end
239
141
 
240
- #This is used to convert a hash to valid post-data recursivly.
241
- def self.post_convert_data(pdata, args = nil)
242
- praw = ""
243
-
244
- if pdata.is_a?(Hash)
245
- pdata.each do |key, val|
246
- praw << "&" if praw != ""
247
-
248
- if args and args[:orig_key]
249
- key = "#{args[:orig_key]}[#{key}]"
250
- end
251
-
252
- if val.is_a?(Hash) or val.is_a?(Array)
253
- praw << self.post_convert_data(val, {:orig_key => key})
254
- else
255
- praw << "#{Http2::Utils.urlenc(key)}=#{Http2::Utils.urlenc(Http2.post_convert_data(val))}"
256
- end
257
- end
258
- elsif pdata.is_a?(Array)
259
- count = 0
260
- pdata.each do |val|
261
- praw << "&" if praw != ""
262
-
263
- if args and args[:orig_key]
264
- key = "#{args[:orig_key]}[#{count}]"
265
- else
266
- key = count
267
- end
268
-
269
- if val.is_a?(Hash) or val.is_a?(Array)
270
- praw << self.post_convert_data(val, {:orig_key => key})
271
- else
272
- praw << "#{Http2::Utils.urlenc(key)}=#{Http2::Utils.urlenc(Http2.post_convert_data(val))}"
273
- end
274
-
275
- count += 1
276
- end
277
- else
278
- return pdata.to_s
279
- end
280
-
281
- return praw
282
- end
283
-
284
- VALID_ARGUMENTS_POST = [:post, :url, :default_headers, :headers, :json, :method, :cookies, :on_content, :content_type]
285
142
  #Posts to a certain page.
286
143
  #===Examples
287
144
  # res = http.post("login.php", {"username" => "John Doe", "password" => 123)
288
145
  def post(args)
289
- args.each do |key, val|
290
- raise "Invalid key: '#{key}'." unless VALID_ARGUMENTS_POST.include?(key)
291
- end
292
-
293
- args = self.parse_args(args)
294
-
295
- if args.key?(:method) && args[:method]
296
- method = args[:method].to_s.upcase
297
- else
298
- method = "POST"
299
- end
300
-
301
- if args[:json]
302
- require "json" unless ::Kernel.const_defined?(:JSON)
303
- praw = args[:json].to_json
304
- content_type = "application/json"
305
- elsif args[:post].is_a?(String)
306
- praw = args[:post]
307
- else
308
- phash = args[:post] ? args[:post].clone : {}
309
- autostate_set_on_post_hash(phash) if @args[:autostate]
310
- praw = Http2.post_convert_data(phash)
311
- end
312
-
313
- content_type = args[:content_type] || content_type || "application/x-www-form-urlencoded"
314
-
315
- @mutex.synchronize do
316
- puts "Http2: Doing post." if @debug
317
-
318
- header_str = "#{method} /#{args[:url]} HTTP/1.1#{@nl}"
319
- header_str << self.header_str({"Content-Length" => praw.bytesize, "Content-Type" => content_type}.merge(self.default_headers(args)), args)
320
- header_str << @nl
321
- header_str << praw
322
-
323
- puts "Http2: Header str: #{header_str}" if @debug
324
-
325
- self.write(header_str)
326
- return self.read_response(args)
327
- end
146
+ ::Http2::PostRequest.new(self, args).execute
328
147
  end
329
148
 
330
149
  #Posts to a certain page using the multipart-method.
331
150
  #===Examples
332
151
  # res = http.post_multipart("upload.php", {"normal_value" => 123, "file" => Tempfile.new(?)})
333
152
  def post_multipart(*args)
334
- args = self.parse_args(*args)
335
-
336
- phash = args[:post].clone
337
- autostate_set_on_post_hash(phash) if @args[:autostate]
338
-
339
- post_multipart_helper = ::Http2::PostMultipartHelper.new(self)
340
-
341
- #Use tempfile to store contents to avoid eating memory if posting something really big.
342
- post_multipart_helper.generate_raw(phash) do |helper, praw|
343
- #Generate header-string containing 'praw'-variable.
344
- header_str = "POST /#{args[:url]} HTTP/1.1#{@nl}"
345
- header_str << header_str(default_headers(args).merge(
346
- "Content-Type" => "multipart/form-data; boundary=#{helper.boundary}",
347
- "Content-Length" => praw.size
348
- ), args)
349
- header_str << @nl
350
-
351
- print "Http2: Headerstr: #{header_str}\n" if @debug
352
-
353
- #Write and return.
354
- @mutex.synchronize do
355
- write(header_str)
356
-
357
- praw.rewind
358
- praw.each_line do |data|
359
- sock_write(data)
360
- end
361
-
362
- return read_response(args)
363
- end
364
- end
365
- end
366
-
367
- def sock_write(str)
368
- str = str.to_s
369
- return nil if str.empty?
370
- count = @sock.write(str)
371
- raise "Couldnt write to socket: '#{count}', '#{str}'." if count <= 0
372
- end
373
-
374
- def sock_puts(str)
375
- self.sock_write("#{str}#{@nl}")
153
+ ::Http2::PostMultipartRequest.new(self, *args).execute
376
154
  end
377
155
 
378
156
  #Returns a header-string which normally would be used for a request in the given state.
379
157
  def header_str(headers_hash, args = {})
380
- if @cookies.length > 0 and (!args.key?(:cookies) or args[:cookies])
381
- cstr = ""
382
-
383
- first = true
384
- @cookies.each do |cookie_name, cookie_data|
385
- cstr << "; " if !first
386
- first = false if first
387
-
388
- if cookie_data.is_a?(Hash)
389
- name = cookie_data["name"]
390
- value = cookie_data["value"]
391
- else
392
- name = cookie_name
393
- value = cookie_data
394
- end
395
-
396
- raise "Unexpected lines: #{value.lines.to_a.length}." if value.lines.to_a.length != 1
397
- cstr << "#{Http2::Utils.urlenc(name)}=#{Http2::Utils.urlenc(value)}"
398
- end
399
-
400
- headers_hash["Cookie"] = cstr
401
- end
158
+ headers_hash["Cookie"] = cookie_header_string
402
159
 
403
160
  headers_str = ""
404
161
  headers_hash.each do |key, val|
@@ -408,6 +165,29 @@ class Http2
408
165
  return headers_str
409
166
  end
410
167
 
168
+ def cookie_header_string
169
+ cstr = ""
170
+
171
+ first = true
172
+ @cookies.each do |cookie_name, cookie_data|
173
+ cstr << "; " unless first
174
+ first = false if first
175
+
176
+ if cookie_data.is_a?(Hash)
177
+ name = cookie_data["name"]
178
+ value = cookie_data["value"]
179
+ else
180
+ name = cookie_name
181
+ value = cookie_data
182
+ end
183
+
184
+ raise "Unexpected lines: #{value.lines.to_a.length}." if value.lines.to_a.length != 1
185
+ cstr << "#{Http2::Utils.urlenc(name)}=#{Http2::Utils.urlenc(value)}"
186
+ end
187
+
188
+ return cstr
189
+ end
190
+
411
191
  def on_content_call(args, str)
412
192
  args[:on_content].call(str) if args.key?(:on_content)
413
193
  end
@@ -416,12 +196,7 @@ class Http2
416
196
  #===Examples
417
197
  # res = http.read_response
418
198
  def read_response(args = {})
419
- Http2::ResponseReader.new(
420
- http2: self,
421
- sock: @sock,
422
- args: args,
423
- debug: @debug
424
- ).response
199
+ ::Http2::ResponseReader.new(http2: self, sock: @sock, args: args).response
425
200
  end
426
201
 
427
202
  private
@@ -462,7 +237,7 @@ private
462
237
 
463
238
  def set_default_values
464
239
  @debug = @args[:debug]
465
- @autostate_values = {} if @args[:autostate]
240
+ @autostate_values = {} if autostate
466
241
  @nl = @args[:nl] || "\r\n"
467
242
 
468
243
  if !@args[:port]
@@ -485,42 +260,4 @@ private
485
260
  @raise_errors = false
486
261
  end
487
262
  end
488
-
489
- def connect_proxy_ssl
490
- print "Http2: Initializing proxy stuff.\n" if @debug
491
- @sock_plain = TCPSocket.new(@args[:proxy][:host], @args[:proxy][:port])
492
-
493
- @sock_plain.write("CONNECT #{@args[:host]}:#{@args[:port]} HTTP/1.0#{@nl}")
494
- @sock_plain.write("User-Agent: #{@uagent}#{@nl}")
495
-
496
- if @args[:proxy][:user] and @args[:proxy][:passwd]
497
- credential = ["#{@args[:proxy][:user]}:#{@args[:proxy][:passwd]}"].pack("m")
498
- credential.delete!("\r\n")
499
- @sock_plain.write("Proxy-Authorization: Basic #{credential}#{@nl}")
500
- end
501
-
502
- @sock_plain.write(@nl)
503
-
504
- res = @sock_plain.gets
505
- raise res if res.to_s.downcase != "http/1.0 200 connection established#{@nl}"
506
- end
507
-
508
- def connect_proxy
509
- puts "Http2: Opening socket connection to '#{@args[:host]}:#{@args[:port]}' through proxy '#{@args[:proxy][:host]}:#{@args[:proxy][:port]}'." if @debug
510
- @sock_plain = TCPSocket.new(@args[:proxy][:host], @args[:proxy][:port].to_i)
511
- end
512
-
513
- def apply_ssl
514
- puts "Http2: Initializing SSL." if @debug
515
- require "openssl" unless ::Kernel.const_defined?(:OpenSSL)
516
-
517
- ssl_context = OpenSSL::SSL::SSLContext.new
518
- #ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
519
-
520
- @sock_ssl = OpenSSL::SSL::SSLSocket.new(@sock_plain, ssl_context)
521
- @sock_ssl.sync_close = true
522
- @sock_ssl.connect
523
-
524
- @sock = @sock_ssl
525
- end
526
263
  end