# =XMPP4R - XMPP Library for Ruby
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
# Website::http://home.gna.org/xmpp4r/
# TODO: eval
"
req_body += ""
req_body += ""
current_rid = @http_rid
@pending_requests += 1
@last_send = Time.now
}
res_body = post(req_body)
sleep(3)
Jabber::debuglog("Connection closed")
end
private
##
# Receive stanzas ensuring that the 'rid' order is kept
# result:: [REXML::Element]
def receive_elements_with_rid(rid, elements)
@pending_rid_lock.synchronize do
# Wait until rid == @pending_rid
if rid > @pending_rid
cv = ConditionVariable.new
@pending_rid_cv << [rid, cv]
@pending_rid_cv.sort!
while rid > @pending_rid
cv.wait(@pending_rid_lock)
end
end
end
elements.each { |e|
receive(e)
}
# Signal waiting elements
@pending_rid_lock.synchronize do
@pending_rid = rid + 1 # Ensure @pending_rid is modified atomically
if @pending_rid_cv.size > 0 && @pending_rid_cv.first.first == @pending_rid
next_rid, cv = @pending_rid_cv.shift
cv.signal
end
end
end
##
# Do a POST request
def post(body)
body = body.to_s
request = Net::HTTP::Post.new(@uri.path)
request.content_length = body.size
request.body = body
request['Content-Type'] = @http_content_type
Jabber::debuglog("HTTP REQUEST (#{@pending_requests}/#{@http_requests}):\n#{request.body}")
net_http_args = [@uri.host, @uri.port]
unless @proxy_args.empty?
unless no_proxy?(@uri)
net_http_args.concat @proxy_args
end
end
http = Net::HTTP.new(*net_http_args)
if @uri.kind_of? URI::HTTPS
http.use_ssl = true
@http_ssl_setup and @http_ssl_setup.call(http)
end
http.read_timeout = @http_wait * 1.1
response = http.start { |http|
http.request(request)
}
Jabber::debuglog("HTTP RESPONSE (#{@pending_requests}/#{@http_requests}): #{response.class}\n#{response.body}")
unless response.kind_of? Net::HTTPSuccess
# Unfortunately, HTTPResponses aren't exceptions
# TODO: rescue'ing code should be able to distinguish
raise Net::HTTPBadResponse, "#{response.class}"
end
body = REXML::Document.new(response.body).root
if body.name != 'body' and body.namespace != 'http://jabber.org/protocol/httpbind'
raise REXML::ParseException.new('Malformed body')
end
body
end
##
# Check whether uri should be accessed without proxy
def no_proxy?(uri)
@no_proxy.each do |host_addr|
return true if uri.host.match(Regexp.quote(host_addr) + '$')
end
return false
end
##
# Prepare data to POST and
# handle the result
def post_data(data, restart = false)
req_body = nil
current_rid = nil
begin
begin
@lock.synchronize {
# Do not send unneeded requests
if data.size < 1 and @pending_requests > 0 and !restart
@pending_requests += 1 # compensate for decrement in ensure clause
Jabber::debuglog "post_data: not sending excessive poll"
return
end
req_body = ""
req_body += data unless restart
req_body += ""
current_rid = @http_rid
@pending_requests += 1
@last_send = Time.now
}
res_body = post(req_body)
ensure
@lock.synchronize {
@pending_requests -= 1
}
end
receive_elements_with_rid(current_rid, res_body.children)
ensure_one_pending_request if @authenticated
rescue REXML::ParseException
if @exception_block
Thread.new do
Thread.current.abort_on_exception = true
close; @exception_block.call(e, self, :parser)
end
else
Jabber::debuglog "Exception caught when parsing HTTP response!"
close
raise
end
rescue StandardError => e
Jabber::debuglog("POST error (will retry): #{e.class}: #{e}")
receive_elements_with_rid(current_rid, [])
# It's not good to resend on *any* exception,
# but there are too many cases (Timeout, 404, 502)
# where resending is appropriate
# TODO: recognize these conditions and act appropriate
send_data(data)
end
end
##
# Restart stream after SASL authentication
def restart
Jabber::debuglog("Restarting after SASL")
@stream_mechanisms = []
@stream_features = {}
@features_sem = Semaphore.new
send_data('', true) # restart
end
##
# Send data,
# buffered and obeying 'polling' and 'requests' limits
def send_data(data, restart = false)
Jabber::debuglog("send_data")
while true do # exit by return
@lock.synchronize do
if @last_send + 0.05 >= Time.now
Jabber::debuglog("send_data too fast: waiting 0.05sec")
next
end
@send_buffer += data
limited_by_polling = false
if @pending_requests + 1 == @http_requests && @send_buffer.size == 0
limited_by_polling = (@last_send + @http_polling >= Time.now)
end
limited_by_requests = (@pending_requests + 1 > @http_requests)
# Can we send?
if !limited_by_polling and !limited_by_requests or !@authenticated
Jabber::debuglog("send_data non_limited")
data = @send_buffer
@send_buffer = ''
Thread.new do
Thread.current.abort_on_exception = true
post_data(data, restart)
end
return
elsif limited_by_requests
Jabber::debuglog("send_data limited by requests")
# Do nothing.
# When a pending request gets an response, it calls send_data('')
return
elsif # limited_by_polling && @authenticated
# Wait until we get some data to send, or @http_polling expires
Jabber::debuglog("send_data limited by polling: #{@http_polling}")
else
Jabber::errorlog("send_data: can't happen: pending_requests=#{@pending_requests} http_requests=#{@http_requests} send_buffer.size=#{@send_buffer.size} limited_by_polling=#{limited_by_polling} limited_by_requests=#{limited_by_requests}")
return
end
end
sleep(0.1)
end
end
end
end
end