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( 2 ), 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( 2 ), 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 "bosh initiating connection to server: " +
115 : ( ( m_connMode ==
ModeLegacyHTTP ) ? std::string(
"LegacyHTTP" )
116 : std::string(
"PersistentHTTP" ) ) ) );
121 void ConnectionBOSH::disconnect()
123 if( ( m_connMode ==
ModePipelining && m_activeConnections.empty() )
124 || ( m_connectionPool.empty() && m_activeConnections.empty() ) )
131 std::string requestBody =
"<body rid='" + util::int2string( 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" );
166 if( !m_connectionPool.empty() )
167 m_connectionPool.front()->recv( 0 );
168 if( !m_activeConnections.empty() )
169 m_activeConnections.front()->recv( timeout );
176 "Sending empty request (or there is data in the send buffer)" );
183 bool ConnectionBOSH::send(
const std::string& data )
189 if( data.substr( 0, 2 ) ==
"<?" )
193 m_streamRestart =
true;
204 else if( data ==
"</stream:stream>" )
207 m_sendBuffer += data;
214 bool ConnectionBOSH::sendXML()
219 "Data sent before connection established (will be buffered)" );
223 if( m_sendBuffer.empty() )
225 time_t now = time( 0 );
226 unsigned int delta = (int)(now - m_lastRequestTime);
227 if( delta < m_minTimePerRequest && m_openRequests > 0 )
237 std::string requestBody =
"<body rid='" + util::int2string( m_rid ) +
"' ";
238 requestBody +=
"sid='" + m_sid +
"' ";
241 if( m_streamRestart )
243 requestBody +=
" xmpp:restart='true' to='" +
m_server +
"' xml:lang='en' xmlns:xmpp='"
249 requestBody +=
">" + m_sendBuffer +
"</body>";
252 if( sendRequest( requestBody ) )
256 m_streamRestart =
false;
262 "Unable to send. Connection not complete, or too many open requests,"
263 " so added to buffer.\n" );
270 bool ConnectionBOSH::sendRequest(
const std::string& xml )
276 std::string request =
"POST " + m_path;
280 request +=
" HTTP/1.0\r\n";
281 request +=
"Connection: close\r\n";
285 request +=
" HTTP/1.1\r\n";
286 request +=
"Connection: keep-alive\r\n";
289 request +=
"Host: " + m_boshedHost +
"\r\n";
290 request +=
"Content-Type: text/xml; charset=utf-8\r\n";
291 request +=
"Content-Length: " + util::int2string( xml.length() ) +
"\r\n";
292 request +=
"User-Agent: gloox/" +
GLOOX_VERSION +
"\r\n\r\n";
296 if( conn->send( request ) )
298 m_lastRequestTime = time( 0 );
308 bool ci_equal(
char ch1,
char ch2 )
310 return std::toupper( (
unsigned char)ch1 ) == std::toupper( (
unsigned char)ch2 );
313 std::string::size_type ci_find(
const std::string& str1,
const std::string& str2 )
315 std::string::const_iterator pos = std::search( str1.begin(), str1.end(),
316 str2.begin(), str2.end(), ci_equal );
317 if( pos == str1.end() )
318 return std::string::npos;
320 return std::distance( str1.begin(), pos );
323 const std::string ConnectionBOSH::getHTTPField(
const std::string& field )
325 std::string::size_type fp = ci_find( m_bufferHeader,
"\r\n" + field +
": " );
327 if( fp == std::string::npos )
330 fp += field.length() + 4;
332 const std::string::size_type fp2 = m_bufferHeader.find(
"\r\n", fp );
333 if( fp2 == std::string::npos )
336 return m_bufferHeader.substr( fp, fp2 - fp );
347 void ConnectionBOSH::cleanup()
355 void ConnectionBOSH::getStatistics(
long int& totalIn,
long int& totalOut )
361 void ConnectionBOSH::handleReceivedData(
const ConnectionBase* ,
362 const std::string& data )
366 std::string::size_type headerLength = 0;
367 while( ( headerLength = m_buffer.find(
"\r\n\r\n" ) ) != std::string::npos )
369 m_bufferHeader = m_buffer.substr( 0, headerLength + 2 );
371 const std::string& statusCode = m_bufferHeader.substr( 9, 3 );
372 if( statusCode !=
"200" )
375 "Received error via legacy HTTP status code: " + statusCode
376 +
". Disconnecting." );
381 m_bufferContentLength = strtol( getHTTPField(
"Content-Length" ).c_str(), 0, 10 );
382 if( !m_bufferContentLength )
385 if( m_connMode !=
ModeLegacyHTTP && ( getHTTPField(
"Connection" ) ==
"close"
386 || m_bufferHeader.substr( 0, 8 ) ==
"HTTP/1.0" ) )
389 "Server indicated lack of support for HTTP/1.1 - falling back to HTTP/1.0" );
393 if( m_buffer.length() >= ( headerLength + 4 + m_bufferContentLength ) )
397 std::string xml = m_buffer.substr( headerLength + 4, m_bufferContentLength );
398 m_parser.
feed( xml );
399 m_buffer.erase( 0, headerLength + 4 + m_bufferContentLength );
400 m_bufferContentLength = 0;
411 void ConnectionBOSH::handleConnect(
const ConnectionBase* )
415 m_rid = rand() % 100000 + 1728679472;
417 Tag requestBody(
"body" );
419 requestBody.setXmlns( XMLNS_XMPP_BOSH,
"xmpp" );
421 requestBody.addAttribute(
"content",
"text/xml; charset=utf-8" );
422 requestBody.addAttribute(
"hold", (
long)m_hold );
423 requestBody.addAttribute(
"rid", (
long)m_rid );
424 requestBody.addAttribute(
"ver",
"1.6" );
425 requestBody.addAttribute(
"wait", (
long)m_wait );
426 requestBody.addAttribute(
"ack", 0 );
427 requestBody.addAttribute(
"secure",
"false" );
428 requestBody.addAttribute(
"route",
"xmpp:" +
m_server +
":5222" );
429 requestBody.addAttribute(
"xml:lang",
"en" );
430 requestBody.addAttribute(
"xmpp:version",
"1.0" );
431 requestBody.addAttribute(
"to",
m_server );
434 sendRequest( requestBody.xml() );
438 void ConnectionBOSH::handleDisconnect(
const ConnectionBase* ,
453 "connection closed - falling back to HTTP/1.0 connection method" );
463 void ConnectionBOSH::handleTag( Tag* tag )
465 if( !
m_handler || tag->name() !=
"body" )
468 if( m_streamRestart )
470 m_streamRestart =
false;
473 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams'"
476 + m_sid +
"' xml:lang='en'>" );
479 if( tag->hasAttribute(
"sid" ) )
482 m_sid = tag->findAttribute(
"sid" );
484 if( tag->hasAttribute(
"requests" ) )
486 const int serverRequests = atoi( tag->findAttribute(
"requests" ).c_str() );
487 if( serverRequests < m_maxOpenRequests )
489 m_maxOpenRequests = serverRequests;
491 "bosh parameter 'requests' now set to " + tag->findAttribute(
"requests" ) );
494 if( tag->hasAttribute(
"hold" ) )
496 const int maxHold = atoi( tag->findAttribute(
"hold" ).c_str() );
497 if( maxHold < m_hold )
501 "bosh parameter 'hold' now set to " + tag->findAttribute(
"hold" ) );
504 if( tag->hasAttribute(
"wait" ) )
506 const int maxWait = atoi( tag->findAttribute(
"wait" ).c_str() );
507 if( maxWait < m_wait )
511 "bosh parameter 'wait' now set to " + tag->findAttribute(
"wait" )
515 if( tag->hasAttribute(
"polling" ) )
517 const int minTime = atoi( tag->findAttribute(
"polling" ).c_str() );
518 m_minTimePerRequest = minTime;
520 "bosh parameter 'polling' now set to " + tag->findAttribute(
"polling" )
530 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
533 +
"' from='" +
m_server +
"' id ='" + m_sid +
"' xml:lang='en'>" );
536 if( tag->findAttribute(
"type" ) ==
"terminate" )
539 "bosh connection closed by server: " + tag->findAttribute(
"condition" ) );
545 const TagList& stanzas = tag->children();
546 TagList::const_iterator it = stanzas.begin();
547 for( ; it != stanzas.end(); ++it )
551 ConnectionBase* ConnectionBOSH::getConnection()
553 if( m_openRequests > 0 && m_openRequests >= m_maxOpenRequests )
556 "Too many requests already open. Cannot send." );
564 if( !m_activeConnections.empty() )
567 return m_activeConnections.front();
569 else if( !m_connectionPool.empty() )
572 "Pipelining selected, but no connection open. Opening one." );
573 return activateConnection();
577 "No available connections to pipeline on." );
582 if( !m_connectionPool.empty() )
585 "using connection from pool." );
586 return activateConnection();
588 else if( !m_activeConnections.empty() )
591 conn = m_activeConnections.front()->newInstance();
592 conn->registerConnectionDataHandler(
this );
593 m_connectionPool.push_back( conn );
598 "No available connections to send on." );
605 ConnectionBase* ConnectionBOSH::activateConnection()
608 m_connectionPool.pop_front();
611 m_activeConnections.push_back( conn );
616 m_connectionPool.push_back( conn );
621 void ConnectionBOSH::putConnection()
631 m_activeConnections.pop_front();
632 m_connectionPool.push_back( conn );
636 m_activeConnections.pop_front();
637 m_connectionPool.push_back( conn );