From 134f86e8d5c414409631b25b8c6f0ee45fbd8631 Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Thu, 3 Nov 2016 21:44:39 +1000 Subject: Initial update to OpenSim 0.8.2.1 source code. --- .../Framework/Servers/HttpServer/BaseHttpServer.cs | 383 ++++++++++++++++++--- 1 file changed, 336 insertions(+), 47 deletions(-) (limited to 'OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs') diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index aa49343..f252bd5 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -46,6 +46,7 @@ using CoolHTTPListener = HttpServer.HttpListener; using HttpListener=System.Net.HttpListener; using LogPrio=HttpServer.LogPrio; using OpenSim.Framework.Monitoring; +using System.IO.Compression; namespace OpenSim.Framework.Servers.HttpServer { @@ -53,6 +54,16 @@ namespace OpenSim.Framework.Servers.HttpServer { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private HttpServerLogWriter httpserverlog = new HttpServerLogWriter(); + private static Encoding UTF8NoBOM = new System.Text.UTF8Encoding(false); + + /// + /// This is a pending websocket request before it got an sucessful upgrade response. + /// The consumer must call handler.HandshakeAndUpgrade() to signal to the handler to + /// start the connection and optionally provide an origin authentication method. + /// + /// + /// + public delegate void WebSocketRequestDelegate(string servicepath, WebSocketHttpServerHandler handler); /// /// Gets or sets the debug level. @@ -71,12 +82,18 @@ namespace OpenSim.Framework.Servers.HttpServer /// public int RequestNumber { get; private set; } + /// + /// Statistic for holding number of requests processed. + /// + private Stat m_requestsProcessedStat; + private volatile int NotSocketErrors = 0; public volatile bool HTTPDRunning = false; // protected HttpListener m_httpListener; protected CoolHTTPListener m_httpListener2; protected Dictionary m_rpcHandlers = new Dictionary(); + protected Dictionary jsonRpcHandlers = new Dictionary(); protected Dictionary m_rpcHandlersKeepAlive = new Dictionary(); protected DefaultLLSDMethod m_defaultLlsdHandler = null; // <-- Moving away from the monolithic.. and going to /registered/ protected Dictionary m_llsdHandlers = new Dictionary(); @@ -86,6 +103,9 @@ namespace OpenSim.Framework.Servers.HttpServer protected Dictionary m_pollHandlers = new Dictionary(); + protected Dictionary m_WebSocketHandlers = + new Dictionary(); + protected uint m_port; protected uint m_sslport; protected bool m_ssl; @@ -95,7 +115,7 @@ namespace OpenSim.Framework.Servers.HttpServer protected IPAddress m_listenIPAddress = IPAddress.Any; - private PollServiceRequestManager m_PollServiceManager; + public PollServiceRequestManager PollServiceRequestManager { get; private set; } public uint SSLPort { @@ -169,6 +189,22 @@ namespace OpenSim.Framework.Servers.HttpServer } } + public void AddWebSocketHandler(string servicepath, WebSocketRequestDelegate handler) + { + lock (m_WebSocketHandlers) + { + if (!m_WebSocketHandlers.ContainsKey(servicepath)) + m_WebSocketHandlers.Add(servicepath, handler); + } + } + + public void RemoveWebSocketHandler(string servicepath) + { + lock (m_WebSocketHandlers) + if (m_WebSocketHandlers.ContainsKey(servicepath)) + m_WebSocketHandlers.Remove(servicepath); + } + public List GetStreamHandlerKeys() { lock (m_streamHandlers) @@ -217,6 +253,37 @@ namespace OpenSim.Framework.Servers.HttpServer return new List(m_rpcHandlers.Keys); } + // JsonRPC + public bool AddJsonRPCHandler(string method, JsonRPCMethod handler) + { + lock(jsonRpcHandlers) + { + jsonRpcHandlers.Add(method, handler); + } + return true; + } + + public JsonRPCMethod GetJsonRPCHandler(string method) + { + lock (jsonRpcHandlers) + { + if (jsonRpcHandlers.ContainsKey(method)) + { + return jsonRpcHandlers[method]; + } + else + { + return null; + } + } + } + + public List GetJsonRpcHandlerKeys() + { + lock (jsonRpcHandlers) + return new List(jsonRpcHandlers.Keys); + } + public bool AddHTTPHandler(string methodName, GenericHTTPMethod handler) { //m_log.DebugFormat("[BASE HTTP SERVER]: Registering {0}", methodName); @@ -309,7 +376,7 @@ namespace OpenSim.Framework.Servers.HttpServer return true; } - private void OnRequest(object source, RequestEventArgs args) + public void OnRequest(object source, RequestEventArgs args) { RequestNumber++; @@ -322,6 +389,8 @@ namespace OpenSim.Framework.Servers.HttpServer if (TryGetPollServiceHTTPHandler(request.UriPath.ToString(), out psEvArgs)) { + psEvArgs.RequestsReceived++; + PollServiceHttpRequest psreq = new PollServiceHttpRequest(psEvArgs, context, request); if (psEvArgs.Request != null) @@ -362,7 +431,7 @@ namespace OpenSim.Framework.Servers.HttpServer psEvArgs.Request(psreq.RequestID, keysvals); } - m_PollServiceManager.Enqueue(psreq); + PollServiceRequestManager.Enqueue(psreq); } else { @@ -375,11 +444,24 @@ namespace OpenSim.Framework.Servers.HttpServer } } - public void OnHandleRequestIOThread(IHttpClientContext context, IHttpRequest request) + private void OnHandleRequestIOThread(IHttpClientContext context, IHttpRequest request) { OSHttpRequest req = new OSHttpRequest(context, request); + WebSocketRequestDelegate dWebSocketRequestDelegate = null; + lock (m_WebSocketHandlers) + { + if (m_WebSocketHandlers.ContainsKey(req.RawUrl)) + dWebSocketRequestDelegate = m_WebSocketHandlers[req.RawUrl]; + } + if (dWebSocketRequestDelegate != null) + { + dWebSocketRequestDelegate(req.Url.AbsolutePath, new WebSocketHttpServerHandler(req, context, 8192)); + return; + } + OSHttpResponse resp = new OSHttpResponse(new HttpResponse(context, request),context); - HandleRequest(req, resp); + resp.ReuseContext = true; + HandleRequest(req, resp); // !!!HACK ALERT!!! // There seems to be a bug in the underlying http code that makes subsequent requests @@ -410,7 +492,9 @@ namespace OpenSim.Framework.Servers.HttpServer { try { - SendHTML500(response); + byte[] buffer500 = SendHTML500(response); + response.OutputStream.Write(buffer500, 0, buffer500.Length); + response.Send(); } catch { @@ -468,7 +552,7 @@ namespace OpenSim.Framework.Servers.HttpServer LogIncomingToStreamHandler(request, requestHandler); response.ContentType = requestHandler.ContentType; // Lets do this defaulting before in case handler has varying content type. - + if (requestHandler is IStreamedRequestHandler) { IStreamedRequestHandler streamedRequestHandler = requestHandler as IStreamedRequestHandler; @@ -556,10 +640,18 @@ namespace OpenSim.Framework.Servers.HttpServer buffer = HandleLLSDRequests(request, response); break; + + case "application/json-rpc": + if (DebugLevel >= 3) + LogIncomingToContentTypeHandler(request); + + buffer = HandleJsonRpcRequests(request, response); + break; case "text/xml": case "application/xml": case "application/json": + default: //m_log.Info("[Debug BASE HTTP SERVER]: in default handler"); // Point of note.. the DoWeHaveA methods check for an EXACT path @@ -600,7 +692,24 @@ namespace OpenSim.Framework.Servers.HttpServer if (buffer != null) { - if (!response.SendChunked) + if (WebUtil.DebugLevel >= 5) + { + string output = System.Text.Encoding.UTF8.GetString(buffer); + + if (WebUtil.DebugLevel >= 6) + { + // Always truncate binary blobs. We don't have a ContentType, so detect them using the request name. + if ((requestHandler != null && requestHandler.Name == "GetMesh")) + { + if (output.Length > WebUtil.MaxRequestDiagLength) + output = output.Substring(0, WebUtil.MaxRequestDiagLength) + "..."; + } + } + + WebUtil.LogResponseDetail(RequestNumber, output); + } + + if (!response.SendChunked && response.ContentLength64 <= 0) response.ContentLength64 = buffer.LongLength; response.OutputStream.Write(buffer, 0, buffer.Length); @@ -630,12 +739,20 @@ namespace OpenSim.Framework.Servers.HttpServer } catch (IOException e) { - m_log.Error(String.Format("[BASE HTTP SERVER]: HandleRequest() threw {0} ", e.StackTrace), e); + m_log.Error("[BASE HTTP SERVER]: HandleRequest() threw exception ", e); } catch (Exception e) { - m_log.Error(String.Format("[BASE HTTP SERVER]: HandleRequest() threw {0} ", e.StackTrace), e); - SendHTML500(response); + m_log.Error("[BASE HTTP SERVER]: HandleRequest() threw exception ", e); + try + { + byte[] buffer500 = SendHTML500(response); + response.OutputStream.Write(buffer500, 0, buffer500.Length); + response.Send(); + } + catch + { + } } finally { @@ -645,7 +762,7 @@ namespace OpenSim.Framework.Servers.HttpServer if (tickdiff > 3000 && requestHandler != null && requestHandler.Name != "GetTexture") { m_log.InfoFormat( - "[BASE HTTP SERVER]: Slow handling of {0} {1} {2} {3} {4} from {5} took {6}ms", + "[LOGHTTP] Slow handling of {0} {1} {2} {3} {4} from {5} took {6}ms", RequestNumber, requestMethod, uriString, @@ -657,7 +774,7 @@ namespace OpenSim.Framework.Servers.HttpServer else if (DebugLevel >= 4) { m_log.DebugFormat( - "[BASE HTTP SERVER]: HTTP IN {0} :{1} took {2}ms", + "[LOGHTTP] HTTP IN {0} :{1} took {2}ms", RequestNumber, Port, tickdiff); @@ -668,7 +785,7 @@ namespace OpenSim.Framework.Servers.HttpServer private void LogIncomingToStreamHandler(OSHttpRequest request, IRequestHandler requestHandler) { m_log.DebugFormat( - "[BASE HTTP SERVER]: HTTP IN {0} :{1} stream handler {2} {3} {4} {5} from {6}", + "[LOGHTTP] HTTP IN {0} :{1} stream handler {2} {3} {4} {5} from {6}", RequestNumber, Port, request.HttpMethod, @@ -684,10 +801,10 @@ namespace OpenSim.Framework.Servers.HttpServer private void LogIncomingToContentTypeHandler(OSHttpRequest request) { m_log.DebugFormat( - "[BASE HTTP SERVER]: HTTP IN {0} :{1} {2} content type handler {3} {4} from {5}", + "[LOGHTTP] HTTP IN {0} :{1} {2} content type handler {3} {4} from {5}", RequestNumber, Port, - (request.ContentType == null || request.ContentType == "") ? "not set" : request.ContentType, + string.IsNullOrEmpty(request.ContentType) ? "not set" : request.ContentType, request.HttpMethod, request.Url.PathAndQuery, request.RemoteIPEndPoint); @@ -699,7 +816,7 @@ namespace OpenSim.Framework.Servers.HttpServer private void LogIncomingToXmlRpcHandler(OSHttpRequest request) { m_log.DebugFormat( - "[BASE HTTP SERVER]: HTTP IN {0} :{1} assumed generic XMLRPC request {2} {3} from {4}", + "[LOGHTTP] HTTP IN {0} :{1} assumed generic XMLRPC request {2} {3} from {4}", RequestNumber, Port, request.HttpMethod, @@ -712,29 +829,49 @@ namespace OpenSim.Framework.Servers.HttpServer private void LogIncomingInDetail(OSHttpRequest request) { - using (StreamReader reader = new StreamReader(Util.Copy(request.InputStream), Encoding.UTF8)) - { - string output; + if (request.ContentType == "application/octet-stream") + return; // never log these; they're just binary data - if (DebugLevel == 5) + Stream inputStream = Util.Copy(request.InputStream); + Stream innerStream = null; + try + { + if ((request.Headers["Content-Encoding"] == "gzip") || (request.Headers["X-Content-Encoding"] == "gzip")) { - const int sampleLength = 80; - char[] sampleChars = new char[sampleLength + 3]; - reader.Read(sampleChars, 0, sampleLength); - sampleChars[80] = '.'; - sampleChars[81] = '.'; - sampleChars[82] = '.'; - output = new string(sampleChars); + innerStream = inputStream; + inputStream = new GZipStream(innerStream, System.IO.Compression.CompressionMode.Decompress); } - else + + using (StreamReader reader = new StreamReader(inputStream, Encoding.UTF8)) { - output = reader.ReadToEnd(); - } + string output; - m_log.DebugFormat("[BASE HTTP SERVER]: {0}", output.Replace("\n", @"\n")); + if (DebugLevel == 5) + { + char[] chars = new char[WebUtil.MaxRequestDiagLength + 1]; // +1 so we know to add "..." only if needed + int len = reader.Read(chars, 0, WebUtil.MaxRequestDiagLength + 1); + output = new string(chars, 0, Math.Min(len, WebUtil.MaxRequestDiagLength)); + if (len > WebUtil.MaxRequestDiagLength) + output += "..."; + } + else + { + output = reader.ReadToEnd(); + } + + m_log.DebugFormat("[LOGHTTP] {0}", Util.BinaryToASCII(output)); + } + } + finally + { + if (innerStream != null) + innerStream.Dispose(); + inputStream.Dispose(); } } + private readonly string HANDLER_SEPARATORS = "/?&#-"; + private bool TryGetStreamHandler(string handlerKey, out IRequestHandler streamHandler) { string bestMatch = null; @@ -743,7 +880,8 @@ namespace OpenSim.Framework.Servers.HttpServer { foreach (string pattern in m_streamHandlers.Keys) { - if (handlerKey.StartsWith(pattern)) + if ((handlerKey == pattern) + || (handlerKey.StartsWith(pattern) && (HANDLER_SEPARATORS.IndexOf(handlerKey[pattern.Length]) >= 0))) { if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) { @@ -773,7 +911,8 @@ namespace OpenSim.Framework.Servers.HttpServer { foreach (string pattern in m_pollHandlers.Keys) { - if (handlerKey.StartsWith(pattern)) + if ((handlerKey == pattern) + || (handlerKey.StartsWith(pattern) && (HANDLER_SEPARATORS.IndexOf(handlerKey[pattern.Length]) >= 0))) { if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) { @@ -805,7 +944,8 @@ namespace OpenSim.Framework.Servers.HttpServer { foreach (string pattern in m_HTTPHandlers.Keys) { - if (handlerKey.StartsWith(pattern)) + if ((handlerKey == pattern) + || (handlerKey.StartsWith(pattern) && (HANDLER_SEPARATORS.IndexOf(handlerKey[pattern.Length]) >= 0))) { if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) { @@ -854,16 +994,33 @@ namespace OpenSim.Framework.Servers.HttpServer /// private byte[] HandleXmlRpcRequests(OSHttpRequest request, OSHttpResponse response) { + String requestBody; + Stream requestStream = request.InputStream; + Stream innerStream = null; + try + { + if ((request.Headers["Content-Encoding"] == "gzip") || (request.Headers["X-Content-Encoding"] == "gzip")) + { + innerStream = requestStream; + requestStream = new GZipStream(innerStream, System.IO.Compression.CompressionMode.Decompress); + } - Encoding encoding = Encoding.UTF8; - StreamReader reader = new StreamReader(requestStream, encoding); + using (StreamReader reader = new StreamReader(requestStream, Encoding.UTF8)) + { + requestBody = reader.ReadToEnd(); + } + } + finally + { + if (innerStream != null) + innerStream.Dispose(); + requestStream.Dispose(); + } - string requestBody = reader.ReadToEnd(); - reader.Close(); - requestStream.Close(); //m_log.Debug(requestBody); requestBody = requestBody.Replace("", ""); + string responseString = String.Empty; XmlRpcRequest xmlRprcRequest = null; @@ -958,7 +1115,19 @@ namespace OpenSim.Framework.Servers.HttpServer } response.ContentType = "text/xml"; - responseString = XmlRpcResponseSerializer.Singleton.Serialize(xmlRpcResponse); + using (MemoryStream outs = new MemoryStream()) + using (XmlTextWriter writer = new XmlTextWriter(outs, UTF8NoBOM)) + { + writer.Formatting = Formatting.None; + XmlRpcResponseSerializer.Singleton.Serialize(writer, xmlRpcResponse); + writer.Flush(); + outs.Flush(); + outs.Position = 0; + using (StreamReader sr = new StreamReader(outs)) + { + responseString = sr.ReadToEnd(); + } + } } else { @@ -985,6 +1154,93 @@ namespace OpenSim.Framework.Servers.HttpServer return buffer; } + // JsonRpc (v2.0 only) + // Batch requests not yet supported + private byte[] HandleJsonRpcRequests(OSHttpRequest request, OSHttpResponse response) + { + Stream requestStream = request.InputStream; + JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(); + OSDMap jsonRpcRequest = null; + + try + { + jsonRpcRequest = (OSDMap)OSDParser.DeserializeJson(requestStream); + } + catch (LitJson.JsonException e) + { + jsonRpcResponse.Error.Code = ErrorCode.InternalError; + jsonRpcResponse.Error.Message = e.Message; + } + + requestStream.Close(); + + if (jsonRpcRequest != null) + { + if (jsonRpcRequest.ContainsKey("jsonrpc") || jsonRpcRequest["jsonrpc"].AsString() == "2.0") + { + jsonRpcResponse.JsonRpc = "2.0"; + + // If we have no id, then it's a "notification" + if (jsonRpcRequest.ContainsKey("id")) + { + jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); + } + + string methodname = jsonRpcRequest["method"]; + JsonRPCMethod method; + + if (jsonRpcHandlers.ContainsKey(methodname)) + { + lock(jsonRpcHandlers) + { + jsonRpcHandlers.TryGetValue(methodname, out method); + } + bool res = false; + try + { + res = method(jsonRpcRequest, ref jsonRpcResponse); + if(!res) + { + // The handler sent back an unspecified error + if(jsonRpcResponse.Error.Code == 0) + { + jsonRpcResponse.Error.Code = ErrorCode.InternalError; + } + } + } + catch (Exception e) + { + string ErrorMessage = string.Format("[BASE HTTP SERVER]: Json-Rpc Handler Error method {0} - {1}", methodname, e.Message); + m_log.Error(ErrorMessage); + jsonRpcResponse.Error.Code = ErrorCode.InternalError; + jsonRpcResponse.Error.Message = ErrorMessage; + } + } + else // Error no hanlder defined for requested method + { + jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; + jsonRpcResponse.Error.Message = string.Format ("No handler defined for {0}", methodname); + } + } + else // not json-rpc 2.0 could be v1 + { + jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; + jsonRpcResponse.Error.Message = "Must be valid json-rpc 2.0 see: http://www.jsonrpc.org/specification"; + + if (jsonRpcRequest.ContainsKey("id")) + jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); + } + } + + response.KeepAlive = true; + string responseData = string.Empty; + + responseData = jsonRpcResponse.Serialize(); + + byte[] buffer = Encoding.UTF8.GetBytes(responseData); + return buffer; + } + private byte[] HandleLLSDRequests(OSHttpRequest request, OSHttpResponse response) { //m_log.Warn("[BASE HTTP SERVER]: We've figured out it's a LLSD Request"); @@ -1575,16 +1831,24 @@ namespace OpenSim.Framework.Servers.HttpServer response.SendChunked = false; response.ContentLength64 = buffer.Length; response.ContentEncoding = Encoding.UTF8; - + + return buffer; } public void Start() { - StartHTTP(); + Start(true); } - private void StartHTTP() + /// + /// Start the http server + /// + /// + /// If true then poll responses are performed asynchronsly. + /// Option exists to allow regression tests to perform processing synchronously. + /// + public void Start(bool performPollResponsesAsync) { m_log.InfoFormat( "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); @@ -1622,8 +1886,9 @@ namespace OpenSim.Framework.Servers.HttpServer m_httpListener2.Start(64); // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events - m_PollServiceManager = new PollServiceRequestManager(this, 3, 25000); - m_PollServiceManager.Start(); + PollServiceRequestManager = new PollServiceRequestManager(this, performPollResponsesAsync, 3, 25000); + PollServiceRequestManager.Start(); + HTTPDRunning = true; //HttpListenerContext context; @@ -1642,6 +1907,21 @@ namespace OpenSim.Framework.Servers.HttpServer // useful without inbound HTTP. throw e; } + + m_requestsProcessedStat + = new Stat( + "HTTPRequestsServed", + "Number of inbound HTTP requests processed", + "", + "requests", + "httpserver", + Port.ToString(), + StatType.Pull, + MeasuresOfInterest.AverageChangeOverTime, + stat => stat.Value = RequestNumber, + StatVerbosity.Debug); + + StatsManager.RegisterStat(m_requestsProcessedStat); } public void httpServerDisconnectMonitor(IHttpClientContext source, SocketError err) @@ -1672,9 +1952,12 @@ namespace OpenSim.Framework.Servers.HttpServer public void Stop() { HTTPDRunning = false; + + StatsManager.DeregisterStat(m_requestsProcessedStat); + try { - m_PollServiceManager.Stop(); + PollServiceRequestManager.Stop(); m_httpListener2.ExceptionThrown -= httpServerException; //m_httpListener2.DisconnectHandler = null; @@ -1741,6 +2024,12 @@ namespace OpenSim.Framework.Servers.HttpServer m_rpcHandlers.Remove(method); } + public void RemoveJsonRPCHandler(string method) + { + lock(jsonRpcHandlers) + jsonRpcHandlers.Remove(method); + } + public bool RemoveLLSDHandler(string path, LLSDMethod handler) { lock (m_llsdHandlers) -- cgit v1.1