The Connection
can be watched for IO events.
It contains the io
object to read/write from, and knows what to do when it can.
It defers connecting until absolutely necessary. Connection
should be triggered from the IO selector (until then, any request will be queued).
A connection boots up its parser after connection is established. All pending requests will be redirected there after connection.
A connection can be prevented from closing by the parser, that is, if there are pending requests. This will signal that the connection was prematurely closed, due to a possible number of conditions:
-
Remote peer closed the connection (“Connection: close”);
-
Remote peer doesn’t support pipelining;
A connection may also route requests for a different host for which the io
was connected to, provided that the IP is the same and the port and scheme as well. This will allow to share the same socket to send HTTP/2 requests to different hosts.
Methods
Public Class
Public Instance
- addresses
- addresses=
- call
- close
- coalescable?
- connecting?
- create_idle
- deactivate
- expired?
- family
- force_reset
- handle_socket_timeout
- idling
- inflight?
- interests
- io
- match?
- merge
- mergeable?
- open?
- options
- origin
- origins
- pending
- purge_pending
- reset
- send
- ssl_session
- state
- terminate
- timeout
- timers
- to_io
- type
- used?
Classes and Modules
Attributes
Public Class methods
# File lib/httpx/connection.rb 50 def initialize(uri, options) 51 @origins = [uri.origin] 52 @origin = Utils.to_uri(uri.origin) 53 @options = Options.new(options) 54 @type = initialize_type(uri, @options) 55 @window_size = @options.window_size 56 @read_buffer = Buffer.new(@options.buffer_size) 57 @write_buffer = Buffer.new(@options.buffer_size) 58 @pending = [] 59 on(:error, &method(:on_error)) 60 if @options.io 61 # if there's an already open IO, get its 62 # peer address, and force-initiate the parser 63 transition(:already_open) 64 @io = build_socket 65 parser 66 else 67 transition(:idle) 68 end 69 70 @inflight = 0 71 @keep_alive_timeout = @options.timeout[:keep_alive_timeout] 72 73 @intervals = [] 74 75 self.addresses = @options.addresses if @options.addresses 76 end
Public Instance methods
# File lib/httpx/connection.rb 88 def addresses 89 @io && @io.addresses 90 end
this is a semi-private method, to be used by the resolver to initiate the io object.
# File lib/httpx/connection.rb 80 def addresses=(addrs) 81 if @io 82 @io.add_addresses(addrs) 83 else 84 @io = build_socket(addrs) 85 end 86 end
# File lib/httpx/connection.rb 192 def call 193 case @state 194 when :idle 195 connect 196 consume 197 when :closed 198 return 199 when :closing 200 consume 201 transition(:closed) 202 when :open 203 consume 204 end 205 nil 206 end
# File lib/httpx/connection.rb 208 def close 209 transition(:active) if @state == :inactive 210 211 @parser.close if @parser 212 end
coalescable connections need to be mergeable! but internally, mergeable?
is called before coalescable?
# File lib/httpx/connection.rb 124 def coalescable?(connection) 125 if @io.protocol == "h2" && 126 @origin.scheme == "https" && 127 connection.origin.scheme == "https" && 128 @io.can_verify_peer? 129 @io.verify_hostname(connection.origin.host) 130 else 131 @origin == connection.origin 132 end 133 end
# File lib/httpx/connection.rb 164 def connecting? 165 @state == :idle 166 end
# File lib/httpx/connection.rb 135 def create_idle(options = {}) 136 self.class.new(@origin, @options.merge(options)) 137 end
# File lib/httpx/connection.rb 274 def deactivate 275 transition(:inactive) 276 end
# File lib/httpx/connection.rb 105 def expired? 106 return false unless @io 107 108 @io.expired? 109 end
bypasses the state machine to force closing of connections still connecting. only used for Happy Eyeballs v2.
# File lib/httpx/connection.rb 222 def force_reset 223 @state = :closing 224 transition(:closed) 225 end
# File lib/httpx/connection.rb 282 def handle_socket_timeout(interval) 283 @intervals.delete_if(&:elapsed?) 284 285 unless @intervals.empty? 286 # remove the intervals which will elapse 287 288 return 289 end 290 291 error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select") 292 error.set_backtrace(caller) 293 on_error(error) 294 end
# File lib/httpx/connection.rb 263 def idling 264 purge_after_closed 265 @write_buffer.clear 266 transition(:idle) 267 @parser = nil if @parser 268 end
# File lib/httpx/connection.rb 168 def inflight? 169 @parser && !@parser.empty? && !@write_buffer.empty? 170 end
# File lib/httpx/connection.rb 172 def interests 173 # connecting 174 if connecting? 175 connect 176 177 return @io.interests if connecting? 178 end 179 180 # if the write buffer is full, we drain it 181 return :w unless @write_buffer.empty? 182 183 return @parser.interests if @parser 184 185 nil 186 end
# File lib/httpx/connection.rb 92 def match?(uri, options) 93 return false if !used? && (@state == :closing || @state == :closed) 94 95 ( 96 @origins.include?(uri.origin) && 97 # if there is more than one origin to match, it means that this connection 98 # was the result of coalescing. To prevent blind trust in the case where the 99 # origin came from an ORIGIN frame, we're going to verify the hostname with the 100 # SSL certificate 101 (@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host))) 102 ) && @options == options 103 end
# File lib/httpx/connection.rb 139 def merge(connection) 140 @origins |= connection.instance_variable_get(:@origins) 141 if connection.ssl_session 142 @ssl_session = connection.ssl_session 143 @io.session_new_cb do |sess| 144 @ssl_session = sess 145 end if @io 146 end 147 connection.purge_pending do |req| 148 send(req) 149 end 150 end
# File lib/httpx/connection.rb 111 def mergeable?(connection) 112 return false if @state == :closing || @state == :closed || !@io 113 114 return false unless connection.addresses 115 116 ( 117 (open? && @origin == connection.origin) || 118 !(@io.addresses & (connection.addresses || [])).empty? 119 ) && @options == connection.options 120 end
# File lib/httpx/connection.rb 278 def open? 279 @state == :open || @state == :inactive 280 end
# File lib/httpx/connection.rb 152 def purge_pending(&block) 153 pendings = [] 154 if @parser 155 @inflight -= @parser.pending.size 156 pendings << @parser.pending 157 end 158 pendings << @pending 159 pendings.each do |pending| 160 pending.reject!(&block) 161 end 162 end
# File lib/httpx/connection.rb 227 def reset 228 return if @state == :closing || @state == :closed 229 230 transition(:closing) 231 232 transition(:closed) 233 end
# File lib/httpx/connection.rb 235 def send(request) 236 if @parser && !@write_buffer.full? 237 if @response_received_at && @keep_alive_timeout && 238 Utils.elapsed_time(@response_received_at) > @keep_alive_timeout 239 # when pushing a request into an existing connection, we have to check whether there 240 # is the possibility that the connection might have extended the keep alive timeout. 241 # for such cases, we want to ping for availability before deciding to shovel requests. 242 log(level: 3) { "keep alive timeout expired, pinging connection..." } 243 @pending << request 244 transition(:active) if @state == :inactive 245 parser.ping 246 return 247 end 248 249 send_request_to_parser(request) 250 else 251 @pending << request 252 end 253 end
# File lib/httpx/connection.rb 214 def terminate 215 @connected_at = nil if @state == :closed 216 217 close 218 end
# File lib/httpx/connection.rb 255 def timeout 256 return @timeout if @timeout 257 258 return @options.timeout[:connect_timeout] if @state == :idle 259 260 @options.timeout[:operation_timeout] 261 end