Class: Jabber::HTTPBinding::Client

Inherits:
Client show all
Defined in:
lib/xmpp4r/httpbinding/client.rb

Overview

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.

Constant Summary

Constants inherited from Stream

Stream::CONNECTED, Stream::DISCONNECTED

Instance Attribute Summary collapse

Attributes inherited from Client

#jid

Attributes inherited from Connection

#allow_tls, #features_timeout, #host, #keepalive_interval, #port, #ssl_capath, #ssl_verifycb, #use_ssl

Attributes inherited from Stream

#fd, #processing, #status

Instance Method Summary collapse

Methods inherited from Client

#auth, #auth_anonymous, #auth_anonymous_sasl, #auth_nonsasl, #auth_sasl, #bind, #password=, #register, #register_info, #remove_registration, #start, #supports_anonymous?, #unbind

Methods inherited from Connection

#accept_features, #close!, #is_tls?, #start, #starttls

Methods inherited from Stream

#add_iq_callback, #add_message_callback, #add_presence_callback, #add_stanza_callback, #add_xml_callback, #close!, #delete_iq_callback, #delete_message_callback, #delete_presence_callback, #delete_stanza_callback, #delete_xml_callback, #iq_callbacks, #is_connected?, #is_disconnected?, #message_callbacks, #on_exception, #parse_failure, #parser_end, #presence_callbacks, #receive, #send, #send_with_id, #stanza_callbacks, #start, #stop, #xml_callbacks

Constructor Details

#initialize(jid) ⇒ Client

Initialize

jid
JID or String

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/xmpp4r/httpbinding/client.rb', line 51

def initialize(jid)
  super

  @lock = Mutex.new
  @pending_requests = 0
  @last_send = Time.at(0)
  @send_buffer = ''

  @http_requests = 1
  @http_wait = 20
  @http_hold = 1
  @http_content_type = 'text/xml; charset=utf-8'

  @no_proxy = []
  @proxy_args = []
end

Instance Attribute Details

#http_content_typeObject

Content-Type to be used for communication (you can set this to “text/html”)


38
39
40
# File 'lib/xmpp4r/httpbinding/client.rb', line 38

def http_content_type
  @http_content_type
end

#http_holdObject

The server may hold this amount of stanzas to reduce number of HTTP requests


44
45
46
# File 'lib/xmpp4r/httpbinding/client.rb', line 44

def http_hold
  @http_hold
end

#http_ssl_setupObject

Hook to initialize SSL parameters on Net::HTTP


46
47
48
# File 'lib/xmpp4r/httpbinding/client.rb', line 46

def http_ssl_setup
  @http_ssl_setup
end

#http_waitObject

The server should wait this value seconds if there is no stanza to be received


41
42
43
# File 'lib/xmpp4r/httpbinding/client.rb', line 41

def http_wait
  @http_wait
end

Instance Method Details

#closeObject

Close the session by sending <body type='terminate'/>


178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/xmpp4r/httpbinding/client.rb', line 178

def close
  @status = DISCONNECTED
  req_body = nil
  @lock.synchronize {
    req_body = "<body"
    req_body += " rid='#{@http_rid += 1}'"
    req_body += " sid='#{@http_sid}'"
    req_body += " type='terminate'"
    req_body += " xmlns='http://jabber.org/protocol/httpbind'"
    req_body += ">"
    req_body += "<presence type='unavailable' xmlns='jabber:client'/>"
    req_body += "</body>"
    current_rid = @http_rid
    @pending_requests += 1
    @last_send = Time.now
  }
  res_body = post(req_body, "terminate sid=#{@http_sid} rid=#{@http_rid}")
  sleep(3)
  Jabber::debuglog("Connection closed")
end

#connect(uri, host = nil, port = 5222) ⇒ Object

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


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/xmpp4r/httpbinding/client.rb', line 118

def connect(uri, host=nil, port=5222)
  uri = URI::parse(uri) unless uri.kind_of? URI::Generic
  @uri = uri

  @allow_tls = false  # Shall be done at HTTP level
  @stream_mechanisms = []
  @stream_features = {}
  @http_rid = IdGenerator.generate_id.to_i
  @pending_rid = @http_rid
  @pending_rid_lock = Mutex.new
  @pending_rid_cv = [] # [ [rid, cv], [rid, cv], ... ]

  req_body = REXML::Element.new('body')
  req_body.attributes['rid'] = @http_rid
  req_body.attributes['content'] = @http_content_type
  req_body.attributes['hold'] = @http_hold.to_s
  req_body.attributes['wait'] = @http_wait.to_s
  req_body.attributes['to'] = @jid.domain
  if host
    req_body.attributes['route'] = "xmpp:#{host}:#{port}"
  end
  req_body.attributes['secure'] = 'true'
  req_body.attributes['xmlns'] = 'http://jabber.org/protocol/httpbind'
  res_body = post(req_body, "sid=new rid=#{@http_rid}")
  unless res_body.name == 'body'
    raise 'Response body is no <body/> element'
  end

  @streamid = res_body.attributes['authid']
  @status = CONNECTED
  @http_sid = res_body.attributes['sid']
  @http_wait = res_body.attributes['wait'].to_i if res_body.attributes['wait']
  @http_hold = res_body.attributes['hold'].to_i if res_body.attributes['hold']
  @http_inactivity = res_body.attributes['inactivity'].to_i
  @http_polling = res_body.attributes['polling'].to_i
  @http_polling = 5 if @http_polling == 0
  @http_requests = res_body.attributes['requests'].to_i
  @http_requests = 1 if @http_requests == 0

  receive_elements_with_rid(@http_rid, res_body.children)

  @features_sem.run
end

#ensure_one_pending_requestObject

Ensure that there is one pending request

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


167
168
169
170
171
172
173
# File 'lib/xmpp4r/httpbinding/client.rb', line 167

def ensure_one_pending_request
  return if is_disconnected?

  if @lock.synchronize { @pending_requests } < 1
    send_data('')
  end
end

#http_proxy_envObject

Set up proxy from the environment variables

Following environment variables are considered HTTP_PROXY, http_proxy NO_PROXY, no_proxy HTTP_PROXY_USER, HTTP_PROXY_PASSWORD


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/xmpp4r/httpbinding/client.rb', line 93

def http_proxy_env
  @proxy_args = []
  env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
  return if env_proxy.nil? or env_proxy.empty?

  uri = URI.parse env_proxy
  unless uri.user or uri.password then
    uri.user     = CGI.escape (ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']) rescue nil
    uri.password = CGI.escape (ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']) rescue nil
  end

  self.http_proxy_uri = uri

  @no_proxy = (ENV['NO_PROXY'] || ENV['no_proxy'] || 'localhost, 127.0.0.1').split(/\s*,\s*/)
end

#http_proxy_uri=(uri) ⇒ Object

url
URI::Generic or String

of the form:

when without proxy authentication

http://proxy_host:proxy_port/

when with proxy authentication

http://proxy_user:[email protected]_host:proxy_port/

76
77
78
79
80
81
82
83
84
# File 'lib/xmpp4r/httpbinding/client.rb', line 76

def http_proxy_uri=(uri)
  uri = URI.parse(uri) unless uri.respond_to?(:host)
  @proxy_args = [
                 uri.host,
                 uri.port,
                 uri.user,
                 uri.password,
                ]
end