17 #include "connectionbosh.h"
32 const std::string& boshHost,
const std::string& xmppServer,
35 m_logInstance( logInstance ), m_parser( this ), m_boshHost( boshHost ), m_path(
"/http-bind/" ),
36 m_rid( 0 ), m_initialStreamSent( false ), m_openRequests( 0 ),
37 m_maxOpenRequests( 2 ), m_wait( 30 ), m_hold( 1 ), m_streamRestart( false ),
38 m_lastRequestTime( std::time( 0 ) ), m_minTimePerRequest( 0 ), m_bufferContentLength( 0 ),
39 m_connMode( ModePipelining )
41 initInstance( connection, xmppServer, xmppPort );
45 const LogSink& logInstance,
const std::string& boshHost,
46 const std::string& xmppServer,
int xmppPort )
48 m_logInstance( logInstance ), m_parser( this ), m_boshHost( boshHost ), m_path(
"/http-bind/" ),
49 m_rid( 0 ), m_initialStreamSent( false ), m_openRequests( 0 ),
50 m_maxOpenRequests( 2 ), m_wait( 30 ), m_hold( 1 ), m_streamRestart( false ),
51 m_lastRequestTime( std::time( 0 ) ), m_minTimePerRequest( 0 ), m_bufferContentLength( 0 ),
52 m_connMode( ModePipelining )
54 initInstance( connection, xmppServer, xmppPort );
57 void ConnectionBOSH::initInstance(
ConnectionBase* connection,
const std::string& xmppServer,
65 m_boshedHost = m_boshHost +
":" + util::int2string(
m_port );
72 m_connectionPool.push_back( connection );
86 if( !m_connectionPool.empty() )
88 pBaseConn = m_connectionPool.front()->newInstance();
90 else if( !m_activeConnections.empty() )
92 pBaseConn = m_activeConnections.front()->newInstance();
113 "Initiating BOSH connection to server: " +
115 : ( ( m_connMode ==
ModeLegacyHTTP ) ? std::string(
"LegacyHTTP" )
116 : std::string(
"PersistentHTTP" ) ) ) );
123 if( ( m_connMode ==
ModePipelining && m_activeConnections.empty() )
124 || ( m_connectionPool.empty() && m_activeConnections.empty() ) )
131 std::string requestBody =
"<body rid='" + util::long2string( m_rid ) +
"' ";
132 requestBody +=
"sid='" + m_sid +
"' ";
133 requestBody +=
"type='terminal' ";
134 requestBody +=
"xml:lang='en' ";
136 if( m_sendBuffer.empty() )
140 requestBody +=
">" + m_sendBuffer +
"</body>";
143 sendRequest( requestBody );
150 "Disconnecting from server in a non-graceful fashion" );
168 if( !m_connectionPool.empty() )
169 ret = m_connectionPool.front()->recv( 0 );
170 if( !m_activeConnections.empty() )
171 ret = m_activeConnections.front()->recv( timeout );
178 "Sending empty request (or there is data in the send buffer)" );
191 if( data.substr( 0, 2 ) ==
"<?" )
195 m_streamRestart =
true;
206 else if( data ==
"</stream:stream>" )
209 m_sendBuffer += data;
216 bool ConnectionBOSH::sendXML()
221 "Data sent before connection established (will be buffered)" );
225 if( m_sendBuffer.empty() )
227 time_t now = time( 0 );
228 unsigned long delta = now - m_lastRequestTime;
229 if( delta < m_minTimePerRequest && m_openRequests > 0 )
239 std::string requestBody =
"<body rid='" + util::long2string( m_rid ) +
"' ";
240 requestBody +=
"sid='" + m_sid +
"' ";
243 if( m_streamRestart )
245 requestBody +=
" xmpp:restart='true' to='" +
m_server +
"' xml:lang='en' xmlns:xmpp='"
251 requestBody +=
">" + m_sendBuffer +
"</body>";
254 if( sendRequest( requestBody ) )
258 m_streamRestart =
false;
264 "Unable to send. Connection not complete, or too many open requests,"
265 " so added to buffer." );
272 bool ConnectionBOSH::sendRequest(
const std::string& xml )
278 std::string request =
"POST " + m_path;
281 request +=
" HTTP/1.0\r\n";
282 request +=
"Connection: close\r\n";
285 request +=
" HTTP/1.1\r\n";
287 request +=
"Host: " + m_boshedHost +
"\r\n";
288 request +=
"Content-Type: text/xml; charset=utf-8\r\n";
289 request +=
"Content-Length: " + util::long2string( xml.length() ) +
"\r\n";
290 request +=
"User-Agent: gloox/" +
GLOOX_VERSION +
"\r\n\r\n";
294 if( conn->send( request ) )
296 m_lastRequestTime = time( 0 );
306 bool ci_equal(
char ch1,
char ch2 )
308 return std::toupper( ch1 ) == std::toupper( ch2 );
311 std::string::size_type ci_find(
const std::string& str1,
const std::string& str2 )
313 std::string::const_iterator pos = std::search( str1.begin(), str1.end(),
314 str2.begin(), str2.end(), ci_equal );
315 if( pos == str1.end() )
316 return std::string::npos;
318 return std::distance( str1.begin(), pos );
321 const std::string ConnectionBOSH::getHTTPField(
const std::string& field )
323 std::string::size_type fp = ci_find( m_bufferHeader,
"\r\n" + field +
": " );
325 if( fp == std::string::npos )
328 fp += field.length() + 4;
330 const std::string::size_type fp2 = m_bufferHeader.find(
"\r\n", fp );
331 if( fp2 == std::string::npos )
334 return m_bufferHeader.substr( fp, fp2 - fp );
360 const std::string& data )
363 std::string::size_type headerLength = 0;
364 while( ( headerLength = m_buffer.find(
"\r\n\r\n" ) ) != std::string::npos )
366 m_bufferHeader = m_buffer.substr( 0, headerLength+2 );
368 const std::string& statusCode = m_bufferHeader.substr( 9, 3 );
369 if( statusCode !=
"200" )
372 "Received error via legacy HTTP status code: " + statusCode
373 +
". Disconnecting." );
378 m_bufferContentLength = atol( getHTTPField(
"Content-Length" ).c_str() );
379 if( !m_bufferContentLength )
382 if( m_connMode !=
ModeLegacyHTTP && ( getHTTPField(
"Connection" ) ==
"close"
383 || m_bufferHeader.substr( 0, 8 ) ==
"HTTP/1.0" ) )
386 "Server indicated lack of support for HTTP/1.1 - falling back to HTTP/1.0" );
390 if( m_buffer.length() >= ( headerLength + 4 + m_bufferContentLength ) )
394 std::string xml = m_buffer.substr( headerLength + 4, m_bufferContentLength );
395 m_parser.
feed( xml );
396 m_buffer.erase( 0, headerLength + 4 + m_bufferContentLength );
397 m_bufferContentLength = 0;
412 m_rid = rand() % 100000 + 1728679472;
414 Tag requestBody(
"body" );
418 requestBody.
addAttribute(
"content",
"text/xml; charset=utf-8" );
420 requestBody.
addAttribute(
"rid",
static_cast<long>( m_rid ) );
431 sendRequest( requestBody.
xml() );
450 "Connection closed - falling back to HTTP/1.0 connection method" );
465 if( m_streamRestart )
467 m_streamRestart =
false;
470 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams'"
473 + m_sid +
"' xml:lang='en'>" );
483 const int serverRequests = atoi( tag->
findAttribute(
"requests" ).c_str() );
484 if( serverRequests < m_maxOpenRequests )
486 m_maxOpenRequests = serverRequests;
488 "BOSH parameter 'requests' now set to " + tag->
findAttribute(
"requests" ) );
493 const int maxHold = atoi( tag->
findAttribute(
"hold" ).c_str() );
494 if( maxHold < m_hold )
498 "BOSH parameter 'hold' now set to " + tag->
findAttribute(
"hold" ) );
503 const int maxWait = atoi( tag->
findAttribute(
"wait" ).c_str() );
504 if( maxWait < m_wait )
508 "BOSH parameter 'wait' now set to " + tag->
findAttribute(
"wait" )
514 const int minTime = atoi( tag->
findAttribute(
"polling" ).c_str() );
515 m_minTimePerRequest = minTime;
517 "BOSH parameter 'polling' now set to " + tag->
findAttribute(
"polling" )
527 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
530 +
"' from='" +
m_server +
"' id ='" + m_sid +
"' xml:lang='en'>" );
536 "BOSH connection closed by server: " + tag->
findAttribute(
"condition" ) );
543 TagList::const_iterator it = stanzas.begin();
544 for( ; it != stanzas.end(); ++it )
550 if( m_openRequests > 0 && m_openRequests >= m_maxOpenRequests )
553 "Too many requests already open. Cannot send." );
561 if( !m_activeConnections.empty() )
564 return m_activeConnections.front();
566 else if( !m_connectionPool.empty() )
569 "Pipelining selected, but no connection open. Opening one." );
570 return activateConnection();
574 "No available connections to pipeline on." );
579 if( !m_connectionPool.empty() )
582 "using connection from pool." );
583 return activateConnection();
585 else if( !m_activeConnections.empty() )
588 conn = m_activeConnections.front()->newInstance();
589 conn->registerConnectionDataHandler(
this );
590 m_connectionPool.push_back( conn );
595 "No available connections to send on." );
602 ConnectionBase* ConnectionBOSH::activateConnection()
605 m_connectionPool.pop_front();
608 m_activeConnections.push_back( conn );
613 m_connectionPool.push_back( conn );
618 void ConnectionBOSH::putConnection()
628 m_activeConnections.pop_front();
629 m_connectionPool.push_back( conn );
633 m_activeConnections.pop_front();
634 m_connectionPool.push_back( conn );
virtual void handleDisconnect(const ConnectionBase *connection, ConnectionError reason)
virtual ConnectionError recv(int timeout=-1)
virtual void handleTag(Tag *tag)
virtual void handleConnect(const ConnectionBase *connection)
virtual void handleReceivedData(const ConnectionBase *connection, const std::string &data)
ConnectionBOSH(ConnectionBase *connection, const LogSink &logInstance, const std::string &boshHost, const std::string &xmppServer, int xmppPort=5222)
virtual ~ConnectionBOSH()
virtual void disconnect()
virtual ConnectionBase * newInstance() const
virtual ConnectionError connect()
virtual bool send(const std::string &data)
virtual ConnectionError receive()
virtual void getStatistics(long int &totalIn, long int &totalOut)
An abstract base class for a connection.
ConnectionDataHandler * m_handler
ConnectionBase(ConnectionDataHandler *cdh)
virtual void getStatistics(long int &totalIn, long int &totalOut)=0
virtual void disconnect()=0
void registerConnectionDataHandler(ConnectionDataHandler *cdh)
This is an abstract base class to receive events from a ConnectionBase-derived object.
virtual void handleReceivedData(const ConnectionBase *connection, const std::string &data)=0
virtual void handleDisconnect(const ConnectionBase *connection, ConnectionError reason)=0
virtual void handleConnect(const ConnectionBase *connection)=0
An implementation of log sink and source.
void warn(LogArea area, const std::string &message) const
void dbg(LogArea area, const std::string &message) const
void err(LogArea area, const std::string &message) const
int feed(std::string &data)
This is an abstraction of an XML element.
bool addAttribute(Attribute *attr)
bool hasAttribute(const std::string &name, const std::string &value=EmptyString) const
const std::string & findAttribute(const std::string &name) const
const std::string & name() const
const std::string xml() const
const TagList & children() const
bool setXmlns(const std::string &xmlns, const std::string &prefix=EmptyString)
bool idna(const std::string &domain, std::string &out)
void clearList(std::list< T * > &L)
The namespace for the gloox library.
std::list< Tag * > TagList
const std::string GLOOX_VERSION
@ LogAreaClassConnectionBOSH
const std::string XMPP_STREAM_VERSION_MAJOR
const std::string XMLNS_CLIENT
const std::string XMPP_STREAM_VERSION_MINOR
const std::string EmptyString
const std::string XMLNS_XMPP_BOSH
const std::string XMLNS_HTTPBIND