Class Jabber::HTTPBinding::Client
In: lib/xmpp4r/httpbinding/client.rb
Parent: Jabber::Client
Message Presence XMPPStanza Iq Singleton IdGenerator XMPPElement X IqQuery Error Connection Client Component Client Comparable JID RuntimeError AuthenticationFailure ErrorException NoNameXmlnsRegistered SOCKS5Error REXML::Element Stream SOCKS5Bytestreams SOCKS5BytestreamsTarget SOCKS5BytestreamsInitiator XMPPElement StreamHost IqSiFileRange IqSiFile StreamHostUsed IqSi XRosterItem RosterItem IqFeature XMUCUserItem XMUCUserInvite IqPubSub Items Item Event Feature Item Identity XDataField XDataReported XDataTitle XDataInstructions IqVcard SOCKS5BytestreamsServerStreamHost TCPSocket SOCKS5Socket IqQuery IqQueryBytestreams IqQueryVersion IqQueryRoster IqQueryMUCOwner IqQueryRPC IqQueryDiscoItems IqQueryDiscoInfo IBB IBBTarget IBBInitiator Responder SimpleResponder Iq IqCommand RosterXItem XRoster RosterX X XMUCUser XMUC XDelay XData XParent MUCClient SimpleMUCClient XMLRPC::ParserWriterChooseMixin Client Server XMLRPC::ParseContentType XMLRPC::BasicServer Base DigestMD5 Plain ServiceHelper NodeHelper FileSource CallbackList Callback Semaphore StreamParser SOCKS5BytestreamsPeer SOCKS5BytestreamsServer IBBQueueItem Responder Helper MUCBrowser NodeBrowser Helper Helper lib/xmpp4r/authenticationfailure.rb lib/xmpp4r/xmppstanza.rb lib/xmpp4r/callbacks.rb lib/xmpp4r/idgenerator.rb lib/xmpp4r/connection.rb lib/xmpp4r/iq.rb lib/xmpp4r/jid.rb lib/xmpp4r/errorexception.rb lib/xmpp4r/client.rb lib/xmpp4r/stream.rb lib/xmpp4r/semaphore.rb lib/xmpp4r/streamparser.rb lib/xmpp4r/x.rb lib/xmpp4r/error.rb lib/xmpp4r/component.rb lib/xmpp4r/query.rb lib/xmpp4r/xmppelement.rb lib/xmpp4r/message.rb lib/xmpp4r/presence.rb lib/xmpp4r/bytestreams/helper/ibb/initiator.rb lib/xmpp4r/bytestreams/iq/si.rb lib/xmpp4r/bytestreams/iq/bytestreams.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb lib/xmpp4r/bytestreams/helper/ibb/base.rb lib/xmpp4r/bytestreams/helper/ibb/target.rb Bytestreams XParent lib/xmpp4r/version/iq/version.rb lib/xmpp4r/version/helper/responder.rb lib/xmpp4r/version/helper/simpleresponder.rb Version lib/xmpp4r/command/iq/command.rb lib/xmpp4r/command/helper/responder.rb Command lib/xmpp4r/roster/helper/roster.rb lib/xmpp4r/roster/iq/roster.rb lib/xmpp4r/roster/x/roster.rb Roster lib/xmpp4r/feature_negotiation/iq/feature.rb FeatureNegotiation lib/xmpp4r/muc/x/muc.rb lib/xmpp4r/muc/helper/mucclient.rb lib/xmpp4r/muc/x/mucuseritem.rb lib/xmpp4r/muc/helper/mucbrowser.rb lib/xmpp4r/muc/x/mucuserinvite.rb lib/xmpp4r/muc/iq/mucowner.rb lib/xmpp4r/muc/helper/simplemucclient.rb MUC lib/xmpp4r/rpc/helper/server.rb lib/xmpp4r/rpc/helper/client.rb lib/xmpp4r/rpc/iq/rpc.rb RPC lib/xmpp4r/sasl.rb SASL lib/xmpp4r/delay/x/delay.rb Delay lib/xmpp4r/pubsub/helper/servicehelper.rb lib/xmpp4r/pubsub/stanzas/item.rb lib/xmpp4r/pubsub/helper/nodehelper.rb lib/xmpp4r/pubsub/iq/pubsub.rb lib/xmpp4r/pubsub/stanzas/event.rb lib/xmpp4r/pubsub/helper/nodebrowser.rb lib/xmpp4r/pubsub/stanzas/items.rb PubSub lib/xmpp4r/httpbinding/client.rb HTTPBinding lib/xmpp4r/bytestreams/helper/filetransfer.rb TransferSource FileTransfer lib/xmpp4r/discovery/iq/discoinfo.rb lib/xmpp4r/discovery/iq/discoitems.rb Discovery lib/xmpp4r/dataforms/x/data.rb Dataforms lib/xmpp4r/vcard/helper/vcard.rb lib/xmpp4r/vcard/iq/vcard.rb Vcard Jabber dot/m_79_0.png

This class implements an alternative Client using HTTP Binding (JEP0124).

This class is designed to be a drop-in replacement for Jabber::Client, except for the Jabber::HTTP::Client#connect method which takes an URI as argument.

HTTP requests are buffered to not exceed the negotiated ‘polling’ and ‘requests’ parameters.

Stanzas in HTTP resonses may be delayed to arrive in the order defined by ‘rid’ parameters.

Debugging

Turning Jabber::debug to true will make debug output not only spit out stanzas but HTTP request/response bodies, too.

Methods

Attributes

http_content_type  [RW]  Content-Type to be used for communication (you can set this to "text/html")
http_hold  [RW]  The server may hold this amount of stanzas to reduce number of HTTP requests
http_wait  [RW]  The server should wait this value seconds if there is no stanza to be received

Public Class methods

Initialize

jid:[JID or String]

[Source]

    # File lib/xmpp4r/httpbinding/client.rb, line 45
45:       def initialize(jid)
46:         super
47: 
48:         @lock = Mutex.new
49:         @pending_requests = 0
50:         @last_send = Time.at(0)
51:         @send_buffer = ''
52: 
53:         @http_wait = 20
54:         @http_hold = 1
55:         @http_content_type = 'text/xml; charset=utf-8'
56:       end

Public Instance methods

Close the session by sending <presence type=‘unavailable’/>

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 139
139:       def close
140:         @status = DISCONNECTED
141:         send(Jabber::Presence.new.set_type(:unavailable))
142:       end

Set up the stream using uri as the HTTP Binding URI

You may optionally pass host and port parameters to make use of the JEP0124 ‘route’ feature.

uri:[URI::Generic or String]
host:[String] Optional host to route to
port:[Fixnum] Port for route feature

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 67
 67:       def connect(uri, host=nil, port=5222)
 68:         uri = URI::parse(uri) unless uri.kind_of? URI::Generic
 69:         @uri = uri
 70: 
 71:         @allow_tls = false  # Shall be done at HTTP level
 72:         @stream_mechanisms = []
 73:         @stream_features = {}
 74:         @http_rid = IdGenerator.generate_id.to_i
 75:         @pending_rid = @http_rid
 76:         @pending_rid_lock = Mutex.new
 77: 
 78:         req_body = REXML::Element.new('body')
 79:         req_body.attributes['rid'] = @http_rid
 80:         req_body.attributes['content'] = @http_content_type
 81:         req_body.attributes['hold'] = @http_hold.to_s
 82:         req_body.attributes['wait'] = @http_wait.to_s
 83:         req_body.attributes['to'] = @jid.domain
 84:         if host
 85:           req_body.attributes['route'] = 'xmpp:#{host}:#{port}'
 86:         end
 87:         req_body.attributes['secure'] = 'true'
 88:         req_body.attributes['xmlns'] = 'http://jabber.org/protocol/httpbind'
 89:         res_body = post(req_body)
 90:         unless res_body.name == 'body'
 91:           raise 'Response body is no <body/> element'
 92:         end
 93: 
 94:         @streamid = res_body.attributes['authid']
 95:         @status = CONNECTED
 96:         @http_sid = res_body.attributes['sid']
 97:         @http_wait = res_body.attributes['wait'].to_i if res_body.attributes['wait']
 98:         @http_hold = res_body.attributes['hold'].to_i if res_body.attributes['hold']
 99:         @http_inactivity = res_body.attributes['inactivity'].to_i
100:         @http_polling = res_body.attributes['polling'].to_i
101:         @http_polling = 5 if @http_polling == 0
102:         @http_requests = res_body.attributes['requests'].to_i
103:         @http_requests = 1 if @http_requests == 0
104: 
105:         receive_elements_with_rid(@http_rid, res_body.children)
106: 
107:         @features_sem.run
108:       end

Ensure that there is one pending request

Will be automatically called if you‘ve sent a stanza.

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 128
128:       def ensure_one_pending_request
129:         return if is_disconnected?
130: 
131:         if @lock.synchronize { @pending_requests } < 1
132:           send_data('')
133:         end
134:       end

Send a stanza, additionally with block

This method ensures a ‘jabber:client’ namespace for the stanza

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 115
115:       def send(xml, &block)
116:         if xml.kind_of? REXML::Element
117:           xml.add_namespace('jabber:client')
118:         end
119: 
120:         super
121:       end

Private Instance methods

Do a POST request

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 164
164:       def post(body)
165:         body = body.to_s
166:         request = Net::HTTP::Post.new(@uri.path)
167:         request.content_length = body.size
168:         request.body = body
169:         request['Content-Type'] = @http_content_type
170:         Jabber::debuglog("HTTP REQUEST (#{@pending_requests}/#{@http_requests}):\n#{request.body}")
171:         response = Net::HTTP.start(@uri.host, @uri.port) { |http|
172:           http.use_ssl = true if @uri.kind_of? URI::HTTPS
173:           http.request(request)
174:         }
175:         Jabber::debuglog("HTTP RESPONSE (#{@pending_requests}/#{@http_requests}):\n#{response.body}")
176: 
177:         unless response.kind_of? Net::HTTPSuccess
178:           # Unfortunately, HTTPResponses aren't exceptions
179:           # TODO: rescue'ing code should be able to distinguish
180:           raise Net::HTTPBadResponse, "#{response.class}"
181:         end
182: 
183:         body = REXML::Document.new(response.body).root
184:         if body.name != 'body' and body.namespace != 'http://jabber.org/protocol/httpbind'
185:           raise REXML::ParseException.new('Malformed body')
186:         end
187:         body
188:       end

Prepare data to POST and handle the result

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 193
193:       def post_data(data)
194:         req_body = nil
195:         current_rid = nil
196: 
197:         begin
198:           begin
199:             @lock.synchronize {
200:               # Do not send unneeded requests
201:               if data.size < 1 and @pending_requests > 0
202:                 return
203:               end
204: 
205:               req_body = "<body"
206:               req_body += " rid='#{@http_rid += 1}'"
207:               req_body += " sid='#{@http_sid}'"
208:               req_body += " xmlns='http://jabber.org/protocol/httpbind'"
209:               req_body += ">"
210:               req_body += data
211:               req_body += "</body>"
212:               current_rid = @http_rid
213: 
214:               @pending_requests += 1
215:               @last_send = Time.now
216:             }
217: 
218:             res_body = post(req_body)
219: 
220:           ensure
221:             @lock.synchronize { @pending_requests -= 1 }
222:           end
223: 
224:           receive_elements_with_rid(current_rid, res_body.children)
225:           ensure_one_pending_request
226: 
227:         rescue REXML::ParseException
228:           if @exception_block
229:             Thread.new do
230:               Thread.current.abort_on_exception = true
231:               close; @exception_block.call(e, self, :parser)
232:             end
233:           else
234:             puts "Exception caught when parsing HTTP response!"
235:             close
236:             raise
237:           end
238: 
239:         rescue StandardError => e
240:           Jabber::debuglog("POST error (will retry): #{e.class}: #{e}")
241:           receive_elements_with_rid(current_rid, [])
242:           # It's not good to resend on *any* exception,
243:           # but there are too many cases (Timeout, 404, 502)
244:           # where resending is appropriate
245:           # TODO: recognize these conditions and act appropriate
246:           send_data(data)
247:         end
248:       end

Receive stanzas ensuring that the ‘rid’ order is kept

result:[REXML::Element]

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 149
149:       def receive_elements_with_rid(rid, elements)
150:         while rid > @pending_rid
151:           @pending_rid_lock.lock
152:         end
153:         @pending_rid = rid + 1
154: 
155:         elements.each { |e|
156:           receive(e)
157:         }
158: 
159:         @pending_rid_lock.unlock
160:       end

Send data, buffered and obeying ‘polling’ and ‘requests’ limits

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 253
253:       def send_data(data)
254:         @lock.synchronize do
255: 
256:           @send_buffer += data
257:           limited_by_polling = (@last_send + @http_polling >= Time.now)
258:           limited_by_requests = (@pending_requests + 1 > @http_requests)
259: 
260:           # Can we send?
261:           if !limited_by_polling and !limited_by_requests
262:             data = @send_buffer
263:             @send_buffer = ''
264: 
265:             Thread.new do
266:               Thread.current.abort_on_exception = true
267:               post_data(data)
268:             end
269: 
270:           elsif !limited_by_requests
271:             Thread.new do
272:               Thread.current.abort_on_exception = true
273:               # Defer until @http_polling has expired
274:               wait = @last_send + @http_polling - Time.now
275:               sleep(wait) if wait > 0
276:               # Ignore locking, it's already threaded ;-)
277:               send_data('')
278:             end
279:           end
280: 
281:         end
282:       end

[Validate]