httpx 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -30,8 +30,30 @@ module HTTPX
30
30
  init_connection
31
31
  end
32
32
 
33
+ def interests
34
+ # waiting for WINDOW_UPDATE frames
35
+ return :r if @buffer.full?
36
+
37
+ return :w if @connection.state == :closed
38
+
39
+ return :r unless (@connection.state == :connected && @handshake_completed)
40
+
41
+ return :w unless @pending.empty?
42
+
43
+ return :w if @streams.each_key.any? { |r| r.interests == :w }
44
+
45
+ return :r if @buffer.empty?
46
+
47
+ :rw
48
+ end
49
+
50
+ def reset
51
+ init_connection
52
+ end
53
+
33
54
  def close
34
55
  @connection.goaway unless @connection.state == :closed
56
+ emit(:close)
35
57
  end
36
58
 
37
59
  def empty?
@@ -39,6 +61,8 @@ module HTTPX
39
61
  end
40
62
 
41
63
  def exhausted?
64
+ return false if @max_requests.zero? && @connection.active_stream_count.zero?
65
+
42
66
  @connection.active_stream_count >= @max_requests
43
67
  end
44
68
 
@@ -68,6 +92,8 @@ module HTTPX
68
92
 
69
93
  def consume
70
94
  @streams.each do |request, stream|
95
+ next if request.state == :done
96
+
71
97
  handle(request, stream)
72
98
  end
73
99
  end
@@ -128,7 +154,7 @@ module HTTPX
128
154
  def handle_stream(stream, request)
129
155
  stream.on(:close, &method(:on_stream_close).curry[stream, request])
130
156
  stream.on(:half_close) do
131
- log(level: 2, label: "#{stream.id}: ") { "waiting for response..." }
157
+ log(level: 2) { "#{stream.id}: waiting for response..." }
132
158
  end
133
159
  stream.on(:altsvc, &method(:on_altsvc).curry[request.origin])
134
160
  stream.on(:headers, &method(:on_stream_headers).curry[stream, request])
@@ -143,8 +169,8 @@ module HTTPX
143
169
  headers[":path"] = headline_uri(request)
144
170
  headers[":authority"] = request.authority
145
171
  headers = headers.merge(request.headers)
146
- log(level: 1, label: "#{stream.id}: ", color: :yellow) do
147
- headers.map { |k, v| "-> HEADER: #{k}: #{v}" }.join("\n")
172
+ log(level: 1, color: :yellow) do
173
+ headers.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
148
174
  end
149
175
  stream.headers(headers, end_stream: request.empty?)
150
176
  end
@@ -155,8 +181,8 @@ module HTTPX
155
181
  chunk = @drains.delete(request) || request.drain_body
156
182
  while chunk
157
183
  next_chunk = request.drain_body
