diff --git a/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs b/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs index 9fe00e067c..6b67da1e3a 100644 --- a/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs +++ b/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs @@ -25,90 +25,229 @@ * 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 Nini.Config; using OpenMetaverse; -using OpenMetaverse.StructuredData; +using OpenMetaverse.Imaging; using OpenSim.Framework; -using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; 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 { public class GetMeshHandler : BaseStreamHandler { -// private static readonly ILog m_log = -// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 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) { 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) { // Try to parse the texture ID from the request URL 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; - 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; - } + m_log.Error("[GETMESH]: Cannot fetch mesh " + textureStr + " without an asset service"); + httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; + } - 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; - httpResponse.Body.Write(data, 0, data.Length); - httpResponse.ContentType = "application/vnd.ll.mesh"; - httpResponse.StatusCode = 200; + if (mesh.Type != (sbyte)AssetType.Mesh) + { + httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; + } + WriteMeshData(httpRequest, httpResponse, mesh); } - // 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; + string textureUrl = m_RedirectURL + "?mesh_id="+ meshID.ToString(); + m_log.Debug("[GETMESH]: Redirecting mesh request to " + textureUrl); + httpResponse.StatusCode = (int)OSHttpStatusCode.RedirectMovedPermanently; + httpResponse.RedirectLocation = textureUrl; + return null; } } - else + else // no redirect { - httpResponse.StatusCode = 404; - httpResponse.ContentType = "text/plain"; - byte[] data = Encoding.UTF8.GetBytes("Your Mesh wasn't found. Sorry!"); - httpResponse.Body.Write(data, 0, data.Length); - httpResponse.KeepAlive = false; + // 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; } + + 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); + } + } + + /// + /// Parse a range header. + /// + /// + /// 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. + /// + /// + /// Start of the range. Undefined if this was not a number. + /// End of the range. Will be -1 if no end specified. Undefined if there was a raw string but this was not a number. + 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; + } } } \ No newline at end of file diff --git a/OpenSim/Capabilities/Handlers/GetMesh/GetMeshServerConnector.cs b/OpenSim/Capabilities/Handlers/GetMesh/GetMeshServerConnector.cs index 9c538623de..19de3cfea5 100644 --- a/OpenSim/Capabilities/Handlers/GetMesh/GetMeshServerConnector.cs +++ b/OpenSim/Capabilities/Handlers/GetMesh/GetMeshServerConnector.cs @@ -25,16 +25,13 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using System; -using System.Collections; 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 OpenSim.Framework.Servers.HttpServer; +using OpenSim.Server.Base; +using OpenSim.Server.Handlers.Base; +using OpenSim.Services.Interfaces; +using System; namespace OpenSim.Capabilities.Handlers { @@ -65,8 +62,15 @@ namespace OpenSim.Capabilities.Handlers if (m_AssetService == null) throw new Exception(String.Format("Failed to load AssetService from {0}; config is {1}", assetService, m_ConfigName)); + string rurl = serverConfig.GetString("GetMeshRedirectURL"); + 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)); } } } \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs index 4aecc99243..f57d857594 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/GetMeshModule.cs @@ -57,6 +57,9 @@ namespace OpenSim.Region.ClientStack.Linden private IAssetService m_AssetService; private bool m_Enabled = true; private string m_URL; + private string m_URL2; + private string m_RedirectURL = null; + private string m_RedirectURL2 = null; #region Region Module interfaceBase Members @@ -74,7 +77,18 @@ namespace OpenSim.Region.ClientStack.Linden m_URL = config.GetString("Cap_GetMesh", string.Empty); // Cap doesn't exist if (m_URL != string.Empty) + { 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) @@ -110,29 +124,46 @@ namespace OpenSim.Region.ClientStack.Linden #endregion + 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 == "localhost") + if (m_URL == string.Empty) { - // 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( "GetMesh", - new GetMeshHandler("/CAPS/" + capID + "/", m_AssetService, "GetMesh", agentID.ToString())); + new GetMeshHandler("/CAPS/" + capID + "/", m_AssetService, "GetMesh", agentID.ToString(), m_RedirectURL)); } else { - // m_log.DebugFormat("[GETTEXTURE]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName); - IExternalCapsModule handler = m_scene.RequestModuleInterface(); - 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); + } + } + } -} \ No newline at end of file +}