Class Jabber::HTTPBinding::Client
In: lib/xmpp4r/httpbinding/client.rb
Parent: Jabber::Client
Message Presence XMPPStanza Iq XMPPElement ErrorResponse X IqQuery JabberError ComponentAuthenticationFailure ArgumentError InvalidChatState SOCKS5Error ServerError NoNameXmlnsRegistered ServerDisconnected ClientAuthenticationFailure Connection Client Component Connection Client Singleton IdGenerator Comparable JID Enumerable CallbackList Items Publish StandardError REXML::Element Stream XMPPElement Location IqFeature StreamHost IqSiFile IqSiFileRange IqSi StreamHostUsed XRosterItem RosterItem C Body HTML UserItem XMUCUserInvite Configuration Retract IqPubSub Item IqPubSubOwner Event Subscription Unsubscribe Tune XDataField XDataReported XDataTitle XDataInstructions Feature Item Identity IqVcard PubSub::ServiceHelper Helper Helper SOCKS5Bytestreams SOCKS5BytestreamsTarget SOCKS5BytestreamsInitiator SOCKS5BytestreamsServerStreamHost TCPSocket SOCKS5Socket IqQuery IqQueryBytestreams IqQueryRoster IqQueryVersion IqQueryRPC IqQueryMUCOwner IqQueryMUCAdmin IqQueryDiscoItems IqQueryDiscoInfo IqQueryLastActivity IBB IBBTarget IBBInitiator RosterXItem XRoster RosterX X XDelay XMUC XMUCUser XData Responder SimpleResponder Iq IqCommand XMLRPC::ParserWriterChooseMixin Client Server XMLRPC::ParseContentType XMLRPC::BasicServer MUCClient SimpleMUCClient MUC::UserItem XMUCUserItem IqQueryMUCAdminItem XParent SubscriptionConfig NodeConfig OwnerNodeConfig EventItems ServiceHelper OAuthServiceHelper NodeHelper EventItem Base Anonymous DigestMD5 Plain FileSource Base Bot Callback StreamParser Semaphore SOCKS5BytestreamsPeer SOCKS5BytestreamsServer IBBQueueItem Helper Responder Helper Listener MUCBrowser NodeBrowser ListenerMocker Helper Responder Helper Helper Helper lib/xmpp4r/message.rb lib/xmpp4r/connection.rb lib/xmpp4r/xmppstanza.rb lib/xmpp4r/iq.rb lib/xmpp4r/callbacks.rb lib/xmpp4r/idgenerator.rb lib/xmpp4r/stream.rb lib/xmpp4r/client.rb lib/xmpp4r/jid.rb lib/xmpp4r/x.rb lib/xmpp4r/streamparser.rb lib/xmpp4r/semaphore.rb lib/xmpp4r/errors.rb lib/xmpp4r/component.rb lib/xmpp4r/presence.rb lib/xmpp4r/xmppelement.rb lib/xmpp4r/query.rb XParent lib/xmpp4r/location/helper/helper.rb lib/xmpp4r/location/location.rb UserLocation lib/xmpp4r/feature_negotiation/iq/feature.rb FeatureNegotiation lib/xmpp4r/bytestreams/iq/si.rb lib/xmpp4r/bytestreams/helper/ibb/initiator.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb lib/xmpp4r/bytestreams/iq/bytestreams.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb lib/xmpp4r/bytestreams/helper/ibb/target.rb lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb lib/xmpp4r/bytestreams/helper/ibb/base.rb Bytestreams lib/xmpp4r/roster/x/roster.rb lib/xmpp4r/roster/helper/roster.rb lib/xmpp4r/roster/iq/roster.rb Roster lib/xmpp4r/version/helper/responder.rb lib/xmpp4r/version/helper/simpleresponder.rb lib/xmpp4r/version/iq/version.rb Version lib/xmpp4r/command/iq/command.rb lib/xmpp4r/command/helper/responder.rb Command lib/xmpp4r/caps/helper/helper.rb lib/xmpp4r/caps/c.rb Caps lib/xmpp4r/reliable.rb Reliable lib/xmpp4r/delay/x/delay.rb Delay lib/xmpp4r/xhtml/html.rb XHTML lib/xmpp4r/rpc/helper/server.rb lib/xmpp4r/rpc/helper/client.rb lib/xmpp4r/rpc/iq/rpc.rb RPC lib/xmpp4r/muc/iq/mucadminitem.rb lib/xmpp4r/muc/x/muc.rb lib/xmpp4r/muc/item.rb lib/xmpp4r/muc/helper/simplemucclient.rb lib/xmpp4r/muc/iq/mucadmin.rb lib/xmpp4r/muc/helper/mucbrowser.rb lib/xmpp4r/muc/x/mucuseritem.rb lib/xmpp4r/muc/x/mucuserinvite.rb lib/xmpp4r/muc/iq/mucowner.rb lib/xmpp4r/muc/helper/mucclient.rb MUC lib/xmpp4r/pubsub/children/item.rb lib/xmpp4r/pubsub/children/configuration.rb lib/xmpp4r/pubsub/children/subscription.rb lib/xmpp4r/pubsub/helper/servicehelper.rb lib/xmpp4r/pubsub/children/unsubscribe.rb lib/xmpp4r/pubsub/children/publish.rb lib/xmpp4r/pubsub/helper/oauth_service_helper.rb lib/xmpp4r/pubsub/children/event.rb lib/xmpp4r/pubsub/iq/pubsub.rb lib/xmpp4r/pubsub/children/retract.rb lib/xmpp4r/pubsub/helper/nodebrowser.rb lib/xmpp4r/pubsub/helper/nodehelper.rb lib/xmpp4r/pubsub/children/items.rb lib/xmpp4r/pubsub/children/subscription_config.rb lib/xmpp4r/pubsub/children/node_config.rb OAuthPubSubStreamHelper PubSub lib/xmpp4r/httpbinding/client.rb HTTPBinding lib/xmpp4r/tune/helper/helper.rb lib/xmpp4r/tune/tune.rb UserTune lib/xmpp4r/sasl.rb SASL lib/xmpp4r/test/listener_mocker.rb Test lib/xmpp4r/dataforms/x/data.rb Dataforms lib/xmpp4r/discovery/helper/helper.rb lib/xmpp4r/discovery/iq/discoinfo.rb lib/xmpp4r/discovery/helper/responder.rb lib/xmpp4r/discovery/iq/discoitems.rb Discovery lib/xmpp4r/bytestreams/helper/filetransfer.rb TransferSource FileTransfer lib/xmpp4r/last/helper/helper.rb lib/xmpp4r/last/iq/last.rb LastActivity lib/xmpp4r/framework/base.rb lib/xmpp4r/framework/bot.rb Framework lib/xmpp4r/vcard/helper/vcard.rb lib/xmpp4r/vcard/iq/vcard.rb Vcard Jabber dot/m_110_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 47
47:       def initialize(jid)
48:         super
49: 
50:         @lock = Mutex.new
51:         @pending_requests = 0
52:         @last_send = Time.at(0)
53:         @send_buffer = ''
54: 
55:         @http_requests = 1
56:         @http_wait = 20
57:         @http_hold = 1
58:         @http_content_type = 'text/xml; charset=utf-8'
59:       end

