revised GetMesh to not use intermediate base64 coding scheme

it delivers binary and has binary as input.
base64 intermediate coding makes no sense.

Signed-off-by: BlueWall <jamesh@bluewallgroup.com>
inv-download
Freaky Tech 2015-03-05 23:52:13 +01:00 committed by BlueWall
parent 5a413c1b2f
commit 4de10a45e9
3 changed files with 244 additions and 70 deletions

View File

@ -25,90 +25,229 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.IO;
using System.Text;
using System.Web;
using log4net; using log4net;
using Nini.Config;
using OpenMetaverse; using OpenMetaverse;
using OpenMetaverse.StructuredData; using OpenMetaverse.Imaging;
using OpenSim.Framework; using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer; using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Services.Interfaces; using OpenSim.Services.Interfaces;
using Caps = OpenSim.Framework.Capabilities.Caps; using System;
using System.Collections.Specialized;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Web;
namespace OpenSim.Capabilities.Handlers namespace OpenSim.Capabilities.Handlers
{ {
public class GetMeshHandler : BaseStreamHandler public class GetMeshHandler : BaseStreamHandler
{ {
// private static readonly ILog m_log = private static readonly ILog m_log =
// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IAssetService m_assetService; private IAssetService m_assetService;
public GetMeshHandler(string path, IAssetService assService, string name, string description) // TODO: Change this to a config option
private string m_RedirectURL = null;
public GetMeshHandler(string path, IAssetService assService, string name, string description, string redirectURL)
: base("GET", path, name, description) : base("GET", path, name, description)
{ {
m_assetService = assService; m_assetService = assService;
m_RedirectURL = redirectURL;
if (m_RedirectURL != null && !m_RedirectURL.EndsWith("/"))
m_RedirectURL += "/";
} }
protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{ {
// Try to parse the texture ID from the request URL // Try to parse the texture ID from the request URL
NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query);
string meshStr = query.GetOne("mesh_id"); string textureStr = query.GetOne("mesh_id");
// m_log.DebugFormat("Fetching mesh {0}", meshStr);
UUID meshID = UUID.Zero;
if (!String.IsNullOrEmpty(meshStr) && UUID.TryParse(meshStr, out meshID))
{
if (m_assetService == null) if (m_assetService == null)
{ {
httpResponse.StatusCode = 404; m_log.Error("[GETMESH]: Cannot fetch mesh " + textureStr + " without an asset service");
httpResponse.ContentType = "text/plain"; httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
byte[] data = Encoding.UTF8.GetBytes("The asset service is unavailable. So is your mesh.");
httpResponse.Body.Write(data, 0, data.Length);
return null;
} }
AssetBase mesh = m_assetService.Get(meshID.ToString()); UUID meshID;
if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out meshID))
{
// OK, we have an array with preferred formats, possibly with only one entry
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
AssetBase mesh;
if (!String.IsNullOrEmpty(m_RedirectURL))
{
// Only try to fetch locally cached meshes. Misses are redirected
mesh = m_assetService.GetCached(meshID.ToString());
if (mesh != null) if (mesh != null)
{ {
if (mesh.Type == (SByte)AssetType.Mesh) if (mesh.Type != (sbyte)AssetType.Mesh)
{ {
byte[] data = mesh.Data; httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
httpResponse.Body.Write(data, 0, data.Length);
httpResponse.ContentType = "application/vnd.ll.mesh";
httpResponse.StatusCode = 200;
}
// Optionally add additional mesh types here
else
{
httpResponse.StatusCode = 404;
httpResponse.ContentType = "text/plain";
byte[] data = Encoding.UTF8.GetBytes("Unfortunately, this asset isn't a mesh.");
httpResponse.Body.Write(data, 0, data.Length);
httpResponse.KeepAlive = false;
} }
WriteMeshData(httpRequest, httpResponse, mesh);
} }
else else
{ {
httpResponse.StatusCode = 404; string textureUrl = m_RedirectURL + "?mesh_id="+ meshID.ToString();
httpResponse.ContentType = "text/plain"; m_log.Debug("[GETMESH]: Redirecting mesh request to " + textureUrl);
byte[] data = Encoding.UTF8.GetBytes("Your Mesh wasn't found. Sorry!"); httpResponse.StatusCode = (int)OSHttpStatusCode.RedirectMovedPermanently;
httpResponse.Body.Write(data, 0, data.Length); httpResponse.RedirectLocation = textureUrl;
httpResponse.KeepAlive = false; return null;
} }
} }
else // no redirect
{
// try the cache
mesh = m_assetService.GetCached(meshID.ToString());
if (mesh == null)
{
// Fetch locally or remotely. Misses return a 404
mesh = m_assetService.Get(meshID.ToString());
if (mesh != null)
{
if (mesh.Type != (sbyte)AssetType.Mesh)
{
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return null;
}
WriteMeshData(httpRequest, httpResponse, mesh);
return null;
}
}
else // it was on the cache
{
if (mesh.Type != (sbyte)AssetType.Mesh)
{
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return null;
}
WriteMeshData(httpRequest, httpResponse, mesh);
return null;
}
}
// not found
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
return null;
}
else
{
m_log.Warn("[GETTEXTURE]: Failed to parse a mesh_id from GetMesh request: " + httpRequest.Url);
}
return null; return null;
} }
private void WriteMeshData(IOSHttpRequest request, IOSHttpResponse response, AssetBase texture)
{
string range = request.Headers.GetOne("Range");
if (!String.IsNullOrEmpty(range))
{
// Range request
int start, end;
if (TryParseRange(range, out start, out end))
{
// Before clamping start make sure we can satisfy it in order to avoid
// sending back the last byte instead of an error status
if (start >= texture.Data.Length)
{
response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent;
response.ContentType = texture.Metadata.ContentType;
}
else
{
// Handle the case where no second range value was given. This is equivalent to requesting
// the rest of the entity.
if (end == -1)
end = int.MaxValue;
end = Utils.Clamp(end, 0, texture.Data.Length - 1);
start = Utils.Clamp(start, 0, end);
int len = end - start + 1;
if (0 == start && len == texture.Data.Length)
{
response.StatusCode = (int)System.Net.HttpStatusCode.OK;
}
else
{
response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent;
response.AddHeader("Content-Range", String.Format("bytes {0}-{1}/{2}", start, end, texture.Data.Length));
}
response.ContentLength = len;
response.ContentType = "application/vnd.ll.mesh";
response.Body.Write(texture.Data, start, len);
}
}
else
{
m_log.Warn("[GETMESH]: Malformed Range header: " + range);
response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
}
}
else
{
// Full content request
response.StatusCode = (int)System.Net.HttpStatusCode.OK;
response.ContentLength = texture.Data.Length;
response.ContentType = "application/vnd.ll.mesh";
response.Body.Write(texture.Data, 0, texture.Data.Length);
}
}
/// <summary>
/// Parse a range header.
/// </summary>
/// <remarks>
/// As per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html,
/// this obeys range headers with two values (e.g. 533-4165) and no second value (e.g. 533-).
/// Where there is no value, -1 is returned.
/// FIXME: Need to cover the case where only a second value is specified (e.g. -4165), probably by returning -1
/// for start.</remarks>
/// <returns></returns>
/// <param name='header'></param>
/// <param name='start'>Start of the range. Undefined if this was not a number.</param>
/// <param name='end'>End of the range. Will be -1 if no end specified. Undefined if there was a raw string but this was not a number.</param>
private bool TryParseRange(string header, out int start, out int end)
{
start = end = 0;
if (header.StartsWith("bytes="))
{
string[] rangeValues = header.Substring(6).Split('-');
if (rangeValues.Length == 2)
{
if (!Int32.TryParse(rangeValues[0], out start))
return false;
string rawEnd = rangeValues[1];
if (rawEnd == "")
{
end = -1;
return true;
}
else if (Int32.TryParse(rawEnd, out end))
{
return true;
}
}
}
start = end = 0;
return false;
}
} }
} }

