EventMachine.run {
http = EventMachine::Protocols::HttpClient.request(
:host => server,
:port => 80,
:request => "/index.html",
:query_string => "parm1=value1&parm2=value2"
)
http.callback {|response|
puts response[:status]
puts response[:headers]
puts response[:content]
}
}
:host => ‘ip/dns’, :port => fixnum, :verb => ‘GET’, :request => ‘path’, :basic_auth => {:username => ’’, :password => ’’}, :content => ‘content’, :contenttype => ‘text/plain’, :query_string => ’’, :host_header => ’’, :cookie => ’’
# File lib/em/protocols/httpclient.rb, line 70
70: def self.request( args = {} )
71: args[:port] ||= 80
72: EventMachine.connect( args[:host], args[:port], self ) {|c|
73: # According to the docs, we will get here AFTER post_init is called.
74: c.instance_eval {@args = args}
75: }
76: end
We send the request when we get a connection. AND, we set an instance variable to indicate we passed through here. That allows # 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 89
89: def connection_completed
90: @connected = true
91: send_request @args
92: end
# File lib/em/protocols/httpclient.rb, line 242
242: def dispatch_response
243: @read_state = :base
244: set_deferred_status :succeeded, {
245: :content => @content,
246: :headers => @headers,
247: :status => @status
248: }
249: # TODO, we close the connection for now, but this is wrong for persistent clients.
250: close_connection
251: end
# File lib/em/protocols/httpclient.rb, line 78
78: def post_init
79: @start_time = Time.now
80: @data = ""
81: @read_state = :base
82: end
# File lib/em/protocols/httpclient.rb, line 161
161: def receive_data data
162: while data and data.length > 0
163: case @read_state
164: when :base
165: # Perform any per-request initialization here and don't consume any data.
166: @data = ""
167: @headers = []
168: @content_length = nil # not zero
169: @content = ""
170: @status = nil
171: @read_state = :header
172: @connection_close = nil
173: when :header
174: ary = data.split( /\r?\n/, 2 )
175: if ary.length == 2
176: data = ary.last
177: if ary.first == ""
178: if (@content_length and @content_length > 0) || @connection_close
179: @read_state = :content
180: else
181: dispatch_response
182: @read_state = :base
183: end
184: else
185: @headers << ary.first
186: if @headers.length == 1
187: parse_response_line
188: elsif ary.first =~ /\Acontent-length:\s*/
189: # Only take the FIRST content-length header that appears,
190: # which we can distinguish because @content_length is nil.
191: # TODO, it's actually a fatal error if there is more than one
192: # content-length header, because the caller is presumptively
193: # a bad guy. (There is an exploit that depends on multiple
194: # content-length headers.)
195: @content_length ||= $'.to_i
196: elsif ary.first =~ /\Aconnection:\s*close/
197: @connection_close = true
198: end
199: end
200: else
201: @data << data
202: data = ""
203: end
204: when :content
205: # If there was no content-length header, we have to wait until the connection
206: # closes. Everything we get until that point is content.
207: # TODO: Must impose a content-size limit, and also must implement chunking.
208: # Also, must support either temporary files for large content, or calling
209: # a content-consumer block supplied by the user.
210: if @content_length
211: bytes_needed = @content_length - @content.length
212: @content += data[0, bytes_needed]
213: data = data[bytes_needed..1] || ""
214: if @content_length == @content.length
215: dispatch_response
216: @read_state = :base
217: end
218: else
219: @content << data
220: data = ""
221: end
222: end
223: end
224: end
# File lib/em/protocols/httpclient.rb, line 94
94: def send_request args
95: args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
96: args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
97:
98: verb = args[:verb].to_s.upcase
99: unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
100: set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
101: return # NOTE THE EARLY RETURN, we're not sending any data.
102: end
103:
104: request = args[:request] || "/"
105: unless request[0,1] == "/"
106: request = "/" + request
107: end
108:
109: qs = args[:query_string] || ""
110: if qs.length > 0 and qs[0,1] != '?'
111: qs = "?" + qs
112: end
113:
114: version = args[:version] || "1.1"
115:
116: # Allow an override for the host header if it's not the connect-string.
117: host = args[:host_header] || args[:host] || "_"
118: # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
119: port = args[:port]
120:
121: # POST items.
122: postcontenttype = args[:contenttype] || "application/octet-stream"
123: postcontent = args[:content] || ""
124: raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
125:
126: # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
127: # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
128: req = [
129: "#{verb} #{request}#{qs} HTTP/#{version}",
130: "Host: #{host}:#{port}",
131: "User-agent: Ruby EventMachine",
132: ]
133:
134: if verb == "POST" || verb == "PUT"
135: req << "Content-type: #{postcontenttype}"
136: req << "Content-length: #{postcontent.length}"
137: end
138:
139: # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
140: # Eventually we will want to deal intelligently with arrays and hashes.
141: if args[:cookie]
142: req << "Cookie: #{args[:cookie]}"
143: end
144:
145: # Basic-auth stanza contributed by Matt Murphy.
146: if args[:basic_auth]
147: basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'')
148: req << "Authorization: Basic #{basic_auth_string}"
149: end
150:
151: req << ""
152: reqstring = req.map {|l| "#{l}\r\n"}.join
153: send_data reqstring
154:
155: if verb == "POST" || verb == "PUT"
156: send_data postcontent
157: end
158: end
# File lib/em/protocols/httpclient.rb, line 253
253: def unbind
254: if !@connected
255: set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
256: elsif (@read_state == :content and @content_length == nil)
257: dispatch_response
258: end
259: end
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 230
230: def parse_response_line
231: if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
232: @status = $1.to_i
233: else
234: set_deferred_status :failed, {
235: :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
236: }
237: close_connection
238: end
239: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.