Public Instance methods

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

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 129
129:       def close
130:         @status = DISCONNECTED
131:         send(Jabber::Presence.new.set_type(:unavailable))
132:       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 70
 70:       def connect(uri, host=nil, port=5222)
 71:         uri = URI::parse(uri) unless uri.kind_of? URI::Generic
 72:         @uri = uri
 73: 
 74:         @allow_tls = false  # Shall be done at HTTP level
 75:         @stream_mechanisms = []
 76:         @stream_features = {}
 77:         @http_rid = IdGenerator.generate_id.to_i
 78:         @pending_rid = @http_rid
 79:         @pending_rid_lock = Semaphore.new
 80: 
 81:         req_body = REXML::Element.new('body')
 82:         req_body.attributes['rid'] = @http_rid
 83:         req_body.attributes['content'] = @http_content_type
 84:         req_body.attributes['hold'] = @http_hold.to_s
 85:         req_body.attributes['wait'] = @http_wait.to_s
 86:         req_body.attributes['to'] = @jid.domain
 87:         if host
 88:           req_body.attributes['route'] = "xmpp:#{host}:#{port}"
 89:         end
 90:         req_body.attributes['secure'] = 'true'
 91:         req_body.attributes['xmlns'] = 'http://jabber.org/protocol/httpbind'
 92:         res_body = post(req_body)
 93:         unless res_body.name == 'body'
 94:           raise 'Response body is no <body/> element'
 95:         end
 96: 
 97:         @streamid = res_body.attributes['authid']
 98:         @status = CONNECTED
 99:         @http_sid = res_body.attributes['sid']
100:         @http_wait = res_body.attributes['wait'].to_i if res_body.attributes['wait']
101:         @http_hold = res_body.attributes['hold'].to_i if res_body.attributes['hold']
102:         @http_inactivity = res_body.attributes['inactivity'].to_i
103:         @http_polling = res_body.attributes['polling'].to_i
104:         @http_polling = 5 if @http_polling == 0
105:         @http_requests = res_body.attributes['requests'].to_i
106:         @http_requests = 1 if @http_requests == 0
107: 
108:         receive_elements_with_rid(@http_rid, res_body.children)
109: 
110:         @features_sem.run
111:       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 118
118:       def ensure_one_pending_request
119:         return if is_disconnected?
120: 
121:         if @lock.synchronize { @pending_requests } < 1
122:           send_data('')
123:         end
124:       end

Private Instance methods

Do a POST request

[Source]

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

Prepare data to POST and handle the result

[Source]

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

Receive stanzas ensuring that the ‘rid’ order is kept

result:[REXML::Element]

[Source]

     # File lib/xmpp4r/httpbinding/client.rb, line 139
139:       def receive_elements_with_rid(rid, elements)
140:         while rid > @pending_rid
141:           @pending_rid_lock.wait
142:         end
143:         @pending_rid = rid + 1
144: 
145:         elements.each { |e|
146:           receive(e)
147:         }
148: 
149:         @pending_rid_lock.run
150:       end

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

[Source]

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

[Validate]