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,71 @@
1
+ class Http2::PostRequest
2
+ VALID_ARGUMENTS_POST = [:post, :url, :default_headers, :headers, :json, :method, :cookies, :on_content, :content_type]
3
+
4
+ def initialize(http2, args)
5
+ args.each do |key, val|
6
+ raise "Invalid key: '#{key}'." unless VALID_ARGUMENTS_POST.include?(key)
7
+ end
8
+
9
+ @http2, @args, @debug, @nl = http2, http2.parse_args(args), http2.debug, http2.nl
10
+ @conn = @http2.connection
11
+ end
12
+
13
+ def execute
14
+ @data = raw_data
15
+
16
+ @http2.mutex.synchronize do
17
+ puts "Http2: Doing post." if @debug
18
+ puts "Http2: Header str: #{header_str}" if @debug
19
+
20
+ @conn.write(header_string)
21
+ return @http2.read_response(@args)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def method
28
+ if @args[:method]
29
+ @args[:method].to_s.upcase
30
+ else
31
+ "POST"
32
+ end
33
+ end
34
+
35
+ def content_type
36
+ if @args[:content_type]
37
+ @args[:content_type]
38
+ elsif @args[:json]
39
+ content_type = "application/json"
40
+ else
41
+ "application/x-www-form-urlencoded"
42
+ end
43
+ end
44
+
45
+ def raw_data
46
+ if @args[:json]
47
+ require "json" unless ::Kernel.const_defined?(:JSON)
48
+ @args[:json].to_json
49
+ elsif @args[:post].is_a?(String)
50
+ @args[:post]
51
+ else
52
+ phash = @args[:post] ? @args[:post].clone : {}
53
+ @http2.autostate_set_on_post_hash(phash) if @http2.args[:autostate]
54
+ ::Http2::PostDataGenerator.new(phash).generate
55
+ end
56
+ end
57
+
58
+ def headers
59
+ headers_hash = {"Content-Length" => @data.bytesize, "Content-Type" => content_type}
60
+ headers_hash.merge! @http2.default_headers(@args)
61
+ end
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
70
+ end
71
+ end
data/include/response.rb CHANGED
@@ -2,22 +2,23 @@
2
2
  class Http2::Response
3
3
  #All the data the response contains. Headers, body, cookies, requested URL and more.
4
4
  attr_reader :args
5
-
5
+ attr_accessor :body, :charset, :code, :content_type, :http_version
6
+
6
7
  #This method should not be called manually.
7
8
  def initialize(args = {})
8
9
  @args = args
9
10
  @args[:headers] = {} if !@args.key?(:headers)
10
- @args[:body] = "" if !@args.key?(:body)
11
+ @body = args[:body] || ""
11
12
  @debug = @args[:debug]
12
13
  end
13
-
14
+
14
15
  #Returns headers given from the host for the result.
15
16
  #===Examples
16
17
  # headers_hash = res.headers
17
18
  def headers
18
19
  return @args[:headers]
19
20
  end
20
-
21
+
21
22
  #Returns a certain header by name or false if not found.
22
23
  #===Examples
23
24
  # val = res.header("content-type")
@@ -25,7 +26,7 @@ class Http2::Response
25
26
  return false if !@args[:headers].key?(key)
26
27
  return @args[:headers][key].first.to_s
27
28
  end
28
-
29
+
29
30
  #Returns true if a header of the given string exists.
30
31
  #===Examples
31
32
  # print "No content-type was given." if !http.header?("content-type")
@@ -33,40 +34,11 @@ class Http2::Response
33
34
  return true if @args[:headers].key?(key) and @args[:headers][key].first.to_s.length > 0
34
35
  return false
35
36
  end
