http2 0.0.31 → 0.0.32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -1
- data/Rakefile +16 -13
- data/lib/http2.rb +25 -22
- data/lib/http2/base_request.rb +22 -0
- data/{include → lib/http2}/connection.rb +10 -9
- data/{include → lib/http2}/cookie.rb +4 -1
- data/{include → lib/http2}/errors.rb +1 -1
- data/lib/http2/get_request.rb +33 -0
- data/{include → lib/http2}/post_data_generator.rb +5 -4
- data/{include → lib/http2}/post_multipart_request.rb +19 -15
- data/{include → lib/http2}/post_request.rb +13 -25
- data/{include → lib/http2}/response.rb +40 -15
- data/{include → lib/http2}/response_reader.rb +21 -13
- data/{include → lib/http2}/url_builder.rb +4 -4
- data/{include → lib/http2}/utils.rb +9 -9
- data/spec/http2/cookies_spec.rb +6 -6
- data/spec/http2/get_request_spec.rb +11 -0
- data/spec/http2/post_multipart_request_spec.rb +11 -0
- data/spec/http2/post_request_spec.rb +11 -0
- data/spec/http2/response_reader_spec.rb +12 -0
- data/spec/http2/response_spec.rb +60 -4
- data/spec/http2/url_builder_spec.rb +1 -1
- data/spec/http2_spec.rb +66 -62
- data/spec/spec_helper.rb +6 -6
- data/spec/spec_root/redirect_test.rhtml +1 -1
- metadata +61 -24
- data/include/get_request.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a6f33cdd27db12f717a7364f0b01e003fa136f4
|
4
|
+
data.tar.gz: e5db49475ea7e1d94d3637273098d3e5fa3c40ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1502af03635d87d6c13253a5f160fb697cf83f5489a99e0b2de1ed7ce3a59b38cc11c970a4848fedc8ab6ddb88c24af31d2d651360b3081ee23a12bf6700770
|
7
|
+
data.tar.gz: f4ac8597f0752a57342e0166a1bf017fc1f320d1eda7b093e7706eec8320827a5f895b320fc6bfe9e55b19dee6e58a59c62a0710a90b9cc0a177be1d5176b5f2
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[![Build Status](https://
|
1
|
+
[![Build Status](https://img.shields.io/shippable/540e7b9b3479c5ea8f9ec21d.svg)](https://app.shippable.com/projects/540e7b9b3479c5ea8f9ec21d/builds/latest)
|
2
2
|
[![Code Climate](https://codeclimate.com/github/kaspernj/http2.png)](https://codeclimate.com/github/kaspernj/http2)
|
3
3
|
[![Code Climate](https://codeclimate.com/github/kaspernj/http2/coverage.png)](https://codeclimate.com/github/kaspernj/http2)
|
4
4
|
|
@@ -36,6 +36,11 @@ You can also use SSL:
|
|
36
36
|
Http2.new(host: "myhost.com", ssl: true, ssl_skip_verify: true)
|
37
37
|
```
|
38
38
|
|
39
|
+
You can make it follow redirects like this:
|
40
|
+
```ruby
|
41
|
+
Http2.new(host: "myhost.com", follow_redirects: true)
|
42
|
+
```
|
43
|
+
|
39
44
|
## Get requests
|
40
45
|
```ruby
|
41
46
|
res = http.get("path/to/something")
|
@@ -155,6 +160,14 @@ http = Http2.new(
|
|
155
160
|
)
|
156
161
|
```
|
157
162
|
|
163
|
+
## Inspect the headers that were sent
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
response = http.get("some_page")
|
167
|
+
request = response.request
|
168
|
+
request.headers_string #=> "GET /some_page\n..."
|
169
|
+
```
|
170
|
+
|
158
171
|
## Contributing to http2
|
159
172
|
|
160
173
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler"
|
5
5
|
begin
|
6
6
|
Bundler.setup(:default, :development)
|
7
7
|
rescue Bundler::BundlerError => e
|
@@ -9,29 +9,32 @@ rescue Bundler::BundlerError => e
|
|
9
9
|
$stderr.puts "Run `bundle install` to install missing gems"
|
10
10
|
exit e.status_code
|
11
11
|
end
|
12
|
-
require
|
12
|
+
require "rake"
|
13
13
|
|
14
|
-
require
|
15
|
-
require
|
14
|
+
require "rspec/core"
|
15
|
+
require "rspec/core/rake_task"
|
16
16
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
17
|
-
spec.pattern = FileList[
|
17
|
+
spec.pattern = FileList["spec/**/*_spec.rb"]
|
18
18
|
end
|
19
19
|
|
20
20
|
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
21
|
-
spec.pattern =
|
21
|
+
spec.pattern = "spec/**/*_spec.rb"
|
22
22
|
spec.rcov = true
|
23
23
|
end
|
24
24
|
|
25
|
-
task :
|
25
|
+
task default: :spec
|
26
26
|
|
27
|
-
require
|
27
|
+
require "rdoc/task"
|
28
28
|
Rake::RDocTask.new do |rdoc|
|
29
|
-
version = File.exist?(
|
29
|
+
version = File.exist?("VERSION") ? File.read("VERSION") : ""
|
30
30
|
|
31
|
-
rdoc.rdoc_dir =
|
31
|
+
rdoc.rdoc_dir = "rdoc"
|
32
32
|
rdoc.title = "http2 #{version}"
|
33
|
-
rdoc.rdoc_files.include(
|
34
|
-
rdoc.rdoc_files.include(
|
33
|
+
rdoc.rdoc_files.include("README*")
|
34
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
35
35
|
end
|
36
36
|
|
37
37
|
Bundler::GemHelper.install_tasks
|
38
|
+
|
39
|
+
require "best_practice_project"
|
40
|
+
BestPracticeProject.load_tasks
|
data/lib/http2.rb
CHANGED
@@ -3,7 +3,7 @@ require "uri"
|
|
3
3
|
require "monitor" unless ::Kernel.const_defined?(:Monitor)
|
4
4
|
require "string-cases"
|
5
5
|
|
6
|
-
#This class tries to emulate a browser in Ruby without any visual stuff. Remember cookies, keep sessions alive, reset connections according to keep-alive rules and more.
|
6
|
+
# This class tries to emulate a browser in Ruby without any visual stuff. Remember cookies, keep sessions alive, reset connections according to keep-alive rules and more.
|
7
7
|
#===Examples
|
8
8
|
# Http2.new(host: "www.somedomain.com", port: 80, ssl: false, debug: false) do |http|
|
9
9
|
# res = http.get("index.rhtml?show=some_page")
|
@@ -17,8 +17,8 @@ require "string-cases"
|
|
17
17
|
class Http2
|
18
18
|
# Autoloader for subclasses.
|
19
19
|
def self.const_missing(name)
|
20
|
-
require "#{File.dirname(__FILE__)}
|
21
|
-
|
20
|
+
require "#{File.dirname(__FILE__)}/http2/#{::StringCases.camel_to_snake(name)}.rb"
|
21
|
+
Http2.const_get(name)
|
22
22
|
end
|
23
23
|
|
24
24
|
# Converts a URL to "is.gd"-short-URL.
|
@@ -45,17 +45,21 @@ class Http2
|
|
45
45
|
begin
|
46
46
|
yield(self)
|
47
47
|
ensure
|
48
|
-
|
48
|
+
destroy
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
def host
|
54
|
-
@args
|
54
|
+
@args.fetch(:host)
|
55
55
|
end
|
56
56
|
|
57
57
|
def port
|
58
|
-
@args
|
58
|
+
@args.fetch(:port)
|
59
|
+
end
|
60
|
+
|
61
|
+
def ssl?
|
62
|
+
@args[:ssl] ? true : false
|
59
63
|
end
|
60
64
|
|
61
65
|
def reconnect
|
@@ -68,7 +72,7 @@ class Http2
|
|
68
72
|
builder.port = port
|
69
73
|
builder.protocol = @args[:protocol]
|
70
74
|
|
71
|
-
|
75
|
+
builder
|
72
76
|
end
|
73
77
|
|
74
78
|
# Closes current connection if any, changes the arguments on the object and reconnects keeping all cookies and other stuff intact.
|
@@ -104,9 +108,9 @@ class Http2
|
|
104
108
|
raise "Invalid arguments: '#{args.class.name}'"
|
105
109
|
end
|
106
110
|
|
107
|
-
raise "Invalid URL: '#{args[:url]}'"
|
111
|
+
raise "Invalid URL: '#{args[:url]}'" if args[:url] != "" && args[:url].to_s.split("\n").length != 1
|
108
112
|
|
109
|
-
|
113
|
+
args
|
110
114
|
end
|
111
115
|
|
112
116
|
# Returns a result-object based on the arguments.
|
@@ -154,7 +158,7 @@ class Http2
|
|
154
158
|
|
155
159
|
headers.merge!(@args[:extra_headers]) if @args[:extra_headers]
|
156
160
|
headers.merge!(args[:headers]) if args[:headers]
|
157
|
-
|
161
|
+
headers
|
158
162
|
end
|
159
163
|
|
160
164
|
# Posts to a certain page.
|
@@ -172,7 +176,7 @@ class Http2
|
|
172
176
|
end
|
173
177
|
|
174
178
|
# Returns a header-string which normally would be used for a request in the given state.
|
175
|
-
def header_str(headers_hash
|
179
|
+
def header_str(headers_hash)
|
176
180
|
headers_hash["Cookie"] = cookie_header_string
|
177
181
|
|
178
182
|
headers_str = ""
|
@@ -180,21 +184,21 @@ class Http2
|
|
180
184
|
headers_str << "#{key}: #{val}#{@nl}"
|
181
185
|
end
|
182
186
|
|
183
|
-
|
187
|
+
headers_str
|
184
188
|
end
|
185
189
|
|
186
190
|
def cookie_header_string
|
187
191
|
cstr = ""
|
188
192
|
|
189
193
|
first = true
|
190
|
-
@cookies.each do |
|
194
|
+
@cookies.each do |_cookie_name, cookie|
|
191
195
|
cstr << "; " unless first
|
192
196
|
first = false if first
|
193
197
|
ensure_single_lines([cookie.name, cookie.value])
|
194
198
|
cstr << "#{Http2::Utils.urlenc(cookie.name)}=#{Http2::Utils.urlenc(cookie.value)}"
|
195
199
|
end
|
196
200
|
|
197
|
-
|
201
|
+
cstr
|
198
202
|
end
|
199
203
|
|
200
204
|
def cookie(name)
|
@@ -216,8 +220,8 @@ class Http2
|
|
216
220
|
# Reads the response after posting headers and data.
|
217
221
|
#===Examples
|
218
222
|
# res = http.read_response
|
219
|
-
def read_response(args = {})
|
220
|
-
::Http2::ResponseReader.new(http2: self, sock: @sock, args: args).response
|
223
|
+
def read_response(request, args = {})
|
224
|
+
::Http2::ResponseReader.new(http2: self, sock: @sock, args: args, request: request).response
|
221
225
|
end
|
222
226
|
|
223
227
|
def to_s
|
@@ -231,13 +235,13 @@ class Http2
|
|
231
235
|
private
|
232
236
|
|
233
237
|
def host_header
|
234
|
-
#Possible to give custom host-argument.
|
238
|
+
# Possible to give custom host-argument.
|
235
239
|
host = args[:host] || self.host
|
236
240
|
port = args[:port] || self.port
|
237
241
|
|
238
242
|
host_header_string = "#{host}" # Copy host string to avoid changing the original string if port has been given!
|
239
243
|
host_header_string << ":#{port}" if port && ![80, 443].include?(port.to_i) && !@args[:skip_port_in_host_header]
|
240
|
-
|
244
|
+
host_header_string
|
241
245
|
end
|
242
246
|
|
243
247
|
# Registers the states from a result.
|
@@ -247,7 +251,6 @@ private
|
|
247
251
|
|
248
252
|
res.body.to_s.scan(/<input type="hidden" name="__(EVENTTARGET|EVENTARGUMENT|VIEWSTATE|LASTFOCUS)" id="(.*?)" value="(.*?)" \/>/) do |match|
|
249
253
|
name = "__#{match[0]}"
|
250
|
-
id = match[1]
|
251
254
|
value = match[2]
|
252
255
|
|
253
256
|
puts "Http2: Registered autostate-value with name '#{name}' and value '#{value}'." if @debug
|
@@ -266,14 +269,14 @@ private
|
|
266
269
|
args = {host: args} if args.is_a?(String)
|
267
270
|
raise "Arguments wasnt a hash." unless args.is_a?(Hash)
|
268
271
|
|
269
|
-
args.each do |key,
|
272
|
+
args.each do |key, _val|
|
270
273
|
raise "Invalid key: '#{key}'." unless VALID_ARGUMENTS_INITIALIZE.include?(key)
|
271
274
|
end
|
272
275
|
|
273
276
|
args[:proxy][:connect] = true if args[:proxy] && !args[:proxy].key?(:connect) && args[:ssl]
|
274
277
|
|
275
278
|
raise "No host was given." unless args[:host]
|
276
|
-
|
279
|
+
args
|
277
280
|
end
|
278
281
|
|
279
282
|
def set_default_values
|
@@ -281,7 +284,7 @@ private
|
|
281
284
|
@autostate_values = {} if autostate
|
282
285
|
@nl = @args[:nl] || "\r\n"
|
283
286
|
|
284
|
-
|
287
|
+
unless @args[:port]
|
285
288
|
if @args[:ssl]
|
286
289
|
@args[:port] = 443
|
287
290
|
else
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Http2::BaseRequest
|
2
|
+
attr_reader :http2, :args, :debug
|
3
|
+
|
4
|
+
VALID_ARGUMENTS_POST = [:post, :url, :default_headers, :headers, :json, :method, :cookies, :on_content, :content_type]
|
5
|
+
|
6
|
+
def initialize(http2, args)
|
7
|
+
@http2 = http2
|
8
|
+
@args = http2.parse_args(args)
|
9
|
+
@debug = http2.debug
|
10
|
+
@nl = http2.nl
|
11
|
+
|
12
|
+
@args.each do |key, _val|
|
13
|
+
raise "Invalid key: '#{key}'." unless VALID_ARGUMENTS_POST.include?(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
@conn = @http2.connection
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
@args.fetch(:url)
|
21
|
+
end
|
22
|
+
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
class Http2::Connection
|
2
2
|
def initialize(http2)
|
3
|
-
@http2
|
3
|
+
@http2 = http2
|
4
|
+
@debug = http2.debug
|
5
|
+
@args = http2.args
|
6
|
+
@nl = http2.nl
|
4
7
|
reconnect
|
5
8
|
end
|
6
9
|
|
@@ -23,10 +26,6 @@ class Http2::Connection
|
|
23
26
|
@sock.read(length)
|
24
27
|
end
|
25
28
|
|
26
|
-
def close
|
27
|
-
@sock.close
|
28
|
-
end
|
29
|
-
|
30
29
|
def closed?
|
31
30
|
@sock.closed?
|
32
31
|
end
|
@@ -46,10 +45,12 @@ class Http2::Connection
|
|
46
45
|
def write(str)
|
47
46
|
reconnect unless socket_working?
|
48
47
|
|
48
|
+
puts "Http2: Writing: #{str}" if @debug
|
49
|
+
|
49
50
|
begin
|
50
51
|
raise Errno::EPIPE, "The socket is closed." if !@sock || @sock.closed?
|
51
52
|
sock_write(str)
|
52
|
-
rescue Errno::EPIPE #this can also be thrown by puts.
|
53
|
+
rescue Errno::EPIPE # this can also be thrown by puts.
|
53
54
|
reconnect
|
54
55
|
sock_write(str)
|
55
56
|
end
|
@@ -61,7 +62,7 @@ class Http2::Connection
|
|
61
62
|
def reconnect
|
62
63
|
puts "Http2: Reconnect." if @debug
|
63
64
|
|
64
|
-
#Open connection.
|
65
|
+
# Open connection.
|
65
66
|
if @args[:proxy]
|
66
67
|
if @args[:proxy][:connect]
|
67
68
|
connect_proxy_connect
|
@@ -84,7 +85,7 @@ class Http2::Connection
|
|
84
85
|
#===Examples
|
85
86
|
# puts "Socket is working." if http.socket_working?
|
86
87
|
def socket_working?
|
87
|
-
return false if !@sock
|
88
|
+
return false if !@sock || @sock.closed?
|
88
89
|
|
89
90
|
if @keepalive_timeout && @request_last
|
90
91
|
between = Time.now.to_i - @request_last.to_i
|
@@ -94,7 +95,7 @@ class Http2::Connection
|
|
94
95
|
end
|
95
96
|
end
|
96
97
|
|
97
|
-
|
98
|
+
true
|
98
99
|
end
|
99
100
|
|
100
101
|
# Closes the current connection if any.
|
@@ -2,7 +2,10 @@ class Http2::Cookie
|
|
2
2
|
attr_reader :name, :value, :path, :expires_raw
|
3
3
|
|
4
4
|
def initialize(args)
|
5
|
-
@name
|
5
|
+
@name = args[:name]
|
6
|
+
@value = args[:value]
|
7
|
+
@path = args[:path]
|
8
|
+
@expires_raw = args[:expires]
|
6
9
|
end
|
7
10
|
|
8
11
|
def inspect
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Http2::GetRequest < Http2::BaseRequest
|
2
|
+
def execute
|
3
|
+
@http2.mutex.synchronize do
|
4
|
+
@http2.connection.write(headers_string)
|
5
|
+
|
6
|
+
puts "Http2: Reading response." if @debug
|
7
|
+
resp = @http2.read_response(self, @args)
|
8
|
+
|
9
|
+
puts "Http2: Done with get request." if @debug
|
10
|
+
return resp
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def headers_string
|
15
|
+
unless @header_str
|
16
|
+
@header_str = "#{method} /#{@args[:url]} HTTP/1.1#{@nl}"
|
17
|
+
@header_str << @http2.header_str(@http2.default_headers(@args))
|
18
|
+
@header_str << @nl
|
19
|
+
end
|
20
|
+
|
21
|
+
@header_str
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def method
|
27
|
+
if @args[:method]
|
28
|
+
@args[:method].to_s.upcase
|
29
|
+
else
|
30
|
+
"GET"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class Http2::PostDataGenerator
|
2
2
|
def initialize(pdata, args = {})
|
3
|
-
@pdata
|
3
|
+
@pdata = pdata
|
4
|
+
@args = args
|
4
5
|
end
|
5
6
|
|
6
7
|
def generate
|
@@ -14,7 +15,7 @@ class Http2::PostDataGenerator
|
|
14
15
|
return @pdata.to_s
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
+
praw
|
18
19
|
end
|
19
20
|
|
20
21
|
private
|
@@ -22,7 +23,7 @@ private
|
|
22
23
|
def generate_for_hash(hash)
|
23
24
|
praw = ""
|
24
25
|
|
25
|
-
|
26
|
+
hash.each do |key, val|
|
26
27
|
praw << "&" if praw != ""
|
27
28
|
key = "#{@args[:orig_key]}[#{key}]" if @args[:orig_key]
|
28
29
|
praw << generate_key_value(key, val)
|
@@ -35,7 +36,7 @@ private
|
|
35
36
|
praw = ""
|
36
37
|
|
37
38
|
count = 0
|
38
|
-
|
39
|
+
array.each do |val|
|
39
40
|
praw << "&" if praw != ""
|
40
41
|
|
41
42
|
if @args[:orig_key]
|
@@ -1,42 +1,44 @@
|
|
1
1
|
require "tempfile"
|
2
2
|
|
3
|
-
class Http2::PostMultipartRequest
|
3
|
+
class Http2::PostMultipartRequest < Http2::BaseRequest
|
4
|
+
attr_reader :headers_string
|
5
|
+
|
4
6
|
def initialize(http2, *args)
|
5
|
-
|
7
|
+
super
|
8
|
+
|
6
9
|
@phash = @args[:post].clone
|
7
10
|
@http2.autostate_set_on_post_hash(phash) if @http2.autostate
|
8
11
|
@boundary = rand(36**50).to_s(36)
|
9
|
-
@conn = @http2.connection
|
10
12
|
end
|
11
13
|
|
12
14
|
def execute
|
13
|
-
generate_raw(@phash) do |
|
14
|
-
puts "Http2: Header string: #{header_string}" if @debug
|
15
|
-
|
15
|
+
generate_raw(@phash) do |_helper, praw|
|
16
16
|
@http2.mutex.synchronize do
|
17
|
-
@conn.write(
|
17
|
+
@conn.write(header_string_with_raw_post(praw))
|
18
18
|
|
19
19
|
praw.rewind
|
20
20
|
praw.each_line do |data|
|
21
21
|
@conn.sock_write(data)
|
22
22
|
end
|
23
23
|
|
24
|
-
return @http2.read_response(@args)
|
24
|
+
return @http2.read_response(self, @args)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
31
|
+
def header_string_with_raw_post(praw)
|
32
|
+
@headers_string = "POST /#{@args[:url]} HTTP/1.1#{@nl}"
|
33
|
+
|
34
|
+
headers = @http2.default_headers(@args).merge(
|
34
35
|
"Content-Type" => "multipart/form-data; boundary=#{@boundary}",
|
35
36
|
"Content-Length" => praw.size
|
36
|
-
)
|
37
|
-
header_str << @nl
|
37
|
+
)
|
38
38
|
|
39
|
-
|
39
|
+
@headers_string << @http2.header_str(headers)
|
40
|
+
@headers_string << @nl
|
41
|
+
@headers_string
|
40
42
|
end
|
41
43
|
|
42
44
|
def generate_raw(phash)
|
@@ -55,10 +57,12 @@ private
|
|
55
57
|
def read_file(path, praw)
|
56
58
|
File.open(path, "r") do |fp|
|
57
59
|
begin
|
58
|
-
while data = fp.sysread(4096)
|
60
|
+
while (data = fp.sysread(4096))
|
59
61
|
praw << data
|
60
62
|
end
|
63
|
+
# rubocop:disable Lint/HandleExceptions
|
61
64
|
rescue EOFError
|
65
|
+
# rubocop:enable Lint/HandleExceptions
|
62
66
|
# Happens when done.
|
63
67
|
end
|
64
68
|
end
|