From 4de10a45e9136c75e33f31b448720db35c2c23b1 Mon Sep 17 00:00:00 2001 From: Freaky Tech Date: Thu, 5 Mar 2015 23:52:13 +0100 Subject: 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 --- .../Handlers/GetMesh/GetMeshHandler.cs | 227 +++++++++++++++++---- 1 file changed, 183 insertions(+), 44 deletions(-) (limited to 'OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs') diff --git a/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs b/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs index 9fe00e0..6b67da1 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); + if (m_assetService == null) + { + m_log.Error("[GETMESH]: Cannot fetch mesh " + textureStr + " without an asset service"); + httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; + } - UUID meshID = UUID.Zero; - if (!String.IsNullOrEmpty(meshStr) && UUID.TryParse(meshStr, out meshID)) + UUID meshID; + if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out meshID)) { - if (m_assetService == null) + // 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)) { - 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; + // Only try to fetch locally cached meshes. Misses are redirected + mesh = m_assetService.GetCached(meshID.ToString()); + + if (mesh != null) + { + if (mesh.Type != (sbyte)AssetType.Mesh) + { + httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; + } + WriteMeshData(httpRequest, httpResponse, mesh); + } + else + { + 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 // 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()); - AssetBase 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; + } + } - if (mesh != 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)) { - if (mesh.Type == (SByte)AssetType.Mesh) + // 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) { - byte[] data = mesh.Data; - httpResponse.Body.Write(data, 0, data.Length); - httpResponse.ContentType = "application/vnd.ll.mesh"; - httpResponse.StatusCode = 200; + response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent; + response.ContentType = texture.Metadata.ContentType; } - // 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; + // 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 { - 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; + 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); + } + } - return null; + /// + /// 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 -- cgit v1.1