View File

@ -25,16 +25,13 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
using System;
using System.Collections;
using Nini.Config; using Nini.Config;
using OpenSim.Server.Base;
using OpenSim.Services.Interfaces;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Server.Handlers.Base;
using OpenSim.Framework.Servers;
using OpenMetaverse; using OpenMetaverse;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Server.Base;
using OpenSim.Server.Handlers.Base;
using OpenSim.Services.Interfaces;
using System;
namespace OpenSim.Capabilities.Handlers namespace OpenSim.Capabilities.Handlers
{ {
@ -65,8 +62,15 @@ namespace OpenSim.Capabilities.Handlers
if (m_AssetService == null) if (m_AssetService == null)
throw new Exception(String.Format("Failed to load AssetService from {0}; config is {1}", assetService, m_ConfigName)); throw new Exception(String.Format("Failed to load AssetService from {0}; config is {1}", assetService, m_ConfigName));
string rurl = serverConfig.GetString("GetMeshRedirectURL");
server.AddStreamHandler( server.AddStreamHandler(
new GetMeshHandler("/CAPS/GetMesh/" /*+ UUID.Random() */, m_AssetService, "GetMesh", null)); new GetTextureHandler("/CAPS/GetMesh/" /*+ UUID.Random() */, m_AssetService, "GetMesh", null, rurl));
rurl = serverConfig.GetString("GetMesh2RedirectURL");
server.AddStreamHandler(
new GetTextureHandler("/CAPS/GetMesh2/" /*+ UUID.Random() */, m_AssetService, "GetMesh2", null, rurl));
} }
} }
} }

