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
17 def initialize(buffer, options)
18   @options = options
19   @max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
20   @max_requests = @options.max_requests
21   @parser = Parser::HTTP1.new(self)
22   @buffer = buffer
23   @version = [1, 1]
24   @pending = []
25   @requests = []
26   @handshake_completed = false
27 end

Public Instance methods

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