http2 0.0.24 → 0.0.25

Sign up to get free protection for your applications and to get access to all the features.
@@ -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