View File

@ -57,6 +57,9 @@ namespace OpenSim.Region.ClientStack.Linden
private IAssetService m_AssetService; private IAssetService m_AssetService;
private bool m_Enabled = true; private bool m_Enabled = true;
private string m_URL; private string m_URL;
private string m_URL2;
private string m_RedirectURL = null;
private string m_RedirectURL2 = null;
#region Region Module interfaceBase Members #region Region Module interfaceBase Members
@ -74,7 +77,18 @@ namespace OpenSim.Region.ClientStack.Linden
m_URL = config.GetString("Cap_GetMesh", string.Empty); m_URL = config.GetString("Cap_GetMesh", string.Empty);
// Cap doesn't exist // Cap doesn't exist
if (m_URL != string.Empty) if (m_URL != string.Empty)
{
m_Enabled = true; m_Enabled = true;
m_RedirectURL = config.GetString("GetMeshRedirectURL");
}
m_URL2 = config.GetString("Cap_GetMesh2", string.Empty);
// Cap doesn't exist
if (m_URL2 != string.Empty)
{
m_Enabled = true;
m_RedirectURL2 = config.GetString("GetMesh2RedirectURL");
}
} }
public void AddRegion(Scene pScene) public void AddRegion(Scene pScene)
@ -110,29 +124,46 @@ namespace OpenSim.Region.ClientStack.Linden
#endregion #endregion
public void RegisterCaps(UUID agentID, Caps caps) public void RegisterCaps(UUID agentID, Caps caps)
{ {
//caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture));
if (m_URL == "localhost")
{
// m_log.DebugFormat("[GETTEXTURE]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName);
UUID capID = UUID.Random(); UUID capID = UUID.Random();
bool getMeshRegistered = false;
if (m_URL == string.Empty)
{
}
else if (m_URL == "localhost")
{
getMeshRegistered = true;
caps.RegisterHandler( caps.RegisterHandler(
"GetMesh", "GetMesh",
new GetMeshHandler("/CAPS/" + capID + "/", m_AssetService, "GetMesh", agentID.ToString())); new GetMeshHandler("/CAPS/" + capID + "/", m_AssetService, "GetMesh", agentID.ToString(), m_RedirectURL));
} }
else else
{ {
// m_log.DebugFormat("[GETTEXTURE]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName);
IExternalCapsModule handler = m_scene.RequestModuleInterface<IExternalCapsModule>();
if (handler != null)
handler.RegisterExternalUserCapsHandler(agentID, caps, "GetMesh", m_URL);
else
caps.RegisterHandler("GetMesh", m_URL); caps.RegisterHandler("GetMesh", m_URL);
} }
if(m_URL2 == string.Empty)
{
} }
else if (m_URL2 == "localhost")
{
if (!getMeshRegistered)
{
caps.RegisterHandler(
"GetMesh2",
new GetMeshHandler("/CAPS/" + capID + "/", m_AssetService, "GetMesh2", agentID.ToString(), m_RedirectURL2));
}
}
else
{
caps.RegisterHandler("GetMesh2", m_URL2);
}
}
} }
} }