http2 0.0.28 → 0.0.33
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.
- checksums.yaml +5 -5
- data/README.md +150 -28
- data/Rakefile +20 -31
- data/lib/http2.rb +105 -70
- data/lib/http2/base_request.rb +22 -0
- data/{include → lib/http2}/connection.rb +56 -28
- data/lib/http2/cookie.rb +22 -0
- data/lib/http2/errors.rb +18 -0
- data/lib/http2/get_request.rb +33 -0
- data/{include → lib/http2}/post_data_generator.rb +7 -6
- data/{include → lib/http2}/post_multipart_request.rb +20 -20
- data/{include → lib/http2}/post_request.rb +17 -22
- data/lib/http2/response.rb +109 -0
- data/{include → lib/http2}/response_reader.rb +75 -42
- data/{include → lib/http2}/url_builder.rb +6 -6
- data/lib/http2/utils.rb +52 -0
- data/spec/helpers.rb +27 -0
- data/spec/http2/cookies_spec.rb +18 -0
- data/spec/http2/get_request_spec.rb +11 -0
- data/spec/http2/post_data_generator_spec.rb +2 -1
- 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 +73 -0
- data/spec/http2/url_builder_spec.rb +1 -1
- data/spec/http2_spec.rb +107 -89
- data/spec/spec_helper.rb +8 -8
- data/spec/spec_root/content_type_test.rhtml +4 -0
- data/spec/spec_root/cookie_test.rhtml +8 -0
- data/spec/spec_root/json_test.rhtml +9 -0
- data/spec/spec_root/multipart_test.rhtml +28 -0
- data/spec/spec_root/redirect_test.rhtml +3 -0
- data/spec/spec_root/unauthorized.rhtml +3 -0
- data/spec/spec_root/unsupported_media_type.rhtml +3 -0
- metadata +118 -36
- data/.document +0 -5
- data/.rspec +0 -1
- data/Gemfile +0 -14
- data/Gemfile.lock +0 -77
- data/VERSION +0 -1
- data/http2.gemspec +0 -77
- data/include/errors.rb +0 -11
- data/include/get_request.rb +0 -34
- data/include/response.rb +0 -73
- data/include/utils.rb +0 -40
- data/shippable.yml +0 -7
@@ -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].freeze
|
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_key do |key|
|
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,17 +1,20 @@
|
|
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
|
|
7
10
|
def destroy
|
8
|
-
@sock.close if @sock
|
11
|
+
@sock.close if @sock && !@sock.closed?
|
9
12
|
@sock = nil
|
10
13
|
|
11
|
-
@sock_plain.close if @sock_plain
|
14
|
+
@sock_plain.close if @sock_plain && !@sock_plain.closed?
|
12
15
|
@sock_plain = nil
|
13
16
|
|
14
|
-
@sock_ssl.close if @sock_ssl
|
17
|
+
@sock_ssl.close if @sock_ssl && !@sock_ssl.closed?
|
15
18
|
@sock_ssl = nil
|
16
19
|
end
|
17
20
|
|
@@ -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
|
@@ -34,6 +33,7 @@ class Http2::Connection
|
|
34
33
|
def sock_write(str)
|
35
34
|
str = str.to_s
|
36
35
|
return if str.empty?
|
36
|
+
|
37
37
|
count = @sock.write(str)
|
38
38
|
raise "Couldnt write to socket: '#{count}', '#{str}'." if count <= 0
|
39
39
|
end
|
@@ -46,10 +46,13 @@ class Http2::Connection
|
|
46
46
|
def write(str)
|
47
47
|
reconnect unless socket_working?
|
48
48
|
|
49
|
+
puts "Http2: Writing: #{str}" if @debug
|
50
|
+
|
49
51
|
begin
|
50
52
|
raise Errno::EPIPE, "The socket is closed." if !@sock || @sock.closed?
|
53
|
+
|
51
54
|
sock_write(str)
|
52
|
-
rescue Errno::EPIPE #this can also be thrown by puts.
|
55
|
+
rescue Errno::EPIPE # this can also be thrown by puts.
|
53
56
|
reconnect
|
54
57
|
sock_write(str)
|
55
58
|
end
|
@@ -61,14 +64,16 @@ class Http2::Connection
|
|
61
64
|
def reconnect
|
62
65
|
puts "Http2: Reconnect." if @debug
|
63
66
|
|
64
|
-
#Open connection.
|
65
|
-
if @args[:proxy]
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
# Open connection.
|
68
|
+
if @args[:proxy]
|
69
|
+
if @args[:proxy][:connect]
|
70
|
+
connect_proxy_connect
|
71
|
+
else
|
72
|
+
connect_proxy
|
73
|
+
end
|
69
74
|
else
|
70
|
-
|
71
|
-
@sock_plain = TCPSocket.new(@
|
75
|
+
puts "Http2: Opening socket connection to '#{@http2.host}:#{@http2.port}'." if @debug
|
76
|
+
@sock_plain = TCPSocket.new(@http2.host, @http2.port)
|
72
77
|
end
|
73
78
|
|
74
79
|
if @args[:ssl]
|
@@ -82,7 +87,7 @@ class Http2::Connection
|
|
82
87
|
#===Examples
|
83
88
|
# puts "Socket is working." if http.socket_working?
|
84
89
|
def socket_working?
|
85
|
-
return false if !@sock
|
90
|
+
return false if !@sock || @sock.closed?
|
86
91
|
|
87
92
|
if @keepalive_timeout && @request_last
|
88
93
|
between = Time.now.to_i - @request_last.to_i
|
@@ -92,7 +97,7 @@ class Http2::Connection
|
|
92
97
|
end
|
93
98
|
end
|
94
99
|
|
95
|
-
|
100
|
+
true
|
96
101
|
end
|
97
102
|
|
98
103
|
# Closes the current connection if any.
|
@@ -102,23 +107,40 @@ class Http2::Connection
|
|
102
107
|
@sock_plain.close if @sock_plain && !@sock_plain.closed?
|
103
108
|
end
|
104
109
|
|
105
|
-
def
|
106
|
-
puts "Http2: Initializing proxy
|
110
|
+
def connect_proxy_connect
|
111
|
+
puts "Http2: Initializing proxy connect to '#{@args[:host]}:#{@args[:port]}' through proxy '#{@args[:proxy][:host]}:#{@args[:proxy][:port]}'." if @debug
|
107
112
|
@sock_plain = TCPSocket.new(@args[:proxy][:host], @args[:proxy][:port])
|
108
113
|
|
109
|
-
|
110
|
-
|
114
|
+
connect = "CONNECT #{@args[:host]}:#{@args[:port]} HTTP/1.1#{@nl}"
|
115
|
+
puts "Http2: Sending connect: #{connect}" if @debug
|
116
|
+
@sock_plain.write(connect)
|
117
|
+
|
118
|
+
headers = {
|
119
|
+
"Host" => "#{@args[:host]}:#{@args[:port]}"
|
120
|
+
}
|
111
121
|
|
112
122
|
if @args[:proxy][:user] && @args[:proxy][:passwd]
|
113
|
-
|
114
|
-
|
115
|
-
|
123
|
+
headers["Proxy-Authorization"] = "Basic #{["#{@args[:proxy][:user]}:#{@args[:proxy][:passwd]}"].pack("m").chomp}"
|
124
|
+
end
|
125
|
+
|
126
|
+
headers.each do |key, value|
|
127
|
+
header = "#{key}: #{value}"
|
128
|
+
puts "Http2: Sending header to proxy: #{header}" if @debug
|
129
|
+
@sock_plain.write("#{header}#{@nl}")
|
116
130
|
end
|
117
131
|
|
118
132
|
@sock_plain.write(@nl)
|
119
133
|
|
120
|
-
res = @sock_plain.gets
|
121
|
-
raise res
|
134
|
+
res = @sock_plain.gets.to_s
|
135
|
+
raise "Couldn't connect through proxy: #{res}" unless res.match(/^http\/1\.(0|1)\s+200/i)
|
136
|
+
|
137
|
+
@sock_plain.gets
|
138
|
+
|
139
|
+
@proxy_connect = true
|
140
|
+
end
|
141
|
+
|
142
|
+
def proxy_connect?
|
143
|
+
@proxy_connect
|
122
144
|
end
|
123
145
|
|
124
146
|
def connect_proxy
|
@@ -131,9 +153,15 @@ class Http2::Connection
|
|
131
153
|
require "openssl" unless ::Kernel.const_defined?(:OpenSSL)
|
132
154
|
|
133
155
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
134
|
-
|
156
|
+
|
157
|
+
if @args[:ssl_skip_verify]
|
158
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
159
|
+
else
|
160
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
161
|
+
end
|
135
162
|
|
136
163
|
@sock_ssl = OpenSSL::SSL::SSLSocket.new(@sock_plain, ssl_context)
|
164
|
+
@sock_ssl.hostname = @http2.host
|
137
165
|
@sock_ssl.sync_close = true
|
138
166
|
@sock_ssl.connect
|
139
167
|
|
data/lib/http2/cookie.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class Http2::Cookie
|
2
|
+
attr_reader :name, :value, :path, :expires_raw
|
3
|
+
|
4
|
+
def initialize(args)
|
5
|
+
@name = args[:name]
|
6
|
+
@value = args[:value]
|
7
|
+
@path = args[:path]
|
8
|
+
@expires_raw = args[:expires]
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
"#<Http2::Cookie name=#{@name} value=#{@value} path=#{@path}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
def expires
|
20
|
+
@expires ||= Time.parse(@expires_raw) if @expires_raw
|
21
|
+
end
|
22
|
+
end
|
data/lib/http2/errors.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# This class holds various classes for error-handeling.
|
2
|
+
class Http2::Errors
|
3
|
+
class BaseError < RuntimeError
|
4
|
+
attr_accessor :response
|
5
|
+
end
|
6
|
+
|
7
|
+
class Noaccess < BaseError; end
|
8
|
+
|
9
|
+
class Internalserver < BaseError; end
|
10
|
+
|
11
|
+
class Notfound < BaseError; end
|
12
|
+
|
13
|
+
class Badrequest < BaseError; end
|
14
|
+
|
15
|
+
class Unauthorized < BaseError; end
|
16
|
+
|
17
|
+
class UnsupportedMediaType < BaseError; end
|
18
|
+
end
|
@@ -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]
|
@@ -53,10 +54,10 @@ private
|
|
53
54
|
|
54
55
|
def generate_key_value(key, value)
|
55
56
|
if value.is_a?(Hash) || value.is_a?(Array)
|
56
|
-
|
57
|
+
::Http2::PostDataGenerator.new(value, orig_key: key).generate
|
57
58
|
else
|
58
59
|
data = ::Http2::PostDataGenerator.new(value).generate
|
59
|
-
|
60
|
+
"#{Http2::Utils.urlenc(key)}=#{Http2::Utils.urlenc(data)}"
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
@@ -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)
|
@@ -54,13 +56,11 @@ private
|
|
54
56
|
|
55
57
|
def read_file(path, praw)
|
56
58
|
File.open(path, "r") do |fp|
|
57
|
-
|
58
|
-
|
59
|
-
praw << data
|
60
|
-
end
|
61
|
-
rescue EOFError
|
62
|
-
# Happens when done.
|
59
|
+
while (data = fp.sysread(4096))
|
60
|
+
praw << data
|
63
61
|
end
|
62
|
+
rescue EOFError
|
63
|
+
# Happens when done.
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
@@ -1,13 +1,13 @@
|
|
1
|
-
class Http2::PostRequest
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
class Http2::PostRequest < Http2::BaseRequest
|
2
|
+
def headers_string
|
3
|
+
unless @headers_string
|
4
|
+
@headers_string = "#{method} /#{@args[:url]} HTTP/1.1#{@nl}"
|
5
|
+
@headers_string << @http2.header_str(headers)
|
6
|
+
@headers_string << @nl
|
7
|
+
@headers_string << @data
|
7
8
|
end
|
8
9
|
|
9
|
-
@
|
10
|
-
@conn = @http2.connection
|
10
|
+
@headers_string
|
11
11
|
end
|
12
12
|
|
13
13
|
def execute
|
@@ -15,10 +15,9 @@ class Http2::PostRequest
|
|
15
15
|
|
16
16
|
@http2.mutex.synchronize do
|
17
17
|
puts "Http2: Doing post." if @debug
|
18
|
-
puts "Http2: Header str: #{header_str}" if @debug
|
19
18
|
|
20
|
-
@conn.write(
|
21
|
-
return @http2.read_response(@args)
|
19
|
+
@conn.write(headers_string)
|
20
|
+
return @http2.read_response(self, @args)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
@@ -36,7 +35,7 @@ private
|
|
36
35
|
if @args[:content_type]
|
37
36
|
@args[:content_type]
|
38
37
|
elsif @args[:json]
|
39
|
-
|
38
|
+
"application/json"
|
40
39
|
else
|
41
40
|
"application/x-www-form-urlencoded"
|
42
41
|
end
|
@@ -56,16 +55,12 @@ private
|
|
56
55
|
end
|
57
56
|
|
58
57
|
def headers
|
59
|
-
headers_hash = {
|
58
|
+
headers_hash = {
|
59
|
+
"Content-Length" => @data.bytesize,
|
60
|
+
"Content-Type" => content_type
|
61
|
+
}
|
60
62
|
headers_hash.merge! @http2.default_headers(@args)
|
61
|
-
|
62
|
-
|
63
|
-
def header_string
|
64
|
-
header_str = "#{method} /#{@args[:url]} HTTP/1.1#{@nl}"
|
65
|
-
header_str << @http2.header_str(headers, @args)
|
66
|
-
header_str << @nl
|
67
|
-
header_str << @data
|
68
|
-
|
69
|
-
header_str
|
63
|
+
headers_hash["Accept"] = "application/json" if @args[:json] && !headers_hash["Accept"]
|
64
|
+
headers_hash
|
70
65
|
end
|
71
66
|
end
|