36
-
37
- #Returns the code of the result (200, 404, 500 etc).
38
- #===Examples
39
- # print "An internal error occurred." if res.code.to_i == 500
40
- def code
41
- return @args[:code]
42
- end
43
-
44
- #Returns the HTTP-version of the result.
45
- #===Examples
46
- # print "We are using HTTP 1.1 and should support keep-alive." if res.http_version.to_s == "1.1"
47
- def http_version
48
- return @args[:http_version]
49
- end
50
-
51
- #Returns the complete body of the result as a string.
52
- #===Examples
53
- # print "Looks like we caught the end of it as well?" if res.body.to_s.downcase.index("</html>") != nil
54
- def body
55
- return @args[:body]
56
- end
57
-
58
- #Returns the charset of the result.
59
- def charset
60
- return @args[:charset]
61
- end
62
-
63
- #Returns the content-type of the result as a string.
64
- #===Examples
65
- # print "This body can be printed - its just plain text!" if http.contenttype == "text/plain"
66
- def contenttype
67
- return @args[:contenttype]
37
+
38
+ def content_length
39
+ header("content-length").to_i if header?("content-length")
68
40
  end
69
-
41
+
70
42
  #Returns the requested URL as a string.
71
43
  #===Examples
72
44
  # res.requested_url #=> "?show=status&action=getstatus"
@@ -74,28 +46,28 @@ class Http2::Response
74
46
  raise "URL could not be detected." if !@args[:request_args][:url]
75
47
  return @args[:request_args][:url]
76
48
  end
77
-
49
+
78
50
  # Checks the data that has been sat on the object and raises various exceptions, if it does not validate somehow.
79
51
  def validate!
80
52
  puts "Http2: Validating response length." if @debug
81
53
  validate_body_versus_content_length!
82
54
  end
83
-
55
+
84
56
  private
85
-
57
+
86
58
  # Checks that the length of the body is the same as the given content-length if given.
87
59
  def validate_body_versus_content_length!
88
60
  unless self.header?("content-length")
89
61
  puts "Http2: No content length given - skipping length validation." if @debug
90
62
  return nil
91
63
  end
92
-
64
+
93
65
  content_length = self.header("content-length").to_i
94
- body_length = @args[:body].to_s.bytesize
95
-
66
+ body_length = @body.bytesize
67
+
96
68
  puts "Http2: Body length: #{body_length}" if @debug
97
69
  puts "Http2: Content length: #{content_length}" if @debug
98
-
70
+
99
71
  raise "Body does not match the given content-length: '#{body_length}', '#{content_length}'." if body_length != content_length
100
72
  end
101
73
  end
@@ -6,23 +6,23 @@ class Http2::ResponseReader
6
6
  @transfer_encoding = nil
7
7
  @response = Http2::Response.new(:request_args => args, :debug => @debug)
8
8
  @rec_count = 0
9
- @args, @debug, @http2, @sock = args[:args], args[:debug], args[:http2], args[:sock]
9
+ @args, @debug, @http2, @sock = args[:args], args[:http2].debug, args[:http2], args[:sock]
10
10
  @nl = @http2.nl
11
+ @conn = @http2.connection
11
12
 
12
13
  read_headers
13
- read_body
14
+ read_body if @length == nil || @length > 0
14
15
  finish
15
16
  end
16
17
 
17
18
  def read_headers
18
19
  loop do
19
- line = @sock.gets
20
+ line = @conn.gets
20
21
  check_line_read(line)
21
22
 
22
23
  if line == "\n" || line == "\r\n" || line == @nl
23
24
  puts "Http2: Changing mode to body!" if @debug
24
25
  raise "No headers was given at all? Possibly corrupt state after last request?" if @response.headers.empty?
25
- break if @length == 0
26
26
  @mode = "body"
27
27
  @http2.on_content_call(@args, @nl)
28
28
  break
@@ -34,71 +34,76 @@ class Http2::ResponseReader
34
34
 
35
35
  def read_body
36
36
  loop do
37
- if @length && @length > 0
38
- line = @sock.read(@length)
37
+ if @length
38
+ line = @conn.read(@length)
39
39
  raise "Expected to get #{@length} of bytes but got #{line.bytesize}" if @length != line.bytesize
40
40
  else
41
- line = @sock.gets
41
+ line = @conn.gets
42
42
  end
43
43
 
44
44
  check_line_read(line)
45
45
  stat = parse_body(line)
46
- break if stat == "break"
47
- next if stat == "next"
46
+ break if stat == :break
47
+ next if stat == :next
48
48
  end
49
49
  end
50
50
 
