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", }.freeze  

Attributes

Public Class methods

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

Public Instance methods

<<(data)
[show source]
   # File lib/httpx/connection/http1.rb
86 def <<(data)
87   @parser << data
88 end
close()
[show source]
   # File lib/httpx/connection/http1.rb
66 def close
67   reset
68   emit(:close)
69 end
consume()
[show source]
    # File lib/httpx/connection/http1.rb
102 def consume
103   requests_limit = [@max_requests, @requests.size].min
104   concurrent_requests_limit = [@max_concurrent_requests, requests_limit].min
105   @requests.each_with_index do |request, idx|
106     break if idx >= concurrent_requests_limit
107     next unless request.can_buffer?
108 
109     handle(request)
110   end
111 end
dispatch(request)
[show source]
    # File lib/httpx/connection/http1.rb
179 def dispatch(request)
180   if request.expects?
181     @parser.reset!
182     return handle(request)
183   end
184 
185   @request = nil
186   @requests.shift
187   response = request.response
188   emit(:response, request, response)
189 
190   if @parser.upgrade?
191     response << @parser.upgrade_data
192     @parser.reset!
193     throw(:called)
194   end
195 
196   @parser.reset!
197   @max_requests -= 1
198   if response.is_a?(ErrorResponse)
199     disable
200   else
201     manage_connection(request, response)
202   end
203 
204   if exhausted?
205     @pending.unshift(*@requests)
206     @requests.clear
207 
208     emit(:exhausted)
209   else
210     send(@pending.shift) unless @pending.empty?
211   end
212 end
empty?()
[show source]
   # File lib/httpx/connection/http1.rb
75 def empty?
76   # this means that for every request there's an available
77   # partial response, so there are no in-flight requests waiting.
78   @requests.empty? || (
79     # checking all responses can be time-consuming. Alas, as in HTTP/1, responses
80     # do not come out of order, we can get away with checking first and last.
81     !@requests.first.response.nil? &&
82     (@requests.size == 1 || !@requests.last.response.nil?)
83   )
84 end
exhausted?()
[show source]
   # File lib/httpx/connection/http1.rb
71 def exhausted?
72   !@max_requests.positive?
73 end
handle_error(ex, request = nil)
[show source]
    # File lib/httpx/connection/http1.rb
214 def handle_error(ex, request = nil)
215   if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request &&
216      (response = @request.response) && response.is_a?(Response) &&
217      !response.headers.key?("content-length") &&
218      !response.headers.key?("transfer-encoding")
219     # if the response does not contain a content-length header, the server closing the
220     # connnection is the indicator of response consumed.
221     # https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.4
222     catch(:called) { on_complete }
223     return
224   end
225 
226   if @pipelining
227     catch(:called) { disable }
228   else
229     while (req = @requests.shift)
230       next if request && request == req
231 
232       emit(:error, req, ex)
233     end
234     while (req = @pending.shift)
235       next if request && request == req
236 
237       emit(:error, req, ex)
238     end
239   end
240 end
interests()
[show source]
   # File lib/httpx/connection/http1.rb
39 def interests
40   request = @request || @requests.first
41 
42   return unless request
43 
44   return :w if request.interests == :w || !@buffer.empty?
45 
46   :r
47 end
on_complete()
[show source]
    # File lib/httpx/connection/http1.rb
170 def on_complete
171   request = @request
172 
173   return unless request
174 
175   request.log(level: 2) { "parsing complete" }
176   dispatch(request)
177 end
on_data(chunk)
[show source]
    # File lib/httpx/connection/http1.rb
151 def on_data(chunk)
152   request = @request
153 
154   return unless request
155 
156   begin
157     request.log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
158     request.log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
159 
160     response = request.response
161 
162     response << chunk
163   rescue StandardError => e
164     error_response = ErrorResponse.new(request, e)
165     request.response = error_response
166     dispatch(request)
167   end
168 end
on_headers(h)
[show source]
    # File lib/httpx/connection/http1.rb
121 def on_headers(h)
122   request = @request = @requests.first
123 
124   return if request.response
125 
126   request.log(level: 2) { "headers received" }
127   headers = request.options.headers_class.new(h)
128   response = request.options.response_class.new(request,
129                                                 @parser.status_code,
130                                                 @parser.http_version.join("."),
131                                                 headers)
132   request.log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
133   request.log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
134 
135   request.response = response
136   on_complete if response.finished?
137 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
117 def on_start
118   log(level: 2) { "parsing begins" }
119 end
on_trailers(h)
[show source]
    # File lib/httpx/connection/http1.rb
139 def on_trailers(h)
140   request = @request
141 
142   return unless request
143 
144   response = request.response
145 
146   request.log(level: 2) { "trailer headers received" }
147   request.log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v.join(", "))}" }.join("\n") }
148   response.merge_headers(h)
149 end
ping()
[show source]
    # File lib/httpx/connection/http1.rb
242 def ping
243   reset
244   emit(:reset)
245   emit(:exhausted)
246 end
reset()
[show source]
   # File lib/httpx/connection/http1.rb
49 def reset
50   @max_requests = @options.max_requests || MAX_REQUESTS
51   @parser.reset!
52   @handshake_completed = false
53   reset_requests
54 end
reset_requests()
[show source]
   # File lib/httpx/connection/http1.rb
56 def reset_requests
57   @requests.reverse_each do |request|
58     next if request.response
59 
60     request.transition(:idle)
61     @pending.unshift(request)
62   end
63   @requests.clear
64 end
send(request)
[show source]
    # File lib/httpx/connection/http1.rb
 90 def send(request)
 91   unless @max_requests.positive?
 92     @pending << request
 93     return
 94   end
 95 
 96   return if @requests.include?(request)
 97 
 98   @requests << request
 99   @pipelining = @max_concurrent_requests > 1 && @requests.size > 1
100 end
timeout()
[show source]
   # File lib/httpx/connection/http1.rb
35 def timeout
36   @options.timeout[:operation_timeout]
37 end
waiting_for_ping?()
[show source]
    # File lib/httpx/connection/http1.rb
248 def waiting_for_ping?
249   false
250 end