class HTTPX::Connection::HTTP1

  1. lib/httpx/connection/http1.rb
Superclass: Object

Included modules

  1. Callbacks
  2. Loggable

Constants

CRLF = "\r\n"  
MAX_REQUESTS = 200  
UPCASED = { "www-authenticate" => "WWW-Authenticate", "http2-settings" => "HTTP2-Settings", "content-md5" => "Content-MD5", "last-event-id" => "Last-Event-ID", }.freeze  

Attributes

Public Class methods

new(buffer, options)
[show source]
   # File lib/httpx/connection/http1.rb
23 def initialize(buffer, options)
24   @options = options
25   @max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
26   @max_requests = @options.max_requests
27   @parser = Parser::HTTP1.new(self, options.max_response_headers, options.max_response_header_value_size)
28   @buffer = buffer
29   @version = [1, 1]
30   @pending = []
31   @requests = []
32   @request = nil
33   @handshake_completed = @pipelining = false
34 end

Public Instance methods

<<(data)
[show source]
   # File lib/httpx/connection/http1.rb
91 def <<(data)
92   @parser << data
93 end
close()
[show source]
   # File lib/httpx/connection/http1.rb
71 def close
72   reset
73   emit(:close)
74 end
consume()
[show source]
    # File lib/httpx/connection/http1.rb
107 def consume
108   requests_limit = [@max_requests, @requests.size].min
109   concurrent_requests_limit = [@max_concurrent_requests, requests_limit].min
110   @requests.each_with_index do |request, idx|
111     break if idx >= concurrent_requests_limit
112     next unless request.can_buffer?
113 
114     handle(request)
115   end
116 end
dispatch(request)
[show source]
    # File lib/httpx/connection/http1.rb
182 def dispatch(request)
183   if request.expects?
184     @parser.reset!
185     return handle(request)
186   end
187 
188   @request = nil
189   @requests.shift
190   response = request.response
191   emit(:response, request, response)
192 
193   if @parser.upgrade?
194     response << @parser.upgrade_data
195     @parser.reset!
196     throw(:called)
197   end
198 
199   @parser.reset!
200   @max_requests -= 1
201   if response.is_a?(ErrorResponse)
202     disable
203   else
204     manage_connection(request, response)
205   end
206 
207   if exhausted?
208     @pending.unshift(*@requests)
209     @requests.clear
210 
211     emit(:exhausted)
212   else
213     send(@pending.shift) unless @pending.empty?
214   end
215 end
empty?()
[show source]
   # File lib/httpx/connection/http1.rb
80 def empty?
81   # this means that for every request there's an available
82   # partial response, so there are no in-flight requests waiting.
83   @requests.empty? || (
84     # checking all responses can be time-consuming. Alas, as in HTTP/1, responses
85     # do not come out of order, we can get away with checking first and last.
86     !@requests.first.response.nil? &&
87     (@requests.size == 1 || !@requests.last.response.nil?)
88   )
89 end
exhausted?()
[show source]
   # File lib/httpx/connection/http1.rb
76 def exhausted?
77   !@max_requests.positive?
78 end
handle_error(ex, request = nil)
[show source]
    # File lib/httpx/connection/http1.rb
217 def handle_error(ex, request = nil)
218   if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request &&
219      (response = @request.response) && response.is_a?(Response) &&
220      !response.headers.key?("content-length") &&
221      !response.headers.key?("transfer-encoding")
222     # if the response does not contain a content-length header, the server closing the
223     # connnection is the indicator of response consumed.
224     # https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.4
225     catch(:called) { on_complete }
226     return
227   end
228 
229   if @pipelining
230     catch(:called) { disable }
231   else
232     while (req = @requests.shift)
233       next if request && request == req
234 
235       emit(:error, req, ex)
236     end
237     while (req = @pending.shift)
238       next if request && request == req
239 
240       emit(:error, req, ex)
241     end
242   end
243 end
interests()
[show source]
   # File lib/httpx/connection/http1.rb
40 def interests
41   request = @request || @requests.first
42 
43   return unless request
44 
45   return :w if request.interests == :w || !@buffer.empty?
46 
47   :r
48 end
on_complete()
[show source]
    # File lib/httpx/connection/http1.rb
173 def on_complete
174   request = @request
175 
176   return unless request
177 
178   request.log(level: 2) { "parsing complete" }
179   dispatch(request)
180 end
on_data(chunk)
[show source]
    # File lib/httpx/connection/http1.rb
160 def on_data(chunk)
161   request = @request
162 
163   return unless request
164 
165   request.log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
166   request.log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
167 
168   response = request.response
169 
170   response << chunk
171 end
on_headers(h)
[show source]
    # File lib/httpx/connection/http1.rb
126 def on_headers(h)
127   request = @request = @requests.first
128 
129   return if request.response
130 
131   request.log(level: 2) { "headers received" }
132   headers = request.options.headers_class.new(h)
133   response = request.options.response_class.new(request,
134                                                 @parser.status_code,
135                                                 @parser.http_version.join("."),
136                                                 headers)
137   request.log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
138   request.log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
139 
140   if response.content_length && response.content_length > request.options.max_response_body_size
141     raise HTTPX::Error, "maximum response body size exceeded"
142   end
143 
144   request.response = response
145   on_complete if response.finished?
146 end
on_start()

HTTP Parser callbacks

must be public methods, or else they won’t be reachable

[show source]
    # File lib/httpx/connection/http1.rb
122 def on_start
123   log(level: 2) { "parsing begins" }
124 end
on_trailers(h)
[show source]
    # File lib/httpx/connection/http1.rb
148 def on_trailers(h)
149   request = @request
150 
151   return unless request
152 
153   response = request.response
154 
155   request.log(level: 2) { "trailer headers received" }
156   request.log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v.join(", "))}" }.join("\n") }
157   response.merge_headers(h)
158 end
ping()
[show source]
    # File lib/httpx/connection/http1.rb
245 def ping
246   reset
247   emit(:reset)
248   emit(:exhausted)
249 end
reset()
[show source]
   # File lib/httpx/connection/http1.rb
50 def reset
51   if @ping_timer
52     @ping_timer.cancel
53     @ping_timer = nil
54   end
55   @max_requests = @options.max_requests || MAX_REQUESTS
56   @parser.reset!
57   @handshake_completed = false
58   reset_requests
59 end
reset_requests()
[show source]
   # File lib/httpx/connection/http1.rb
61 def reset_requests
62   @requests.reverse_each do |request|
63     next if request.response
64 
65     request.transition(:idle)
66     @pending.unshift(request)
67   end
68   @requests.clear
69 end
send(request)
[show source]
    # File lib/httpx/connection/http1.rb
 95 def send(request)
 96   unless @max_requests.positive?
 97     @pending << request
 98     return
 99   end
100 
101   return if @requests.include?(request)
102 
103   @requests << request
104   @pipelining = @max_concurrent_requests > 1 && @requests.size > 1
105 end
timeout()
[show source]
   # File lib/httpx/connection/http1.rb
36 def timeout
37   @options.timeout[:operation_timeout]
38 end
waiting_for_ping?()
[show source]
    # File lib/httpx/connection/http1.rb
251 def waiting_for_ping?
252   false
253 end