158
- log(level: 1, label: "#{stream.id}: ", color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
159
- log(level: 2, label: "#{stream.id}: ", color: :green) { "-> #{chunk.inspect}" }
184
+ log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
185
+ log(level: 2, color: :green) { "#{stream.id}: -> #{chunk.inspect}" }
160
186
  stream.data(chunk, end_stream: !next_chunk)
161
187
  if next_chunk && @buffer.full?
162
188
  @drains[request] = next_chunk
@@ -171,25 +197,25 @@ module HTTPX
171
197
  ######
172
198
 
173
199
  def on_stream_headers(stream, request, h)
174
- log(label: "#{stream.id}:", color: :yellow) do
175
- h.map { |k, v| "<- HEADER: #{k}: #{v}" }.join("\n")
200
+ log(color: :yellow) do
201
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{v}" }.join("\n")
176
202
  end
177
203
  _, status = h.shift
178
204
  headers = request.options.headers_class.new(h)
179
205
  response = request.options.response_class.new(request, status, "2.0", headers)
180
206
  request.response = response
181
207
  @streams[request] = stream
208
+
209
+ handle(request, stream) if request.expects?
182
210
  end
183
211
 
184
212
  def on_stream_data(stream, request, data)
185
- log(level: 1, label: "#{stream.id}: ", color: :green) { "<- DATA: #{data.bytesize} bytes..." }
186
- log(level: 2, label: "#{stream.id}: ", color: :green) { "<- #{data.inspect}" }
213
+ log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
214
+ log(level: 2, color: :green) { "#{stream.id}: <- #{data.inspect}" }
187
215
  request.response << data
188
216
  end
189
217
 
190
218
  def on_stream_close(stream, request, error)
191
- return handle(request, stream) if request.expects?
192
-
193
219
  if error && error != :no_error
194
220
  ex = Error.new(stream.id, error)
195
221
  ex.set_backtrace(caller)
@@ -204,14 +230,13 @@ module HTTPX
204
230
  emit(:response, request, response)
205
231
  end
206
232
  end
207
- log(level: 2, label: "#{stream.id}: ") { "closing stream" }
233
+ log(level: 2) { "#{stream.id}: closing stream" }
208
234
 
209
235
  @streams.delete(request)
210
236
  send(@pending.shift) unless @pending.empty?
211
237
  return unless @streams.empty? && exhausted?
212
238
 
213
239
  close
214
- emit(:close)
215
240
  emit(:exhausted) unless @pending.empty?
216
241
  end
217
242
 
@@ -222,7 +247,11 @@ module HTTPX
222
247
  def on_settings(*)
223
248
  @handshake_completed = true
224
249
 
225
- @max_requests = [@max_requests, @connection.remote_settings[:settings_max_concurrent_streams]].max
250
+ if @max_requests.zero?
251
+ @max_requests = @connection.remote_settings[:settings_max_concurrent_streams]
252
+
253
+ @connection.max_streams = @max_requests if @connection.respond_to?(:max_streams=) && @max_requests.positive?
254
+ end
226
255
 
227
256
  @max_concurrent_requests = [@max_concurrent_requests, @max_requests].min
228
257
  send_pending
@@ -242,32 +271,26 @@ module HTTPX
242
271
  end
243
272
 
244
273
  def on_frame_sent(frame)
245
- log(level: 2, label: "#{frame[:stream]}: ") { "frame was sent!" }
246
- log(level: 2, label: "#{frame[:stream]}: ", color: :blue) do
247
- case frame[:type]
248
- when :data
249
- frame.merge(payload: frame[:payload].bytesize).inspect
250
- else
251
- frame.inspect
252
- end
274
+ log(level: 2) { "#{frame[:stream]}: frame was sent!" }
275
+ log(level: 2, color: :blue) do
276
+ payload = frame
277
+ payload = payload.merge(payload: frame[:payload].bytesize) if frame[:type] == :data
278
+ "#{frame[:stream]}: #{payload}"
253
279
  end
254
280
  end
255
281
 
256
282
  def on_frame_received(frame)
257
- log(level: 2, label: "#{frame[:stream]}: ") { "frame was received!" }
258
- log(level: 2, label: "#{frame[:stream]}: ", color: :magenta) do
259
- case frame[:type]
260
- when :data
261
- frame.merge(payload: frame[:payload].bytesize).inspect
262
- else
263
- frame.inspect
264
- end
283
+ log(level: 2) { "#{frame[:stream]}: frame was received!" }
284
+ log(level: 2, color: :magenta) do
285
+ payload = frame
286
+ payload = payload.merge(payload: frame[:payload].bytesize) if frame[:type] == :data
287
+ "#{frame[:stream]}: #{payload}"
265
288
  end
266
289
  end
267
290
 
268
291
  def on_altsvc(origin, frame)
269
- log(level: 2, label: "#{frame[:stream]}: ") { "altsvc frame was received" }
270
- log(level: 2, label: "#{frame[:stream]}: ") { frame.inspect }
292
+ log(level: 2) { "#{frame[:stream]}: altsvc frame was received" }
293
+ log(level: 2) { "#{frame[:stream]}: #{frame.inspect}" }
271
294
  alt_origin = URI.parse("#{frame[:proto]}://#{frame[:host]}:#{frame[:port]}")
272
295
  params = { "ma" => frame[:max_age] }
273
296
  emit(:altsvc, origin, alt_origin, origin, params)
@@ -81,4 +81,4 @@ module HTTPX
81
81
  end
82
82
  end
83
83
  end
84
- end
84
+ end
@@ -20,6 +20,10 @@ module HTTPX
20
20
  @state = :negotiated if @keep_open
21
21
  end
22
22
 
23
+ def interests
24
+ @interests || super
25
+ end
26
+
23
27
  def protocol
24
28
  @io.alpn_protocol || super
25
29
  rescue StandardError
@@ -62,15 +66,18 @@ module HTTPX
62
66
  @io.connect_nonblock
63
67
  @io.post_connection_check(@hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
64
68
  transition(:negotiated)
65
- rescue ::IO::WaitReadable,
66
- ::IO::WaitWritable
69
+ rescue ::IO::WaitReadable
70
+ @interests = :r
71
+ rescue ::IO::WaitWritable
72
+ @interests = :w
67
73
  end
68
74
 
69
75
  # :nocov:
70
76
  if RUBY_VERSION < "2.3"
71
- def read(*)
77
+ def read(_, buffer)
72
78
  super
73
79
  rescue ::IO::WaitWritable
80
+ buffer.clear
74
81
  0
75
82
  end
76
83
 
@@ -86,6 +93,7 @@ module HTTPX
86
93
  buffer.bytesize
87
94
  rescue ::IO::WaitReadable,
88
95
  ::IO::WaitWritable
96
+ buffer.clear
89
97
  0
90
98
  rescue EOFError
91
99
  nil
@@ -11,6 +11,8 @@ module HTTPX
11
11
 
12
12
  attr_reader :addresses
13
13
 
14
+ attr_reader :state
15
+
14
16
  alias_method :host, :ip
15
17
 
16
18
  def initialize(origin, addresses, options)
@@ -41,6 +43,10 @@ module HTTPX
41
43
  @io ||= build_socket
42
44
  end
43
45
 
46
+ def interests
47
+ :w
48
+ end
49
+
44
50
  def to_io
45
51
  @io.to_io
46
52
  end
@@ -67,7 +73,7 @@ module HTTPX
67
73
  @ip_index -= 1
68
74
  retry
69
75
  rescue Errno::ETIMEDOUT => e
70
- raise ConnectTimeout, e.message if @ip_index <= 0
76
+ raise ConnectTimeoutError, e.message if @ip_index <= 0
71
77
 
72
78
  @ip_index -= 1
73
79
  retry
@@ -82,6 +88,7 @@ module HTTPX
82
88
  @io.read_nonblock(size, buffer)
83
89
  buffer.bytesize
84
90
  rescue ::IO::WaitReadable
91
+ buffer.clear
85
92
  0
86
93
  rescue EOFError
87
94
  nil
@@ -100,7 +107,10 @@ module HTTPX
100
107
  else
101
108
  def read(size, buffer)
102
109
  ret = @io.read_nonblock(size, buffer, exception: false)
103
- return 0 if ret == :wait_readable
110
+ if ret == :wait_readable
111
+ buffer.clear
112
+ return 0
113
+ end
104
114
  return if ret.nil?
105
115
 
106
116
  buffer.bytesize
@@ -13,35 +13,35 @@ module HTTPX
13
13
  white: 37,
14
14
  }.freeze
15
15
 
16
- def log(level: @options.debug_level, label: "", color: nil, &msg)
16
+ def log(level: @options.debug_level, color: nil, &msg)
17
17
  return unless @options.debug
18
18
  return unless @options.debug_level >= level
19
19
 
20
20
  debug_stream = @options.debug
21
21
 
22
- message = (+label << msg.call << "\n")
22
+ message = (+"" << msg.call << "\n")
23
23
  message = "\e[#{COLORS[color]}m#{message}\e[0m" if debug_stream.respond_to?(:isatty) && debug_stream.isatty
24
24
  debug_stream << message
25
25
  end
26
26
 
27
27
  if !Exception.instance_methods.include?(:full_message)
28
28
 
29
- def log_exception(ex, level: @options.debug_level, label: "", color: nil)
29
+ def log_exception(ex, level: @options.debug_level, color: nil)
30
30
  return unless @options.debug
31
31
  return unless @options.debug_level >= level
32
32
 
33
33
  message = +"#{ex.message} (#{ex.class})"
34
34
  message << "\n" << ex.backtrace.join("\n") unless ex.backtrace.nil?
35
- log(level: level, label: label, color: color) { message }
35
+ log(level: level, color: color) { message }
36
36
  end
37
37
 
38
38
  else
39
39
 
40
- def log_exception(ex, level: @options.debug_level, label: "", color: nil)
40
+ def log_exception(ex, level: @options.debug_level, color: nil)
41
41
  return unless @options.debug
42
42
  return unless @options.debug_level >= level
43
43
 
44
- log(level: level, label: label, color: color) { ex.full_message }
44
+ log(level: level, color: color) { ex.full_message }
45
45
  end
46
46
 
47
47
  end
@@ -25,23 +25,28 @@ module HTTPX
25
25
 
26
26
  def def_option(name, &interpreter)
27
27
  defined_options << name.to_sym
28
- interpreter ||= ->(v) { v }
29
28
 
30
29
  attr_reader name
31
30
 
32
- define_method(:"#{name}=") do |value|
33
- return if value.nil?
31
+ if interpreter
32
+ define_method(:"#{name}=") do |value|
33
+ return if value.nil?
34
34
 
35
- instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
36
- end
35
+ instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
36
+ end
37
37
 
38
- protected :"#{name}="
38
+ define_method(:"with_#{name}") do |value|
39
+ merge(name => instance_exec(value, &interpreter))
40
+ end
41
+ else
42
+ attr_writer name
39
43
 
40
- define_method(:"with_#{name}") do |value|
41
- other = dup
42
- other.send(:"#{name}=", other.instance_exec(value, &interpreter))
43
- other
44
+ define_method(:"with_#{name}") do |value|
45
+ merge(name => value)
46
+ end
44
47
  end
48
+
49
+ protected :"#{name}="
45
50
  end
46
51
  end
47
52
 
@@ -70,7 +75,6 @@ module HTTPX
70
75
  }
