diff options
Diffstat (limited to 'OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs')
-rw-r--r-- | OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs | 332 |
1 files changed, 175 insertions, 157 deletions
diff --git a/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs b/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs index 6b67da1..a9b81f3 100644 --- a/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs +++ b/OpenSim/Capabilities/Handlers/GetMesh/GetMeshHandler.cs | |||
@@ -25,224 +25,242 @@ | |||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ | 26 | */ |
27 | 27 | ||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Specialized; | ||
31 | using System.Reflection; | ||
32 | using System.IO; | ||
33 | using System.Web; | ||
28 | using log4net; | 34 | using log4net; |
35 | using Nini.Config; | ||
29 | using OpenMetaverse; | 36 | using OpenMetaverse; |
30 | using OpenMetaverse.Imaging; | 37 | using OpenMetaverse.StructuredData; |
31 | using OpenSim.Framework; | 38 | using OpenSim.Framework; |
39 | using OpenSim.Framework.Servers; | ||
32 | using OpenSim.Framework.Servers.HttpServer; | 40 | using OpenSim.Framework.Servers.HttpServer; |
33 | using OpenSim.Services.Interfaces; | 41 | using OpenSim.Services.Interfaces; |
34 | using System; | 42 | using Caps = OpenSim.Framework.Capabilities.Caps; |
35 | using System.Collections.Specialized; | 43 | |
36 | using System.Drawing; | 44 | |
37 | using System.Drawing.Imaging; | 45 | |
38 | using System.IO; | ||
39 | using System.Reflection; | ||
40 | using System.Web; | ||
41 | 46 | ||
42 | namespace OpenSim.Capabilities.Handlers | 47 | namespace OpenSim.Capabilities.Handlers |
43 | { | 48 | { |
44 | public class GetMeshHandler : BaseStreamHandler | 49 | public class GetMeshHandler |
45 | { | 50 | { |
46 | private static readonly ILog m_log = | 51 | private static readonly ILog m_log = |
47 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 52 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
53 | |||
48 | private IAssetService m_assetService; | 54 | private IAssetService m_assetService; |
49 | 55 | ||
50 | // TODO: Change this to a config option | 56 | public const string DefaultFormat = "vnd.ll.mesh"; |
51 | private string m_RedirectURL = null; | ||
52 | 57 | ||
53 | public GetMeshHandler(string path, IAssetService assService, string name, string description, string redirectURL) | 58 | public GetMeshHandler(IAssetService assService) |
54 | : base("GET", path, name, description) | ||
55 | { | 59 | { |
56 | m_assetService = assService; | 60 | m_assetService = assService; |
57 | m_RedirectURL = redirectURL; | ||
58 | if (m_RedirectURL != null && !m_RedirectURL.EndsWith("/")) | ||
59 | m_RedirectURL += "/"; | ||
60 | } | 61 | } |
61 | 62 | public Hashtable Handle(Hashtable request) | |
62 | protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
63 | { | 63 | { |
64 | // Try to parse the texture ID from the request URL | 64 | Hashtable ret = new Hashtable(); |
65 | NameValueCollection query = HttpUtility.ParseQueryString(httpRequest.Url.Query); | 65 | ret["int_response_code"] = (int)System.Net.HttpStatusCode.NotFound; |
66 | string textureStr = query.GetOne("mesh_id"); | 66 | ret["content_type"] = "text/plain"; |
67 | ret["keepalive"] = false; | ||
68 | ret["reusecontext"] = false; | ||
69 | ret["int_bytes"] = 0; | ||
70 | ret["int_lod"] = 0; | ||
71 | string MeshStr = (string)request["mesh_id"]; | ||
72 | |||
73 | |||
74 | //m_log.DebugFormat("[GETMESH]: called {0}", MeshStr); | ||
67 | 75 | ||
68 | if (m_assetService == null) | 76 | if (m_assetService == null) |
69 | { | 77 | { |
70 | m_log.Error("[GETMESH]: Cannot fetch mesh " + textureStr + " without an asset service"); | 78 | m_log.Error("[GETMESH]: Cannot fetch mesh " + MeshStr + " without an asset service"); |
71 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; | ||
72 | } | 79 | } |
73 | 80 | ||
74 | UUID meshID; | 81 | UUID meshID; |
75 | if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out meshID)) | 82 | if (!String.IsNullOrEmpty(MeshStr) && UUID.TryParse(MeshStr, out meshID)) |
76 | { | 83 | { |
77 | // OK, we have an array with preferred formats, possibly with only one entry | 84 | // m_log.DebugFormat("[GETMESH]: Received request for mesh id {0}", meshID); |
78 | |||
79 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; | ||
80 | AssetBase mesh; | ||
81 | |||
82 | if (!String.IsNullOrEmpty(m_RedirectURL)) | ||
83 | { | ||
84 | // Only try to fetch locally cached meshes. Misses are redirected | ||
85 | mesh = m_assetService.GetCached(meshID.ToString()); | ||
86 | 85 | ||
87 | if (mesh != null) | ||
88 | { | ||
89 | if (mesh.Type != (sbyte)AssetType.Mesh) | ||
90 | { | ||
91 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; | ||
92 | } | ||
93 | WriteMeshData(httpRequest, httpResponse, mesh); | ||
94 | } | ||
95 | else | ||
96 | { | ||
97 | string textureUrl = m_RedirectURL + "?mesh_id="+ meshID.ToString(); | ||
98 | m_log.Debug("[GETMESH]: Redirecting mesh request to " + textureUrl); | ||
99 | httpResponse.StatusCode = (int)OSHttpStatusCode.RedirectMovedPermanently; | ||
100 | httpResponse.RedirectLocation = textureUrl; | ||
101 | return null; | ||
102 | } | ||
103 | } | ||
104 | else // no redirect | ||
105 | { | ||
106 | // try the cache | ||
107 | mesh = m_assetService.GetCached(meshID.ToString()); | ||
108 | 86 | ||
109 | if (mesh == null) | 87 | ret = ProcessGetMesh(request, UUID.Zero, null); |
110 | { | ||
111 | // Fetch locally or remotely. Misses return a 404 | ||
112 | mesh = m_assetService.Get(meshID.ToString()); | ||
113 | 88 | ||
114 | if (mesh != null) | ||
115 | { | ||
116 | if (mesh.Type != (sbyte)AssetType.Mesh) | ||
117 | { | ||
118 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; | ||
119 | return null; | ||
120 | } | ||
121 | WriteMeshData(httpRequest, httpResponse, mesh); | ||
122 | return null; | ||
123 | } | ||
124 | } | ||
125 | else // it was on the cache | ||
126 | { | ||
127 | if (mesh.Type != (sbyte)AssetType.Mesh) | ||
128 | { | ||
129 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; | ||
130 | return null; | ||
131 | } | ||
132 | WriteMeshData(httpRequest, httpResponse, mesh); | ||
133 | return null; | ||
134 | } | ||
135 | } | ||
136 | 89 | ||
137 | // not found | ||
138 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; | ||
139 | return null; | ||
140 | } | 90 | } |
141 | else | 91 | else |
142 | { | 92 | { |
143 | m_log.Warn("[GETTEXTURE]: Failed to parse a mesh_id from GetMesh request: " + httpRequest.Url); | 93 | m_log.Warn("[GETMESH]: Failed to parse a mesh_id from GetMesh request: " + (string)request["uri"]); |
144 | } | 94 | } |
145 | 95 | ||
146 | return null; | ||
147 | } | ||
148 | 96 | ||
149 | private void WriteMeshData(IOSHttpRequest request, IOSHttpResponse response, AssetBase texture) | 97 | return ret; |
98 | } | ||
99 | public Hashtable ProcessGetMesh(Hashtable request, UUID AgentId, Caps cap) | ||
150 | { | 100 | { |
151 | string range = request.Headers.GetOne("Range"); | 101 | Hashtable responsedata = new Hashtable(); |
102 | responsedata["int_response_code"] = 400; //501; //410; //404; | ||
103 | responsedata["content_type"] = "text/plain"; | ||
104 | responsedata["keepalive"] = false; | ||
105 | responsedata["str_response_string"] = "Request wasn't what was expected"; | ||
106 | responsedata["reusecontext"] = false; | ||
107 | responsedata["int_lod"] = 0; | ||
108 | responsedata["int_bytes"] = 0; | ||
109 | |||
110 | string meshStr = string.Empty; | ||
152 | 111 | ||
153 | if (!String.IsNullOrEmpty(range)) | 112 | if (request.ContainsKey("mesh_id")) |
113 | meshStr = request["mesh_id"].ToString(); | ||
114 | |||
115 | UUID meshID = UUID.Zero; | ||
116 | if (!String.IsNullOrEmpty(meshStr) && UUID.TryParse(meshStr, out meshID)) | ||
154 | { | 117 | { |
155 | // Range request | 118 | if (m_assetService == null) |
156 | int start, end; | ||
157 | if (TryParseRange(range, out start, out end)) | ||
158 | { | 119 | { |
159 | // Before clamping start make sure we can satisfy it in order to avoid | 120 | responsedata["int_response_code"] = 404; //501; //410; //404; |
160 | // sending back the last byte instead of an error status | 121 | responsedata["content_type"] = "text/plain"; |
161 | if (start >= texture.Data.Length) | 122 | responsedata["keepalive"] = false; |
162 | { | 123 | responsedata["str_response_string"] = "The asset service is unavailable. So is your mesh."; |
163 | response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent; | 124 | responsedata["reusecontext"] = false; |
164 | response.ContentType = texture.Metadata.ContentType; | 125 | return responsedata; |
165 | } | 126 | } |
166 | else | 127 | |
128 | AssetBase mesh = m_assetService.Get(meshID.ToString()); | ||
129 | |||
130 | if (mesh != null) | ||
131 | { | ||
132 | if (mesh.Type == (SByte)AssetType.Mesh) | ||
167 | { | 133 | { |
168 | // Handle the case where no second range value was given. This is equivalent to requesting | ||
169 | // the rest of the entity. | ||
170 | if (end == -1) | ||
171 | end = int.MaxValue; | ||
172 | 134 | ||
173 | end = Utils.Clamp(end, 0, texture.Data.Length - 1); | 135 | Hashtable headers = new Hashtable(); |
174 | start = Utils.Clamp(start, 0, end); | 136 | responsedata["headers"] = headers; |
175 | int len = end - start + 1; | 137 | |
138 | string range = String.Empty; | ||
139 | |||
140 | if (((Hashtable)request["headers"])["range"] != null) | ||
141 | range = (string)((Hashtable)request["headers"])["range"]; | ||
176 | 142 | ||
177 | if (0 == start && len == texture.Data.Length) | 143 | else if (((Hashtable)request["headers"])["Range"] != null) |
144 | range = (string)((Hashtable)request["headers"])["Range"]; | ||
145 | |||
146 | if (!String.IsNullOrEmpty(range)) // Mesh Asset LOD // Physics | ||
178 | { | 147 | { |
179 | response.StatusCode = (int)System.Net.HttpStatusCode.OK; | 148 | // Range request |
149 | int start, end; | ||
150 | if (TryParseRange(range, out start, out end)) | ||
151 | { | ||
152 | // Before clamping start make sure we can satisfy it in order to avoid | ||
153 | // sending back the last byte instead of an error status | ||
154 | if (start >= mesh.Data.Length) | ||
155 | { | ||
156 | responsedata["int_response_code"] = 404; //501; //410; //404; | ||
157 | responsedata["content_type"] = "text/plain"; | ||
158 | responsedata["keepalive"] = false; | ||
159 | responsedata["str_response_string"] = "This range doesnt exist."; | ||
160 | responsedata["reusecontext"] = false; | ||
161 | responsedata["int_lod"] = 3; | ||
162 | return responsedata; | ||
163 | } | ||
164 | else | ||
165 | { | ||
166 | end = Utils.Clamp(end, 0, mesh.Data.Length - 1); | ||
167 | start = Utils.Clamp(start, 0, end); | ||
168 | int len = end - start + 1; | ||
169 | |||
170 | //m_log.Debug("Serving " + start + " to " + end + " of " + texture.Data.Length + " bytes for texture " + texture.ID); | ||
171 | |||
172 | if (start > 20000) | ||
173 | { | ||
174 | responsedata["int_lod"] = 3; | ||
175 | } | ||
176 | else if (start < 4097) | ||
177 | { | ||
178 | responsedata["int_lod"] = 1; | ||
179 | } | ||
180 | else | ||
181 | { | ||
182 | responsedata["int_lod"] = 2; | ||
183 | } | ||
184 | |||
185 | |||
186 | if (start == 0 && len == mesh.Data.Length) // well redudante maybe | ||
187 | { | ||
188 | responsedata["int_response_code"] = (int)System.Net.HttpStatusCode.OK; | ||
189 | responsedata["bin_response_data"] = mesh.Data; | ||
190 | responsedata["int_bytes"] = mesh.Data.Length; | ||
191 | responsedata["reusecontext"] = false; | ||
192 | responsedata["int_lod"] = 3; | ||
193 | |||
194 | } | ||
195 | else | ||
196 | { | ||
197 | responsedata["int_response_code"] = | ||
198 | (int)System.Net.HttpStatusCode.PartialContent; | ||
199 | headers["Content-Range"] = String.Format("bytes {0}-{1}/{2}", start, end, | ||
200 | mesh.Data.Length); | ||
201 | |||
202 | byte[] d = new byte[len]; | ||
203 | Array.Copy(mesh.Data, start, d, 0, len); | ||
204 | responsedata["bin_response_data"] = d; | ||
205 | responsedata["int_bytes"] = len; | ||
206 | responsedata["reusecontext"] = false; | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | else | ||
211 | { | ||
212 | m_log.Warn("[GETMESH]: Failed to parse a range from GetMesh request, sending full asset: " + (string)request["uri"]); | ||
213 | responsedata["str_response_string"] = Convert.ToBase64String(mesh.Data); | ||
214 | responsedata["content_type"] = "application/vnd.ll.mesh"; | ||
215 | responsedata["int_response_code"] = 200; | ||
216 | responsedata["reusecontext"] = false; | ||
217 | responsedata["int_lod"] = 3; | ||
218 | } | ||
180 | } | 219 | } |
181 | else | 220 | else |
182 | { | 221 | { |
183 | response.StatusCode = (int)System.Net.HttpStatusCode.PartialContent; | 222 | responsedata["str_response_string"] = Convert.ToBase64String(mesh.Data); |
184 | response.AddHeader("Content-Range", String.Format("bytes {0}-{1}/{2}", start, end, texture.Data.Length)); | 223 | responsedata["content_type"] = "application/vnd.ll.mesh"; |
224 | responsedata["int_response_code"] = 200; | ||
225 | responsedata["reusecontext"] = false; | ||
226 | responsedata["int_lod"] = 3; | ||
185 | } | 227 | } |
186 | 228 | } | |
187 | response.ContentLength = len; | 229 | // Optionally add additional mesh types here |
188 | response.ContentType = "application/vnd.ll.mesh"; | 230 | else |
189 | 231 | { | |
190 | response.Body.Write(texture.Data, start, len); | 232 | responsedata["int_response_code"] = 404; //501; //410; //404; |
233 | responsedata["content_type"] = "text/plain"; | ||
234 | responsedata["keepalive"] = false; | ||
235 | responsedata["str_response_string"] = "Unfortunately, this asset isn't a mesh."; | ||
236 | responsedata["reusecontext"] = false; | ||
237 | responsedata["int_lod"] = 1; | ||
238 | return responsedata; | ||
191 | } | 239 | } |
192 | } | 240 | } |
193 | else | 241 | else |
194 | { | 242 | { |
195 | m_log.Warn("[GETMESH]: Malformed Range header: " + range); | 243 | responsedata["int_response_code"] = 404; //501; //410; //404; |
196 | response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; | 244 | responsedata["content_type"] = "text/plain"; |
245 | responsedata["keepalive"] = false; | ||
246 | responsedata["str_response_string"] = "Your Mesh wasn't found. Sorry!"; | ||
247 | responsedata["reusecontext"] = false; | ||
248 | responsedata["int_lod"] = 0; | ||
249 | return responsedata; | ||
197 | } | 250 | } |
198 | } | 251 | } |
199 | else | ||
200 | { | ||
201 | // Full content request | ||
202 | response.StatusCode = (int)System.Net.HttpStatusCode.OK; | ||
203 | response.ContentLength = texture.Data.Length; | ||
204 | response.ContentType = "application/vnd.ll.mesh"; | ||
205 | response.Body.Write(texture.Data, 0, texture.Data.Length); | ||
206 | } | ||
207 | } | ||
208 | 252 | ||
209 | /// <summary> | 253 | return responsedata; |
210 | /// Parse a range header. | 254 | } |
211 | /// </summary> | ||
212 | /// <remarks> | ||
213 | /// As per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, | ||
214 | /// this obeys range headers with two values (e.g. 533-4165) and no second value (e.g. 533-). | ||
215 | /// Where there is no value, -1 is returned. | ||
216 | /// FIXME: Need to cover the case where only a second value is specified (e.g. -4165), probably by returning -1 | ||
217 | /// for start.</remarks> | ||
218 | /// <returns></returns> | ||
219 | /// <param name='header'></param> | ||
220 | /// <param name='start'>Start of the range. Undefined if this was not a number.</param> | ||
221 | /// <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> | ||
222 | private bool TryParseRange(string header, out int start, out int end) | 255 | private bool TryParseRange(string header, out int start, out int end) |
223 | { | 256 | { |
224 | start = end = 0; | ||
225 | |||
226 | if (header.StartsWith("bytes=")) | 257 | if (header.StartsWith("bytes=")) |
227 | { | 258 | { |
228 | string[] rangeValues = header.Substring(6).Split('-'); | 259 | string[] rangeValues = header.Substring(6).Split('-'); |
229 | |||
230 | if (rangeValues.Length == 2) | 260 | if (rangeValues.Length == 2) |
231 | { | 261 | { |
232 | if (!Int32.TryParse(rangeValues[0], out start)) | 262 | if (Int32.TryParse(rangeValues[0], out start) && Int32.TryParse(rangeValues[1], out end)) |
233 | return false; | ||
234 | |||
235 | string rawEnd = rangeValues[1]; | ||
236 | |||
237 | if (rawEnd == "") | ||
238 | { | ||
239 | end = -1; | ||
240 | return true; | 263 | return true; |
241 | } | ||
242 | else if (Int32.TryParse(rawEnd, out end)) | ||
243 | { | ||
244 | return true; | ||
245 | } | ||
246 | } | 264 | } |
247 | } | 265 | } |
248 | 266 | ||