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
parent
5a413c1b2f
commit
4de10a45e9
|
@ -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);
|
if (m_assetService == null)
|
||||||
|
|
||||||
UUID meshID = UUID.Zero;
|
|
||||||
if (!String.IsNullOrEmpty(meshStr) && UUID.TryParse(meshStr, out meshID))
|
|
||||||
{
|
{
|
||||||
if (m_assetService == null)
|
m_log.Error("[GETMESH]: Cannot fetch mesh " + textureStr + " without an asset service");
|
||||||
{
|
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||||
httpResponse.StatusCode = 404;
|
}
|
||||||
httpResponse.ContentType = "text/plain";
|
|
||||||
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
|
||||||
|
|
||||||
if (mesh != null)
|
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||||
|
AssetBase mesh;
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(m_RedirectURL))
|
||||||
{
|
{
|
||||||
if (mesh.Type == (SByte)AssetType.Mesh)
|
// Only try to fetch locally cached meshes. Misses are redirected
|
||||||
|
mesh = m_assetService.GetCached(meshID.ToString());
|
||||||
|
|
||||||
|
if (mesh != null)
|
||||||
{
|
{
|
||||||
byte[] data = mesh.Data;
|
if (mesh.Type != (sbyte)AssetType.Mesh)
|
||||||
httpResponse.Body.Write(data, 0, data.Length);
|
{
|
||||||
httpResponse.ContentType = "application/vnd.ll.mesh";
|
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
|
||||||
httpResponse.StatusCode = 200;
|
}
|
||||||
|
WriteMeshData(httpRequest, httpResponse, mesh);
|
||||||
}
|
}
|
||||||
// Optionally add additional mesh types here
|
|
||||||
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("Unfortunately, this asset isn't a mesh.");
|
httpResponse.StatusCode = (int)OSHttpStatusCode.RedirectMovedPermanently;
|
||||||
httpResponse.Body.Write(data, 0, data.Length);
|
httpResponse.RedirectLocation = textureUrl;
|
||||||
httpResponse.KeepAlive = false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else // no redirect
|
||||||
{
|
{
|
||||||
httpResponse.StatusCode = 404;
|
// try the cache
|
||||||
httpResponse.ContentType = "text/plain";
|
mesh = m_assetService.GetCached(meshID.ToString());
|
||||||
byte[] data = Encoding.UTF8.GetBytes("Your Mesh wasn't found. Sorry!");
|
|
||||||
httpResponse.Body.Write(data, 0, data.Length);
|
if (mesh == null)
|
||||||
httpResponse.KeepAlive = false;
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
UUID capID = UUID.Random();
|
||||||
|
bool getMeshRegistered = false;
|
||||||
|
|
||||||
//caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture));
|
if (m_URL == string.Empty)
|
||||||
if (m_URL == "localhost")
|
|
||||||
{
|
{
|
||||||
// m_log.DebugFormat("[GETTEXTURE]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName);
|
|
||||||
|
|
||||||
UUID capID = UUID.Random();
|
|
||||||
|
|
||||||
|
}
|
||||||
|
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);
|
caps.RegisterHandler("GetMesh", m_URL);
|
||||||
IExternalCapsModule handler = m_scene.RequestModuleInterface<IExternalCapsModule>();
|
|
||||||
if (handler != null)
|
|
||||||
handler.RegisterExternalUserCapsHandler(agentID, caps, "GetMesh", m_URL);
|
|
||||||
else
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue