Class: EventMachine::Protocols::HttpClient
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
Connection
|
|
Instance Chain:
self,
Deferrable ,
Connection
|
|
Inherits: |
EventMachine::Connection
|
Defined in: | lib/em/protocols/httpclient.rb |
Overview
Constant Summary
-
MaxPostContentLength =
# File 'lib/em/protocols/httpclient.rb', line 6620 * 1024 * 1024
Deferrable
- Included
Class Method Summary
- .new ⇒ HttpClient constructor
- .request(args = {})
Connection
- Inherited
.new | Override .new so subclasses don't have to call super and can ignore connection-specific arguments. |
Instance Attribute Summary
Connection
- Inherited
#comm_inactivity_timeout | comm_inactivity_timeout returns the current value (float in seconds) of the inactivity-timeout property of network-connection and datagram-socket objects. |
#comm_inactivity_timeout= | Allows you to set the inactivity-timeout property for a network connection or datagram socket. |
#error? | Returns true if the connection is in an error state, false otherwise. |
#notify_readable= | Watches connection for readability. |
#notify_readable?, | |
#notify_writable= | Watches connection for writeability. |
#notify_writable? | Returns true if the connection is being watched for writability. |
#paused?, | |
#pending_connect_timeout | The duration after which a TCP connection in the connecting state will fail. |
#pending_connect_timeout= | Sets the duration after which a TCP connection in a connecting state will fail. |
#signature, #watch_only? |
Instance Method Summary
-
#connection_completed
We send the request when we get a connection.
- #dispatch_response
- #post_init
- #receive_data(data)
- #send_request(args)
- #unbind
-
#parse_response_line
private
We get called here when we have received an HTTP response line.
Deferrable
- Included
#callback | Specify a block to be executed if and when the |
#cancel_callback | Cancels an outstanding callback to &block if any. |
#cancel_errback | Cancels an outstanding errback to &block if any. |
#cancel_timeout | Cancels an outstanding timeout if any. |
#errback | Specify a block to be executed if and when the |
#fail | Sugar for set_deferred_status(:failed, ...). |
#set_deferred_failure | Alias for Deferrable#fail. |
#set_deferred_status | Sets the "disposition" (status) of the |
#set_deferred_success | Alias for Deferrable#succeed. |
#succeed | Sugar for set_deferred_status(:succeeded, ...). |
#timeout | Setting a timeout on a |
Connection
- Inherited
#associate_callback_target | conn_associate_callback_target. |
#close_connection | EventMachine::Connection#close_connection is called only by user code, and never by the event loop. |
#close_connection_after_writing | A variant of EventMachine#close_connection. |
#connection_completed | Called by the event loop when a remote TCP connection attempt completes successfully. |
#detach | Removes given connection from the event loop. |
#disable_keepalive | t_disable_keepalive. |
#enable_keepalive | t_enable_keepalive. |
#get_cipher_bits, #get_cipher_name, #get_cipher_protocol, | |
#get_idle_time | The number of seconds since the last send/receive activity on this connection. |
#get_outbound_data_size | conn_get_outbound_data_size. |
#get_peer_cert | If TLS is active on the connection, returns the remote X509 certificate as a string, in the popular PEM format. |
#get_peername | This method is used with stream-connections to obtain the identity of the remotely-connected peer. |
#get_pid | Returns the PID (kernel process identifier) of a subprocess associated with this |
#get_proxied_bytes | The number of bytes proxied to another connection. |
#get_sni_hostname, #get_sock_opt, | |
#get_sockname | Used with stream-connections to obtain the identity of the local side of the connection. |
#get_status | Returns a subprocess exit status. |
#initialize | Stubbed initialize so legacy superclasses can safely call super. |
#original_method, | |
#pause | Pause a connection so that EventMachine#send_data and #receive_data events are not fired until |
#post_init | Called by the event loop immediately after the network connection has been established, and before resumption of the network loop. |
#proxy_completed | called when the reactor finished proxying all of the requested bytes. |
#proxy_incoming_to | EventMachine::Connection#proxy_incoming_to is called only by user code. |
#proxy_target_unbound | Called by the reactor after attempting to relay incoming data to a descriptor (set as a proxy target descriptor with EventMachine.enable_proxy) that has already been closed. |
#receive_data | Called by the event loop whenever data has been received by the network connection. |
#reconnect | Reconnect to a given host/port with the current instance. |
#resume | Resume a connection's EventMachine#send_data and #receive_data events. |
#send_data | Call this method to send data to the remote end of the network connection. |
#send_datagram | Sends UDP messages. |
#send_file_data | Like Connection#send_data, this sends data to the remote end of the network connection. |
#set_sock_opt, | |
#ssl_handshake_completed | Called by |
#ssl_verify_peer | Called by |
#start_tls | Call EventMachine#start_tls at any point to initiate TLS encryption on connected streams. |
#stop_proxying | A helper method for EventMachine.disable_proxy |
#stream_file_data | Open a file on the filesystem and send it to the remote peer. |
#unbind | called by the framework whenever a connection (either a server or client connection) is closed. |
Constructor Details
.new ⇒ HttpClient
# File 'lib/em/protocols/httpclient.rb', line 68
def initialize warn "HttpClient is deprecated and will be removed. EM-Http-Request should be used instead." @connected = false end
Class Method Details
.request(args = {})
# File 'lib/em/protocols/httpclient.rb', line 84
def self.request( args = {} ) args[:port] ||= 80 EventMachine.connect( args[:host], args[:port], self ) {|c| # According to the docs, we will get here AFTER post_init is called. c.instance_eval {@args = args} } end
Instance Method Details
#connection_completed
We send the request when we get a connection. AND, we set an instance variable to indicate we passed through here. That allows #unbind to know whether there was a successful connection. NB: This naive technique won't work when we have to support multiple requests on a single connection.
# File 'lib/em/protocols/httpclient.rb', line 103
def connection_completed @connected = true send_request @args end
#dispatch_response
[ GitHub ]# File 'lib/em/protocols/httpclient.rb', line 285
def dispatch_response @read_state = :base set_deferred_status :succeeded, { :content => @content, :headers => @headers, :status => @status } # TODO, we close the connection for now, but this is wrong for persistent clients. close_connection end
#parse_response_line (private)
We get called here when we have received an HTTP response line. It's an opportunity to throw an exception or trigger other exceptional handling.
# File 'lib/em/protocols/httpclient.rb', line 273
def parse_response_line if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/ @status = $1.to_i else set_deferred_status :failed, { :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this. } close_connection end end
#post_init
[ GitHub ]# File 'lib/em/protocols/httpclient.rb', line 92
def post_init @start_time = Time.now @data = "".dup @read_state = :base end
#receive_data(data)
[ GitHub ]# File 'lib/em/protocols/httpclient.rb', line 180
def receive_data data while data and data.length > 0 case @read_state when :base # Perform any per-request initialization here and don't consume any data. @data = "".dup @headers = [] @content_length = nil # not zero @content = "".dup @status = nil @chunked = false @chunk_length = nil @read_state = :header @connection_close = nil when :header ary = data.split( /\r?\n/m, 2 ) if ary.length == 2 data = ary.last if ary.first == "" if (@content_length and @content_length > 0) || @chunked || @connection_close @read_state = :content else dispatch_response @read_state = :base end else @headers << ary.first if @headers.length == 1 parse_response_line elsif ary.first =~ /\Acontent-length:\s*/i # Only take the FIRST content-length header that appears, # which we can distinguish because @content_length is nil. # TODO, it's actually a fatal error if there is more than one # content-length header, because the caller is presumptively # a bad guy. (There is an exploit that depends on multiple # content-length headers.) @content_length ||= $'.to_i elsif ary.first =~ /\Aconnection:\s*close/i @connection_close = true elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i @chunked = true end end else @data << data data = "" end when :content if @chunked && @chunk_length bytes_needed = @chunk_length - @chunk_read new_data = data[0, bytes_needed] @chunk_read += new_data.length @content += new_data data = data[bytes_needed..-1] || "" if @chunk_length == @chunk_read && data[0,2] == "\r\n" @chunk_length = nil data = data[2..-1] end elsif @chunked if (m = data.match(/\A(\S*)\r\n/m)) data = data[m[0].length..-1] @chunk_length = m[1].to_i(16) @chunk_read = 0 if @chunk_length == 0 dispatch_response @read_state = :base end end elsif @content_length # If there was no content-length header, we have to wait until the connection # closes. Everything we get until that point is content. # TODO: Must impose a content-size limit, and also must implement chunking. # Also, must support either temporary files for large content, or calling # a content-consumer block supplied by the user. bytes_needed = @content_length - @content.length @content += data[0, bytes_needed] data = data[bytes_needed..-1] || "" if @content_length == @content.length dispatch_response @read_state = :base end else @content << data data = "" end end end end
#send_request(args)
[ GitHub ]# File 'lib/em/protocols/httpclient.rb', line 108
def send_request args args[:verb] ||= args[:method] # Support :method as an alternative to :verb. args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified? verb = args[:verb].to_s.upcase unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb) set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type return # NOTE THE EARLY RETURN, we're not sending any data. end request = args[:request] || "/" unless request[0,1] == "/" request = "/" + request end qs = args[:query_string] || "" if qs.length > 0 and qs[0,1] != '?' qs = "?" + qs end version = args[:version] || "1.1" # Allow an override for the host header if it's not the connect-string. host = args[:host_header] || args[:host] || "_" # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default. port = args[:port].to_i != 80 ? ":#{args[:port]}" : "" # POST items. postcontenttype = args[:contenttype] || "application/octet-stream" postcontent = args[:content] || "" raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise. # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption. req = [ "#{verb} #{request}#{qs} HTTP/#{version}", "Host: #{host}#{port}", "User-agent: Ruby EventMachine", ] if verb == "POST" || verb == "PUT" req << "Content-type: #{postcontenttype}" req << "Content-length: #{postcontent.length}" end # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string. # Eventually we will want to deal intelligently with arrays and hashes. if args[: ] req << "Cookie: #{args[: ]}" end # Allow custom HTTP headers, e.g. SOAPAction args[:custom_headers].each do |k,v| req << "#{k}: #{v}" end if args[:custom_headers] # Basic-auth stanza contributed by Matt Murphy. if args[:basic_auth] basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'') req << "Authorization: Basic #{basic_auth_string}" end req << "" reqstring = req.map {|l| "#{l}\r\n"}.join send_data reqstring if verb == "POST" || verb == "PUT" send_data postcontent end end
#unbind
[ GitHub ]# File 'lib/em/protocols/httpclient.rb', line 296
def unbind if !@connected set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error. elsif (@read_state == :content and @content_length == nil) dispatch_response end end