Connection
start HTTP request once we establish connection to host
# File lib/em-http/client.rb, line 212 212: def connection_completed 213: # if connecting to proxy, then first negotiate the connection 214: # to intermediate server and wait for 200 response 215: if @options[:proxy] and @state == :response_header 216: @state = :response_proxy 217: send_request_header 218: 219: # if connecting via proxy, then state will be :proxy_connected, 220: # indicating successful tunnel. from here, initiate normal http 221: # exchange 222: else 223: @state = :response_header 224: ssl = @options[:tls] || @options[:ssl] || {} 225: start_tls(ssl) if @uri.scheme == "https" or @uri.port == 443 226: send_request_header 227: send_request_body 228: end 229: end
assign disconnect callback for websocket
# File lib/em-http/client.rb, line 257 257: def disconnect(&blk) 258: @disconnect = blk 259: end
Response processing
# File lib/em-http/client.rb, line 406 406: def dispatch 407: while case @state 408: when :response_proxy 409: parse_response_header 410: when :response_header 411: parse_response_header 412: when :chunk_header 413: parse_chunk_header 414: when :chunk_body 415: process_chunk_body 416: when :chunk_footer 417: process_chunk_footer 418: when :response_footer 419: process_response_footer 420: when :body 421: process_body 422: when :websocket 423: process_websocket 424: when :finished, :invalid 425: break 426: else raise RuntimeError, "invalid state: #{@state}" 427: end 428: end
# File lib/em-http/client.rb, line 275 275: def normalize_body 276: @normalized_body ||= begin 277: if @options[:body].is_a? Hash 278: @options[:body].to_params 279: else 280: @options[:body] 281: end 282: end 283: end
Called when part of the body has been read
# File lib/em-http/client.rb, line 357 357: def on_body_data(data) 358: if @content_decoder 359: begin 360: @content_decoder << data 361: rescue HttpDecoders::DecoderError 362: on_error "Content-decoder error" 363: end 364: else 365: on_decoded_body_data(data) 366: end 367: end
# File lib/em-http/client.rb, line 369 369: def on_decoded_body_data(data) 370: if @stream 371: @stream.call(data) 372: else 373: @response << data 374: end 375: end
request failed, invoke errback
# File lib/em-http/client.rb, line 243 243: def on_error(msg, dns_error = false) 244: @error = msg 245: 246: # no connection signature on DNS failures 247: # fail the connection directly 248: dns_error == true ? fail(self) : unbind 249: end
request is done, invoke the callback
# File lib/em-http/client.rb, line 232 232: def on_request_complete 233: begin 234: @content_decoder.finalize! if @content_decoder 235: rescue HttpDecoders::DecoderError 236: on_error "Content-decoder error" 237: end 238: 239: close_connection 240: end
# File lib/em-http/client.rb, line 195 195: def post_init 196: @parser = HttpClientParser.new 197: @data = EventMachine::Buffer.new 198: @chunk_header = HttpChunkHeader.new 199: @response_header = HttpResponseHeader.new 200: @parser_nbytes = 0 201: @redirects = 0 202: @response = '' 203: @error = '' 204: @last_effective_url = nil 205: @content_decoder = nil 206: @stream = nil 207: @disconnect = nil 208: @state = :response_header 209: end
# File lib/em-http/client.rb, line 351 351: def receive_data(data) 352: @data << data 353: dispatch 354: end
raw data push from the client (WebSocket) should only be invoked after handshake, otherwise it will inject data into the header exchange
frames need to start with 0x00-0x7f byte and end with an 0xFF byte. Per spec, we can also set the first byte to a value betweent 0x80 and 0xFF, followed by a leading length indicator
# File lib/em-http/client.rb, line 269 269: def send(data) 270: if @state == :websocket 271: send_data("\x00#{data}\xff") 272: end 273: end
# File lib/em-http/client.rb, line 341 341: def send_request_body 342: if @options[:body] 343: body = normalize_body 344: send_data body 345: return 346: elsif @options[:file] 347: stream_file_data @options[:file], :http_chunks => false 348: end 349: end
# File lib/em-http/client.rb, line 287 287: def send_request_header 288: query = @options[:query] 289: head = @options[:head] ? munge_header_keys(@options[:head]) : {} 290: file = @options[:file] 291: body = normalize_body 292: request_header = nil 293: 294: if @state == :response_proxy 295: proxy = @options[:proxy] 296: 297: # initialize headers to establish the HTTP tunnel 298: head = proxy[:head] ? munge_header_keys(proxy[:head]) : {} 299: head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization] 300: request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"] 301: 302: elsif websocket? 303: head['upgrade'] = 'WebSocket' 304: head['connection'] = 'Upgrade' 305: head['origin'] = @options[:origin] || @uri.host 306: 307: else 308: # Set the Content-Length if file is given 309: head['content-length'] = File.size(file) if file 310: 311: # Set the Content-Length if body is given 312: head['content-length'] = body.bytesize if body 313: 314: # Set the cookie header if provided 315: if cookie = head.delete('cookie') 316: head['cookie'] = encode_cookie(cookie) 317: end 318: 319: # Set content-type header if missing and body is a Ruby hash 320: if not head['content-type'] and options[:body].is_a? Hash 321: head['content-type'] = "application/x-www-form-urlencoded" 322: end 323: end 324: 325: # Set the Host header if it hasn't been specified already 326: head['host'] ||= encode_host 327: 328: # Set the User-Agent if it hasn't been specified 329: head['user-agent'] ||= "EventMachine HttpClient" 330: 331: # Record last seen URL 332: @last_effective_url = @uri 333: 334: # Build the request headers 335: request_header ||= encode_request(@method, @uri.path, query, @uri.query) 336: request_header << encode_headers(head) 337: request_header << CRLF 338: send_data request_header 339: end
assign a stream processing block
# File lib/em-http/client.rb, line 252 252: def stream(&blk) 253: @stream = blk 254: end
# File lib/em-http/client.rb, line 377 377: def unbind 378: if (@state == :finished) && (@last_effective_url != @uri) && (@redirects < @options[:redirects]) 379: # update uri to redirect location if we're allowed to traverse deeper 380: @uri = @last_effective_url 381: 382: # keep track of the depth of requests we made in this session 383: @redirects += 1 384: 385: # swap current connection and reassign current handler 386: req = HttpOptions.new(@method, @uri, @options) 387: reconnect(req.host, req.port) 388: 389: @response_header = HttpResponseHeader.new 390: @state = :response_header 391: @data.clear 392: else 393: if @state == :finished || (@state == :body && @bytes_remaining.nil?) 394: succeed(self) 395: else 396: @disconnect.call(self) if @state == :websocket and @disconnect 397: fail(self) 398: end 399: end 400: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.