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