Add regression test for http inventory fetch.

Involved some restructuring to allow regression tests to dequeue inventory requests and perform poll responses synchronously rather than async
0.8.0.3
Justin Clark-Casey (justincc) 2014-03-17 20:51:35 +00:00
parent 873eee5431
commit f3e177814a
11 changed files with 784 additions and 407 deletions

View File

@ -50,8 +50,7 @@ namespace OpenSim.Framework.Capabilities
public class Caps public class Caps
{ {
// private static readonly ILog m_log = // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private string m_httpListenerHostName; private string m_httpListenerHostName;
private uint m_httpListenPort; private uint m_httpListenPort;
@ -152,6 +151,10 @@ namespace OpenSim.Framework.Capabilities
public void RegisterPollHandler(string capName, PollServiceEventArgs pollServiceHandler) public void RegisterPollHandler(string capName, PollServiceEventArgs pollServiceHandler)
{ {
// m_log.DebugFormat(
// "[CAPS]: Registering handler with name {0}, url {1} for {2}",
// capName, pollServiceHandler.Url, m_agentID, m_regionName);
m_pollServiceHandlers.Add(capName, pollServiceHandler); m_pollServiceHandlers.Add(capName, pollServiceHandler);
m_httpListener.AddPollServiceHTTPHandler(pollServiceHandler.Url, pollServiceHandler); m_httpListener.AddPollServiceHTTPHandler(pollServiceHandler.Url, pollServiceHandler);

View File

@ -113,7 +113,7 @@ namespace OpenSim.Framework.Servers.HttpServer
protected IPAddress m_listenIPAddress = IPAddress.Any; protected IPAddress m_listenIPAddress = IPAddress.Any;
private PollServiceRequestManager m_PollServiceManager; public PollServiceRequestManager PollServiceRequestManager { get; private set; }
public uint SSLPort public uint SSLPort
{ {
@ -374,7 +374,7 @@ namespace OpenSim.Framework.Servers.HttpServer
return true; return true;
} }
private void OnRequest(object source, RequestEventArgs args) public void OnRequest(object source, RequestEventArgs args)
{ {
RequestNumber++; RequestNumber++;
@ -429,7 +429,7 @@ namespace OpenSim.Framework.Servers.HttpServer
psEvArgs.Request(psreq.RequestID, keysvals); psEvArgs.Request(psreq.RequestID, keysvals);
} }
m_PollServiceManager.Enqueue(psreq); PollServiceRequestManager.Enqueue(psreq);
} }
else else
{ {
@ -1781,10 +1781,17 @@ namespace OpenSim.Framework.Servers.HttpServer
public void Start() public void Start()
{ {
StartHTTP(); Start(true);
} }
private void StartHTTP() /// <summary>
/// Start the http server
/// </summary>
/// <param name='processPollRequestsAsync'>
/// If true then poll responses are performed asynchronsly.
/// Option exists to allow regression tests to perform processing synchronously.
/// </param>
public void Start(bool performPollResponsesAsync)
{ {
m_log.InfoFormat( m_log.InfoFormat(
"[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port);
@ -1822,8 +1829,9 @@ namespace OpenSim.Framework.Servers.HttpServer
m_httpListener2.Start(64); m_httpListener2.Start(64);
// Long Poll Service Manager with 3 worker threads a 25 second timeout for no events // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events
m_PollServiceManager = new PollServiceRequestManager(this, 3, 25000); PollServiceRequestManager = new PollServiceRequestManager(this, performPollResponsesAsync, 3, 25000);
m_PollServiceManager.Start(); PollServiceRequestManager.Start();
HTTPDRunning = true; HTTPDRunning = true;
//HttpListenerContext context; //HttpListenerContext context;
@ -1892,7 +1900,7 @@ namespace OpenSim.Framework.Servers.HttpServer
try try
{ {
m_PollServiceManager.Stop(); PollServiceRequestManager.Stop();
m_httpListener2.ExceptionThrown -= httpServerException; m_httpListener2.ExceptionThrown -= httpServerException;
//m_httpListener2.DisconnectHandler = null; //m_httpListener2.DisconnectHandler = null;

View File

@ -44,6 +44,20 @@ namespace OpenSim.Framework.Servers.HttpServer
{ {
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Is the poll service request manager running?
/// </summary>
/// <remarks>
/// Can be running either synchronously or asynchronously
/// </remarks>
public bool IsRunning { get; private set; }
/// <summary>
/// Is the poll service performing responses asynchronously (with its own threads) or synchronously (via
/// external calls)?
/// </summary>
public bool PerformResponsesAsync { get; private set; }
private readonly BaseHttpServer m_server; private readonly BaseHttpServer m_server;
private BlockingQueue<PollServiceHttpRequest> m_requests = new BlockingQueue<PollServiceHttpRequest>(); private BlockingQueue<PollServiceHttpRequest> m_requests = new BlockingQueue<PollServiceHttpRequest>();
@ -52,48 +66,53 @@ namespace OpenSim.Framework.Servers.HttpServer
private uint m_WorkerThreadCount = 0; private uint m_WorkerThreadCount = 0;
private Thread[] m_workerThreads; private Thread[] m_workerThreads;
private bool m_running = true;
private SmartThreadPool m_threadPool = new SmartThreadPool(20000, 12, 2); private SmartThreadPool m_threadPool = new SmartThreadPool(20000, 12, 2);
// private int m_timeout = 1000; // increase timeout 250; now use the event one // private int m_timeout = 1000; // increase timeout 250; now use the event one
public PollServiceRequestManager(BaseHttpServer pSrv, uint pWorkerThreadCount, int pTimeout) public PollServiceRequestManager(
BaseHttpServer pSrv, bool performResponsesAsync, uint pWorkerThreadCount, int pTimeout)
{ {
m_server = pSrv; m_server = pSrv;
PerformResponsesAsync = performResponsesAsync;
m_WorkerThreadCount = pWorkerThreadCount; m_WorkerThreadCount = pWorkerThreadCount;
m_workerThreads = new Thread[m_WorkerThreadCount]; m_workerThreads = new Thread[m_WorkerThreadCount];
} }
public void Start() public void Start()
{ {
//startup worker threads IsRunning = true;
for (uint i = 0; i < m_WorkerThreadCount; i++)
{
m_workerThreads[i]
= Watchdog.StartThread(
PoolWorkerJob,
string.Format("PollServiceWorkerThread{0}:{1}", i, m_server.Port),
ThreadPriority.Normal,
false,
false,
null,
int.MaxValue);
}
Watchdog.StartThread( if (PerformResponsesAsync)
this.CheckLongPollThreads, {
string.Format("LongPollServiceWatcherThread:{0}", m_server.Port), //startup worker threads
ThreadPriority.Normal, for (uint i = 0; i < m_WorkerThreadCount; i++)
false, {
true, m_workerThreads[i]
null, = Watchdog.StartThread(
1000 * 60 * 10); PoolWorkerJob,
string.Format("PollServiceWorkerThread{0}:{1}", i, m_server.Port),
ThreadPriority.Normal,
false,
false,
null,
int.MaxValue);
}
Watchdog.StartThread(
this.CheckLongPollThreads,
string.Format("LongPollServiceWatcherThread:{0}", m_server.Port),
ThreadPriority.Normal,
false,
true,
null,
1000 * 60 * 10);
}
} }
private void ReQueueEvent(PollServiceHttpRequest req) private void ReQueueEvent(PollServiceHttpRequest req)
{ {
if (m_running) if (IsRunning)
{ {
// delay the enqueueing for 100ms. There's no need to have the event // delay the enqueueing for 100ms. There's no need to have the event
// actively on the queue // actively on the queue
@ -109,7 +128,7 @@ namespace OpenSim.Framework.Servers.HttpServer
public void Enqueue(PollServiceHttpRequest req) public void Enqueue(PollServiceHttpRequest req)
{ {
if (m_running) if (IsRunning)
{ {
if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll)
{ {
@ -129,7 +148,7 @@ namespace OpenSim.Framework.Servers.HttpServer
// All other types of tasks (Inventory handlers, http-in, etc) don't have the long-poll nature, // All other types of tasks (Inventory handlers, http-in, etc) don't have the long-poll nature,
// so if they aren't ready to be served by a worker thread (no events), they are placed // so if they aren't ready to be served by a worker thread (no events), they are placed
// directly back in the "ready-to-serve" queue by the worker thread. // directly back in the "ready-to-serve" queue by the worker thread.
while (m_running) while (IsRunning)
{ {
Thread.Sleep(500); Thread.Sleep(500);
Watchdog.UpdateThread(); Watchdog.UpdateThread();
@ -137,7 +156,7 @@ namespace OpenSim.Framework.Servers.HttpServer
// List<PollServiceHttpRequest> not_ready = new List<PollServiceHttpRequest>(); // List<PollServiceHttpRequest> not_ready = new List<PollServiceHttpRequest>();
lock (m_longPollRequests) lock (m_longPollRequests)
{ {
if (m_longPollRequests.Count > 0 && m_running) if (m_longPollRequests.Count > 0 && IsRunning)
{ {
List<PollServiceHttpRequest> ready = m_longPollRequests.FindAll(req => List<PollServiceHttpRequest> ready = m_longPollRequests.FindAll(req =>
(req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id) || // there are events in this EQ (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id) || // there are events in this EQ
@ -158,7 +177,7 @@ namespace OpenSim.Framework.Servers.HttpServer
public void Stop() public void Stop()
{ {
m_running = false; IsRunning = false;
// m_timeout = -10000; // cause all to expire // m_timeout = -10000; // cause all to expire
Thread.Sleep(1000); // let the world move Thread.Sleep(1000); // let the world move
@ -169,7 +188,7 @@ namespace OpenSim.Framework.Servers.HttpServer
lock (m_longPollRequests) lock (m_longPollRequests)
{ {
if (m_longPollRequests.Count > 0 && m_running) if (m_longPollRequests.Count > 0 && IsRunning)
m_longPollRequests.ForEach(req => m_requests.Enqueue(req)); m_longPollRequests.ForEach(req => m_requests.Enqueue(req));
} }
@ -194,68 +213,82 @@ namespace OpenSim.Framework.Servers.HttpServer
private void PoolWorkerJob() private void PoolWorkerJob()
{ {
while (m_running) while (IsRunning)
{ {
PollServiceHttpRequest req = m_requests.Dequeue(5000);
//m_log.WarnFormat("[YYY]: Dequeued {0}", (req == null ? "null" : req.PollServiceArgs.Type.ToString()));
Watchdog.UpdateThread(); Watchdog.UpdateThread();
if (req != null) WaitPerformResponse();
}
}
public void WaitPerformResponse()
{
PollServiceHttpRequest req = m_requests.Dequeue(5000);
// m_log.DebugFormat("[YYY]: Dequeued {0}", (req == null ? "null" : req.PollServiceArgs.Type.ToString()));
if (req != null)
{
try
{ {
try if (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id))
{ {
if (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id)) Hashtable responsedata = req.PollServiceArgs.GetEvents(req.RequestID, req.PollServiceArgs.Id);
if (responsedata == null)
return;
// This is the event queue.
// Even if we're not running we can still perform responses by explicit request.
if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll
|| !PerformResponsesAsync)
{ {
Hashtable responsedata = req.PollServiceArgs.GetEvents(req.RequestID, req.PollServiceArgs.Id); try
{
if (responsedata == null) req.DoHTTPGruntWork(m_server, responsedata);
continue; }
catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream
if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) // This is the event queue {
// Ignore it, no need to reply
m_log.Error(e);
}
}
else
{
m_threadPool.QueueWorkItem(x =>
{ {
try try
{ {
req.DoHTTPGruntWork(m_server, responsedata); req.DoHTTPGruntWork(m_server, responsedata);
} }
catch (ObjectDisposedException) // Browser aborted before we could read body, server closed the stream catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream
{ {
// Ignore it, no need to reply // Ignore it, no need to reply
m_log.Error(e);
} }
} catch (Exception e)
else
{
m_threadPool.QueueWorkItem(x =>
{ {
try m_log.Error(e);
{ }
req.DoHTTPGruntWork(m_server, responsedata);
}
catch (ObjectDisposedException) // Browser aborted before we could read body, server closed the stream
{
// Ignore it, no need to reply
}
return null; return null;
}, null); }, null);
} }
}
else
{
if ((Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms)
{
req.DoHTTPGruntWork(
m_server, req.PollServiceArgs.NoEvents(req.RequestID, req.PollServiceArgs.Id));
} }
else else
{ {
if ((Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms) ReQueueEvent(req);
{
req.DoHTTPGruntWork(
m_server, req.PollServiceArgs.NoEvents(req.RequestID, req.PollServiceArgs.Id));
}
else
{
ReQueueEvent(req);
}
} }
} }
catch (Exception e) }
{ catch (Exception e)
m_log.ErrorFormat("Exception in poll service thread: " + e.ToString()); {
} m_log.ErrorFormat("Exception in poll service thread: " + e.ToString());
} }
} }
} }

View File

@ -41,323 +41,7 @@ namespace OpenSim.Framework.Servers.Tests
{ {
[TestFixture] [TestFixture]
public class OSHttpTests : OpenSimTestCase public class OSHttpTests : OpenSimTestCase
{ {
// we need an IHttpClientContext for our tests
public class TestHttpClientContext: IHttpClientContext
{
private bool _secured;
public bool IsSecured
{
get { return _secured; }
}
public bool Secured
{
get { return _secured; }
}
public TestHttpClientContext(bool secured)
{
_secured = secured;
}
public void Disconnect(SocketError error) {}
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {}
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {}
public void Respond(string body) {}
public void Send(byte[] buffer) {}
public void Send(byte[] buffer, int offset, int size) {}
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {}
public void Close() { }
public bool EndWhenDone { get { return false;} set { return;}}
public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing()
{
return new HTTPNetworkContext();
}
public event EventHandler<DisconnectedEventArgs> Disconnected = delegate { };
/// <summary>
/// A request have been received in the context.
/// </summary>
public event EventHandler<RequestEventArgs> RequestReceived = delegate { };
}
public class TestHttpRequest: IHttpRequest
{
private string _uriPath;
public bool BodyIsComplete
{
get { return true; }
}
public string[] AcceptTypes
{
get {return _acceptTypes; }
}
private string[] _acceptTypes;
public Stream Body
{
get { return _body; }
set { _body = value;}
}
private Stream _body;
public ConnectionType Connection
{
get { return _connection; }
set { _connection = value; }
}
private ConnectionType _connection;
public int ContentLength
{
get { return _contentLength; }
set { _contentLength = value; }
}
private int _contentLength;
public NameValueCollection Headers
{
get { return _headers; }
}
private NameValueCollection _headers = new NameValueCollection();
public string HttpVersion
{
get { return _httpVersion; }
set { _httpVersion = value; }
}
private string _httpVersion = null;
public string Method
{
get { return _method; }
set { _method = value; }
}
private string _method = null;
public HttpInput QueryString
{
get { return _queryString; }
}
private HttpInput _queryString = null;
public Uri Uri
{
get { return _uri; }
set { _uri = value; }
}
private Uri _uri = null;
public string[] UriParts
{
get { return _uri.Segments; }
}
public HttpParam Param
{
get { return null; }
}
public HttpForm Form
{
get { return null; }
}
public bool IsAjax
{
get { return false; }
}
public RequestCookies Cookies
{
get { return null; }
}
public TestHttpRequest() {}
public TestHttpRequest(string contentEncoding, string contentType, string userAgent,
string remoteAddr, string remotePort, string[] acceptTypes,
ConnectionType connectionType, int contentLength, Uri uri)
{
_headers["content-encoding"] = contentEncoding;
_headers["content-type"] = contentType;
_headers["user-agent"] = userAgent;
_headers["remote_addr"] = remoteAddr;
_headers["remote_port"] = remotePort;
_acceptTypes = acceptTypes;
_connection = connectionType;
_contentLength = contentLength;
_uri = uri;
}
public void DecodeBody(FormDecoderProvider providers) {}
public void SetCookies(RequestCookies cookies) {}
public void AddHeader(string name, string value)
{
_headers.Add(name, value);
}
public int AddToBody(byte[] bytes, int offset, int length)
{
return 0;
}
public void Clear() {}
public object Clone()
{
TestHttpRequest clone = new TestHttpRequest();
clone._acceptTypes = _acceptTypes;
clone._connection = _connection;
clone._contentLength = _contentLength;
clone._uri = _uri;
clone._headers = new NameValueCollection(_headers);
return clone;
}
public IHttpResponse CreateResponse(IHttpClientContext context)
{
return new HttpResponse(context, this);
}
/// <summary>
/// Path and query (will be merged with the host header) and put in Uri
/// </summary>
/// <see cref="Uri"/>
public string UriPath
{
get { return _uriPath; }
set
{
_uriPath = value;
}
}
}
public class TestHttpResponse: IHttpResponse
{
public Stream Body
{
get { return _body; }
set { _body = value; }
}
private Stream _body;
public string ProtocolVersion
{
get { return _protocolVersion; }
set { _protocolVersion = value; }
}
private string _protocolVersion;
public bool Chunked
{
get { return _chunked; }
set { _chunked = value; }
}
private bool _chunked;
public ConnectionType Connection
{
get { return _connection; }
set { _connection = value; }
}
private ConnectionType _connection;
public Encoding Encoding
{
get { return _encoding; }
set { _encoding = value; }
}
private Encoding _encoding;
public int KeepAlive
{
get { return _keepAlive; }
set { _keepAlive = value; }
}
private int _keepAlive;
public HttpStatusCode Status
{
get { return _status; }
set { _status = value; }
}
private HttpStatusCode _status;
public string Reason
{
get { return _reason; }
set { _reason = value; }
}
private string _reason;
public long ContentLength
{
get { return _contentLength; }
set { _contentLength = value; }
}
private long _contentLength;
public string ContentType
{
get { return _contentType; }
set { _contentType = value; }
}
private string _contentType;
public bool HeadersSent
{
get { return _headersSent; }
}
private bool _headersSent;
public bool Sent
{
get { return _sent; }
}
private bool _sent;
public ResponseCookies Cookies
{
get { return _cookies; }
}
private ResponseCookies _cookies = null;
public TestHttpResponse()
{
_headersSent = false;
_sent = false;
}
public void AddHeader(string name, string value) {}
public void Send()
{
if (!_headersSent) SendHeaders();
if (_sent) throw new InvalidOperationException("stuff already sent");
_sent = true;
}
public void SendBody(byte[] buffer, int offset, int count)
{
if (!_headersSent) SendHeaders();
_sent = true;
}
public void SendBody(byte[] buffer)
{
if (!_headersSent) SendHeaders();
_sent = true;
}
public void SendHeaders()
{
if (_headersSent) throw new InvalidOperationException("headers already sent");
_headersSent = true;
}
public void Redirect(Uri uri) {}
public void Redirect(string url) {}
}
public OSHttpRequest req0; public OSHttpRequest req0;
public OSHttpRequest req1; public OSHttpRequest req1;
@ -429,4 +113,4 @@ namespace OpenSim.Framework.Servers.Tests
Assert.That(rsp0.ContentType, Is.EqualTo("text/xml")); Assert.That(rsp0.ContentType, Is.EqualTo("text/xml"));
} }
} }
} }

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names 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 DEVELOPERS ``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 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.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using HttpServer;
using log4net.Config;
using Nini.Config;
using NUnit.Framework;
using OpenMetaverse;
using OpenMetaverse.Packets;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Framework.Capabilities;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.ClientStack.Linden;
using OpenSim.Region.CoreModules.Framework;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
using OpenSim.Tests.Common;
using OpenSim.Tests.Common.Mock;
using OSDArray = OpenMetaverse.StructuredData.OSDArray;
using OSDMap = OpenMetaverse.StructuredData.OSDMap;
namespace OpenSim.Region.ClientStack.Linden.Caps.Tests
{
[TestFixture]
public class WebFetchInvDescModuleTests : OpenSimTestCase
{
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
// Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread.
Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest;
}
[TestFixtureTearDown]
public void TestFixureTearDown()
{
// We must set this back afterwards, otherwise later tests will fail since they're expecting multiple
// threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression
// tests really shouldn't).
Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod;
}
[SetUp]
public override void SetUp()
{
base.SetUp();
// This is an unfortunate bit of clean up we have to do because MainServer manages things through static
// variables and the VM is not restarted between tests.
uint port = 9999;
MainServer.RemoveHttpServer(port);
BaseHttpServer server = new BaseHttpServer(port, false, 0, "");
MainServer.AddHttpServer(server);
MainServer.Instance = server;
server.Start(false);
}
[Test]
public void TestInventoryDescendentsFetch()
{
TestHelpers.InMethod();
TestHelpers.EnableLogging();
BaseHttpServer httpServer = MainServer.Instance;
Scene scene = new SceneHelpers().SetupScene();
CapabilitiesModule capsModule = new CapabilitiesModule();
WebFetchInvDescModule wfidModule = new WebFetchInvDescModule(false);
IConfigSource config = new IniConfigSource();
config.AddConfig("ClientStack.LindenCaps");
config.Configs["ClientStack.LindenCaps"].Set("Cap_FetchInventoryDescendents2", "localhost");
SceneHelpers.SetupSceneModules(scene, config, capsModule, wfidModule);
UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1));
// We need a user present to have any capabilities set up
SceneHelpers.AddScenePresence(scene, ua.PrincipalID);
TestHttpRequest req = new TestHttpRequest();
OpenSim.Framework.Capabilities.Caps userCaps = capsModule.GetCapsForUser(ua.PrincipalID);
PollServiceEventArgs pseArgs;
userCaps.TryGetPollHandler("FetchInventoryDescendents2", out pseArgs);
req.UriPath = pseArgs.Url;
req.Uri = new Uri(req.UriPath);
// Retrieve root folder details directly so that we can request
InventoryFolderBase folder = scene.InventoryService.GetRootFolder(ua.PrincipalID);
OSDMap osdFolder = new OSDMap();
osdFolder["folder_id"] = folder.ID;
osdFolder["owner_id"] = ua.PrincipalID;
osdFolder["fetch_folders"] = true;
osdFolder["fetch_items"] = true;
osdFolder["sort_order"] = 0;
OSDArray osdFoldersArray = new OSDArray();
osdFoldersArray.Add(osdFolder);
OSDMap osdReqMap = new OSDMap();
osdReqMap["folders"] = osdFoldersArray;
req.Body = new MemoryStream(OSDParser.SerializeLLSDXmlBytes(osdReqMap));
TestHttpClientContext context = new TestHttpClientContext(false);
MainServer.Instance.OnRequest(context, new RequestEventArgs(req));
// Drive processing of the queued inventory request synchronously.
wfidModule.WaitProcessQueuedInventoryRequest();
MainServer.Instance.PollServiceRequestManager.WaitPerformResponse();
// System.Threading.Thread.Sleep(10000);
OSDMap responseOsd = (OSDMap)OSDParser.DeserializeLLSDXml(context.ResponseBody);
OSDArray foldersOsd = (OSDArray)responseOsd["folders"];
OSDMap folderOsd = (OSDMap)foldersOsd[0];
// A sanity check that the response has the expected number of descendents for a default inventory
// TODO: Need a more thorough check.
Assert.That((int)folderOsd["descendents"], Is.EqualTo(14));
}
}
}

View File

@ -65,6 +65,15 @@ namespace OpenSim.Region.ClientStack.Linden
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Control whether requests will be processed asynchronously.
/// </summary>
/// <remarks>
/// Defaults to true. Can currently not be changed once a region has been added to the module.
/// </remarks>
public bool ProcessQueuedRequestsAsync { get; private set; }
private Scene m_scene; private Scene m_scene;
private IInventoryService m_InventoryService; private IInventoryService m_InventoryService;
@ -84,6 +93,13 @@ namespace OpenSim.Region.ClientStack.Linden
#region ISharedRegionModule Members #region ISharedRegionModule Members
public WebFetchInvDescModule() : this(true) {}
public WebFetchInvDescModule(bool processQueuedResultsAsync)
{
ProcessQueuedRequestsAsync = processQueuedResultsAsync;
}
public void Initialise(IConfigSource source) public void Initialise(IConfigSource source)
{ {
IConfig config = source.Configs["ClientStack.LindenCaps"]; IConfig config = source.Configs["ClientStack.LindenCaps"];
@ -114,8 +130,16 @@ namespace OpenSim.Region.ClientStack.Linden
m_scene.EventManager.OnRegisterCaps -= RegisterCaps; m_scene.EventManager.OnRegisterCaps -= RegisterCaps;
foreach (Thread t in m_workerThreads) if (ProcessQueuedRequestsAsync)
Watchdog.AbortThread(t.ManagedThreadId); {
if (m_workerThreads != null)
{
foreach (Thread t in m_workerThreads)
Watchdog.AbortThread(t.ManagedThreadId);
m_workerThreads = null;
}
}
m_scene = null; m_scene = null;
} }
@ -133,7 +157,7 @@ namespace OpenSim.Region.ClientStack.Linden
m_scene.EventManager.OnRegisterCaps += RegisterCaps; m_scene.EventManager.OnRegisterCaps += RegisterCaps;
if (m_workerThreads == null) if (ProcessQueuedRequestsAsync && m_workerThreads == null)
{ {
m_workerThreads = new Thread[2]; m_workerThreads = new Thread[2];
@ -358,11 +382,16 @@ namespace OpenSim.Region.ClientStack.Linden
{ {
Watchdog.UpdateThread(); Watchdog.UpdateThread();
aPollRequest poolreq = m_queue.Dequeue(); WaitProcessQueuedInventoryRequest();
if (poolreq != null && poolreq.thepoll != null)
poolreq.thepoll.Process(poolreq);
} }
} }
public void WaitProcessQueuedInventoryRequest()
{
aPollRequest poolreq = m_queue.Dequeue();
if (poolreq != null && poolreq.thepoll != null)
poolreq.thepoll.Process(poolreq);
}
} }
} }

View File

@ -137,6 +137,10 @@ namespace OpenSim.Region.CoreModules.Framework
// agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath); // agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath);
} }
// m_log.DebugFormat(
// "[CAPS]: Adding capabilities for agent {0} in {1} with path {2}",
// agentId, m_scene.RegionInfo.RegionName, capsObjectPath);
caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName, caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName,
(MainServer.Instance == null) ? 0: MainServer.Instance.Port, (MainServer.Instance == null) ? 0: MainServer.Instance.Port,
capsObjectPath, agentId, m_scene.RegionInfo.RegionName); capsObjectPath, agentId, m_scene.RegionInfo.RegionName);

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names 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 DEVELOPERS ``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 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.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using HttpServer;
using OpenSim.Framework;
namespace OpenSim.Tests.Common
{
public class TestHttpClientContext: IHttpClientContext
{
/// <summary>
/// Bodies of responses from the server.
/// </summary>
public string ResponseBody
{
get { return Encoding.UTF8.GetString(m_responseStream.ToArray()); }
}
public Byte[] ResponseBodyBytes
{
get{ return m_responseStream.ToArray(); }
}
private MemoryStream m_responseStream = new MemoryStream();
public bool IsSecured { get; set; }
public bool Secured
{
get { return IsSecured; }
set { IsSecured = value; }
}
public TestHttpClientContext(bool secured)
{
Secured = secured;
}
public void Disconnect(SocketError error)
{
// Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error);
}
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");}
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");}
public void Respond(string body) { Console.WriteLine("xxx");}
public void Send(byte[] buffer)
{
// Getting header data here
// Console.WriteLine("xxxx: Got {0}", Encoding.UTF8.GetString(buffer));
}
public void Send(byte[] buffer, int offset, int size)
{
// Util.PrintCallStack();
//
// Console.WriteLine(
// "TestHttpClientContext.Send(byte[], int, int) got offset={0}, size={1}, buffer={2}",
// offset, size, Encoding.UTF8.GetString(buffer));
m_responseStream.Write(buffer, offset, size);
}
public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {Console.WriteLine("xxxxxx");}
public void Close() { }
public bool EndWhenDone { get { return false;} set { return;}}
public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing()
{
return new HTTPNetworkContext();
}
public event EventHandler<DisconnectedEventArgs> Disconnected = delegate { };
/// <summary>
/// A request have been received in the context.
/// </summary>
public event EventHandler<RequestEventArgs> RequestReceived = delegate { };
}
}

View File

@ -0,0 +1,174 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names 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 DEVELOPERS ``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 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.
*/
using System;
using System.Collections.Specialized;
using System.IO;
using HttpServer;
using HttpServer.FormDecoders;
namespace OpenSim.Tests.Common
{
public class TestHttpRequest: IHttpRequest
{
private string _uriPath;
public bool BodyIsComplete
{
get { return true; }
}
public string[] AcceptTypes
{
get {return _acceptTypes; }
}
private string[] _acceptTypes;
public Stream Body
{
get { return _body; }
set { _body = value;}
}
private Stream _body;
public ConnectionType Connection
{
get { return _connection; }
set { _connection = value; }
}
private ConnectionType _connection;
public int ContentLength
{
get { return _contentLength; }
set { _contentLength = value; }
}
private int _contentLength;
public NameValueCollection Headers
{
get { return _headers; }
}
private NameValueCollection _headers = new NameValueCollection();
public string HttpVersion { get; set; }
public string Method
{
get { return _method; }
set { _method = value; }
}
private string _method = null;
public HttpInput QueryString
{
get { return _queryString; }
}
private HttpInput _queryString = null;
public Uri Uri
{
get { return _uri; }
set { _uri = value; }
}
private Uri _uri = null;
public string[] UriParts
{
get { return _uri.Segments; }
}
public HttpParam Param
{
get { return null; }
}
public HttpForm Form
{
get { return null; }
}
public bool IsAjax
{
get { return false; }
}
public RequestCookies Cookies
{
get { return null; }
}
public TestHttpRequest()
{
HttpVersion = "HTTP/1.1";
}
public TestHttpRequest(string contentEncoding, string contentType, string userAgent,
string remoteAddr, string remotePort, string[] acceptTypes,
ConnectionType connectionType, int contentLength, Uri uri) : base()
{
_headers["content-encoding"] = contentEncoding;
_headers["content-type"] = contentType;
_headers["user-agent"] = userAgent;
_headers["remote_addr"] = remoteAddr;
_headers["remote_port"] = remotePort;
_acceptTypes = acceptTypes;
_connection = connectionType;
_contentLength = contentLength;
_uri = uri;
}
public void DecodeBody(FormDecoderProvider providers) {}
public void SetCookies(RequestCookies cookies) {}
public void AddHeader(string name, string value)
{
_headers.Add(name, value);
}
public int AddToBody(byte[] bytes, int offset, int length)
{
return 0;
}
public void Clear() {}
public object Clone()
{
TestHttpRequest clone = new TestHttpRequest();
clone._acceptTypes = _acceptTypes;
clone._connection = _connection;
clone._contentLength = _contentLength;
clone._uri = _uri;
clone._headers = new NameValueCollection(_headers);
return clone;
}
public IHttpResponse CreateResponse(IHttpClientContext context)
{
return new HttpResponse(context, this);
}
/// <summary>
/// Path and query (will be merged with the host header) and put in Uri
/// </summary>
/// <see cref="Uri"/>
public string UriPath
{
get { return _uriPath; }
set
{
_uriPath = value;
}
}
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names 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 DEVELOPERS ``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 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.
*/
using System;
using System.IO;
using System.Net;
using System.Text;
using HttpServer;
namespace OpenSim.Tests.Common
{
public class TestHttpResponse: IHttpResponse
{
public Stream Body
{
get { return _body; }
set { _body = value; }
}
private Stream _body;
public string ProtocolVersion
{
get { return _protocolVersion; }
set { _protocolVersion = value; }
}
private string _protocolVersion;
public bool Chunked
{
get { return _chunked; }
set { _chunked = value; }
}
private bool _chunked;
public ConnectionType Connection
{
get { return _connection; }
set { _connection = value; }
}
private ConnectionType _connection;
public Encoding Encoding
{
get { return _encoding; }
set { _encoding = value; }
}
private Encoding _encoding;
public int KeepAlive
{
get { return _keepAlive; }
set { _keepAlive = value; }
}
private int _keepAlive;
public HttpStatusCode Status
{
get { return _status; }
set { _status = value; }
}
private HttpStatusCode _status;
public string Reason
{
get { return _reason; }
set { _reason = value; }
}
private string _reason;
public long ContentLength
{
get { return _contentLength; }
set { _contentLength = value; }
}
private long _contentLength;
public string ContentType
{
get { return _contentType; }
set { _contentType = value; }
}
private string _contentType;
public bool HeadersSent
{
get { return _headersSent; }
}
private bool _headersSent;
public bool Sent
{
get { return _sent; }
}
private bool _sent;
public ResponseCookies Cookies
{
get { return _cookies; }
}
private ResponseCookies _cookies = null;
public TestHttpResponse()
{
_headersSent = false;
_sent = false;
}
public void AddHeader(string name, string value) {}
public void Send()
{
if (!_headersSent) SendHeaders();
if (_sent) throw new InvalidOperationException("stuff already sent");
_sent = true;
}
public void SendBody(byte[] buffer, int offset, int count)
{
if (!_headersSent) SendHeaders();
_sent = true;
}
public void SendBody(byte[] buffer)
{
if (!_headersSent) SendHeaders();
_sent = true;
}
public void SendHeaders()
{
if (_headersSent) throw new InvalidOperationException("headers already sent");
_headersSent = true;
}
public void Redirect(Uri uri) {}
public void Redirect(string url) {}
}
}

View File

@ -3321,12 +3321,14 @@
<ReferencePath>../../../../../bin/</ReferencePath> <ReferencePath>../../../../../bin/</ReferencePath>
<Reference name="System"/> <Reference name="System"/>
<Reference name="System.Xml"/> <Reference name="System.Xml"/>
<Reference name="HttpServer_OpenSim" path="../../../../../bin/"/>
<Reference name="log4net" path="../../../../../bin/"/> <Reference name="log4net" path="../../../../../bin/"/>
<Reference name="Nini" path="../../../../../bin/"/> <Reference name="Nini" path="../../../../../bin/"/>
<Reference name="nunit.framework" path="../../../../../bin/"/> <Reference name="nunit.framework" path="../../../../../bin/"/>
<Reference name="OpenMetaverse" path="../../../../../bin/"/> <Reference name="OpenMetaverse" path="../../../../../bin/"/>
<Reference name="OpenMetaverseTypes" path="../../../../../bin/"/> <Reference name="OpenMetaverseTypes" path="../../../../../bin/"/>
<Reference name="OpenMetaverse.StructuredData" path="../../../../../bin/"/> <Reference name="OpenMetaverse.StructuredData" path="../../../../../bin/"/>
<Reference name="OpenSim.Capabilities"/>
<Reference name="OpenSim.Framework"/> <Reference name="OpenSim.Framework"/>
<Reference name="OpenSim.Framework.Communications"/> <Reference name="OpenSim.Framework.Communications"/>
<Reference name="OpenSim.Framework.Monitoring"/> <Reference name="OpenSim.Framework.Monitoring"/>
@ -3341,6 +3343,7 @@
<Reference name="OpenSim.Tests.Common"/> <Reference name="OpenSim.Tests.Common"/>
<Files> <Files>
<Match path="Tests" pattern="*.cs" recurse="false"/>
<Match path="EventQueue/Tests" pattern="*.cs" recurse="false"/> <Match path="EventQueue/Tests" pattern="*.cs" recurse="false"/>
</Files> </Files>
</Project> </Project>