51
51
  def finish
52
52
  #Check if we should reconnect based on keep-alive-max.
53
- if @keepalive_max == 1 or @connection == "close"
54
- @sock.close unless @sock.closed?
53
+ if @keepalive_max == 1 || @connection == "close"
54
+ @conn.close unless @conn.closed?
55
55
  end
56
56
 
57
57
  # Validate that the response is as it should be.
58
58
  puts "Http2: Validating response." if @debug
59
59
 
60
- if !@response.args[:code]
61
- raise "No status-code was received from the server. Headers: '#{@response.headers}' Body: '#{resp.args[:body]}'."
60
+ if !@response.code
61
+ raise "No status-code was received from the server. Headers: '#{@response.headers}' Body: '#{resp.body}'."
62
62
  end
63
63
 
64
64
  @response.validate!
65
65
  check_and_decode
66
- check_and_follow_redirect
66
+ @http2.autostate_register(@response) if @http2.args[:autostate]
67
67
  handle_errors
68
68
 
69
- @http2.autostate_register(@response) if @http2.args[:autostate]
69
+ if response = check_and_follow_redirect
70
+ @response = response
71
+ end
70
72
  end
71
73
 
72
74
  private
73
75
 
74
76
  def check_and_follow_redirect
75
- if (@response.args[:code].to_s == "302" || @response.args[:code].to_s == "307") && @response.header?("location") && (!@http2.args.key?(:follow_redirects) || @http2.args[:follow_redirects])
76
- uri = URI.parse(@response.header("location"))
77
- url = uri.path
78
- url << "?#{uri.query}" if uri.query.to_s.length > 0
79
-
80
- args = {:host => uri.host}
81
- args[:ssl] = true if uri.scheme == "https"
82
- args[:port] = uri.port if uri.port
77
+ if (@response.code == "302" || @response.code == "307") && @response.header?("location") && @http2.args[:follow_redirects]
78
+ url, args = url_and_args_from_location
83
79
 
84
- puts "Http2: Redirecting from location-header to '#{url}'." if @debug
85
-
86
- if !args[:host] or args[:host] == @args[:host]
87
- return self.get(url)
80
+ if !args[:host] || args[:host] == @args[:host]
81
+ return @http2.get(url)
88
82
  else
89
- http = Http2.new(args)
90
- return http.get(url)
83
+ ::Http2.new(args).get(url)
91
84
  end
92
85
  end
93
86
  end
94
87
 
88
+ def url_and_args_from_location
89
+ uri = URI.parse(@response.header("location"))
90
+ url = uri.path
91
+ url << "?#{uri.query}" if uri.query.to_s.length > 0
92
+
93
+ args = {host: uri.host}
94
+ args[:ssl] = true if uri.scheme == "https"
95
+ args[:port] = uri.port if uri.port
96
+
97
+ return [url, args]
98
+ end
99
+
95
100
  def check_and_decode
96
101
  # Check if the content is gzip-encoded - if so: decode it!
97
102
  if @encoding == "gzip"
98
103
  puts "Http2: Decoding GZip." if @debug
99
104
  require "zlib"
100
105
  require "stringio"
101
- io = StringIO.new(@response.args[:body])
106
+ io = StringIO.new(@response.body)
102
107
  gz = Zlib::GzipReader.new(io)
103
108
  untrusted_str = gz.read
104
109
 
@@ -108,21 +113,21 @@ private
108
113
  valid_string = untrusted_str.force_encoding("UTF-8").encode("UTF-8", :invalid => :replace, :replace => "").encode("UTF-8")
109
114
  end
110
115
 
111
- @response.args[:body] = valid_string
116
+ @response.body = valid_string
112
117
  end
113
118
  end
114
119
 
115
120
  def handle_errors
