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.