Avoid a race condition where an incoming request to a script external URL can trigger an exception is the URL was being removed at the same time.

This involves three steps
1) Return gracefully in UrlModule.HttpRequestHandler() instead of throwing an exception when the url cannot be found in its index
2) Return true instead of false in HasEvents() if no matching request is found in the map.  This call will only happen in the first place for raced requests.
3) Return a 404 in GetEvents() if the request is not in the index, rather than a blank 200 OK.

Many thanks to Tom Haines in http://opensimulator.org/mantis/view.php?id=6051 for doing some of the work on this.
0.7.3-extended
Justin Clark-Casey (justincc) 2012-06-22 23:16:18 +01:00
parent 689cafec63
commit 1cfaacb88b
3 changed files with 87 additions and 43 deletions

View File

@ -28,6 +28,7 @@
using System; using System;
using System.Collections; using System.Collections;
using OpenMetaverse; using OpenMetaverse;
namespace OpenSim.Framework.Servers.HttpServer namespace OpenSim.Framework.Servers.HttpServer
{ {
public delegate void RequestMethod(UUID requestID, Hashtable request); public delegate void RequestMethod(UUID requestID, Hashtable request);
@ -44,7 +45,11 @@ namespace OpenSim.Framework.Servers.HttpServer
public NoEventsMethod NoEvents; public NoEventsMethod NoEvents;
public RequestMethod Request; public RequestMethod Request;
public UUID Id; public UUID Id;
public PollServiceEventArgs(RequestMethod pRequest, HasEventsMethod pHasEvents, GetEventsMethod pGetEvents, NoEventsMethod pNoEvents,UUID pId)
public PollServiceEventArgs(
RequestMethod pRequest,
HasEventsMethod pHasEvents, GetEventsMethod pGetEvents, NoEventsMethod pNoEvents,
UUID pId)
{ {
Request = pRequest; Request = pRequest;
HasEvents = pHasEvents; HasEvents = pHasEvents;

View File

@ -31,7 +31,6 @@ using OpenMetaverse;
namespace OpenSim.Framework.Servers.HttpServer namespace OpenSim.Framework.Servers.HttpServer
{ {
public class PollServiceHttpRequest public class PollServiceHttpRequest
{ {
public readonly PollServiceEventArgs PollServiceArgs; public readonly PollServiceEventArgs PollServiceArgs;
@ -39,7 +38,9 @@ namespace OpenSim.Framework.Servers.HttpServer
public readonly IHttpRequest Request; public readonly IHttpRequest Request;
public readonly int RequestTime; public readonly int RequestTime;
public readonly UUID RequestID; public readonly UUID RequestID;
public PollServiceHttpRequest(PollServiceEventArgs pPollServiceArgs, IHttpClientContext pHttpContext, IHttpRequest pRequest)
public PollServiceHttpRequest(
PollServiceEventArgs pPollServiceArgs, IHttpClientContext pHttpContext, IHttpRequest pRequest)
{ {
PollServiceArgs = pPollServiceArgs; PollServiceArgs = pPollServiceArgs;
HttpContext = pHttpContext; HttpContext = pHttpContext;

View File

@ -64,17 +64,25 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
public string uri; public string uri;
} }
/// <summary>
/// This module provides external URLs for in-world scripts.
/// </summary>
public class UrlModule : ISharedRegionModule, IUrlModule public class UrlModule : ISharedRegionModule, IUrlModule
{ {
private static readonly ILog m_log = private static readonly ILog m_log =
LogManager.GetLogger( LogManager.GetLogger(
MethodBase.GetCurrentMethod().DeclaringType); MethodBase.GetCurrentMethod().DeclaringType);
private Dictionary<UUID, UrlData> m_RequestMap = /// <summary>
new Dictionary<UUID, UrlData>(); /// Indexs the URL request metadata (which script requested it, outstanding requests, etc.) by the request ID
/// randomly generated when a request is received for this URL.
/// </summary>
private Dictionary<UUID, UrlData> m_RequestMap = new Dictionary<UUID, UrlData>();
private Dictionary<string, UrlData> m_UrlMap = /// <summary>
new Dictionary<string, UrlData>(); /// Indexs the URL request metadata (which script requested it, outstanding requests, etc.) by the full URL
/// </summary>
private Dictionary<string, UrlData> m_UrlMap = new Dictionary<string, UrlData>();
/// <summary> /// <summary>
/// Maximum number of external urls that can be set up by this module. /// Maximum number of external urls that can be set up by this module.
@ -215,7 +223,6 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
urlData.urlcode = urlcode; urlData.urlcode = urlcode;
urlData.requests = new Dictionary<UUID, RequestData>(); urlData.requests = new Dictionary<UUID, RequestData>();
m_UrlMap[url] = urlData; m_UrlMap[url] = urlData;
string uri = "/lslhttps/" + urlcode.ToString() + "/"; string uri = "/lslhttps/" + urlcode.ToString() + "/";
@ -269,7 +276,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
{ {
if (m_RequestMap.ContainsKey(requestId)) if (m_RequestMap.ContainsKey(requestId))
{ {
UrlData urlData=m_RequestMap[requestId]; UrlData urlData = m_RequestMap[requestId];
string value; string value;
if (urlData.requests[requestId].headers.TryGetValue(header,out value)) if (urlData.requests[requestId].headers.TryGetValue(header,out value))
return value; return value;
@ -278,6 +285,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
{ {
m_log.Warn("[HttpRequestHandler] There was no http-in request with id " + requestId); m_log.Warn("[HttpRequestHandler] There was no http-in request with id " + requestId);
} }
return String.Empty; return String.Empty;
} }
@ -322,6 +330,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
{ {
RemoveUrl(url.Value); RemoveUrl(url.Value);
removeURLs.Add(url.Key); removeURLs.Add(url.Key);
foreach (UUID req in url.Value.requests.Keys) foreach (UUID req in url.Value.requests.Keys)
m_RequestMap.Remove(req); m_RequestMap.Remove(req);
} }
@ -332,20 +341,31 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
} }
} }
private void RemoveUrl(UrlData data) private void RemoveUrl(UrlData data)
{ {
m_HttpServer.RemoveHTTPHandler("", "/lslhttp/"+data.urlcode.ToString()+"/"); m_HttpServer.RemoveHTTPHandler("", "/lslhttp/" + data.urlcode.ToString() + "/");
} }
private Hashtable NoEvents(UUID requestID, UUID sessionID) private Hashtable NoEvents(UUID requestID, UUID sessionID)
{ {
Hashtable response = new Hashtable(); Hashtable response = new Hashtable();
UrlData url; UrlData url;
lock (m_RequestMap) lock (m_RequestMap)
{ {
// We need to return a 404 here in case the request URL was removed at exactly the same time that a
// request was made. In this case, the request thread can outrace llRemoveURL() and still be polling
// for the request ID.
if (!m_RequestMap.ContainsKey(requestID)) if (!m_RequestMap.ContainsKey(requestID))
{
response["int_response_code"] = 404;
response["str_response_string"] = "";
response["keepalive"] = false;
response["reusecontext"] = false;
return response; return response;
}
url = m_RequestMap[requestID]; url = m_RequestMap[requestID];
} }
@ -367,53 +387,57 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
return response; return response;
} }
return response; return response;
} }
private bool HasEvents(UUID requestID, UUID sessionID) private bool HasEvents(UUID requestID, UUID sessionID)
{ {
UrlData url=null; UrlData url = null;
lock (m_RequestMap) lock (m_RequestMap)
{ {
// We return true here because an external URL request that happened at the same time as an llRemoveURL()
// can still make it through to HttpRequestHandler(). That will return without setting up a request
// when it detects that the URL has been removed. The poller, however, will continue to ask for
// events for that request, so here we will signal that there are events and in GetEvents we will
// return a 404.
if (!m_RequestMap.ContainsKey(requestID)) if (!m_RequestMap.ContainsKey(requestID))
{ {
return false; return true;
} }
url = m_RequestMap[requestID]; url = m_RequestMap[requestID];
if (!url.requests.ContainsKey(requestID)) if (!url.requests.ContainsKey(requestID))
{ {
return false; return true;
} }
} }
if (System.Environment.TickCount-url.requests[requestID].startTime>25000) // Trigger return of timeout response.
if (System.Environment.TickCount - url.requests[requestID].startTime > 25000)
{ {
return true; return true;
} }
if (url.requests[requestID].requestDone) return url.requests[requestID].requestDone;
return true;
else
return false;
} }
private Hashtable GetEvents(UUID requestID, UUID sessionID, string request) private Hashtable GetEvents(UUID requestID, UUID sessionID, string request)
{ {
UrlData url = null; UrlData url = null;
RequestData requestData = null; RequestData requestData = null;
lock (m_RequestMap) lock (m_RequestMap)
{ {
if (!m_RequestMap.ContainsKey(requestID)) if (!m_RequestMap.ContainsKey(requestID))
return NoEvents(requestID,sessionID); return NoEvents(requestID, sessionID);
url = m_RequestMap[requestID]; url = m_RequestMap[requestID];
requestData = url.requests[requestID]; requestData = url.requests[requestID];
} }
if (!requestData.requestDone) if (!requestData.requestDone)
return NoEvents(requestID,sessionID); return NoEvents(requestID, sessionID);
Hashtable response = new Hashtable(); Hashtable response = new Hashtable();
@ -426,6 +450,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
response["reusecontext"] = false; response["reusecontext"] = false;
return response; return response;
} }
//put response //put response
response["int_response_code"] = requestData.responseCode; response["int_response_code"] = requestData.responseCode;
response["str_response_string"] = requestData.responseBody; response["str_response_string"] = requestData.responseBody;
@ -442,6 +467,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
return response; return response;
} }
public void HttpRequestHandler(UUID requestID, Hashtable request) public void HttpRequestHandler(UUID requestID, Hashtable request)
{ {
lock (request) lock (request)
@ -466,11 +492,22 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
pathInfo = uri.Substring(pos3); pathInfo = uri.Substring(pos3);
UrlData url = null; UrlData urlData = null;
if (!is_ssl)
url = m_UrlMap["http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp]; lock (m_UrlMap)
else {
url = m_UrlMap["https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + uri_tmp]; string url;
if (is_ssl)
url = "https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + uri_tmp;
else
url = "http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp;
// Avoid a race - the request URL may have been released via llRequestUrl() whilst this
// request was being processed.
if (!m_UrlMap.TryGetValue(url, out urlData))
return;
}
//for llGetHttpHeader support we need to store original URI here //for llGetHttpHeader support we need to store original URI here
//to make x-path-info / x-query-string / x-script-url / x-remote-ip headers //to make x-path-info / x-query-string / x-script-url / x-remote-ip headers
@ -503,11 +540,10 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
queryString = queryString + key + "=" + val + "&"; queryString = queryString + key + "=" + val + "&";
} }
} }
if (queryString.Length > 1) if (queryString.Length > 1)
queryString = queryString.Substring(0, queryString.Length - 1); queryString = queryString.Substring(0, queryString.Length - 1);
} }
} }
//if this machine is behind DNAT/port forwarding, currently this is being //if this machine is behind DNAT/port forwarding, currently this is being
@ -515,26 +551,28 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp
requestData.headers["x-remote-ip"] = requestData.headers["remote_addr"]; requestData.headers["x-remote-ip"] = requestData.headers["remote_addr"];
requestData.headers["x-path-info"] = pathInfo; requestData.headers["x-path-info"] = pathInfo;
requestData.headers["x-query-string"] = queryString; requestData.headers["x-query-string"] = queryString;
requestData.headers["x-script-url"] = url.url; requestData.headers["x-script-url"] = urlData.url;
//requestData.ev = new ManualResetEvent(false); //requestData.ev = new ManualResetEvent(false);
lock (url.requests) lock (urlData.requests)
{ {
url.requests.Add(requestID, requestData); urlData.requests.Add(requestID, requestData);
}
lock (m_RequestMap)
{
//add to request map
m_RequestMap.Add(requestID, url);
} }
url.engine.PostScriptEvent(url.itemID, "http_request", new Object[] { requestID.ToString(), request["http-method"].ToString(), request["body"].ToString() }); lock (m_RequestMap)
{
m_RequestMap.Add(requestID, urlData);
}
urlData.engine.PostScriptEvent(
urlData.itemID,
"http_request",
new Object[] { requestID.ToString(), request["http-method"].ToString(), request["body"].ToString() });
//send initial response? //send initial response?
// Hashtable response = new Hashtable(); // Hashtable response = new Hashtable();
return; return;
} }
catch (Exception we) catch (Exception we)
{ {