71
76
 
72
77
  defaults.merge!(options)
73
- defaults[:headers] = Headers.new(defaults[:headers])
74
78
  defaults.each do |(k, v)|
75
79
  __send__(:"#{k}=", v)
76
80
  end
@@ -80,7 +84,7 @@ module HTTPX
80
84
  if self.headers
81
85
  self.headers.merge(headers)
82
86
  else
83
- headers
87
+ Headers.new(headers)
84
88
  end
85
89
  end
86
90
 
@@ -116,8 +120,7 @@ module HTTPX
116
120
  end
117
121
 
118
122
  %w[
119
- params form json body
120
- follow ssl http2_settings
123
+ params form json body ssl http2_settings
121
124
  request_class response_class headers_class request_body_class response_body_class connection_class
122
125
  io fallback_protocol debug debug_level transport_options resolver_class resolver_options
123
126
  persistent
@@ -37,6 +37,8 @@ module HTTPX
37
37
  return if @body.nil?
38
38
 
39
39
  @headers.get("content-encoding").each do |encoding|
40
+ next if encoding == "identity"
41
+
40
42
  @body = Encoder.new(@body, Compression.registry(encoding).encoder)
41
43
  end
42
44
  @headers["content-length"] = @body.bytesize unless chunked?
@@ -54,6 +56,8 @@ module HTTPX
54
56
  return unless @headers.key?("content-encoding")
55
57
 
56
58
  @_decoders = @headers.get("content-encoding").map do |encoding|
59
+ next if encoding == "identity"
60
+
57
61
  decoder = Compression.registry(encoding).decoder
58
62
  # do not uncompress if there is no decoder available. In fact, we can't reliably
59
63
  # continue decompressing beyond that, so ignore.
@@ -61,7 +65,7 @@ module HTTPX
61
65
 
62
66
  @encodings << encoding
63
67
  decoder
64
- end
68
+ end.compact
65
69
 
66
70
  # remove encodings that we are able to decode
67
71
  @headers["content-encoding"] = @headers.get("content-encoding") - @encodings
@@ -15,31 +15,41 @@ module HTTPX
15
15
  end
16
16
 
17
17
  class Encoder
18
+ def initialize
19
+ @compressed_chunk = "".b
20
+ end
21
+
18
22
  def deflate(raw, buffer, chunk_size:)
19
23
  gzip = Zlib::GzipWriter.new(self)
20
24
 
21
- while (chunk = raw.read(chunk_size))
22
- gzip.write(chunk)
23
- gzip.flush
24
- compressed = compressed_chunk
25
- buffer << compressed
26
- yield compressed if block_given?
25
+ begin
26
+ while (chunk = raw.read(chunk_size))
27
+ gzip.write(chunk)
28
+ gzip.flush
29
+ compressed = compressed_chunk
30
+ buffer << compressed
31
+ yield compressed if block_given?
32
+ end
33
+ ensure
34
+ gzip.close
27
35
  end
28
- ensure
29
- gzip.close
36
+
37
+ return unless (compressed = compressed_chunk)
38
+
39
+ buffer << compressed
40
+ yield compressed if block_given?
30
41
  end
31
42
 
32
43
  private
33
44
 
34
45
  def write(chunk)
35
- @compressed_chunk = chunk
46
+ @compressed_chunk << chunk
36
47
  end
37
48
 
38
49
  def compressed_chunk
39
- compressed = @compressed_chunk
40
- compressed
50
+ @compressed_chunk.dup
41
51
  ensure
42
- @compressed_chunk = nil
52
+ @compressed_chunk.clear
43
53
  end
44
54
  end
45
55