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   @handshake_completed = false
32 end

Public Instance methods

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