116
- if @http2.raise_errors
117
- if @response.args[:code].to_i == 500
118
- err = Http2::Errors::Internalserver.new("A internal server error occurred")
119
- elsif @response.args[:code].to_i == 403
120
- err = Http2::Errors::Noaccess.new("No access")
121
- elsif @response.args[:code].to_i == 400
122
- err = Http2::Errors::Badrequest.new("Bad request")
123
- elsif @response.args[:code].to_i == 404
124
- err = Http2::Errors::Notfound.new("Not found")
125
- end
121
+ return unless @http2.raise_errors
122
+
123
+ if @response.code == "500"
124
+ err = Http2::Errors::Internalserver.new("A internal server error occurred")
125
+ elsif @response.code == "403"
126
+ err = Http2::Errors::Noaccess.new("No access")
127
+ elsif @response.code == "400"
128
+ err = Http2::Errors::Badrequest.new("Bad request")
129
+ elsif @response.code == "404"
130
+ err = Http2::Errors::Notfound.new("Not found")
126
131
  end
127
132
 
128
133
  if err
@@ -135,8 +140,7 @@ private
135
140
  if line
136
141
  @rec_count += line.length
137
142
  elsif !line && @rec_count <= 0
138
- @sock = nil
139
- raise Errno::ECONNABORTED, "Server closed the connection before being able to read anything (KeepAliveMax: '#{@keepalive_max}', Connection: '#{@connection}', PID: '#{Process.pid}')."
143
+ raise Errno::ECONNABORTED, "Server closed the connection before being able to read anything (KeepAliveMax: '#{@http2.keepalive_max}', Connection: '#{@connection}', PID: '#{Process.pid}')."
140
144
  end
141
145
  end
142
146
 
@@ -147,26 +151,25 @@ private
147
151
  end
148
152
 
149
153
  def parse_keep_alive(keep_alive_line)
150
- if ka_max = keep_alive_line.match(/max=(\d+)/)
151
- @keepalive_max = ka_max[1].to_i
152
- print "Http2: Keepalive-max set to: '#{@keepalive_max}'.\n" if @debug
153
- end
154
-
155
- if ka_timeout = keep_alive_line.match(/timeout=(\d+)/)
156
- @keepalive_timeout = ka_timeout[1].to_i
157
- print "Http2: Keepalive-timeout set to: '#{@keepalive_timeout}'.\n" if @debug
154
+ keep_alive_line.scan(/([a-z]+)=(\d+)/) do |match|
155
+ if match[0] == "timeout"
156
+ puts "Http2: Keepalive-max set to: '#{@keepalive_max}'." if @debug
157
+ @http2.keepalive_timeout = match[1].to_i
158
+ elsif match[0] == "max"
159
+ puts "Http2: Keepalive-timeout set to: '#{@keepalive_timeout}'." if @debug
160
+ @http2.keepalive_max = match[1].to_i
161
+ end
158
162
  end
159
163
  end
160
164
 
161
165
  def parse_content_type(content_type_line)
162
166
  if match_charset = content_type_line.match(/\s*;\s*charset=(.+)/i)
163
167
  @charset = match_charset[1].downcase
164
- @response.args[:charset] = @charset
168
+ @response.charset = @charset
165
169
  content_type_line.gsub!(match_charset[0], "")
166
170
  end
167
171
 
168
- @ctype = content_type_line
169
- @response.args[:contenttype] = @content_type_line
172
+ @response.content_type = @content_type_line
170
173
  end
171
174
 
172
175
  #Parse a header-line and saves it on the object.
@@ -174,56 +177,57 @@ private
174
177
  # http.parse_header("Content-Type: text/html\r\n")
175
178
  def parse_header(line)
