diff --git a/OpenSim/Region/Environment/Interfaces/IHttpRequests.cs b/OpenSim/Region/Environment/Interfaces/IHttpRequests.cs index 03571393fb..c974616665 100644 --- a/OpenSim/Region/Environment/Interfaces/IHttpRequests.cs +++ b/OpenSim/Region/Environment/Interfaces/IHttpRequests.cs @@ -27,11 +27,16 @@ */ using libsecondlife; +using OpenSim.Region.Environment.Modules; +using System.Collections.Generic; namespace OpenSim.Region.Environment.Interfaces { public interface IHttpRequests { - LLUUID MakeHttpRequest(string url, string type, string body); + LLUUID MakeHttpRequest(string url, string parameters, string body); + LLUUID StartHttpRequest(uint localID, LLUUID itemID, string url, List parameters, string body); + void StopHttpRequest(uint m_localID, LLUUID m_itemID); + HttpRequestClass GetNextCompletedRequest(); } } \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/ScriptsHttpRequests.cs b/OpenSim/Region/Environment/Modules/ScriptsHttpRequests.cs index 5ac0b39156..dc4ef352b1 100644 --- a/OpenSim/Region/Environment/Modules/ScriptsHttpRequests.cs +++ b/OpenSim/Region/Environment/Modules/ScriptsHttpRequests.cs @@ -26,9 +26,324 @@ * */ +using System; +using System.IO; +using System.Net; +using System.Text; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using libsecondlife; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework.Servers; + +/***************************************************** + * + * ScriptsHttpRequests + * + * Implements the llHttpRequest and http_response + * callback. + * + * Some stuff was already in LSLLongCmdHandler, and then + * there was this file with a stub class in it. So, + * I am moving some of the objects and functions out of + * LSLLongCmdHandler, such as the HttpRequestClass, the + * start and stop methods, and setting up pending and + * completed queues. These are processed in the + * LSLLongCmdHandler polling loop. Similiar to the + * XMLRPCModule, since that seems to work. + * + * //TODO + * + * This probably needs some throttling mechanism but + * its wide open right now. This applies to both + * number of requests and data volume. + * + * Linden puts all kinds of header fields in the requests. + * Not doing any of that: + * User-Agent + * X-SecondLife-Shard + * X-SecondLife-Object-Name + * X-SecondLife-Object-Key + * X-SecondLife-Region + * X-SecondLife-Local-Position + * X-SecondLife-Local-Velocity + * X-SecondLife-Local-Rotation + * X-SecondLife-Owner-Name + * X-SecondLife-Owner-Key + * + * HTTPS support + * + * Configurable timeout? + * Configurable max repsonse size? + * Configurable + * + * **************************************************/ + namespace OpenSim.Region.Environment.Modules { - internal class ScriptsHttpRequests + public class ScriptHTTPRequests : IRegionModule, IHttpRequests + { + private Scene m_scene; + private Queue rpcQueue = new Queue(); + private object HttpListLock = new object(); + private string m_name = "HttpScriptRequests"; + private int httpTimeout = 300; + + // + private Dictionary m_pendingRequests; + + public ScriptHTTPRequests() + { + } + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + + m_scene.RegisterModuleInterface(this); + + m_pendingRequests = new Dictionary(); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return m_name; } + } + + public bool IsSharedModule + { + get { return true; } + } + + public LLUUID MakeHttpRequest(string url, string parameters, string body) { + return LLUUID.Zero; + } + + public LLUUID StartHttpRequest(uint localID, LLUUID itemID, string url, List parameters, string body) + { + LLUUID reqID = LLUUID.Random(); + HttpRequestClass htc = new HttpRequestClass(); + + // Parameters are expected in {key, value, ... , key, value} + if( parameters != null ) + { + string[] parms = parameters.ToArray(); + for (int i = 0; i < parms.Length / 2; i += 2) + { + switch( Int32.Parse(parms[i]) ) + { + case HttpRequestClass.HTTP_METHOD: + + htc.httpMethod = parms[i + 1]; + break; + + case HttpRequestClass.HTTP_MIMETYPE: + + htc.httpMIMEType = parms[i + 1]; + break; + + case HttpRequestClass.HTTP_BODY_MAXLENGTH: + + // TODO implement me + break; + + case HttpRequestClass.HTTP_VERIFY_CERT: + + // TODO implement me + break; + } + } + } + + htc.localID = localID; + htc.itemID = itemID; + htc.url = url; + htc.reqID = reqID; + htc.httpTimeout = httpTimeout; + htc.outbound_body = body; + + lock (HttpListLock) + { + m_pendingRequests.Add(reqID, htc); + } + + htc.process(); + + return reqID; + } + + public void StopHttpRequest(uint m_localID, LLUUID m_itemID) + { + lock (HttpListLock) + { + + HttpRequestClass tmpReq; + if (m_pendingRequests.TryGetValue(m_itemID, out tmpReq)) + { + tmpReq.Stop(); + m_pendingRequests.Remove(m_itemID); + } + } + } + + /* + * TODO + * Not sure how important ordering is is here - the next first + * one completed in the list is returned, based soley on its list + * position, not the order in which the request was started or + * finsihed. I thought about setting up a queue for this, but + * it will need some refactoring and this works 'enough' right now + */ + public HttpRequestClass GetNextCompletedRequest() + { + lock (HttpListLock) + { + foreach (LLUUID luid in m_pendingRequests.Keys) + { + HttpRequestClass tmpReq; + + if (m_pendingRequests.TryGetValue(luid, out tmpReq)) + { + if (tmpReq.finished) + { + m_pendingRequests.Remove(luid); + return tmpReq; + } + } + } + } + return null; + } + + + } + + // + // HTTP REAQUEST + // This class was originally in LSLLongCmdHandler + // + // TODO: setter/getter methods, maybe pass some in + // constructor + // + + public class HttpRequestClass { + // Constants for parameters + public const int HTTP_METHOD = 0; + public const int HTTP_MIMETYPE = 1; + public const int HTTP_BODY_MAXLENGTH = 2; + public const int HTTP_VERIFY_CERT = 3; + + // Parameter members and default values + public string httpMethod = "GET"; + public string httpMIMEType = "text/plain;charset=utf-8"; + public int httpBodyMaxLen = 2048; // not implemented + public bool httpVerifyCert = true; // not implemented + + // Request info + public uint localID; + public LLUUID itemID; + public LLUUID reqID; + public int httpTimeout; + public string url; + public string outbound_body; + public DateTime next; + public int status; + public bool finished; + public List response_metadata; + public string response_body; + public HttpWebRequest request; + private Thread httpThread; + + public void process() + { + httpThread = new Thread(SendRequest); + httpThread.Name = "HttpRequestThread"; + httpThread.Priority = ThreadPriority.BelowNormal; + httpThread.IsBackground = true; + httpThread.Start(); + } + + /* + * TODO: More work on the response codes. Right now + * returning 200 for success or 499 for exception + */ + public void SendRequest() + { + + HttpWebResponse response = null; + StringBuilder sb = new StringBuilder(); + byte[] buf = new byte[8192]; + string tempString = null; + int count = 0; + + try + { + request = (HttpWebRequest) + WebRequest.Create(url); + request.Method = httpMethod; + request.ContentType = httpMIMEType; + + request.Timeout = httpTimeout; + // execute the request + response = (HttpWebResponse) + request.GetResponse(); + + Stream resStream = response.GetResponseStream(); + + do + { + // fill the buffer with data + count = resStream.Read(buf, 0, buf.Length); + + // make sure we read some data + if (count != 0) + { + // translate from bytes to ASCII text + tempString = Encoding.ASCII.GetString(buf, 0, count); + + // continue building the string + sb.Append(tempString); + } + } + while (count > 0); // any more data to read? + + response_body = sb.ToString(); + + } + catch (Exception e) + { + status = 499; + response_body = e.Message; + finished = true; + return; + } + + status = 200; + finished = true; + + } + + public void Stop() + { + try + { + httpThread.Abort(); + } + catch (Exception e) { } + } } -} \ No newline at end of file + + } \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/XMLRPCModule.cs b/OpenSim/Region/Environment/Modules/XMLRPCModule.cs index 1223f5c74e..2618b17d5d 100644 --- a/OpenSim/Region/Environment/Modules/XMLRPCModule.cs +++ b/OpenSim/Region/Environment/Modules/XMLRPCModule.cs @@ -121,7 +121,7 @@ namespace OpenSim.Region.Environment.Modules public bool IsSharedModule { - get { return false; } + get { return true; } } /********************************************** diff --git a/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands_Interface.cs b/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands_Interface.cs index 545b99cb77..10e71b36e1 100644 --- a/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands_Interface.cs +++ b/OpenSim/Region/ScriptEngine/Common/LSL_BuiltIn_Commands_Interface.cs @@ -614,7 +614,7 @@ namespace OpenSim.Region.ScriptEngine.Common int llGetRegionFlags(); //wiki: string llXorBase64StringsCorrect(string str1, string str2) string llXorBase64StringsCorrect(string str1, string str2); - void llHTTPRequest(string url, List parameters, string body); + string llHTTPRequest(string url, List parameters, string body); //wiki: llResetLandBanList() void llResetLandBanList(); //wiki: llResetLandPassList() diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs index deabec3d20..99f8d3b91f 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/LSL/LSL_BaseClass.cs @@ -1759,9 +1759,9 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.LSL return m_LSL_Functions.llXorBase64StringsCorrect(str1, str2); } - public void llHTTPRequest(string url, List parameters, string body) + public string llHTTPRequest(string url, List parameters, string body) { - m_LSL_Functions.llHTTPRequest(url, parameters, body); + return m_LSL_Functions.llHTTPRequest(url, parameters, body); } public void llResetLandBanList() diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs index 94479a0e77..aaac2940ef 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/Server_API/LSL_BuiltIn_Commands.cs @@ -2451,9 +2451,18 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler return llStringToBase64(ret); } - public void llHTTPRequest(string url, List parameters, string body) + public string llHTTPRequest(string url, List parameters, string body) { - m_ScriptEngine.m_LSLLongCmdHandler.StartHttpRequest(m_localID, m_itemID, url, parameters, body); + IHttpRequests httpScriptMod = + m_ScriptEngine.World.RequestModuleInterface(); + + LLUUID reqID = httpScriptMod. + StartHttpRequest(m_localID, m_itemID, url, parameters, body); + + if( reqID != null ) + return reqID.ToString(); + else + return null; } public void llResetLandBanList() diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/LSLLongCmdHandler.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/LSLLongCmdHandler.cs index 50616296b3..ddc0c620dc 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/LSLLongCmdHandler.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/LSLLongCmdHandler.cs @@ -110,7 +110,9 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine // Remove from: Timers UnSetTimerEvents(localID, itemID); // Remove from: HttpRequest - StopHttpRequest(localID, itemID); + IHttpRequests iHttpReq = + m_ScriptEngine.World.RequestModuleInterface(); + iHttpReq.StopHttpRequest(localID, itemID); } #region TIMER @@ -198,114 +200,42 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine #region HTTP REQUEST - // - // HTTP REAQUEST - // - private class HttpClass - { - public uint localID; - public LLUUID itemID; - public string url; - public List parameters; - public string body; - public DateTime next; - - public string response_request_id; - public int response_status; - public List response_metadata; - public string response_body; - - public void SendRequest() - { - // TODO: SEND REQUEST!!! - } - - public void Stop() - { - // TODO: Cancel any ongoing request - } - - public bool CheckResponse() - { - // TODO: Check if we got a response yet, return true if so -- false if not - return true; - - // TODO: If we got a response, set the following then return true - //response_request_id - //response_status - //response_metadata - //response_body - } - } - - private List HttpRequests = new List(); - private object HttpListLock = new object(); - - public void StartHttpRequest(uint localID, LLUUID itemID, string url, List parameters, string body) - { - Console.WriteLine("StartHttpRequest"); - - HttpClass htc = new HttpClass(); - htc.localID = localID; - htc.itemID = itemID; - htc.url = url; - htc.parameters = parameters; - htc.body = body; - lock (HttpListLock) - { - //ADD REQUEST - HttpRequests.Add(htc); - } - } - - public void StopHttpRequest(uint m_localID, LLUUID m_itemID) - { - // Remove from list - lock (HttpListLock) - { - List NewHttpList = new List(); - foreach (HttpClass ts in HttpRequests) - { - if (ts.localID != m_localID && ts.itemID != m_itemID) - { - // Keeping this one - NewHttpList.Add(ts); - } - else - { - // Shutting this one down - ts.Stop(); - } - } - HttpRequests.Clear(); - HttpRequests = NewHttpList; - } - } - public void CheckHttpRequests() { - // Nothing to do here? - if (HttpRequests.Count == 0) - return; + IHttpRequests iHttpReq = + m_ScriptEngine.World.RequestModuleInterface(); - lock (HttpListLock) + HttpRequestClass httpInfo = null; + + if( iHttpReq != null ) + httpInfo = iHttpReq.GetNextCompletedRequest(); + + while ( httpInfo != null ) { - foreach (HttpClass ts in HttpRequests) - { - if (ts.CheckResponse() == true) + + Console.WriteLine("PICKED HTTP REQ:" + httpInfo.response_body + httpInfo.status); + + // Deliver data to prim's remote_data handler + // + // TODO: Returning null for metadata, since the lsl function + // only returns the byte for HTTP_BODY_TRUNCATED, which is not + // implemented here yet anyway. Should be fixed if/when maxsize + // is supported + + object[] resobj = new object[] { - // Add it to event queue - //key request_id, integer status, list metadata, string body - object[] resobj = - new object[] - {ts.response_request_id, ts.response_status, ts.response_metadata, ts.response_body}; - m_ScriptEngine.m_EventQueueManager.AddToScriptQueue(ts.localID, ts.itemID, "http_response", - resobj); - // Now stop it - StopHttpRequest(ts.localID, ts.itemID); - } - } - } // lock + httpInfo.reqID.ToString(), httpInfo.status, null, httpInfo.response_body + }; + + m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( + httpInfo.localID, httpInfo.itemID, "http_response", resobj + ); + + httpInfo.Stop(); + httpInfo = null; + + httpInfo = iHttpReq.GetNextCompletedRequest(); + } } #endregion @@ -314,20 +244,23 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine { IXMLRPC xmlrpc = m_ScriptEngine.World.RequestModuleInterface(); - while (xmlrpc.hasRequests()) + if (xmlrpc != null) { - RPCRequestInfo rInfo = xmlrpc.GetNextRequest(); - Console.WriteLine("PICKED REQUEST"); + while (xmlrpc.hasRequests()) + { + RPCRequestInfo rInfo = xmlrpc.GetNextRequest(); + Console.WriteLine("PICKED REQUEST"); - //Deliver data to prim's remote_data handler - object[] resobj = new object[] - { - 2, rInfo.GetChannelKey().ToString(), rInfo.GetMessageID().ToString(), "", rInfo.GetIntValue(), - rInfo.GetStrVal() - }; - m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( - rInfo.GetLocalID(), rInfo.GetItemID(), "remote_data", resobj - ); + //Deliver data to prim's remote_data handler + object[] resobj = new object[] + { + 2, rInfo.GetChannelKey().ToString(), rInfo.GetMessageID().ToString(), "", rInfo.GetIntValue(), + rInfo.GetStrVal() + }; + m_ScriptEngine.m_EventQueueManager.AddToScriptQueue( + rInfo.GetLocalID(), rInfo.GetItemID(), "remote_data", resobj + ); + } } } @@ -338,7 +271,6 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine while (comms.HasMessages()) { ListenerInfo lInfo = comms.GetNextMessage(); - Console.WriteLine("PICKED LISTENER"); //Deliver data to prim's listen handler object[] resobj = new object[]