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