176
179
  if match = line.match(/^(.+?):\s*(.+)#{@nl}$/)
177
- key = match[1].to_s.downcase
178
-
179
- parse_cookie(match[2]) if key == "set-cookie"
180
- parse_keep_alive(match[2]) if key == "keep-alive"
181
- parse_content_type(match[2]) if key == "content-type"
182
-
183
- if key == "connection"
184
- @connection = match[2].to_s.downcase
185
- elsif key == "content-encoding"
186
- @encoding = match[2].to_s.downcase
187
- puts "Http2: Setting encoding to #{@encoding}" if @debug
188
- elsif key == "content-length"
189
- @length = match[2].to_i
190
- elsif key == "transfer-encoding"
191
- @transfer_encoding = match[2].to_s.downcase.strip
192
- end
180
+ key = match[1].downcase
181
+ set_header_special_values(key, match[2])
182
+ parse_normal_header(line, key, match[1], match[2])
183
+ elsif match = line.match(/^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/)
184
+ @response.code = match[2]
185
+ @response.http_version = match[1]
186
+ @http2.on_content_call(@args, line)
187
+ else
188
+ raise "Could not understand header string: '#{line}'."
189
+ end
190
+ end
193
191
 
194
- puts "Http2: Parsed header: #{match[1]}: #{match[2]}" if @debug
195
- @response.headers[key] = [] unless @response.headers.key?(key)
196
- @response.headers[key] << match[2]
192
+ def set_header_special_values(key, value)
193
+ parse_cookie(value) if key == "set-cookie"
194
+ parse_keep_alive(value) if key == "keep-alive"
195
+ parse_content_type(value) if key == "content-type"
196
+
197
+ if key == "connection"
198
+ @connection = value.downcase
199
+ elsif key == "content-encoding"
200
+ @encoding = value.downcase
201
+ puts "Http2: Setting encoding to #{@encoding}" if @debug
202
+ elsif key == "content-length"
203
+ @length = value.to_i
204
+ elsif key == "transfer-encoding"
205
+ @transfer_encoding = value.downcase.strip
206
+ end
207
+ end
197
208
 
198
- if key != "transfer-encoding" && key != "content-length" && key != "connection" && key != "keep-alive"
199
- @http2.on_content_call(@args, line)
200
- end
201
- elsif match = line.match(/^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/)
202
- @response.args[:code] = match[2]
203
- @response.args[:http_version] = match[1]
209
+ def parse_normal_header(line, key, orig_key, value)
210
+ puts "Http2: Parsed header: #{orig_key}: #{value}" if @debug
211
+ @response.headers[key] = [] unless @response.headers.key?(key)
212
+ @response.headers[key] << value
204
213
 
214
+ if key != "transfer-encoding" && key != "content-length" && key != "connection" && key != "keep-alive"
205
215
  @http2.on_content_call(@args, line)
206
- else
207
- raise "Could not understand header string: '#{line}'.\n\n#{@sock.read(409600)}"
208
216
  end
209
217
  end
210
218
 
211
219
  #Parses the body based on given headers and saves it to the result-object.
212
220
  # http.parse_body(str)
213
221
  def parse_body(line)
214
- if @response.args[:http_version] = "1.1"
215
- return "break" if @length == 0
222
+ return :break if @length == 0
216
223
 
217
- if @transfer_encoding == "chunked"
218
- parse_body_chunked(line)
219
- else
220
- puts "Http2: Adding #{line.to_s.bytesize} to the body." if @debug
221
- @response.args[:body] << line.to_s
222
- @http2.on_content_call(@args, line)
223
- return "break" if @response.header?("content-length") && @response.args[:body].length >= @response.header("content-length").to_i
224
- end
224
+ if @transfer_encoding == "chunked"
225
+ return parse_body_chunked(line)
225
226
  else
226
- raise "Dont know how to read HTTP version: '#{@resp.args[:http_version]}'."
227
+ puts "Http2: Adding #{line.to_s.bytesize} to the body." if @debug
228
+ @response.body << line
229
+ @http2.on_content_call(@args, line)
230
+ return :break if @response.content_length && @response.body.length >= @response.content_length
227
231
  end
228
232
  end
229
233
 
@@ -231,16 +235,16 @@ private
231
235
  len = line.strip.hex
232
236
 
233
237
  if len > 0
234
- read = @sock.read(len)
235
- return "break" if read == "" or (read == "\n" || read == "\r\n")
236
- @response.args[:body] << read
238
+ read = @conn.read(len)
239
+ return :break if read == "" || read == "\n" || read == "\r\n"
240
+ @response.body << read
237
241
  @http2.on_content_call(@args, read)
238
242
  end
239
243
 
240
- nl = @sock.gets
244
+ nl = @conn.gets
241
245
  if len == 0
242
246
  if nl == "\n" || nl == "\r\n"
243
- return "break"
247
+ return :break
244
248
  else
245
249
  raise "Dont know what to do :'-("
246
250
  end