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;
279 request +=
" HTTP/1.0\r\n";
280 request +=
"Connection: close\r\n";
283 request +=
" HTTP/1.1\r\n";
285 request +=
"Host: " + m_boshedHost +
"\r\n";
286 request +=
"Content-Type: text/xml; charset=utf-8\r\n";
287 request +=
"Content-Length: " + util::int2string( xml.length() ) +
"\r\n";
288 request +=
"User-Agent: gloox/" +
GLOOX_VERSION +
"\r\n\r\n";
292 if( conn->send( request ) )
294 m_lastRequestTime = time( 0 );
304 bool ci_equal(
char ch1,
char ch2 )
306 return std::toupper( (
unsigned char)ch1 ) == std::toupper( (
unsigned char)ch2 );
309 std::string::size_type ci_find(
const std::string& str1,
const std::string& str2 )
311 std::string::const_iterator pos = std::search( str1.begin(), str1.end(),
312 str2.begin(), str2.end(), ci_equal );
313 if( pos == str1.end() )
314 return std::string::npos;
316 return std::distance( str1.begin(), pos );
319 const std::string ConnectionBOSH::getHTTPField(
const std::string& field )
321 std::string::size_type fp = ci_find( m_bufferHeader,
"\r\n" + field +
": " );
323 if( fp == std::string::npos )
326 fp += field.length() + 4;
328 const std::string::size_type fp2 = m_bufferHeader.find(
"\r\n", fp );
329 if( fp2 == std::string::npos )
332 return m_bufferHeader.substr( fp, fp2 - fp );
343 void ConnectionBOSH::cleanup()
351 void ConnectionBOSH::getStatistics(
long int& totalIn,
long int& totalOut )
357 void ConnectionBOSH::handleReceivedData(
const ConnectionBase* ,
358 const std::string& data )
361 std::string::size_type headerLength = 0;
362 while( ( headerLength = m_buffer.find(
"\r\n\r\n" ) ) != std::string::npos )
364 m_bufferHeader = m_buffer.substr( 0, headerLength+2 );
366 const std::string& statusCode = m_bufferHeader.substr( 9, 3 );
367 if( statusCode !=
"200" )
370 "Received error via legacy HTTP status code: " + statusCode
371 +
". Disconnecting." );
376 m_bufferContentLength = atol( getHTTPField(
"Content-Length" ).c_str() );
377 if( !m_bufferContentLength )
380 if( m_connMode !=
ModeLegacyHTTP && ( getHTTPField(
"Connection" ) ==
"close"
381 || m_bufferHeader.substr( 0, 8 ) ==
"HTTP/1.0" ) )
384 "Server indicated lack of support for HTTP/1.1 - falling back to HTTP/1.0" );
388 if( m_buffer.length() >= ( headerLength + 4 + m_bufferContentLength ) )
392 std::string xml = m_buffer.substr( headerLength + 4, m_bufferContentLength );
393 m_parser.
feed( xml );
394 m_buffer.erase( 0, headerLength + 4 + m_bufferContentLength );
395 m_bufferContentLength = 0;
406 void ConnectionBOSH::handleConnect(
const ConnectionBase* )
410 m_rid = rand() % 100000 + 1728679472;
412 Tag requestBody(
"body" );
414 requestBody.setXmlns( XMLNS_XMPP_BOSH,
"xmpp" );
416 requestBody.addAttribute(
"content",
"text/xml; charset=utf-8" );
417 requestBody.addAttribute(
"hold", (
long)m_hold );
418 requestBody.addAttribute(
"rid", (
long)m_rid );
419 requestBody.addAttribute(
"ver",
"1.6" );
420 requestBody.addAttribute(
"wait", (
long)m_wait );
421 requestBody.addAttribute(
"ack", 0 );
422 requestBody.addAttribute(
"secure",
"false" );
423 requestBody.addAttribute(
"route",
"xmpp:" +
m_server +
":5222" );
424 requestBody.addAttribute(
"xml:lang",
"en" );
425 requestBody.addAttribute(
"xmpp:version",
"1.0" );
426 requestBody.addAttribute(
"to",
m_server );
429 sendRequest( requestBody.xml() );
433 void ConnectionBOSH::handleDisconnect(
const ConnectionBase* ,
448 "connection closed - falling back to HTTP/1.0 connection method" );
458 void ConnectionBOSH::handleTag( Tag* tag )
460 if( !
m_handler || tag->name() !=
"body" )
463 if( m_streamRestart )
465 m_streamRestart =
false;
468 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams'"
471 + m_sid +
"' xml:lang='en'>" );
474 if( tag->hasAttribute(
"sid" ) )
477 m_sid = tag->findAttribute(
"sid" );
479 if( tag->hasAttribute(
"requests" ) )
481 const int serverRequests = atoi( tag->findAttribute(
"requests" ).c_str() );
482 if( serverRequests < m_maxOpenRequests )
484 m_maxOpenRequests = serverRequests;
486 "bosh parameter 'requests' now set to " + tag->findAttribute(
"requests" ) );
489 if( tag->hasAttribute(
"hold" ) )
491 const int maxHold = atoi( tag->findAttribute(
"hold" ).c_str() );
492 if( maxHold < m_hold )
496 "bosh parameter 'hold' now set to " + tag->findAttribute(
"hold" ) );
499 if( tag->hasAttribute(
"wait" ) )
501 const int maxWait = atoi( tag->findAttribute(
"wait" ).c_str() );
502 if( maxWait < m_wait )
506 "bosh parameter 'wait' now set to " + tag->findAttribute(
"wait" )
510 if( tag->hasAttribute(
"polling" ) )
512 const int minTime = atoi( tag->findAttribute(
"polling" ).c_str() );
513 m_minTimePerRequest = minTime;
515 "bosh parameter 'polling' now set to " + tag->findAttribute(
"polling" )
525 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
528 +
"' from='" +
m_server +
"' id ='" + m_sid +
"' xml:lang='en'>" );
531 if( tag->findAttribute(
"type" ) ==
"terminate" )
534 "bosh connection closed by server: " + tag->findAttribute(
"condition" ) );
540 const TagList& stanzas = tag->children();
541 TagList::const_iterator it = stanzas.begin();
542 for( ; it != stanzas.end(); ++it )
546 ConnectionBase* ConnectionBOSH::getConnection()
548 if( m_openRequests > 0 && m_openRequests >= m_maxOpenRequests )
551 "Too many requests already open. Cannot send." );
559 if( !m_activeConnections.empty() )
562 return m_activeConnections.front();
564 else if( !m_connectionPool.empty() )
567 "Pipelining selected, but no connection open. Opening one." );
568 return activateConnection();
572 "No available connections to pipeline on." );
577 if( !m_connectionPool.empty() )
580 "using connection from pool." );
581 return activateConnection();
583 else if( !m_activeConnections.empty() )
586 conn = m_activeConnections.front()->newInstance();
587 conn->registerConnectionDataHandler(
this );
588 m_connectionPool.push_back( conn );
593 "No available connections to send on." );
600 ConnectionBase* ConnectionBOSH::activateConnection()
603 m_connectionPool.pop_front();
606 m_activeConnections.push_back( conn );
611 m_connectionPool.push_back( conn );
616 void ConnectionBOSH::putConnection()
626 m_activeConnections.pop_front();
627 m_connectionPool.push_back( conn );
631 m_activeConnections.pop_front();
632 m_connectionPool.push_back( conn );