If the GetTexture capability receives a request for a range of data beyond that of an otherwise valid asset, return HTTP PartialContent rather than RequestedRangeNotSatisfiable.

This is because recent viewers (3.2.1, 3.3.4) and probably earlier ones using the http GetTexture capability will sometimes make such invalid range requests.
This appears to happen if the viewer's estimate of texture sizes at discard levels > 0 (chiefly 2) exceeds the total texture size.
I believe this does not normally happen but can occur for dynamic textures with are large but mainly blank.
If this happens, returning a RequestedRangeNotSatisfiable will cause the viewer to not render the texture at the final resolution.
However, returning a PartialContent (or OK) even with 0 data will allow the viewer to render the final texture.
integration
Justin Clark-Casey (justincc) 2012-09-06 00:11:47 +01:00
parent 15d5f3d09d
commit a0d0c9f751
3 changed files with 62 additions and 8 deletions

View File

@ -163,7 +163,7 @@ namespace OpenSim.Capabilities.Handlers
if (texture == null) if (texture == null)
{ {
//m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache"); // m_log.DebugFormat("[GETTEXTURE]: texture was not in the cache");
// Fetch locally or remotely. Misses return a 404 // Fetch locally or remotely. Misses return a 404
texture = m_assetService.Get(textureID.ToString()); texture = m_assetService.Get(textureID.ToString());
@ -197,7 +197,7 @@ namespace OpenSim.Capabilities.Handlers
} }
else // it was on the cache else // it was on the cache
{ {
//m_log.DebugFormat("[GETTEXTURE]: texture was in the cache"); // m_log.DebugFormat("[GETTEXTURE]: texture was in the cache");
WriteTextureData(httpRequest, httpResponse, texture, format); WriteTextureData(httpRequest, httpResponse, texture, format);
return true; return true;
} }
@ -219,12 +219,30 @@ namespace OpenSim.Capabilities.Handlers
int start, end; int start, end;
if (TryParseRange(range, out start, out end)) if (TryParseRange(range, out start, out end))
{ {
// Before clamping start make sure we can satisfy it in order to avoid // Before clamping start make sure we can satisfy it in order to avoid
// sending back the last byte instead of an error status // sending back the last byte instead of an error status
if (start >= texture.Data.Length) if (start >= texture.Data.Length)
{ {
response.StatusCode = (int)System.Net.HttpStatusCode.RequestedRangeNotSatisfiable; m_log.DebugFormat(
"[GETTEXTURE]: Client requested range for texture {0} starting at {1} but texture has end of {2}",
texture.ID, start, texture.Data.Length);
// Stricly speaking, as per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, we should be sending back
// Requested Range Not Satisfiable (416) here. However, it appears that at least recent implementations
// of the Linden Lab viewer (3.2.1 and 3.3.4 and probably earlier), a viewer that has previously
// received a very small texture may attempt to fetch bytes from the server past the
// range of data that it received originally. Whether this happens appears to depend on whether
// the viewer's estimation of how large a request it needs to make for certain discard levels
// (http://wiki.secondlife.com/wiki/Image_System#Discard_Level_and_Mip_Mapping), chiefly discard
// level 2. If this estimate is greater than the total texture size, returning a RequestedRangeNotSatisfiable
// here will cause the viewer to treat the texture as bad and never display the full resolution
// However, if we return PartialContent (or OK) instead, the viewer will display that resolution.
// response.StatusCode = (int)System.Net.HttpStatusCode.RequestedRangeNotSatisfiable;
// response.AddHeader("Content-Range", String.Format("bytes */{0}", texture.Data.Length));
// response.StatusCode = (int)System.Net.HttpStatusCode.OK;
response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent;
response.ContentType = texture.Metadata.ContentType;
} }
else else
{ {
@ -232,12 +250,18 @@ namespace OpenSim.Capabilities.Handlers
start = Utils.Clamp(start, 0, end); start = Utils.Clamp(start, 0, end);
int len = end - start + 1; int len = end - start + 1;
//m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID); // m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID);
// Always return PartialContent, even if the range covered the entire data length // Always return PartialContent, even if the range covered the entire data length
// We were accidentally sending back 404 before in this situation // We were accidentally sending back 404 before in this situation
// https://issues.apache.org/bugzilla/show_bug.cgi?id=51878 supports sending 206 even if the // https://issues.apache.org/bugzilla/show_bug.cgi?id=51878 supports sending 206 even if the
// entire range is requested, and viewer 3.2.2 (and very probably earlier) seems fine with this. // entire range is requested, and viewer 3.2.2 (and very probably earlier) seems fine with this.
//
// We also do not want to send back OK even if the whole range was satisfiable since this causes
// HTTP textures on at least Imprudence 1.4.0-beta2 to never display the final texture quality.
// if (end > maxEnd)
// response.StatusCode = (int)System.Net.HttpStatusCode.OK;
// else
response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent; response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent;
response.ContentLength = len; response.ContentLength = len;

View File

@ -42,7 +42,7 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
{ {
public class DynamicTextureModule : IRegionModule, IDynamicTextureManager public class DynamicTextureModule : IRegionModule, IDynamicTextureManager
{ {
//private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private const int ALL_SIDES = -1; private const int ALL_SIDES = -1;
@ -249,10 +249,18 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
} }
} }
// m_log.DebugFormat(
// "[DYNAMIC TEXTURE MODULE]: Requesting generation of new dynamic texture for {0} in {1}",
// part.Name, part.ParentGroup.Scene.Name);
RenderPlugins[contentType].AsyncConvertData(updater.UpdaterID, data, extraParams); RenderPlugins[contentType].AsyncConvertData(updater.UpdaterID, data, extraParams);
} }
else else
{ {
// m_log.DebugFormat(
// "[DYNAMIC TEXTURE MODULE]: Reusing cached texture {0} for {1} in {2}",
// objReusableTextureUUID, part.Name, part.ParentGroup.Scene.Name);
// No need to add to updaters as the texture is always the same. Not that this functionality // No need to add to updaters as the texture is always the same. Not that this functionality
// apppears to be implemented anyway. // apppears to be implemented anyway.
updater.UpdatePart(part, (UUID)objReusableTextureUUID); updater.UpdatePart(part, (UUID)objReusableTextureUUID);
@ -448,8 +456,10 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture
IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface<IJ2KDecoder>(); IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface<IJ2KDecoder>();
if (cacheLayerDecode != null) if (cacheLayerDecode != null)
{ {
cacheLayerDecode.Decode(asset.FullID, asset.Data); if (!cacheLayerDecode.Decode(asset.FullID, asset.Data))
cacheLayerDecode = null; m_log.WarnFormat(
"[DYNAMIC TEXTURE MODULE]: Decoding of dynamically generated asset {0} for {1} in {2} failed",
asset.ID, part.Name, part.ParentGroup.Scene.Name);
} }
UUID oldID = UpdatePart(part, asset.FullID); UUID oldID = UpdatePart(part, asset.FullID);

View File

@ -46,6 +46,11 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
{ {
public class VectorRenderModule : IRegionModule, IDynamicTextureRender public class VectorRenderModule : IRegionModule, IDynamicTextureRender
{ {
// These fields exist for testing purposes, please do not remove.
// private static bool s_flipper;
// private static byte[] s_asset1Data;
// private static byte[] s_asset2Data;
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private Scene m_scene; private Scene m_scene;
@ -161,6 +166,13 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
{ {
m_textureManager.RegisterRender(GetContentType(), this); m_textureManager.RegisterRender(GetContentType(), this);
} }
// This code exists for testing purposes, please do not remove.
// s_asset1Data = m_scene.AssetService.Get("00000000-0000-1111-9999-000000000001").Data;
// s_asset1Data = m_scene.AssetService.Get("9f4acf0d-1841-4e15-bdb8-3a12efc9dd8f").Data;
// Terrain dirt - smallest bin/assets file (6004 bytes)
// s_asset2Data = m_scene.AssetService.Get("b8d3965a-ad78-bf43-699b-bff8eca6c975").Data;
} }
public void Close() public void Close()
@ -365,6 +377,14 @@ namespace OpenSim.Region.CoreModules.Scripting.VectorRender
byte[] imageJ2000 = new byte[0]; byte[] imageJ2000 = new byte[0];
// This code exists for testing purposes, please do not remove.
// if (s_flipper)
// imageJ2000 = s_asset1Data;
// else
// imageJ2000 = s_asset2Data;
//
// s_flipper = !s_flipper;
try try
{ {
imageJ2000 = OpenJPEG.EncodeFromImage(bitmap, true); imageJ2000 = OpenJPEG.EncodeFromImage(bitmap, true);