2007-12-06 00:58:28 +00:00
/ *
2015-11-06 19:40:28 +01:00
* Copyright ( c ) 2006 - 2016 , openmetaverse . co
2007-12-06 00:58:28 +00:00
* All rights reserved .
*
* - Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* - Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
2015-11-06 19:00:05 +01:00
* - Neither the name of the openmetaverse . co nor the names
2007-12-06 00:58:28 +00:00
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR
* CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS
* INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
* CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE .
* /
2022-04-14 16:39:52 +01:00
using System ;
2022-04-14 16:29:40 +01:00
using System.Collections ;
2007-12-06 00:58:28 +00:00
using System.Net ;
2008-04-22 18:59:39 +00:00
using System.Threading ;
2008-07-21 21:12:59 +00:00
using OpenMetaverse.StructuredData ;
2007-12-06 00:58:28 +00:00
2008-12-29 20:44:28 +00:00
namespace OpenMetaverse.Http
2007-12-06 00:58:28 +00:00
{
public class EventQueueClient
{
2019-10-20 20:57:44 -05:00
private const string REQUEST_CONTENT_TYPE = "application/llsd+xml" ;
/// <summary>Viewer defauls to 30 for main grid, 60 for others</summary>
public const int REQUEST_TIMEOUT = 60 * 1000 ;
/// <summary>For exponential backoff on error.</summary>
public const int REQUEST_BACKOFF_SECONDS = 15 * 1000 ; // 15 seconds start
public const int REQUEST_BACKOFF_SECONDS_INC = 5 * 1000 ; // 5 seconds increase
public const int REQUEST_BACKOFF_SECONDS_MAX = 5 * 60 * 1000 ; // 5 minutes
2009-05-01 06:04:32 +00:00
2007-12-06 00:58:28 +00:00
public delegate void ConnectedCallback ( ) ;
2008-10-30 01:50:59 +00:00
public delegate void EventCallback ( string eventName , OSDMap body ) ;
2007-12-06 00:58:28 +00:00
public ConnectedCallback OnConnected ;
public EventCallback OnEvent ;
2019-08-11 10:08:35 -05:00
public bool Running = > _Running ;
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
protected Uri _Address ;
2007-12-06 00:58:28 +00:00
protected bool _Dead ;
protected bool _Running ;
2009-05-01 06:04:32 +00:00
protected HttpWebRequest _Request ;
2007-12-06 00:58:28 +00:00
2009-06-15 17:49:04 +00:00
/// <summary>Number of times we've received an unknown CAPS exception in series.</summary>
private int _errorCount ;
2007-12-06 00:58:28 +00:00
public EventQueueClient ( Uri eventQueueLocation )
{
2009-05-01 06:04:32 +00:00
_Address = eventQueueLocation ;
2007-12-06 00:58:28 +00:00
}
public void Start ( )
{
_Dead = false ;
2009-05-01 06:04:32 +00:00
// Create an EventQueueGet request
2019-08-11 10:08:35 -05:00
OSDMap request = new OSDMap { [ "ack" ] = new OSD ( ) , [ "done" ] = OSD . FromBoolean ( false ) } ;
2009-05-01 06:04:32 +00:00
byte [ ] postData = OSDParser . SerializeLLSDXmlBytes ( request ) ;
2021-09-24 11:26:29 -05:00
_Request = CapsBase . PostDataAsync ( _Address , null , REQUEST_CONTENT_TYPE , postData , REQUEST_TIMEOUT , OpenWriteHandler , null , RequestCompletedHandler ) ;
2007-12-06 00:58:28 +00:00
}
public void Stop ( bool immediate )
{
_Dead = true ;
if ( immediate )
_Running = false ;
2019-08-11 10:08:35 -05:00
_Request ? . Abort ( ) ;
2007-12-06 00:58:28 +00:00
}
2009-05-01 06:04:32 +00:00
void OpenWriteHandler ( HttpWebRequest request )
2007-12-06 00:58:28 +00:00
{
2009-05-01 06:04:32 +00:00
_Running = true ;
_Request = request ;
2007-12-06 00:58:28 +00:00
2010-04-01 01:01:01 +00:00
Logger . DebugLog ( "Capabilities event queue connected" ) ;
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
// The event queue is starting up for the first time
if ( OnConnected ! = null )
{
try { OnConnected ( ) ; }
2010-04-01 01:01:01 +00:00
catch ( Exception ex ) { Logger . Log ( ex . Message , Helpers . LogLevel . Error , ex ) ; }
2007-12-06 00:58:28 +00:00
}
2009-05-01 06:04:32 +00:00
}
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
void RequestCompletedHandler ( HttpWebRequest request , HttpWebResponse response , byte [ ] responseData , Exception error )
{
// We don't care about this request now that it has completed
_Request = null ;
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
OSDArray events = null ;
int ack = 0 ;
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
if ( responseData ! = null )
2007-12-06 00:58:28 +00:00
{
2009-06-15 17:49:04 +00:00
_errorCount = 0 ;
2009-05-01 06:04:32 +00:00
// Got a response
2018-01-28 13:08:15 -06:00
if ( OSDParser . DeserializeLLSDXml ( responseData ) is OSDMap result )
2007-12-06 00:58:28 +00:00
{
2009-05-01 06:04:32 +00:00
events = result [ "events" ] as OSDArray ;
ack = result [ "id" ] . AsInteger ( ) ;
}
else
{
2010-04-01 01:01:01 +00:00
Logger . Log ( "Got an unparseable response from the event queue: \"" +
System . Text . Encoding . UTF8 . GetString ( responseData ) + "\"" , Helpers . LogLevel . Warning ) ;
2007-12-06 00:58:28 +00:00
}
}
2009-05-01 06:04:32 +00:00
else if ( error ! = null )
2007-12-06 00:58:28 +00:00
{
2009-05-01 06:04:32 +00:00
#region Error handling
2008-08-12 22:38:02 +00:00
HttpStatusCode code = HttpStatusCode . OK ;
2009-05-01 06:04:32 +00:00
2018-01-28 13:08:15 -06:00
if ( error is WebException webException )
2009-05-01 06:04:32 +00:00
{
2022-04-13 01:23:22 +01:00
// Filter out some of the status requests to skip handling
2019-10-20 20:57:44 -05:00
switch ( webException . Status )
{
case WebExceptionStatus . RequestCanceled :
case WebExceptionStatus . KeepAliveFailure :
goto HandlingDone ;
}
2009-05-01 06:04:32 +00:00
if ( webException . Response ! = null )
code = ( ( HttpWebResponse ) webException . Response ) . StatusCode ;
}
2018-01-28 13:08:15 -06:00
switch ( code )
2007-12-06 00:58:28 +00:00
{
2018-01-28 13:08:15 -06:00
case HttpStatusCode . NotFound :
case HttpStatusCode . Gone :
Logger . Log ( $"Closing event queue at {_Address} due to missing caps URI" , Helpers . LogLevel . Info ) ;
_Running = false ;
_Dead = true ;
break ;
2022-04-14 16:39:52 +01:00
case ( HttpStatusCode ) 499 : // weird error returned occasionally, ignore for now
// I believe this is the timeout error invented by LL for LSL HTTP-out requests (gwyneth 20220413)
Logger . Log ( $"Possible HTTP-out timeout error from {_Address}, no need to continue" , Helpers . LogLevel . Debug ) ;
_Running = false ;
_Dead = true ;
break ;
case HttpStatusCode . InternalServerError :
// As per LL's instructions, we ought to consider this a
// 'request to close client' (gwyneth 20220413)
Logger . Log ( $"Grid sent a {code} at {_Address}, closing connection" , Helpers . LogLevel . Debug ) ;
// ... but do we happen to have an InnerException? Log it!
if ( error . InnerException ! = null )
{
// unravel the whole inner error message, so we finally figure out what it is!
// (gwyneth 20220414)
Logger . Log ( $"Unrecognized internal caps exception from {_Address}: '{error.InnerException.Message}'" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nMessage ---\n{error.Message}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nHelpLink ---\n{ex.HelpLink}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nSource ---\n{error.Source}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nStackTrace ---\n{error.StackTrace}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nTargetSite ---\n{error.TargetSite}" , Helpers . LogLevel . Warning ) ;
if ( error . Data . Count > 0 )
{
Logger . Log ( " Extra details:" , Helpers . LogLevel . Warning ) ;
foreach ( DictionaryEntry de in error . Data )
2022-04-18 21:30:49 +01:00
Logger . Log ( String . Format ( " Key: {0,-20} Value: '{1}'" ,
de . Key , de . Value ) ,
2022-04-14 16:39:52 +01:00
Helpers . LogLevel . Warning ) ;
}
// but we'll nevertheless close this connection (gwyneth 20220414)
}
_Running = false ;
_Dead = true ;
break ;
2018-01-28 13:08:15 -06:00
case HttpStatusCode . BadGateway :
// This is not good (server) protocol design, but it's normal.
// The EventQueue server is a proxy that connects to a Squid
// cache which will time out periodically. The EventQueue server
// interprets this as a generic error and returns a 502 to us
2022-04-14 16:39:52 +01:00
// that we ignore
//
// Note: if this condition persists, it _might_ be the grid trying to request
// that the client closes the connection, as per LL's specs (gwyneth 20220414)
Logger . Log ( $"Grid sent a Bad Gateway Error at {_Address}; probably a time-out from the grid's EventQueue server (normal) -- ignoring and continuing" , Helpers . LogLevel . Debug ) ;
2018-01-28 13:08:15 -06:00
break ;
default :
+ + _errorCount ;
// Try to log a meaningful error message
if ( code ! = HttpStatusCode . OK )
{
2022-04-13 01:23:22 +01:00
Logger . Log ( $"Unrecognized caps connection problem from {_Address}: {code}" ,
2018-01-28 13:08:15 -06:00
Helpers . LogLevel . Warning ) ;
}
else if ( error . InnerException ! = null )
2022-04-14 16:39:52 +01:00
{
// see comment above (gwyneth 20220414)
Logger . Log ( $"Unrecognized internal caps exception from {_Address}: '{error.InnerException.Message}'" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nMessage ---\n{error.Message}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nHelpLink ---\n{ex.HelpLink}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nSource ---\n{error.Source}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nStackTrace ---\n{error.StackTrace}" , Helpers . LogLevel . Warning ) ;
Logger . Log ( "\nTargetSite ---\n{error.TargetSite}" , Helpers . LogLevel . Warning ) ;
if ( error . Data . Count > 0 )
{
Logger . Log ( " Extra details:" , Helpers . LogLevel . Warning ) ;
foreach ( DictionaryEntry de in error . Data )
Logger . Log ( String . Format ( " Key: {0,-20} Value: {1}" ,
2022-04-15 18:22:05 +01:00
"'" + de . Key + "'" , de . Value ) ,
2022-04-14 16:39:52 +01:00
Helpers . LogLevel . Warning ) ;
}
2018-01-28 13:08:15 -06:00
}
else
{
Logger . Log ( $"Unrecognized caps exception from {_Address}: {error.Message}" ,
Helpers . LogLevel . Warning ) ;
}
break ;
2022-04-14 16:29:40 +01:00
} // end switch
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
#endregion Error handling
2007-12-06 00:58:28 +00:00
}
2009-05-01 06:04:32 +00:00
else
2007-12-21 06:25:13 +00:00
{
2009-06-15 17:49:04 +00:00
+ + _errorCount ;
2010-04-01 01:01:01 +00:00
Logger . Log ( "No response from the event queue but no reported error either" , Helpers . LogLevel . Warning ) ;
2007-12-21 06:25:13 +00:00
}
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
HandlingDone :
#region Resume the connection
2007-12-06 00:58:28 +00:00
if ( _Running )
{
2009-05-01 06:04:32 +00:00
OSDMap osdRequest = new OSDMap ( ) ;
if ( ack ! = 0 ) osdRequest [ "ack" ] = OSD . FromInteger ( ack ) ;
else osdRequest [ "ack" ] = new OSD ( ) ;
osdRequest [ "done" ] = OSD . FromBoolean ( _Dead ) ;
2007-12-06 00:58:28 +00:00
2009-05-01 06:04:32 +00:00
byte [ ] postData = OSDParser . SerializeLLSDXmlBytes ( osdRequest ) ;
2007-12-06 00:58:28 +00:00
2009-06-15 17:49:04 +00:00
if ( _errorCount > 0 ) // Exponentially back off, so we don't hammer the CPU
2019-10-20 20:57:44 -05:00
Thread . Sleep ( Math . Min ( REQUEST_BACKOFF_SECONDS + _errorCount * REQUEST_BACKOFF_SECONDS_INC , REQUEST_BACKOFF_SECONDS_MAX ) ) ;
2009-06-15 17:49:04 +00:00
2009-05-01 06:04:32 +00:00
// Resume the connection. The event handler for the connection opening
// just sets class _Request variable to the current HttpWebRequest
2021-09-24 11:26:29 -05:00
CapsBase . PostDataAsync ( _Address , null , REQUEST_CONTENT_TYPE , postData , REQUEST_TIMEOUT ,
2009-05-01 06:04:32 +00:00
delegate ( HttpWebRequest newRequest ) { _Request = newRequest ; } , null , RequestCompletedHandler ) ;
2007-12-06 00:58:28 +00:00
// If the event queue is dead at this point, turn it off since
// that was the last thing we want to do
if ( _Dead )
{
_Running = false ;
2010-04-01 01:01:01 +00:00
Logger . DebugLog ( "Sent event queue shutdown message" ) ;
2007-12-06 00:58:28 +00:00
}
}
2009-05-01 06:04:32 +00:00
#endregion Resume the connection
#region Handle incoming events
2019-06-08 17:58:54 -05:00
if ( OnEvent = = null | | events = = null | | events . Count < = 0 ) return ;
// Fire callbacks for each event received
foreach ( var osd in events )
2007-12-06 00:58:28 +00:00
{
2019-06-08 17:58:54 -05:00
var evt = ( OSDMap ) osd ;
string msg = evt [ "message" ] . AsString ( ) ;
OSDMap body = ( OSDMap ) evt [ "body" ] ;
2007-12-06 00:58:28 +00:00
2019-06-08 17:58:54 -05:00
try { OnEvent ( msg , body ) ; }
catch ( Exception ex ) { Logger . Log ( ex . Message , Helpers . LogLevel . Error , ex ) ; }
2007-12-06 00:58:28 +00:00
}
2009-05-01 06:04:32 +00:00
#endregion Handle incoming events
}
2007-12-06 00:58:28 +00:00
}
}