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   @callbacks = @request = nil
33   @handshake_completed = @pipelining = false
34 end

Public Instance methods

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