diff options
Diffstat (limited to 'OpenSim/Framework/Servers')
48 files changed, 9994 insertions, 0 deletions
diff --git a/OpenSim/Framework/Servers/BaseOpenSimServer.cs b/OpenSim/Framework/Servers/BaseOpenSimServer.cs new file mode 100644 index 0000000..828a852 --- /dev/null +++ b/OpenSim/Framework/Servers/BaseOpenSimServer.cs | |||
@@ -0,0 +1,178 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Reflection; | ||
32 | using System.Text; | ||
33 | using System.Text.RegularExpressions; | ||
34 | using System.Threading; | ||
35 | using System.Timers; | ||
36 | using log4net; | ||
37 | using log4net.Appender; | ||
38 | using log4net.Core; | ||
39 | using log4net.Repository; | ||
40 | using OpenMetaverse; | ||
41 | using OpenMetaverse.StructuredData; | ||
42 | using OpenSim.Framework; | ||
43 | using OpenSim.Framework.Console; | ||
44 | using OpenSim.Framework.Monitoring; | ||
45 | using OpenSim.Framework.Servers; | ||
46 | using OpenSim.Framework.Servers.HttpServer; | ||
47 | using Timer=System.Timers.Timer; | ||
48 | using Nini.Config; | ||
49 | |||
50 | namespace OpenSim.Framework.Servers | ||
51 | { | ||
52 | /// <summary> | ||
53 | /// Common base for the main OpenSimServers (user, grid, inventory, region, etc) | ||
54 | /// </summary> | ||
55 | public abstract class BaseOpenSimServer : ServerBase | ||
56 | { | ||
57 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
58 | |||
59 | /// <summary> | ||
60 | /// Used by tests to suppress Environment.Exit(0) so that post-run operations are possible. | ||
61 | /// </summary> | ||
62 | public bool SuppressExit { get; set; } | ||
63 | |||
64 | /// <summary> | ||
65 | /// This will control a periodic log printout of the current 'show stats' (if they are active) for this | ||
66 | /// server. | ||
67 | /// </summary> | ||
68 | private int m_periodDiagnosticTimerMS = 60 * 60 * 1000; | ||
69 | private Timer m_periodicDiagnosticsTimer = new Timer(60 * 60 * 1000); | ||
70 | |||
71 | /// <summary> | ||
72 | /// Random uuid for private data | ||
73 | /// </summary> | ||
74 | protected string m_osSecret = String.Empty; | ||
75 | |||
76 | protected BaseHttpServer m_httpServer; | ||
77 | public BaseHttpServer HttpServer | ||
78 | { | ||
79 | get { return m_httpServer; } | ||
80 | } | ||
81 | |||
82 | public BaseOpenSimServer() : base() | ||
83 | { | ||
84 | // Random uuid for private data | ||
85 | m_osSecret = UUID.Random().ToString(); | ||
86 | |||
87 | } | ||
88 | |||
89 | /// <summary> | ||
90 | /// Must be overriden by child classes for their own server specific startup behaviour. | ||
91 | /// </summary> | ||
92 | protected virtual void StartupSpecific() | ||
93 | { | ||
94 | StatsManager.SimExtraStats = new SimExtraStatsCollector(); | ||
95 | RegisterCommonCommands(); | ||
96 | RegisterCommonComponents(Config); | ||
97 | |||
98 | IConfig startupConfig = Config.Configs["Startup"]; | ||
99 | int logShowStatsSeconds = startupConfig.GetInt("LogShowStatsSeconds", m_periodDiagnosticTimerMS / 1000); | ||
100 | m_periodDiagnosticTimerMS = logShowStatsSeconds * 1000; | ||
101 | m_periodicDiagnosticsTimer.Elapsed += new ElapsedEventHandler(LogDiagnostics); | ||
102 | if (m_periodDiagnosticTimerMS != 0) | ||
103 | { | ||
104 | m_periodicDiagnosticsTimer.Interval = m_periodDiagnosticTimerMS; | ||
105 | m_periodicDiagnosticsTimer.Enabled = true; | ||
106 | } | ||
107 | } | ||
108 | |||
109 | protected override void ShutdownSpecific() | ||
110 | { | ||
111 | m_log.Info("[SHUTDOWN]: Shutdown processing on main thread complete. Exiting..."); | ||
112 | |||
113 | RemovePIDFile(); | ||
114 | |||
115 | base.ShutdownSpecific(); | ||
116 | |||
117 | if (!SuppressExit) | ||
118 | Environment.Exit(0); | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// Provides a list of help topics that are available. Overriding classes should append their topics to the | ||
123 | /// information returned when the base method is called. | ||
124 | /// </summary> | ||
125 | /// | ||
126 | /// <returns> | ||
127 | /// A list of strings that represent different help topics on which more information is available | ||
128 | /// </returns> | ||
129 | protected virtual List<string> GetHelpTopics() { return new List<string>(); } | ||
130 | |||
131 | /// <summary> | ||
132 | /// Print statistics to the logfile, if they are active | ||
133 | /// </summary> | ||
134 | protected void LogDiagnostics(object source, ElapsedEventArgs e) | ||
135 | { | ||
136 | StringBuilder sb = new StringBuilder("DIAGNOSTICS\n\n"); | ||
137 | sb.Append(GetUptimeReport()); | ||
138 | sb.Append(StatsManager.SimExtraStats.Report()); | ||
139 | sb.Append(Environment.NewLine); | ||
140 | sb.Append(GetThreadsReport()); | ||
141 | |||
142 | m_log.Debug(sb); | ||
143 | } | ||
144 | |||
145 | /// <summary> | ||
146 | /// Performs initialisation of the scene, such as loading configuration from disk. | ||
147 | /// </summary> | ||
148 | public virtual void Startup() | ||
149 | { | ||
150 | StartupSpecific(); | ||
151 | |||
152 | TimeSpan timeTaken = DateTime.Now - m_startuptime; | ||
153 | |||
154 | MainConsole.Instance.OutputFormat( | ||
155 | "PLEASE WAIT FOR LOGINS TO BE ENABLED ON REGIONS ONCE SCRIPTS HAVE STARTED. Non-script portion of startup took {0}m {1}s.", | ||
156 | timeTaken.Minutes, timeTaken.Seconds); | ||
157 | } | ||
158 | |||
159 | public string osSecret | ||
160 | { | ||
161 | // Secret uuid for the simulator | ||
162 | get { return m_osSecret; } | ||
163 | } | ||
164 | |||
165 | public string StatReport(IOSHttpRequest httpRequest) | ||
166 | { | ||
167 | // If we catch a request for "callback", wrap the response in the value for jsonp | ||
168 | if (httpRequest.Query.ContainsKey("callback")) | ||
169 | { | ||
170 | return httpRequest.Query["callback"].ToString() + "(" + StatsManager.SimExtraStats.XReport((DateTime.Now - m_startuptime).ToString() , m_version) + ");"; | ||
171 | } | ||
172 | else | ||
173 | { | ||
174 | return StatsManager.SimExtraStats.XReport((DateTime.Now - m_startuptime).ToString() , m_version); | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHTTPHandler.cs b/OpenSim/Framework/Servers/HttpServer/BaseHTTPHandler.cs new file mode 100644 index 0000000..9f8f4a8 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BaseHTTPHandler.cs | |||
@@ -0,0 +1,41 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Collections; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | public abstract class BaseHTTPHandler : BaseRequestHandler, IGenericHTTPHandler | ||
33 | { | ||
34 | public abstract Hashtable Handle(string path, Hashtable Request); | ||
35 | |||
36 | protected BaseHTTPHandler(string httpMethod, string path) : this(httpMethod, path, null, null) {} | ||
37 | |||
38 | protected BaseHTTPHandler(string httpMethod, string path, string name, string description) | ||
39 | : base(httpMethod, path, name, description) {} | ||
40 | } | ||
41 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs new file mode 100644 index 0000000..f252bd5 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs | |||
@@ -0,0 +1,2147 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Collections.Specialized; | ||
32 | using System.IO; | ||
33 | using System.Net; | ||
34 | using System.Net.Sockets; | ||
35 | using System.Security.Cryptography.X509Certificates; | ||
36 | using System.Reflection; | ||
37 | using System.Globalization; | ||
38 | using System.Text; | ||
39 | using System.Threading; | ||
40 | using System.Xml; | ||
41 | using HttpServer; | ||
42 | using log4net; | ||
43 | using Nwc.XmlRpc; | ||
44 | using OpenMetaverse.StructuredData; | ||
45 | using CoolHTTPListener = HttpServer.HttpListener; | ||
46 | using HttpListener=System.Net.HttpListener; | ||
47 | using LogPrio=HttpServer.LogPrio; | ||
48 | using OpenSim.Framework.Monitoring; | ||
49 | using System.IO.Compression; | ||
50 | |||
51 | namespace OpenSim.Framework.Servers.HttpServer | ||
52 | { | ||
53 | public class BaseHttpServer : IHttpServer | ||
54 | { | ||
55 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
56 | private HttpServerLogWriter httpserverlog = new HttpServerLogWriter(); | ||
57 | private static Encoding UTF8NoBOM = new System.Text.UTF8Encoding(false); | ||
58 | |||
59 | /// <summary> | ||
60 | /// This is a pending websocket request before it got an sucessful upgrade response. | ||
61 | /// The consumer must call handler.HandshakeAndUpgrade() to signal to the handler to | ||
62 | /// start the connection and optionally provide an origin authentication method. | ||
63 | /// </summary> | ||
64 | /// <param name="servicepath"></param> | ||
65 | /// <param name="handler"></param> | ||
66 | public delegate void WebSocketRequestDelegate(string servicepath, WebSocketHttpServerHandler handler); | ||
67 | |||
68 | /// <summary> | ||
69 | /// Gets or sets the debug level. | ||
70 | /// </summary> | ||
71 | /// <value> | ||
72 | /// See MainServer.DebugLevel. | ||
73 | /// </value> | ||
74 | public int DebugLevel { get; set; } | ||
75 | |||
76 | /// <summary> | ||
77 | /// Request number for diagnostic purposes. | ||
78 | /// </summary> | ||
79 | /// <remarks> | ||
80 | /// This is an internal number. In some debug situations an external number may also be supplied in the | ||
81 | /// opensim-request-id header but we are not currently logging this. | ||
82 | /// </remarks> | ||
83 | public int RequestNumber { get; private set; } | ||
84 | |||
85 | /// <summary> | ||
86 | /// Statistic for holding number of requests processed. | ||
87 | /// </summary> | ||
88 | private Stat m_requestsProcessedStat; | ||
89 | |||
90 | private volatile int NotSocketErrors = 0; | ||
91 | public volatile bool HTTPDRunning = false; | ||
92 | |||
93 | // protected HttpListener m_httpListener; | ||
94 | protected CoolHTTPListener m_httpListener2; | ||
95 | protected Dictionary<string, XmlRpcMethod> m_rpcHandlers = new Dictionary<string, XmlRpcMethod>(); | ||
96 | protected Dictionary<string, JsonRPCMethod> jsonRpcHandlers = new Dictionary<string, JsonRPCMethod>(); | ||
97 | protected Dictionary<string, bool> m_rpcHandlersKeepAlive = new Dictionary<string, bool>(); | ||
98 | protected DefaultLLSDMethod m_defaultLlsdHandler = null; // <-- Moving away from the monolithic.. and going to /registered/ | ||
99 | protected Dictionary<string, LLSDMethod> m_llsdHandlers = new Dictionary<string, LLSDMethod>(); | ||
100 | protected Dictionary<string, IRequestHandler> m_streamHandlers = new Dictionary<string, IRequestHandler>(); | ||
101 | protected Dictionary<string, GenericHTTPMethod> m_HTTPHandlers = new Dictionary<string, GenericHTTPMethod>(); | ||
102 | // protected Dictionary<string, IHttpAgentHandler> m_agentHandlers = new Dictionary<string, IHttpAgentHandler>(); | ||
103 | protected Dictionary<string, PollServiceEventArgs> m_pollHandlers = | ||
104 | new Dictionary<string, PollServiceEventArgs>(); | ||
105 | |||
106 | protected Dictionary<string, WebSocketRequestDelegate> m_WebSocketHandlers = | ||
107 | new Dictionary<string, WebSocketRequestDelegate>(); | ||
108 | |||
109 | protected uint m_port; | ||
110 | protected uint m_sslport; | ||
111 | protected bool m_ssl; | ||
112 | private X509Certificate2 m_cert; | ||
113 | protected bool m_firstcaps = true; | ||
114 | protected string m_SSLCommonName = ""; | ||
115 | |||
116 | protected IPAddress m_listenIPAddress = IPAddress.Any; | ||
117 | |||
118 | public PollServiceRequestManager PollServiceRequestManager { get; private set; } | ||
119 | |||
120 | public uint SSLPort | ||
121 | { | ||
122 | get { return m_sslport; } | ||
123 | } | ||
124 | |||
125 | public string SSLCommonName | ||
126 | { | ||
127 | get { return m_SSLCommonName; } | ||
128 | } | ||
129 | |||
130 | public uint Port | ||
131 | { | ||
132 | get { return m_port; } | ||
133 | } | ||
134 | |||
135 | public bool UseSSL | ||
136 | { | ||
137 | get { return m_ssl; } | ||
138 | } | ||
139 | |||
140 | public IPAddress ListenIPAddress | ||
141 | { | ||
142 | get { return m_listenIPAddress; } | ||
143 | set { m_listenIPAddress = value; } | ||
144 | } | ||
145 | |||
146 | public BaseHttpServer(uint port) | ||
147 | { | ||
148 | m_port = port; | ||
149 | } | ||
150 | |||
151 | public BaseHttpServer(uint port, bool ssl) : this (port) | ||
152 | { | ||
153 | m_ssl = ssl; | ||
154 | } | ||
155 | |||
156 | public BaseHttpServer(uint port, bool ssl, uint sslport, string CN) : this (port, ssl) | ||
157 | { | ||
158 | if (m_ssl) | ||
159 | { | ||
160 | m_sslport = sslport; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | public BaseHttpServer(uint port, bool ssl, string CPath, string CPass) : this (port, ssl) | ||
165 | { | ||
166 | if (m_ssl) | ||
167 | { | ||
168 | m_cert = new X509Certificate2(CPath, CPass); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | /// <summary> | ||
173 | /// Add a stream handler to the http server. If the handler already exists, then nothing happens. | ||
174 | /// </summary> | ||
175 | /// <param name="handler"></param> | ||
176 | public void AddStreamHandler(IRequestHandler handler) | ||
177 | { | ||
178 | string httpMethod = handler.HttpMethod; | ||
179 | string path = handler.Path; | ||
180 | string handlerKey = GetHandlerKey(httpMethod, path); | ||
181 | |||
182 | lock (m_streamHandlers) | ||
183 | { | ||
184 | if (!m_streamHandlers.ContainsKey(handlerKey)) | ||
185 | { | ||
186 | // m_log.DebugFormat("[BASE HTTP SERVER]: Adding handler key {0}", handlerKey); | ||
187 | m_streamHandlers.Add(handlerKey, handler); | ||
188 | } | ||
189 | } | ||
190 | } | ||
191 | |||
192 | public void AddWebSocketHandler(string servicepath, WebSocketRequestDelegate handler) | ||
193 | { | ||
194 | lock (m_WebSocketHandlers) | ||
195 | { | ||
196 | if (!m_WebSocketHandlers.ContainsKey(servicepath)) | ||
197 | m_WebSocketHandlers.Add(servicepath, handler); | ||
198 | } | ||
199 | } | ||
200 | |||
201 | public void RemoveWebSocketHandler(string servicepath) | ||
202 | { | ||
203 | lock (m_WebSocketHandlers) | ||
204 | if (m_WebSocketHandlers.ContainsKey(servicepath)) | ||
205 | m_WebSocketHandlers.Remove(servicepath); | ||
206 | } | ||
207 | |||
208 | public List<string> GetStreamHandlerKeys() | ||
209 | { | ||
210 | lock (m_streamHandlers) | ||
211 | return new List<string>(m_streamHandlers.Keys); | ||
212 | } | ||
213 | |||
214 | private static string GetHandlerKey(string httpMethod, string path) | ||
215 | { | ||
216 | return httpMethod + ":" + path; | ||
217 | } | ||
218 | |||
219 | public bool AddXmlRPCHandler(string method, XmlRpcMethod handler) | ||
220 | { | ||
221 | return AddXmlRPCHandler(method, handler, true); | ||
222 | } | ||
223 | |||
224 | public bool AddXmlRPCHandler(string method, XmlRpcMethod handler, bool keepAlive) | ||
225 | { | ||
226 | lock (m_rpcHandlers) | ||
227 | { | ||
228 | m_rpcHandlers[method] = handler; | ||
229 | m_rpcHandlersKeepAlive[method] = keepAlive; // default | ||
230 | } | ||
231 | |||
232 | return true; | ||
233 | } | ||
234 | |||
235 | public XmlRpcMethod GetXmlRPCHandler(string method) | ||
236 | { | ||
237 | lock (m_rpcHandlers) | ||
238 | { | ||
239 | if (m_rpcHandlers.ContainsKey(method)) | ||
240 | { | ||
241 | return m_rpcHandlers[method]; | ||
242 | } | ||
243 | else | ||
244 | { | ||
245 | return null; | ||
246 | } | ||
247 | } | ||
248 | } | ||
249 | |||
250 | public List<string> GetXmlRpcHandlerKeys() | ||
251 | { | ||
252 | lock (m_rpcHandlers) | ||
253 | return new List<string>(m_rpcHandlers.Keys); | ||
254 | } | ||
255 | |||
256 | // JsonRPC | ||
257 | public bool AddJsonRPCHandler(string method, JsonRPCMethod handler) | ||
258 | { | ||
259 | lock(jsonRpcHandlers) | ||
260 | { | ||
261 | jsonRpcHandlers.Add(method, handler); | ||
262 | } | ||
263 | return true; | ||
264 | } | ||
265 | |||
266 | public JsonRPCMethod GetJsonRPCHandler(string method) | ||
267 | { | ||
268 | lock (jsonRpcHandlers) | ||
269 | { | ||
270 | if (jsonRpcHandlers.ContainsKey(method)) | ||
271 | { | ||
272 | return jsonRpcHandlers[method]; | ||
273 | } | ||
274 | else | ||
275 | { | ||
276 | return null; | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | public List<string> GetJsonRpcHandlerKeys() | ||
282 | { | ||
283 | lock (jsonRpcHandlers) | ||
284 | return new List<string>(jsonRpcHandlers.Keys); | ||
285 | } | ||
286 | |||
287 | public bool AddHTTPHandler(string methodName, GenericHTTPMethod handler) | ||
288 | { | ||
289 | //m_log.DebugFormat("[BASE HTTP SERVER]: Registering {0}", methodName); | ||
290 | |||
291 | lock (m_HTTPHandlers) | ||
292 | { | ||
293 | if (!m_HTTPHandlers.ContainsKey(methodName)) | ||
294 | { | ||
295 | m_HTTPHandlers.Add(methodName, handler); | ||
296 | return true; | ||
297 | } | ||
298 | } | ||
299 | |||
300 | //must already have a handler for that path so return false | ||
301 | return false; | ||
302 | } | ||
303 | |||
304 | public List<string> GetHTTPHandlerKeys() | ||
305 | { | ||
306 | lock (m_HTTPHandlers) | ||
307 | return new List<string>(m_HTTPHandlers.Keys); | ||
308 | } | ||
309 | |||
310 | public bool AddPollServiceHTTPHandler(string methodName, PollServiceEventArgs args) | ||
311 | { | ||
312 | lock (m_pollHandlers) | ||
313 | { | ||
314 | if (!m_pollHandlers.ContainsKey(methodName)) | ||
315 | { | ||
316 | m_pollHandlers.Add(methodName, args); | ||
317 | return true; | ||
318 | } | ||
319 | } | ||
320 | |||
321 | return false; | ||
322 | } | ||
323 | |||
324 | public List<string> GetPollServiceHandlerKeys() | ||
325 | { | ||
326 | lock (m_pollHandlers) | ||
327 | return new List<string>(m_pollHandlers.Keys); | ||
328 | } | ||
329 | |||
330 | // // Note that the agent string is provided simply to differentiate | ||
331 | // // the handlers - it is NOT required to be an actual agent header | ||
332 | // // value. | ||
333 | // public bool AddAgentHandler(string agent, IHttpAgentHandler handler) | ||
334 | // { | ||
335 | // lock (m_agentHandlers) | ||
336 | // { | ||
337 | // if (!m_agentHandlers.ContainsKey(agent)) | ||
338 | // { | ||
339 | // m_agentHandlers.Add(agent, handler); | ||
340 | // return true; | ||
341 | // } | ||
342 | // } | ||
343 | // | ||
344 | // //must already have a handler for that path so return false | ||
345 | // return false; | ||
346 | // } | ||
347 | // | ||
348 | // public List<string> GetAgentHandlerKeys() | ||
349 | // { | ||
350 | // lock (m_agentHandlers) | ||
351 | // return new List<string>(m_agentHandlers.Keys); | ||
352 | // } | ||
353 | |||
354 | public bool AddLLSDHandler(string path, LLSDMethod handler) | ||
355 | { | ||
356 | lock (m_llsdHandlers) | ||
357 | { | ||
358 | if (!m_llsdHandlers.ContainsKey(path)) | ||
359 | { | ||
360 | m_llsdHandlers.Add(path, handler); | ||
361 | return true; | ||
362 | } | ||
363 | } | ||
364 | return false; | ||
365 | } | ||
366 | |||
367 | public List<string> GetLLSDHandlerKeys() | ||
368 | { | ||
369 | lock (m_llsdHandlers) | ||
370 | return new List<string>(m_llsdHandlers.Keys); | ||
371 | } | ||
372 | |||
373 | public bool SetDefaultLLSDHandler(DefaultLLSDMethod handler) | ||
374 | { | ||
375 | m_defaultLlsdHandler = handler; | ||
376 | return true; | ||
377 | } | ||
378 | |||
379 | public void OnRequest(object source, RequestEventArgs args) | ||
380 | { | ||
381 | RequestNumber++; | ||
382 | |||
383 | try | ||
384 | { | ||
385 | IHttpClientContext context = (IHttpClientContext)source; | ||
386 | IHttpRequest request = args.Request; | ||
387 | |||
388 | PollServiceEventArgs psEvArgs; | ||
389 | |||
390 | if (TryGetPollServiceHTTPHandler(request.UriPath.ToString(), out psEvArgs)) | ||
391 | { | ||
392 | psEvArgs.RequestsReceived++; | ||
393 | |||
394 | PollServiceHttpRequest psreq = new PollServiceHttpRequest(psEvArgs, context, request); | ||
395 | |||
396 | if (psEvArgs.Request != null) | ||
397 | { | ||
398 | OSHttpRequest req = new OSHttpRequest(context, request); | ||
399 | |||
400 | Stream requestStream = req.InputStream; | ||
401 | |||
402 | Encoding encoding = Encoding.UTF8; | ||
403 | StreamReader reader = new StreamReader(requestStream, encoding); | ||
404 | |||
405 | string requestBody = reader.ReadToEnd(); | ||
406 | |||
407 | Hashtable keysvals = new Hashtable(); | ||
408 | Hashtable headervals = new Hashtable(); | ||
409 | |||
410 | string[] querystringkeys = req.QueryString.AllKeys; | ||
411 | string[] rHeaders = req.Headers.AllKeys; | ||
412 | |||
413 | keysvals.Add("body", requestBody); | ||
414 | keysvals.Add("uri", req.RawUrl); | ||
415 | keysvals.Add("content-type", req.ContentType); | ||
416 | keysvals.Add("http-method", req.HttpMethod); | ||
417 | |||
418 | foreach (string queryname in querystringkeys) | ||
419 | { | ||
420 | keysvals.Add(queryname, req.QueryString[queryname]); | ||
421 | } | ||
422 | |||
423 | foreach (string headername in rHeaders) | ||
424 | { | ||
425 | headervals[headername] = req.Headers[headername]; | ||
426 | } | ||
427 | |||
428 | keysvals.Add("headers", headervals); | ||
429 | keysvals.Add("querystringkeys", querystringkeys); | ||
430 | |||
431 | psEvArgs.Request(psreq.RequestID, keysvals); | ||
432 | } | ||
433 | |||
434 | PollServiceRequestManager.Enqueue(psreq); | ||
435 | } | ||
436 | else | ||
437 | { | ||
438 | OnHandleRequestIOThread(context, request); | ||
439 | } | ||
440 | } | ||
441 | catch (Exception e) | ||
442 | { | ||
443 | m_log.Error(String.Format("[BASE HTTP SERVER]: OnRequest() failed: {0} ", e.Message), e); | ||
444 | } | ||
445 | } | ||
446 | |||
447 | private void OnHandleRequestIOThread(IHttpClientContext context, IHttpRequest request) | ||
448 | { | ||
449 | OSHttpRequest req = new OSHttpRequest(context, request); | ||
450 | WebSocketRequestDelegate dWebSocketRequestDelegate = null; | ||
451 | lock (m_WebSocketHandlers) | ||
452 | { | ||
453 | if (m_WebSocketHandlers.ContainsKey(req.RawUrl)) | ||
454 | dWebSocketRequestDelegate = m_WebSocketHandlers[req.RawUrl]; | ||
455 | } | ||
456 | if (dWebSocketRequestDelegate != null) | ||
457 | { | ||
458 | dWebSocketRequestDelegate(req.Url.AbsolutePath, new WebSocketHttpServerHandler(req, context, 8192)); | ||
459 | return; | ||
460 | } | ||
461 | |||
462 | OSHttpResponse resp = new OSHttpResponse(new HttpResponse(context, request),context); | ||
463 | resp.ReuseContext = true; | ||
464 | HandleRequest(req, resp); | ||
465 | |||
466 | // !!!HACK ALERT!!! | ||
467 | // There seems to be a bug in the underlying http code that makes subsequent requests | ||
468 | // come up with trash in Accept headers. Until that gets fixed, we're cleaning them up here. | ||
469 | if (request.AcceptTypes != null) | ||
470 | for (int i = 0; i < request.AcceptTypes.Length; i++) | ||
471 | request.AcceptTypes[i] = string.Empty; | ||
472 | } | ||
473 | |||
474 | // public void ConvertIHttpClientContextToOSHttp(object stateinfo) | ||
475 | // { | ||
476 | // HttpServerContextObj objstate = (HttpServerContextObj)stateinfo; | ||
477 | |||
478 | // OSHttpRequest request = objstate.oreq; | ||
479 | // OSHttpResponse resp = objstate.oresp; | ||
480 | |||
481 | // HandleRequest(request,resp); | ||
482 | // } | ||
483 | |||
484 | /// <summary> | ||
485 | /// This methods is the start of incoming HTTP request handling. | ||
486 | /// </summary> | ||
487 | /// <param name="request"></param> | ||
488 | /// <param name="response"></param> | ||
489 | public virtual void HandleRequest(OSHttpRequest request, OSHttpResponse response) | ||
490 | { | ||
491 | if (request.HttpMethod == String.Empty) // Can't handle empty requests, not wasting a thread | ||
492 | { | ||
493 | try | ||
494 | { | ||
495 | byte[] buffer500 = SendHTML500(response); | ||
496 | response.OutputStream.Write(buffer500, 0, buffer500.Length); | ||
497 | response.Send(); | ||
498 | } | ||
499 | catch | ||
500 | { | ||
501 | } | ||
502 | |||
503 | return; | ||
504 | } | ||
505 | |||
506 | string requestMethod = request.HttpMethod; | ||
507 | string uriString = request.RawUrl; | ||
508 | |||
509 | int requestStartTick = Environment.TickCount; | ||
510 | |||
511 | // Will be adjusted later on. | ||
512 | int requestEndTick = requestStartTick; | ||
513 | |||
514 | IRequestHandler requestHandler = null; | ||
515 | |||
516 | try | ||
517 | { | ||
518 | // OpenSim.Framework.WebUtil.OSHeaderRequestID | ||
519 | // if (request.Headers["opensim-request-id"] != null) | ||
520 | // reqnum = String.Format("{0}:{1}",request.RemoteIPEndPoint,request.Headers["opensim-request-id"]); | ||
521 | //m_log.DebugFormat("[BASE HTTP SERVER]: <{0}> handle request for {1}",reqnum,request.RawUrl); | ||
522 | |||
523 | Culture.SetCurrentCulture(); | ||
524 | |||
525 | // // This is the REST agent interface. We require an agent to properly identify | ||
526 | // // itself. If the REST handler recognizes the prefix it will attempt to | ||
527 | // // satisfy the request. If it is not recognizable, and no damage has occurred | ||
528 | // // the request can be passed through to the other handlers. This is a low | ||
529 | // // probability event; if a request is matched it is normally expected to be | ||
530 | // // handled | ||
531 | // IHttpAgentHandler agentHandler; | ||
532 | // | ||
533 | // if (TryGetAgentHandler(request, response, out agentHandler)) | ||
534 | // { | ||
535 | // if (HandleAgentRequest(agentHandler, request, response)) | ||
536 | // { | ||
537 | // requestEndTick = Environment.TickCount; | ||
538 | // return; | ||
539 | // } | ||
540 | // } | ||
541 | |||
542 | //response.KeepAlive = true; | ||
543 | response.SendChunked = false; | ||
544 | |||
545 | string path = request.RawUrl; | ||
546 | string handlerKey = GetHandlerKey(request.HttpMethod, path); | ||
547 | byte[] buffer = null; | ||
548 | |||
549 | if (TryGetStreamHandler(handlerKey, out requestHandler)) | ||
550 | { | ||
551 | if (DebugLevel >= 3) | ||
552 | LogIncomingToStreamHandler(request, requestHandler); | ||
553 | |||
554 | response.ContentType = requestHandler.ContentType; // Lets do this defaulting before in case handler has varying content type. | ||
555 | |||
556 | if (requestHandler is IStreamedRequestHandler) | ||
557 | { | ||
558 | IStreamedRequestHandler streamedRequestHandler = requestHandler as IStreamedRequestHandler; | ||
559 | |||
560 | buffer = streamedRequestHandler.Handle(path, request.InputStream, request, response); | ||
561 | } | ||
562 | else if (requestHandler is IGenericHTTPHandler) | ||
563 | { | ||
564 | //m_log.Debug("[BASE HTTP SERVER]: Found Caps based HTTP Handler"); | ||
565 | IGenericHTTPHandler HTTPRequestHandler = requestHandler as IGenericHTTPHandler; | ||
566 | Stream requestStream = request.InputStream; | ||
567 | |||
568 | Encoding encoding = Encoding.UTF8; | ||
569 | StreamReader reader = new StreamReader(requestStream, encoding); | ||
570 | |||
571 | string requestBody = reader.ReadToEnd(); | ||
572 | |||
573 | reader.Close(); | ||
574 | //requestStream.Close(); | ||
575 | |||
576 | Hashtable keysvals = new Hashtable(); | ||
577 | Hashtable headervals = new Hashtable(); | ||
578 | //string host = String.Empty; | ||
579 | |||
580 | string[] querystringkeys = request.QueryString.AllKeys; | ||
581 | string[] rHeaders = request.Headers.AllKeys; | ||
582 | |||
583 | foreach (string queryname in querystringkeys) | ||
584 | { | ||
585 | keysvals.Add(queryname, request.QueryString[queryname]); | ||
586 | } | ||
587 | |||
588 | foreach (string headername in rHeaders) | ||
589 | { | ||
590 | //m_log.Warn("[HEADER]: " + headername + "=" + request.Headers[headername]); | ||
591 | headervals[headername] = request.Headers[headername]; | ||
592 | } | ||
593 | |||
594 | // if (headervals.Contains("Host")) | ||
595 | // { | ||
596 | // host = (string)headervals["Host"]; | ||
597 | // } | ||
598 | |||
599 | keysvals.Add("requestbody", requestBody); | ||
600 | keysvals.Add("headers",headervals); | ||
601 | if (keysvals.Contains("method")) | ||
602 | { | ||
603 | //m_log.Warn("[HTTP]: Contains Method"); | ||
604 | //string method = (string)keysvals["method"]; | ||
605 | //m_log.Warn("[HTTP]: " + requestBody); | ||
606 | |||
607 | } | ||
608 | |||
609 | buffer = DoHTTPGruntWork(HTTPRequestHandler.Handle(path, keysvals), response); | ||
610 | } | ||
611 | else | ||
612 | { | ||
613 | IStreamHandler streamHandler = (IStreamHandler)requestHandler; | ||
614 | |||
615 | using (MemoryStream memoryStream = new MemoryStream()) | ||
616 | { | ||
617 | streamHandler.Handle(path, request.InputStream, memoryStream, request, response); | ||
618 | memoryStream.Flush(); | ||
619 | buffer = memoryStream.ToArray(); | ||
620 | } | ||
621 | } | ||
622 | } | ||
623 | else | ||
624 | { | ||
625 | switch (request.ContentType) | ||
626 | { | ||
627 | case null: | ||
628 | case "text/html": | ||
629 | if (DebugLevel >= 3) | ||
630 | LogIncomingToContentTypeHandler(request); | ||
631 | |||
632 | buffer = HandleHTTPRequest(request, response); | ||
633 | break; | ||
634 | |||
635 | case "application/llsd+xml": | ||
636 | case "application/xml+llsd": | ||
637 | case "application/llsd+json": | ||
638 | if (DebugLevel >= 3) | ||
639 | LogIncomingToContentTypeHandler(request); | ||
640 | |||
641 | buffer = HandleLLSDRequests(request, response); | ||
642 | break; | ||
643 | |||
644 | case "application/json-rpc": | ||
645 | if (DebugLevel >= 3) | ||
646 | LogIncomingToContentTypeHandler(request); | ||
647 | |||
648 | buffer = HandleJsonRpcRequests(request, response); | ||
649 | break; | ||
650 | |||
651 | case "text/xml": | ||
652 | case "application/xml": | ||
653 | case "application/json": | ||
654 | |||
655 | default: | ||
656 | //m_log.Info("[Debug BASE HTTP SERVER]: in default handler"); | ||
657 | // Point of note.. the DoWeHaveA methods check for an EXACT path | ||
658 | // if (request.RawUrl.Contains("/CAPS/EQG")) | ||
659 | // { | ||
660 | // int i = 1; | ||
661 | // } | ||
662 | //m_log.Info("[Debug BASE HTTP SERVER]: Checking for LLSD Handler"); | ||
663 | if (DoWeHaveALLSDHandler(request.RawUrl)) | ||
664 | { | ||
665 | if (DebugLevel >= 3) | ||
666 | LogIncomingToContentTypeHandler(request); | ||
667 | |||
668 | buffer = HandleLLSDRequests(request, response); | ||
669 | } | ||
670 | // m_log.DebugFormat("[BASE HTTP SERVER]: Checking for HTTP Handler for request {0}", request.RawUrl); | ||
671 | else if (DoWeHaveAHTTPHandler(request.RawUrl)) | ||
672 | { | ||
673 | if (DebugLevel >= 3) | ||
674 | LogIncomingToContentTypeHandler(request); | ||
675 | |||
676 | buffer = HandleHTTPRequest(request, response); | ||
677 | } | ||
678 | else | ||
679 | { | ||
680 | if (DebugLevel >= 3) | ||
681 | LogIncomingToXmlRpcHandler(request); | ||
682 | |||
683 | // generic login request. | ||
684 | buffer = HandleXmlRpcRequests(request, response); | ||
685 | } | ||
686 | |||
687 | break; | ||
688 | } | ||
689 | } | ||
690 | |||
691 | request.InputStream.Close(); | ||
692 | |||
693 | if (buffer != null) | ||
694 | { | ||
695 | if (WebUtil.DebugLevel >= 5) | ||
696 | { | ||
697 | string output = System.Text.Encoding.UTF8.GetString(buffer); | ||
698 | |||
699 | if (WebUtil.DebugLevel >= 6) | ||
700 | { | ||
701 | // Always truncate binary blobs. We don't have a ContentType, so detect them using the request name. | ||
702 | if ((requestHandler != null && requestHandler.Name == "GetMesh")) | ||
703 | { | ||
704 | if (output.Length > WebUtil.MaxRequestDiagLength) | ||
705 | output = output.Substring(0, WebUtil.MaxRequestDiagLength) + "..."; | ||
706 | } | ||
707 | } | ||
708 | |||
709 | WebUtil.LogResponseDetail(RequestNumber, output); | ||
710 | } | ||
711 | |||
712 | if (!response.SendChunked && response.ContentLength64 <= 0) | ||
713 | response.ContentLength64 = buffer.LongLength; | ||
714 | |||
715 | response.OutputStream.Write(buffer, 0, buffer.Length); | ||
716 | } | ||
717 | |||
718 | // Do not include the time taken to actually send the response to the caller in the measurement | ||
719 | // time. This is to avoid logging when it's the client that is slow to process rather than the | ||
720 | // server | ||
721 | requestEndTick = Environment.TickCount; | ||
722 | |||
723 | response.Send(); | ||
724 | |||
725 | //response.OutputStream.Close(); | ||
726 | |||
727 | //response.FreeContext(); | ||
728 | } | ||
729 | catch (SocketException e) | ||
730 | { | ||
731 | // At least on linux, it appears that if the client makes a request without requiring the response, | ||
732 | // an unconnected socket exception is thrown when we close the response output stream. There's no | ||
733 | // obvious way to tell if the client didn't require the response, so instead we'll catch and ignore | ||
734 | // the exception instead. | ||
735 | // | ||
736 | // An alternative may be to turn off all response write exceptions on the HttpListener, but let's go | ||
737 | // with the minimum first | ||
738 | m_log.Warn(String.Format("[BASE HTTP SERVER]: HandleRequest threw {0}.\nNOTE: this may be spurious on Linux ", e.Message), e); | ||
739 | } | ||
740 | catch (IOException e) | ||
741 | { | ||
742 | m_log.Error("[BASE HTTP SERVER]: HandleRequest() threw exception ", e); | ||
743 | } | ||
744 | catch (Exception e) | ||
745 | { | ||
746 | m_log.Error("[BASE HTTP SERVER]: HandleRequest() threw exception ", e); | ||
747 | try | ||
748 | { | ||
749 | byte[] buffer500 = SendHTML500(response); | ||
750 | response.OutputStream.Write(buffer500, 0, buffer500.Length); | ||
751 | response.Send(); | ||
752 | } | ||
753 | catch | ||
754 | { | ||
755 | } | ||
756 | } | ||
757 | finally | ||
758 | { | ||
759 | // Every month or so this will wrap and give bad numbers, not really a problem | ||
760 | // since its just for reporting | ||
761 | int tickdiff = requestEndTick - requestStartTick; | ||
762 | if (tickdiff > 3000 && requestHandler != null && requestHandler.Name != "GetTexture") | ||
763 | { | ||
764 | m_log.InfoFormat( | ||
765 | "[LOGHTTP] Slow handling of {0} {1} {2} {3} {4} from {5} took {6}ms", | ||
766 | RequestNumber, | ||
767 | requestMethod, | ||
768 | uriString, | ||
769 | requestHandler != null ? requestHandler.Name : "", | ||
770 | requestHandler != null ? requestHandler.Description : "", | ||
771 | request.RemoteIPEndPoint, | ||
772 | tickdiff); | ||
773 | } | ||
774 | else if (DebugLevel >= 4) | ||
775 | { | ||
776 | m_log.DebugFormat( | ||
777 | "[LOGHTTP] HTTP IN {0} :{1} took {2}ms", | ||
778 | RequestNumber, | ||
779 | Port, | ||
780 | tickdiff); | ||
781 | } | ||
782 | } | ||
783 | } | ||
784 | |||
785 | private void LogIncomingToStreamHandler(OSHttpRequest request, IRequestHandler requestHandler) | ||
786 | { | ||
787 | m_log.DebugFormat( | ||
788 | "[LOGHTTP] HTTP IN {0} :{1} stream handler {2} {3} {4} {5} from {6}", | ||
789 | RequestNumber, | ||
790 | Port, | ||
791 | request.HttpMethod, | ||
792 | request.Url.PathAndQuery, | ||
793 | requestHandler.Name, | ||
794 | requestHandler.Description, | ||
795 | request.RemoteIPEndPoint); | ||
796 | |||
797 | if (DebugLevel >= 5) | ||
798 | LogIncomingInDetail(request); | ||
799 | } | ||
800 | |||
801 | private void LogIncomingToContentTypeHandler(OSHttpRequest request) | ||
802 | { | ||
803 | m_log.DebugFormat( | ||
804 | "[LOGHTTP] HTTP IN {0} :{1} {2} content type handler {3} {4} from {5}", | ||
805 | RequestNumber, | ||
806 | Port, | ||
807 | string.IsNullOrEmpty(request.ContentType) ? "not set" : request.ContentType, | ||
808 | request.HttpMethod, | ||
809 | request.Url.PathAndQuery, | ||
810 | request.RemoteIPEndPoint); | ||
811 | |||
812 | if (DebugLevel >= 5) | ||
813 | LogIncomingInDetail(request); | ||
814 | } | ||
815 | |||
816 | private void LogIncomingToXmlRpcHandler(OSHttpRequest request) | ||
817 | { | ||
818 | m_log.DebugFormat( | ||
819 | "[LOGHTTP] HTTP IN {0} :{1} assumed generic XMLRPC request {2} {3} from {4}", | ||
820 | RequestNumber, | ||
821 | Port, | ||
822 | request.HttpMethod, | ||
823 | request.Url.PathAndQuery, | ||
824 | request.RemoteIPEndPoint); | ||
825 | |||
826 | if (DebugLevel >= 5) | ||
827 | LogIncomingInDetail(request); | ||
828 | } | ||
829 | |||
830 | private void LogIncomingInDetail(OSHttpRequest request) | ||
831 | { | ||
832 | if (request.ContentType == "application/octet-stream") | ||
833 | return; // never log these; they're just binary data | ||
834 | |||
835 | Stream inputStream = Util.Copy(request.InputStream); | ||
836 | Stream innerStream = null; | ||
837 | try | ||
838 | { | ||
839 | if ((request.Headers["Content-Encoding"] == "gzip") || (request.Headers["X-Content-Encoding"] == "gzip")) | ||
840 | { | ||
841 | innerStream = inputStream; | ||
842 | inputStream = new GZipStream(innerStream, System.IO.Compression.CompressionMode.Decompress); | ||
843 | } | ||
844 | |||
845 | using (StreamReader reader = new StreamReader(inputStream, Encoding.UTF8)) | ||
846 | { | ||
847 | string output; | ||
848 | |||
849 | if (DebugLevel == 5) | ||
850 | { | ||
851 | char[] chars = new char[WebUtil.MaxRequestDiagLength + 1]; // +1 so we know to add "..." only if needed | ||
852 | int len = reader.Read(chars, 0, WebUtil.MaxRequestDiagLength + 1); | ||
853 | output = new string(chars, 0, Math.Min(len, WebUtil.MaxRequestDiagLength)); | ||
854 | if (len > WebUtil.MaxRequestDiagLength) | ||
855 | output += "..."; | ||
856 | } | ||
857 | else | ||
858 | { | ||
859 | output = reader.ReadToEnd(); | ||
860 | } | ||
861 | |||
862 | m_log.DebugFormat("[LOGHTTP] {0}", Util.BinaryToASCII(output)); | ||
863 | } | ||
864 | } | ||
865 | finally | ||
866 | { | ||
867 | if (innerStream != null) | ||
868 | innerStream.Dispose(); | ||
869 | inputStream.Dispose(); | ||
870 | } | ||
871 | } | ||
872 | |||
873 | private readonly string HANDLER_SEPARATORS = "/?&#-"; | ||
874 | |||
875 | private bool TryGetStreamHandler(string handlerKey, out IRequestHandler streamHandler) | ||
876 | { | ||
877 | string bestMatch = null; | ||
878 | |||
879 | lock (m_streamHandlers) | ||
880 | { | ||
881 | foreach (string pattern in m_streamHandlers.Keys) | ||
882 | { | ||
883 | if ((handlerKey == pattern) | ||
884 | || (handlerKey.StartsWith(pattern) && (HANDLER_SEPARATORS.IndexOf(handlerKey[pattern.Length]) >= 0))) | ||
885 | { | ||
886 | if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) | ||
887 | { | ||
888 | bestMatch = pattern; | ||
889 | } | ||
890 | } | ||
891 | } | ||
892 | |||
893 | if (String.IsNullOrEmpty(bestMatch)) | ||
894 | { | ||
895 | streamHandler = null; | ||
896 | return false; | ||
897 | } | ||
898 | else | ||
899 | { | ||
900 | streamHandler = m_streamHandlers[bestMatch]; | ||
901 | return true; | ||
902 | } | ||
903 | } | ||
904 | } | ||
905 | |||
906 | private bool TryGetPollServiceHTTPHandler(string handlerKey, out PollServiceEventArgs oServiceEventArgs) | ||
907 | { | ||
908 | string bestMatch = null; | ||
909 | |||
910 | lock (m_pollHandlers) | ||
911 | { | ||
912 | foreach (string pattern in m_pollHandlers.Keys) | ||
913 | { | ||
914 | if ((handlerKey == pattern) | ||
915 | || (handlerKey.StartsWith(pattern) && (HANDLER_SEPARATORS.IndexOf(handlerKey[pattern.Length]) >= 0))) | ||
916 | { | ||
917 | if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) | ||
918 | { | ||
919 | bestMatch = pattern; | ||
920 | } | ||
921 | } | ||
922 | } | ||
923 | |||
924 | if (String.IsNullOrEmpty(bestMatch)) | ||
925 | { | ||
926 | oServiceEventArgs = null; | ||
927 | return false; | ||
928 | } | ||
929 | else | ||
930 | { | ||
931 | oServiceEventArgs = m_pollHandlers[bestMatch]; | ||
932 | return true; | ||
933 | } | ||
934 | } | ||
935 | } | ||
936 | |||
937 | private bool TryGetHTTPHandler(string handlerKey, out GenericHTTPMethod HTTPHandler) | ||
938 | { | ||
939 | // m_log.DebugFormat("[BASE HTTP HANDLER]: Looking for HTTP handler for {0}", handlerKey); | ||
940 | |||
941 | string bestMatch = null; | ||
942 | |||
943 | lock (m_HTTPHandlers) | ||
944 | { | ||
945 | foreach (string pattern in m_HTTPHandlers.Keys) | ||
946 | { | ||
947 | if ((handlerKey == pattern) | ||
948 | || (handlerKey.StartsWith(pattern) && (HANDLER_SEPARATORS.IndexOf(handlerKey[pattern.Length]) >= 0))) | ||
949 | { | ||
950 | if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) | ||
951 | { | ||
952 | bestMatch = pattern; | ||
953 | } | ||
954 | } | ||
955 | } | ||
956 | |||
957 | if (String.IsNullOrEmpty(bestMatch)) | ||
958 | { | ||
959 | HTTPHandler = null; | ||
960 | return false; | ||
961 | } | ||
962 | else | ||
963 | { | ||
964 | HTTPHandler = m_HTTPHandlers[bestMatch]; | ||
965 | return true; | ||
966 | } | ||
967 | } | ||
968 | } | ||
969 | |||
970 | // private bool TryGetAgentHandler(OSHttpRequest request, OSHttpResponse response, out IHttpAgentHandler agentHandler) | ||
971 | // { | ||
972 | // agentHandler = null; | ||
973 | // | ||
974 | // lock (m_agentHandlers) | ||
975 | // { | ||
976 | // foreach (IHttpAgentHandler handler in m_agentHandlers.Values) | ||
977 | // { | ||
978 | // if (handler.Match(request, response)) | ||
979 | // { | ||
980 | // agentHandler = handler; | ||
981 | // return true; | ||
982 | // } | ||
983 | // } | ||
984 | // } | ||
985 | // | ||
986 | // return false; | ||
987 | // } | ||
988 | |||
989 | /// <summary> | ||
990 | /// Try all the registered xmlrpc handlers when an xmlrpc request is received. | ||
991 | /// Sends back an XMLRPC unknown request response if no handler is registered for the requested method. | ||
992 | /// </summary> | ||
993 | /// <param name="request"></param> | ||
994 | /// <param name="response"></param> | ||
995 | private byte[] HandleXmlRpcRequests(OSHttpRequest request, OSHttpResponse response) | ||
996 | { | ||
997 | String requestBody; | ||
998 | |||
999 | Stream requestStream = request.InputStream; | ||
1000 | Stream innerStream = null; | ||
1001 | try | ||
1002 | { | ||
1003 | if ((request.Headers["Content-Encoding"] == "gzip") || (request.Headers["X-Content-Encoding"] == "gzip")) | ||
1004 | { | ||
1005 | innerStream = requestStream; | ||
1006 | requestStream = new GZipStream(innerStream, System.IO.Compression.CompressionMode.Decompress); | ||
1007 | } | ||
1008 | |||
1009 | using (StreamReader reader = new StreamReader(requestStream, Encoding.UTF8)) | ||
1010 | { | ||
1011 | requestBody = reader.ReadToEnd(); | ||
1012 | } | ||
1013 | } | ||
1014 | finally | ||
1015 | { | ||
1016 | if (innerStream != null) | ||
1017 | innerStream.Dispose(); | ||
1018 | requestStream.Dispose(); | ||
1019 | } | ||
1020 | |||
1021 | //m_log.Debug(requestBody); | ||
1022 | requestBody = requestBody.Replace("<base64></base64>", ""); | ||
1023 | |||
1024 | string responseString = String.Empty; | ||
1025 | XmlRpcRequest xmlRprcRequest = null; | ||
1026 | |||
1027 | try | ||
1028 | { | ||
1029 | xmlRprcRequest = (XmlRpcRequest) (new XmlRpcRequestDeserializer()).Deserialize(requestBody); | ||
1030 | } | ||
1031 | catch (XmlException e) | ||
1032 | { | ||
1033 | if (DebugLevel >= 1) | ||
1034 | { | ||
1035 | if (DebugLevel >= 2) | ||
1036 | m_log.Warn( | ||
1037 | string.Format( | ||
1038 | "[BASE HTTP SERVER]: Got XMLRPC request with invalid XML from {0}. XML was '{1}'. Sending blank response. Exception ", | ||
1039 | request.RemoteIPEndPoint, requestBody), | ||
1040 | e); | ||
1041 | else | ||
1042 | { | ||
1043 | m_log.WarnFormat( | ||
1044 | "[BASE HTTP SERVER]: Got XMLRPC request with invalid XML from {0}, length {1}. Sending blank response.", | ||
1045 | request.RemoteIPEndPoint, requestBody.Length); | ||
1046 | } | ||
1047 | } | ||
1048 | } | ||
1049 | |||
1050 | if (xmlRprcRequest != null) | ||
1051 | { | ||
1052 | string methodName = xmlRprcRequest.MethodName; | ||
1053 | if (methodName != null) | ||
1054 | { | ||
1055 | xmlRprcRequest.Params.Add(request.RemoteIPEndPoint); // Param[1] | ||
1056 | XmlRpcResponse xmlRpcResponse; | ||
1057 | |||
1058 | XmlRpcMethod method; | ||
1059 | bool methodWasFound; | ||
1060 | bool keepAlive = false; | ||
1061 | lock (m_rpcHandlers) | ||
1062 | { | ||
1063 | methodWasFound = m_rpcHandlers.TryGetValue(methodName, out method); | ||
1064 | if (methodWasFound) | ||
1065 | keepAlive = m_rpcHandlersKeepAlive[methodName]; | ||
1066 | } | ||
1067 | |||
1068 | if (methodWasFound) | ||
1069 | { | ||
1070 | xmlRprcRequest.Params.Add(request.Url); // Param[2] | ||
1071 | |||
1072 | string xff = "X-Forwarded-For"; | ||
1073 | string xfflower = xff.ToLower(); | ||
1074 | foreach (string s in request.Headers.AllKeys) | ||
1075 | { | ||
1076 | if (s != null && s.Equals(xfflower)) | ||
1077 | { | ||
1078 | xff = xfflower; | ||
1079 | break; | ||
1080 | } | ||
1081 | } | ||
1082 | xmlRprcRequest.Params.Add(request.Headers.Get(xff)); // Param[3] | ||
1083 | |||
1084 | try | ||
1085 | { | ||
1086 | xmlRpcResponse = method(xmlRprcRequest, request.RemoteIPEndPoint); | ||
1087 | } | ||
1088 | catch(Exception e) | ||
1089 | { | ||
1090 | string errorMessage | ||
1091 | = String.Format( | ||
1092 | "Requested method [{0}] from {1} threw exception: {2} {3}", | ||
1093 | methodName, request.RemoteIPEndPoint.Address, e.Message, e.StackTrace); | ||
1094 | |||
1095 | m_log.ErrorFormat("[BASE HTTP SERVER]: {0}", errorMessage); | ||
1096 | |||
1097 | // if the registered XmlRpc method threw an exception, we pass a fault-code along | ||
1098 | xmlRpcResponse = new XmlRpcResponse(); | ||
1099 | |||
1100 | // Code probably set in accordance with http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php | ||
1101 | xmlRpcResponse.SetFault(-32603, errorMessage); | ||
1102 | } | ||
1103 | |||
1104 | // if the method wasn't found, we can't determine KeepAlive state anyway, so lets do it only here | ||
1105 | response.KeepAlive = keepAlive; | ||
1106 | } | ||
1107 | else | ||
1108 | { | ||
1109 | xmlRpcResponse = new XmlRpcResponse(); | ||
1110 | |||
1111 | // Code set in accordance with http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php | ||
1112 | xmlRpcResponse.SetFault( | ||
1113 | XmlRpcErrorCodes.SERVER_ERROR_METHOD, | ||
1114 | String.Format("Requested method [{0}] not found", methodName)); | ||
1115 | } | ||
1116 | |||
1117 | response.ContentType = "text/xml"; | ||
1118 | using (MemoryStream outs = new MemoryStream()) | ||
1119 | using (XmlTextWriter writer = new XmlTextWriter(outs, UTF8NoBOM)) | ||
1120 | { | ||
1121 | writer.Formatting = Formatting.None; | ||
1122 | XmlRpcResponseSerializer.Singleton.Serialize(writer, xmlRpcResponse); | ||
1123 | writer.Flush(); | ||
1124 | outs.Flush(); | ||
1125 | outs.Position = 0; | ||
1126 | using (StreamReader sr = new StreamReader(outs)) | ||
1127 | { | ||
1128 | responseString = sr.ReadToEnd(); | ||
1129 | } | ||
1130 | } | ||
1131 | } | ||
1132 | else | ||
1133 | { | ||
1134 | //HandleLLSDRequests(request, response); | ||
1135 | response.ContentType = "text/plain"; | ||
1136 | response.StatusCode = 404; | ||
1137 | response.StatusDescription = "Not Found"; | ||
1138 | response.ProtocolVersion = "HTTP/1.0"; | ||
1139 | responseString = "Not found"; | ||
1140 | response.KeepAlive = false; | ||
1141 | |||
1142 | m_log.ErrorFormat( | ||
1143 | "[BASE HTTP SERVER]: Handler not found for http request {0} {1}", | ||
1144 | request.HttpMethod, request.Url.PathAndQuery); | ||
1145 | } | ||
1146 | } | ||
1147 | |||
1148 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); | ||
1149 | |||
1150 | response.SendChunked = false; | ||
1151 | response.ContentLength64 = buffer.Length; | ||
1152 | response.ContentEncoding = Encoding.UTF8; | ||
1153 | |||
1154 | return buffer; | ||
1155 | } | ||
1156 | |||
1157 | // JsonRpc (v2.0 only) | ||
1158 | // Batch requests not yet supported | ||
1159 | private byte[] HandleJsonRpcRequests(OSHttpRequest request, OSHttpResponse response) | ||
1160 | { | ||
1161 | Stream requestStream = request.InputStream; | ||
1162 | JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(); | ||
1163 | OSDMap jsonRpcRequest = null; | ||
1164 | |||
1165 | try | ||
1166 | { | ||
1167 | jsonRpcRequest = (OSDMap)OSDParser.DeserializeJson(requestStream); | ||
1168 | } | ||
1169 | catch (LitJson.JsonException e) | ||
1170 | { | ||
1171 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; | ||
1172 | jsonRpcResponse.Error.Message = e.Message; | ||
1173 | } | ||
1174 | |||
1175 | requestStream.Close(); | ||
1176 | |||
1177 | if (jsonRpcRequest != null) | ||
1178 | { | ||
1179 | if (jsonRpcRequest.ContainsKey("jsonrpc") || jsonRpcRequest["jsonrpc"].AsString() == "2.0") | ||
1180 | { | ||
1181 | jsonRpcResponse.JsonRpc = "2.0"; | ||
1182 | |||
1183 | // If we have no id, then it's a "notification" | ||
1184 | if (jsonRpcRequest.ContainsKey("id")) | ||
1185 | { | ||
1186 | jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); | ||
1187 | } | ||
1188 | |||
1189 | string methodname = jsonRpcRequest["method"]; | ||
1190 | JsonRPCMethod method; | ||
1191 | |||
1192 | if (jsonRpcHandlers.ContainsKey(methodname)) | ||
1193 | { | ||
1194 | lock(jsonRpcHandlers) | ||
1195 | { | ||
1196 | jsonRpcHandlers.TryGetValue(methodname, out method); | ||
1197 | } | ||
1198 | bool res = false; | ||
1199 | try | ||
1200 | { | ||
1201 | res = method(jsonRpcRequest, ref jsonRpcResponse); | ||
1202 | if(!res) | ||
1203 | { | ||
1204 | // The handler sent back an unspecified error | ||
1205 | if(jsonRpcResponse.Error.Code == 0) | ||
1206 | { | ||
1207 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; | ||
1208 | } | ||
1209 | } | ||
1210 | } | ||
1211 | catch (Exception e) | ||
1212 | { | ||
1213 | string ErrorMessage = string.Format("[BASE HTTP SERVER]: Json-Rpc Handler Error method {0} - {1}", methodname, e.Message); | ||
1214 | m_log.Error(ErrorMessage); | ||
1215 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; | ||
1216 | jsonRpcResponse.Error.Message = ErrorMessage; | ||
1217 | } | ||
1218 | } | ||
1219 | else // Error no hanlder defined for requested method | ||
1220 | { | ||
1221 | jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; | ||
1222 | jsonRpcResponse.Error.Message = string.Format ("No handler defined for {0}", methodname); | ||
1223 | } | ||
1224 | } | ||
1225 | else // not json-rpc 2.0 could be v1 | ||
1226 | { | ||
1227 | jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; | ||
1228 | jsonRpcResponse.Error.Message = "Must be valid json-rpc 2.0 see: http://www.jsonrpc.org/specification"; | ||
1229 | |||
1230 | if (jsonRpcRequest.ContainsKey("id")) | ||
1231 | jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); | ||
1232 | } | ||
1233 | } | ||
1234 | |||
1235 | response.KeepAlive = true; | ||
1236 | string responseData = string.Empty; | ||
1237 | |||
1238 | responseData = jsonRpcResponse.Serialize(); | ||
1239 | |||
1240 | byte[] buffer = Encoding.UTF8.GetBytes(responseData); | ||
1241 | return buffer; | ||
1242 | } | ||
1243 | |||
1244 | private byte[] HandleLLSDRequests(OSHttpRequest request, OSHttpResponse response) | ||
1245 | { | ||
1246 | //m_log.Warn("[BASE HTTP SERVER]: We've figured out it's a LLSD Request"); | ||
1247 | Stream requestStream = request.InputStream; | ||
1248 | |||
1249 | Encoding encoding = Encoding.UTF8; | ||
1250 | StreamReader reader = new StreamReader(requestStream, encoding); | ||
1251 | |||
1252 | string requestBody = reader.ReadToEnd(); | ||
1253 | reader.Close(); | ||
1254 | requestStream.Close(); | ||
1255 | |||
1256 | //m_log.DebugFormat("[OGP]: {0}:{1}", request.RawUrl, requestBody); | ||
1257 | response.KeepAlive = true; | ||
1258 | |||
1259 | OSD llsdRequest = null; | ||
1260 | OSD llsdResponse = null; | ||
1261 | |||
1262 | bool LegacyLLSDLoginLibOMV = (requestBody.Contains("passwd") && requestBody.Contains("mac") && requestBody.Contains("viewer_digest")); | ||
1263 | |||
1264 | if (requestBody.Length == 0) | ||
1265 | // Get Request | ||
1266 | { | ||
1267 | requestBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><llsd><map><key>request</key><string>get</string></map></llsd>"; | ||
1268 | } | ||
1269 | try | ||
1270 | { | ||
1271 | llsdRequest = OSDParser.Deserialize(requestBody); | ||
1272 | } | ||
1273 | catch (Exception ex) | ||
1274 | { | ||
1275 | m_log.Warn("[BASE HTTP SERVER]: Error - " + ex.Message); | ||
1276 | } | ||
1277 | |||
1278 | if (llsdRequest != null)// && m_defaultLlsdHandler != null) | ||
1279 | { | ||
1280 | LLSDMethod llsdhandler = null; | ||
1281 | |||
1282 | if (TryGetLLSDHandler(request.RawUrl, out llsdhandler) && !LegacyLLSDLoginLibOMV) | ||
1283 | { | ||
1284 | // we found a registered llsd handler to service this request | ||
1285 | llsdResponse = llsdhandler(request.RawUrl, llsdRequest, request.RemoteIPEndPoint.ToString()); | ||
1286 | } | ||
1287 | else | ||
1288 | { | ||
1289 | // we didn't find a registered llsd handler to service this request | ||
1290 | // check if we have a default llsd handler | ||
1291 | |||
1292 | if (m_defaultLlsdHandler != null) | ||
1293 | { | ||
1294 | // LibOMV path | ||
1295 | llsdResponse = m_defaultLlsdHandler(llsdRequest, request.RemoteIPEndPoint); | ||
1296 | } | ||
1297 | else | ||
1298 | { | ||
1299 | // Oops, no handler for this.. give em the failed message | ||
1300 | llsdResponse = GenerateNoLLSDHandlerResponse(); | ||
1301 | } | ||
1302 | } | ||
1303 | } | ||
1304 | else | ||
1305 | { | ||
1306 | llsdResponse = GenerateNoLLSDHandlerResponse(); | ||
1307 | } | ||
1308 | |||
1309 | byte[] buffer = new byte[0]; | ||
1310 | |||
1311 | if (llsdResponse.ToString() == "shutdown404!") | ||
1312 | { | ||
1313 | response.ContentType = "text/plain"; | ||
1314 | response.StatusCode = 404; | ||
1315 | response.StatusDescription = "Not Found"; | ||
1316 | response.ProtocolVersion = "HTTP/1.0"; | ||
1317 | buffer = Encoding.UTF8.GetBytes("Not found"); | ||
1318 | } | ||
1319 | else | ||
1320 | { | ||
1321 | // Select an appropriate response format | ||
1322 | buffer = BuildLLSDResponse(request, response, llsdResponse); | ||
1323 | } | ||
1324 | |||
1325 | response.SendChunked = false; | ||
1326 | response.ContentLength64 = buffer.Length; | ||
1327 | response.ContentEncoding = Encoding.UTF8; | ||
1328 | response.KeepAlive = true; | ||
1329 | |||
1330 | return buffer; | ||
1331 | } | ||
1332 | |||
1333 | private byte[] BuildLLSDResponse(OSHttpRequest request, OSHttpResponse response, OSD llsdResponse) | ||
1334 | { | ||
1335 | if (request.AcceptTypes != null && request.AcceptTypes.Length > 0) | ||
1336 | { | ||
1337 | foreach (string strAccept in request.AcceptTypes) | ||
1338 | { | ||
1339 | switch (strAccept) | ||
1340 | { | ||
1341 | case "application/llsd+xml": | ||
1342 | case "application/xml": | ||
1343 | case "text/xml": | ||
1344 | response.ContentType = strAccept; | ||
1345 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); | ||
1346 | case "application/llsd+json": | ||
1347 | case "application/json": | ||
1348 | response.ContentType = strAccept; | ||
1349 | return Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); | ||
1350 | } | ||
1351 | } | ||
1352 | } | ||
1353 | |||
1354 | if (!String.IsNullOrEmpty(request.ContentType)) | ||
1355 | { | ||
1356 | switch (request.ContentType) | ||
1357 | { | ||
1358 | case "application/llsd+xml": | ||
1359 | case "application/xml": | ||
1360 | case "text/xml": | ||
1361 | response.ContentType = request.ContentType; | ||
1362 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); | ||
1363 | case "application/llsd+json": | ||
1364 | case "application/json": | ||
1365 | response.ContentType = request.ContentType; | ||
1366 | return Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); | ||
1367 | } | ||
1368 | } | ||
1369 | |||
1370 | // response.ContentType = "application/llsd+json"; | ||
1371 | // return Util.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); | ||
1372 | response.ContentType = "application/llsd+xml"; | ||
1373 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); | ||
1374 | } | ||
1375 | |||
1376 | /// <summary> | ||
1377 | /// Checks if we have an Exact path in the LLSD handlers for the path provided | ||
1378 | /// </summary> | ||
1379 | /// <param name="path">URI of the request</param> | ||
1380 | /// <returns>true if we have one, false if not</returns> | ||
1381 | private bool DoWeHaveALLSDHandler(string path) | ||
1382 | { | ||
1383 | string[] pathbase = path.Split('/'); | ||
1384 | string searchquery = "/"; | ||
1385 | |||
1386 | if (pathbase.Length < 1) | ||
1387 | return false; | ||
1388 | |||
1389 | for (int i = 1; i < pathbase.Length; i++) | ||
1390 | { | ||
1391 | searchquery += pathbase[i]; | ||
1392 | if (pathbase.Length - 1 != i) | ||
1393 | searchquery += "/"; | ||
1394 | } | ||
1395 | |||
1396 | string bestMatch = null; | ||
1397 | |||
1398 | lock (m_llsdHandlers) | ||
1399 | { | ||
1400 | foreach (string pattern in m_llsdHandlers.Keys) | ||
1401 | { | ||
1402 | if (searchquery.StartsWith(pattern) && searchquery.Length >= pattern.Length) | ||
1403 | bestMatch = pattern; | ||
1404 | } | ||
1405 | } | ||
1406 | |||
1407 | // extra kicker to remove the default XMLRPC login case.. just in case.. | ||
1408 | if (path != "/" && bestMatch == "/" && searchquery != "/") | ||
1409 | return false; | ||
1410 | |||
1411 | if (path == "/") | ||
1412 | return false; | ||
1413 | |||
1414 | if (String.IsNullOrEmpty(bestMatch)) | ||
1415 | { | ||
1416 | return false; | ||
1417 | } | ||
1418 | else | ||
1419 | { | ||
1420 | return true; | ||
1421 | } | ||
1422 | } | ||
1423 | |||
1424 | /// <summary> | ||
1425 | /// Checks if we have an Exact path in the HTTP handlers for the path provided | ||
1426 | /// </summary> | ||
1427 | /// <param name="path">URI of the request</param> | ||
1428 | /// <returns>true if we have one, false if not</returns> | ||
1429 | private bool DoWeHaveAHTTPHandler(string path) | ||
1430 | { | ||
1431 | string[] pathbase = path.Split('/'); | ||
1432 | string searchquery = "/"; | ||
1433 | |||
1434 | if (pathbase.Length < 1) | ||
1435 | return false; | ||
1436 | |||
1437 | for (int i = 1; i < pathbase.Length; i++) | ||
1438 | { | ||
1439 | searchquery += pathbase[i]; | ||
1440 | if (pathbase.Length - 1 != i) | ||
1441 | searchquery += "/"; | ||
1442 | } | ||
1443 | |||
1444 | string bestMatch = null; | ||
1445 | |||
1446 | //m_log.DebugFormat("[BASE HTTP HANDLER]: Checking if we have an HTTP handler for {0}", searchquery); | ||
1447 | |||
1448 | lock (m_HTTPHandlers) | ||
1449 | { | ||
1450 | foreach (string pattern in m_HTTPHandlers.Keys) | ||
1451 | { | ||
1452 | if (searchquery.StartsWith(pattern) && searchquery.Length >= pattern.Length) | ||
1453 | { | ||
1454 | bestMatch = pattern; | ||
1455 | } | ||
1456 | } | ||
1457 | |||
1458 | // extra kicker to remove the default XMLRPC login case.. just in case.. | ||
1459 | if (path == "/") | ||
1460 | return false; | ||
1461 | |||
1462 | if (String.IsNullOrEmpty(bestMatch)) | ||
1463 | { | ||
1464 | return false; | ||
1465 | } | ||
1466 | else | ||
1467 | { | ||
1468 | return true; | ||
1469 | } | ||
1470 | } | ||
1471 | } | ||
1472 | |||
1473 | private bool TryGetLLSDHandler(string path, out LLSDMethod llsdHandler) | ||
1474 | { | ||
1475 | llsdHandler = null; | ||
1476 | // Pull out the first part of the path | ||
1477 | // splitting the path by '/' means we'll get the following return.. | ||
1478 | // {0}/{1}/{2} | ||
1479 | // where {0} isn't something we really control 100% | ||
1480 | |||
1481 | string[] pathbase = path.Split('/'); | ||
1482 | string searchquery = "/"; | ||
1483 | |||
1484 | if (pathbase.Length < 1) | ||
1485 | return false; | ||
1486 | |||
1487 | for (int i=1; i<pathbase.Length; i++) | ||
1488 | { | ||
1489 | searchquery += pathbase[i]; | ||
1490 | if (pathbase.Length-1 != i) | ||
1491 | searchquery += "/"; | ||
1492 | } | ||
1493 | |||
1494 | // while the matching algorithm below doesn't require it, we're expecting a query in the form | ||
1495 | // | ||
1496 | // [] = optional | ||
1497 | // /resource/UUID/action[/action] | ||
1498 | // | ||
1499 | // now try to get the closest match to the reigstered path | ||
1500 | // at least for OGP, registered path would probably only consist of the /resource/ | ||
1501 | |||
1502 | string bestMatch = null; | ||
1503 | |||
1504 | lock (m_llsdHandlers) | ||
1505 | { | ||
1506 | foreach (string pattern in m_llsdHandlers.Keys) | ||
1507 | { | ||
1508 | if (searchquery.ToLower().StartsWith(pattern.ToLower())) | ||
1509 | { | ||
1510 | if (String.IsNullOrEmpty(bestMatch) || searchquery.Length > bestMatch.Length) | ||
1511 | { | ||
1512 | // You have to specifically register for '/' and to get it, you must specificaly request it | ||
1513 | // | ||
1514 | if (pattern == "/" && searchquery == "/" || pattern != "/") | ||
1515 | bestMatch = pattern; | ||
1516 | } | ||
1517 | } | ||
1518 | } | ||
1519 | |||
1520 | if (String.IsNullOrEmpty(bestMatch)) | ||
1521 | { | ||
1522 | llsdHandler = null; | ||
1523 | return false; | ||
1524 | } | ||
1525 | else | ||
1526 | { | ||
1527 | llsdHandler = m_llsdHandlers[bestMatch]; | ||
1528 | return true; | ||
1529 | } | ||
1530 | } | ||
1531 | } | ||
1532 | |||
1533 | private OSDMap GenerateNoLLSDHandlerResponse() | ||
1534 | { | ||
1535 | OSDMap map = new OSDMap(); | ||
1536 | map["reason"] = OSD.FromString("LLSDRequest"); | ||
1537 | map["message"] = OSD.FromString("No handler registered for LLSD Requests"); | ||
1538 | map["login"] = OSD.FromString("false"); | ||
1539 | return map; | ||
1540 | } | ||
1541 | |||
1542 | public byte[] HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response) | ||
1543 | { | ||
1544 | // m_log.DebugFormat( | ||
1545 | // "[BASE HTTP SERVER]: HandleHTTPRequest for request to {0}, method {1}", | ||
1546 | // request.RawUrl, request.HttpMethod); | ||
1547 | |||
1548 | switch (request.HttpMethod) | ||
1549 | { | ||
1550 | case "OPTIONS": | ||
1551 | response.StatusCode = (int)OSHttpStatusCode.SuccessOk; | ||
1552 | return null; | ||
1553 | |||
1554 | default: | ||
1555 | return HandleContentVerbs(request, response); | ||
1556 | } | ||
1557 | } | ||
1558 | |||
1559 | private byte[] HandleContentVerbs(OSHttpRequest request, OSHttpResponse response) | ||
1560 | { | ||
1561 | // m_log.DebugFormat("[BASE HTTP SERVER]: HandleContentVerbs for request to {0}", request.RawUrl); | ||
1562 | |||
1563 | // This is a test. There's a workable alternative.. as this way sucks. | ||
1564 | // We'd like to put this into a text file parhaps that's easily editable. | ||
1565 | // | ||
1566 | // For this test to work, I used the following secondlife.exe parameters | ||
1567 | // "C:\Program Files\SecondLifeWindLight\SecondLifeWindLight.exe" -settings settings_windlight.xml -channel "Second Life WindLight" -set SystemLanguage en-us -loginpage http://10.1.1.2:8002/?show_login_form=TRUE -loginuri http://10.1.1.2:8002 -user 10.1.1.2 | ||
1568 | // | ||
1569 | // Even after all that, there's still an error, but it's a start. | ||
1570 | // | ||
1571 | // I depend on show_login_form being in the secondlife.exe parameters to figure out | ||
1572 | // to display the form, or process it. | ||
1573 | // a better way would be nifty. | ||
1574 | |||
1575 | byte[] buffer; | ||
1576 | |||
1577 | Stream requestStream = request.InputStream; | ||
1578 | |||
1579 | Encoding encoding = Encoding.UTF8; | ||
1580 | StreamReader reader = new StreamReader(requestStream, encoding); | ||
1581 | |||
1582 | string requestBody = reader.ReadToEnd(); | ||
1583 | // avoid warning for now | ||
1584 | reader.ReadToEnd(); | ||
1585 | reader.Close(); | ||
1586 | requestStream.Close(); | ||
1587 | |||
1588 | Hashtable keysvals = new Hashtable(); | ||
1589 | Hashtable headervals = new Hashtable(); | ||
1590 | |||
1591 | Hashtable requestVars = new Hashtable(); | ||
1592 | |||
1593 | string host = String.Empty; | ||
1594 | |||
1595 | string[] querystringkeys = request.QueryString.AllKeys; | ||
1596 | string[] rHeaders = request.Headers.AllKeys; | ||
1597 | |||
1598 | keysvals.Add("body", requestBody); | ||
1599 | keysvals.Add("uri", request.RawUrl); | ||
1600 | keysvals.Add("content-type", request.ContentType); | ||
1601 | keysvals.Add("http-method", request.HttpMethod); | ||
1602 | |||
1603 | foreach (string queryname in querystringkeys) | ||
1604 | { | ||
1605 | // m_log.DebugFormat( | ||
1606 | // "[BASE HTTP SERVER]: Got query paremeter {0}={1}", queryname, request.QueryString[queryname]); | ||
1607 | keysvals.Add(queryname, request.QueryString[queryname]); | ||
1608 | requestVars.Add(queryname, keysvals[queryname]); | ||
1609 | } | ||
1610 | |||
1611 | foreach (string headername in rHeaders) | ||
1612 | { | ||
1613 | // m_log.Debug("[BASE HTTP SERVER]: " + headername + "=" + request.Headers[headername]); | ||
1614 | headervals[headername] = request.Headers[headername]; | ||
1615 | } | ||
1616 | |||
1617 | if (headervals.Contains("Host")) | ||
1618 | { | ||
1619 | host = (string)headervals["Host"]; | ||
1620 | } | ||
1621 | |||
1622 | keysvals.Add("headers", headervals); | ||
1623 | keysvals.Add("querystringkeys", querystringkeys); | ||
1624 | keysvals.Add("requestvars", requestVars); | ||
1625 | // keysvals.Add("form", request.Form); | ||
1626 | |||
1627 | if (keysvals.Contains("method")) | ||
1628 | { | ||
1629 | // m_log.Debug("[BASE HTTP SERVER]: Contains Method"); | ||
1630 | string method = (string) keysvals["method"]; | ||
1631 | // m_log.Debug("[BASE HTTP SERVER]: " + requestBody); | ||
1632 | GenericHTTPMethod requestprocessor; | ||
1633 | bool foundHandler = TryGetHTTPHandler(method, out requestprocessor); | ||
1634 | if (foundHandler) | ||
1635 | { | ||
1636 | Hashtable responsedata1 = requestprocessor(keysvals); | ||
1637 | buffer = DoHTTPGruntWork(responsedata1,response); | ||
1638 | |||
1639 | //SendHTML500(response); | ||
1640 | } | ||
1641 | else | ||
1642 | { | ||
1643 | // m_log.Warn("[BASE HTTP SERVER]: Handler Not Found"); | ||
1644 | buffer = SendHTML404(response, host); | ||
1645 | } | ||
1646 | } | ||
1647 | else | ||
1648 | { | ||
1649 | GenericHTTPMethod requestprocessor; | ||
1650 | bool foundHandler = TryGetHTTPHandlerPathBased(request.RawUrl, out requestprocessor); | ||
1651 | if (foundHandler) | ||
1652 | { | ||
1653 | Hashtable responsedata2 = requestprocessor(keysvals); | ||
1654 | buffer = DoHTTPGruntWork(responsedata2, response); | ||
1655 | |||
1656 | //SendHTML500(response); | ||
1657 | } | ||
1658 | else | ||
1659 | { | ||
1660 | // m_log.Warn("[BASE HTTP SERVER]: Handler Not Found2"); | ||
1661 | buffer = SendHTML404(response, host); | ||
1662 | } | ||
1663 | } | ||
1664 | |||
1665 | return buffer; | ||
1666 | } | ||
1667 | |||
1668 | private bool TryGetHTTPHandlerPathBased(string path, out GenericHTTPMethod httpHandler) | ||
1669 | { | ||
1670 | httpHandler = null; | ||
1671 | // Pull out the first part of the path | ||
1672 | // splitting the path by '/' means we'll get the following return.. | ||
1673 | // {0}/{1}/{2} | ||
1674 | // where {0} isn't something we really control 100% | ||
1675 | |||
1676 | string[] pathbase = path.Split('/'); | ||
1677 | string searchquery = "/"; | ||
1678 | |||
1679 | if (pathbase.Length < 1) | ||
1680 | return false; | ||
1681 | |||
1682 | for (int i = 1; i < pathbase.Length; i++) | ||
1683 | { | ||
1684 | searchquery += pathbase[i]; | ||
1685 | if (pathbase.Length - 1 != i) | ||
1686 | searchquery += "/"; | ||
1687 | } | ||
1688 | |||
1689 | // while the matching algorithm below doesn't require it, we're expecting a query in the form | ||
1690 | // | ||
1691 | // [] = optional | ||
1692 | // /resource/UUID/action[/action] | ||
1693 | // | ||
1694 | // now try to get the closest match to the reigstered path | ||
1695 | // at least for OGP, registered path would probably only consist of the /resource/ | ||
1696 | |||
1697 | string bestMatch = null; | ||
1698 | |||
1699 | // m_log.DebugFormat( | ||
1700 | // "[BASE HTTP HANDLER]: TryGetHTTPHandlerPathBased() looking for HTTP handler to match {0}", searchquery); | ||
1701 | |||
1702 | lock (m_HTTPHandlers) | ||
1703 | { | ||
1704 | foreach (string pattern in m_HTTPHandlers.Keys) | ||
1705 | { | ||
1706 | if (searchquery.ToLower().StartsWith(pattern.ToLower())) | ||
1707 | { | ||
1708 | if (String.IsNullOrEmpty(bestMatch) || searchquery.Length > bestMatch.Length) | ||
1709 | { | ||
1710 | // You have to specifically register for '/' and to get it, you must specifically request it | ||
1711 | if (pattern == "/" && searchquery == "/" || pattern != "/") | ||
1712 | bestMatch = pattern; | ||
1713 | } | ||
1714 | } | ||
1715 | } | ||
1716 | |||
1717 | if (String.IsNullOrEmpty(bestMatch)) | ||
1718 | { | ||
1719 | httpHandler = null; | ||
1720 | return false; | ||
1721 | } | ||
1722 | else | ||
1723 | { | ||
1724 | if (bestMatch == "/" && searchquery != "/") | ||
1725 | return false; | ||
1726 | |||
1727 | httpHandler = m_HTTPHandlers[bestMatch]; | ||
1728 | return true; | ||
1729 | } | ||
1730 | } | ||
1731 | } | ||
1732 | |||
1733 | internal byte[] DoHTTPGruntWork(Hashtable responsedata, OSHttpResponse response) | ||
1734 | { | ||
1735 | //m_log.Info("[BASE HTTP SERVER]: Doing HTTP Grunt work with response"); | ||
1736 | int responsecode = (int)responsedata["int_response_code"]; | ||
1737 | string responseString = (string)responsedata["str_response_string"]; | ||
1738 | string contentType = (string)responsedata["content_type"]; | ||
1739 | |||
1740 | if (responsedata.ContainsKey("error_status_text")) | ||
1741 | { | ||
1742 | response.StatusDescription = (string)responsedata["error_status_text"]; | ||
1743 | } | ||
1744 | if (responsedata.ContainsKey("http_protocol_version")) | ||
1745 | { | ||
1746 | response.ProtocolVersion = (string)responsedata["http_protocol_version"]; | ||
1747 | } | ||
1748 | |||
1749 | if (responsedata.ContainsKey("keepalive")) | ||
1750 | { | ||
1751 | bool keepalive = (bool)responsedata["keepalive"]; | ||
1752 | response.KeepAlive = keepalive; | ||
1753 | |||
1754 | } | ||
1755 | |||
1756 | if (responsedata.ContainsKey("reusecontext")) | ||
1757 | response.ReuseContext = (bool) responsedata["reusecontext"]; | ||
1758 | |||
1759 | // Cross-Origin Resource Sharing with simple requests | ||
1760 | if (responsedata.ContainsKey("access_control_allow_origin")) | ||
1761 | response.AddHeader("Access-Control-Allow-Origin", (string)responsedata["access_control_allow_origin"]); | ||
1762 | |||
1763 | //Even though only one other part of the entire code uses HTTPHandlers, we shouldn't expect this | ||
1764 | //and should check for NullReferenceExceptions | ||
1765 | |||
1766 | if (string.IsNullOrEmpty(contentType)) | ||
1767 | { | ||
1768 | contentType = "text/html"; | ||
1769 | } | ||
1770 | |||
1771 | // The client ignores anything but 200 here for web login, so ensure that this is 200 for that | ||
1772 | |||
1773 | response.StatusCode = responsecode; | ||
1774 | |||
1775 | if (responsecode == (int)OSHttpStatusCode.RedirectMovedPermanently) | ||
1776 | { | ||
1777 | response.RedirectLocation = (string)responsedata["str_redirect_location"]; | ||
1778 | response.StatusCode = responsecode; | ||
1779 | } | ||
1780 | |||
1781 | response.AddHeader("Content-Type", contentType); | ||
1782 | |||
1783 | byte[] buffer; | ||
1784 | |||
1785 | if (!(contentType.Contains("image") | ||
1786 | || contentType.Contains("x-shockwave-flash") | ||
1787 | || contentType.Contains("application/x-oar") | ||
1788 | || contentType.Contains("application/vnd.ll.mesh"))) | ||
1789 | { | ||
1790 | // Text | ||
1791 | buffer = Encoding.UTF8.GetBytes(responseString); | ||
1792 | } | ||
1793 | else | ||
1794 | { | ||
1795 | // Binary! | ||
1796 | buffer = Convert.FromBase64String(responseString); | ||
1797 | } | ||
1798 | |||
1799 | response.SendChunked = false; | ||
1800 | response.ContentLength64 = buffer.Length; | ||
1801 | response.ContentEncoding = Encoding.UTF8; | ||
1802 | |||
1803 | return buffer; | ||
1804 | } | ||
1805 | |||
1806 | public byte[] SendHTML404(OSHttpResponse response, string host) | ||
1807 | { | ||
1808 | // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s | ||
1809 | response.StatusCode = 404; | ||
1810 | response.AddHeader("Content-type", "text/html"); | ||
1811 | |||
1812 | string responseString = GetHTTP404(host); | ||
1813 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); | ||
1814 | |||
1815 | response.SendChunked = false; | ||
1816 | response.ContentLength64 = buffer.Length; | ||
1817 | response.ContentEncoding = Encoding.UTF8; | ||
1818 | |||
1819 | return buffer; | ||
1820 | } | ||
1821 | |||
1822 | public byte[] SendHTML500(OSHttpResponse response) | ||
1823 | { | ||
1824 | // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s | ||
1825 | response.StatusCode = (int)OSHttpStatusCode.SuccessOk; | ||
1826 | response.AddHeader("Content-type", "text/html"); | ||
1827 | |||
1828 | string responseString = GetHTTP500(); | ||
1829 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); | ||
1830 | |||
1831 | response.SendChunked = false; | ||
1832 | response.ContentLength64 = buffer.Length; | ||
1833 | response.ContentEncoding = Encoding.UTF8; | ||
1834 | |||
1835 | |||
1836 | return buffer; | ||
1837 | } | ||
1838 | |||
1839 | public void Start() | ||
1840 | { | ||
1841 | Start(true); | ||
1842 | } | ||
1843 | |||
1844 | /// <summary> | ||
1845 | /// Start the http server | ||
1846 | /// </summary> | ||
1847 | /// <param name='processPollRequestsAsync'> | ||
1848 | /// If true then poll responses are performed asynchronsly. | ||
1849 | /// Option exists to allow regression tests to perform processing synchronously. | ||
1850 | /// </param> | ||
1851 | public void Start(bool performPollResponsesAsync) | ||
1852 | { | ||
1853 | m_log.InfoFormat( | ||
1854 | "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); | ||
1855 | |||
1856 | try | ||
1857 | { | ||
1858 | //m_httpListener = new HttpListener(); | ||
1859 | |||
1860 | NotSocketErrors = 0; | ||
1861 | if (!m_ssl) | ||
1862 | { | ||
1863 | //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); | ||
1864 | //m_httpListener.Prefixes.Add("http://10.1.1.5:" + m_port + "/"); | ||
1865 | m_httpListener2 = CoolHTTPListener.Create(m_listenIPAddress, (int)m_port); | ||
1866 | m_httpListener2.ExceptionThrown += httpServerException; | ||
1867 | m_httpListener2.LogWriter = httpserverlog; | ||
1868 | |||
1869 | // Uncomment this line in addition to those in HttpServerLogWriter | ||
1870 | // if you want more detailed trace information from the HttpServer | ||
1871 | //m_httpListener2.UseTraceLogs = true; | ||
1872 | |||
1873 | //m_httpListener2.DisconnectHandler = httpServerDisconnectMonitor; | ||
1874 | } | ||
1875 | else | ||
1876 | { | ||
1877 | //m_httpListener.Prefixes.Add("https://+:" + (m_sslport) + "/"); | ||
1878 | //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); | ||
1879 | m_httpListener2 = CoolHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert); | ||
1880 | m_httpListener2.ExceptionThrown += httpServerException; | ||
1881 | m_httpListener2.LogWriter = httpserverlog; | ||
1882 | } | ||
1883 | |||
1884 | m_httpListener2.RequestReceived += OnRequest; | ||
1885 | //m_httpListener.Start(); | ||
1886 | m_httpListener2.Start(64); | ||
1887 | |||
1888 | // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events | ||
1889 | PollServiceRequestManager = new PollServiceRequestManager(this, performPollResponsesAsync, 3, 25000); | ||
1890 | PollServiceRequestManager.Start(); | ||
1891 | |||
1892 | HTTPDRunning = true; | ||
1893 | |||
1894 | //HttpListenerContext context; | ||
1895 | //while (true) | ||
1896 | //{ | ||
1897 | // context = m_httpListener.GetContext(); | ||
1898 | // ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(HandleRequest), context); | ||
1899 | // } | ||
1900 | } | ||
1901 | catch (Exception e) | ||
1902 | { | ||
1903 | m_log.Error("[BASE HTTP SERVER]: Error - " + e.Message); | ||
1904 | m_log.Error("[BASE HTTP SERVER]: Tip: Do you have permission to listen on port " + m_port + ", " + m_sslport + "?"); | ||
1905 | |||
1906 | // We want this exception to halt the entire server since in current configurations we aren't too | ||
1907 | // useful without inbound HTTP. | ||
1908 | throw e; | ||
1909 | } | ||
1910 | |||
1911 | m_requestsProcessedStat | ||
1912 | = new Stat( | ||
1913 | "HTTPRequestsServed", | ||
1914 | "Number of inbound HTTP requests processed", | ||
1915 | "", | ||
1916 | "requests", | ||
1917 | "httpserver", | ||
1918 | Port.ToString(), | ||
1919 | StatType.Pull, | ||
1920 | MeasuresOfInterest.AverageChangeOverTime, | ||
1921 | stat => stat.Value = RequestNumber, | ||
1922 | StatVerbosity.Debug); | ||
1923 | |||
1924 | StatsManager.RegisterStat(m_requestsProcessedStat); | ||
1925 | } | ||
1926 | |||
1927 | public void httpServerDisconnectMonitor(IHttpClientContext source, SocketError err) | ||
1928 | { | ||
1929 | switch (err) | ||
1930 | { | ||
1931 | case SocketError.NotSocket: | ||
1932 | NotSocketErrors++; | ||
1933 | |||
1934 | break; | ||
1935 | } | ||
1936 | } | ||
1937 | |||
1938 | public void httpServerException(object source, Exception exception) | ||
1939 | { | ||
1940 | m_log.Error(String.Format("[BASE HTTP SERVER]: {0} had an exception: {1} ", source.ToString(), exception.Message), exception); | ||
1941 | /* | ||
1942 | if (HTTPDRunning)// && NotSocketErrors > 5) | ||
1943 | { | ||
1944 | Stop(); | ||
1945 | Thread.Sleep(200); | ||
1946 | StartHTTP(); | ||
1947 | m_log.Warn("[HTTPSERVER]: Died. Trying to kick....."); | ||
1948 | } | ||
1949 | */ | ||
1950 | } | ||
1951 | |||
1952 | public void Stop() | ||
1953 | { | ||
1954 | HTTPDRunning = false; | ||
1955 | |||
1956 | StatsManager.DeregisterStat(m_requestsProcessedStat); | ||
1957 | |||
1958 | try | ||
1959 | { | ||
1960 | PollServiceRequestManager.Stop(); | ||
1961 | |||
1962 | m_httpListener2.ExceptionThrown -= httpServerException; | ||
1963 | //m_httpListener2.DisconnectHandler = null; | ||
1964 | |||
1965 | m_httpListener2.LogWriter = null; | ||
1966 | m_httpListener2.RequestReceived -= OnRequest; | ||
1967 | m_httpListener2.Stop(); | ||
1968 | } | ||
1969 | catch (NullReferenceException) | ||
1970 | { | ||
1971 | m_log.Warn("[BASE HTTP SERVER]: Null Reference when stopping HttpServer."); | ||
1972 | } | ||
1973 | } | ||
1974 | |||
1975 | public void RemoveStreamHandler(string httpMethod, string path) | ||
1976 | { | ||
1977 | string handlerKey = GetHandlerKey(httpMethod, path); | ||
1978 | |||
1979 | //m_log.DebugFormat("[BASE HTTP SERVER]: Removing handler key {0}", handlerKey); | ||
1980 | |||
1981 | lock (m_streamHandlers) | ||
1982 | m_streamHandlers.Remove(handlerKey); | ||
1983 | } | ||
1984 | |||
1985 | public void RemoveHTTPHandler(string httpMethod, string path) | ||
1986 | { | ||
1987 | lock (m_HTTPHandlers) | ||
1988 | { | ||
1989 | if (httpMethod != null && httpMethod.Length == 0) | ||
1990 | { | ||
1991 | m_HTTPHandlers.Remove(path); | ||
1992 | return; | ||
1993 | } | ||
1994 | |||
1995 | m_HTTPHandlers.Remove(GetHandlerKey(httpMethod, path)); | ||
1996 | } | ||
1997 | } | ||
1998 | |||
1999 | public void RemovePollServiceHTTPHandler(string httpMethod, string path) | ||
2000 | { | ||
2001 | lock (m_pollHandlers) | ||
2002 | m_pollHandlers.Remove(path); | ||
2003 | } | ||
2004 | |||
2005 | // public bool RemoveAgentHandler(string agent, IHttpAgentHandler handler) | ||
2006 | // { | ||
2007 | // lock (m_agentHandlers) | ||
2008 | // { | ||
2009 | // IHttpAgentHandler foundHandler; | ||
2010 | // | ||
2011 | // if (m_agentHandlers.TryGetValue(agent, out foundHandler) && foundHandler == handler) | ||
2012 | // { | ||
2013 | // m_agentHandlers.Remove(agent); | ||
2014 | // return true; | ||
2015 | // } | ||
2016 | // } | ||
2017 | // | ||
2018 | // return false; | ||
2019 | // } | ||
2020 | |||
2021 | public void RemoveXmlRPCHandler(string method) | ||
2022 | { | ||
2023 | lock (m_rpcHandlers) | ||
2024 | m_rpcHandlers.Remove(method); | ||
2025 | } | ||
2026 | |||
2027 | public void RemoveJsonRPCHandler(string method) | ||
2028 | { | ||
2029 | lock(jsonRpcHandlers) | ||
2030 | jsonRpcHandlers.Remove(method); | ||
2031 | } | ||
2032 | |||
2033 | public bool RemoveLLSDHandler(string path, LLSDMethod handler) | ||
2034 | { | ||
2035 | lock (m_llsdHandlers) | ||
2036 | { | ||
2037 | LLSDMethod foundHandler; | ||
2038 | |||
2039 | if (m_llsdHandlers.TryGetValue(path, out foundHandler) && foundHandler == handler) | ||
2040 | { | ||
2041 | m_llsdHandlers.Remove(path); | ||
2042 | return true; | ||
2043 | } | ||
2044 | } | ||
2045 | |||
2046 | return false; | ||
2047 | } | ||
2048 | |||
2049 | public string GetHTTP404(string host) | ||
2050 | { | ||
2051 | string file = Path.Combine(".", "http_404.html"); | ||
2052 | if (!File.Exists(file)) | ||
2053 | return getDefaultHTTP404(host); | ||
2054 | |||
2055 | StreamReader sr = File.OpenText(file); | ||
2056 | string result = sr.ReadToEnd(); | ||
2057 | sr.Close(); | ||
2058 | return result; | ||
2059 | } | ||
2060 | |||
2061 | public string GetHTTP500() | ||
2062 | { | ||
2063 | string file = Path.Combine(".", "http_500.html"); | ||
2064 | if (!File.Exists(file)) | ||
2065 | return getDefaultHTTP500(); | ||
2066 | |||
2067 | StreamReader sr = File.OpenText(file); | ||
2068 | string result = sr.ReadToEnd(); | ||
2069 | sr.Close(); | ||
2070 | return result; | ||
2071 | } | ||
2072 | |||
2073 | // Fallback HTTP responses in case the HTTP error response files don't exist | ||
2074 | private static string getDefaultHTTP404(string host) | ||
2075 | { | ||
2076 | return "<HTML><HEAD><TITLE>404 Page not found</TITLE><BODY><BR /><H1>Ooops!</H1><P>The page you requested has been obsconded with by knomes. Find hippos quick!</P><P>If you are trying to log-in, your link parameters should have: "-loginpage http://" + host + "/?method=login -loginuri http://" + host + "/" in your link </P></BODY></HTML>"; | ||
2077 | } | ||
2078 | |||
2079 | private static string getDefaultHTTP500() | ||
2080 | { | ||
2081 | return "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE><BODY><BR /><H1>Ooops!</H1><P>The server you requested is overun by knomes! Find hippos quick!</P></BODY></HTML>"; | ||
2082 | } | ||
2083 | } | ||
2084 | |||
2085 | public class HttpServerContextObj | ||
2086 | { | ||
2087 | public IHttpClientContext context = null; | ||
2088 | public IHttpRequest req = null; | ||
2089 | public OSHttpRequest oreq = null; | ||
2090 | public OSHttpResponse oresp = null; | ||
2091 | |||
2092 | public HttpServerContextObj(IHttpClientContext contxt, IHttpRequest reqs) | ||
2093 | { | ||
2094 | context = contxt; | ||
2095 | req = reqs; | ||
2096 | } | ||
2097 | |||
2098 | public HttpServerContextObj(OSHttpRequest osreq, OSHttpResponse osresp) | ||
2099 | { | ||
2100 | oreq = osreq; | ||
2101 | oresp = osresp; | ||
2102 | } | ||
2103 | } | ||
2104 | |||
2105 | /// <summary> | ||
2106 | /// Relays HttpServer log messages to our own logging mechanism. | ||
2107 | /// </summary> | ||
2108 | /// To use this you must uncomment the switch section | ||
2109 | /// | ||
2110 | /// You may also be able to get additional trace information from HttpServer if you uncomment the UseTraceLogs | ||
2111 | /// property in StartHttp() for the HttpListener | ||
2112 | public class HttpServerLogWriter : ILogWriter | ||
2113 | { | ||
2114 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
2115 | |||
2116 | public void Write(object source, LogPrio priority, string message) | ||
2117 | { | ||
2118 | /* | ||
2119 | switch (priority) | ||
2120 | { | ||
2121 | case LogPrio.Trace: | ||
2122 | m_log.DebugFormat("[{0}]: {1}", source, message); | ||
2123 | break; | ||
2124 | case LogPrio.Debug: | ||
2125 | m_log.DebugFormat("[{0}]: {1}", source, message); | ||
2126 | break; | ||
2127 | case LogPrio.Error: | ||
2128 | m_log.ErrorFormat("[{0}]: {1}", source, message); | ||
2129 | break; | ||
2130 | case LogPrio.Info: | ||
2131 | m_log.InfoFormat("[{0}]: {1}", source, message); | ||
2132 | break; | ||
2133 | case LogPrio.Warning: | ||
2134 | m_log.WarnFormat("[{0}]: {1}", source, message); | ||
2135 | break; | ||
2136 | case LogPrio.Fatal: | ||
2137 | m_log.ErrorFormat("[{0}]: FATAL! - {1}", source, message); | ||
2138 | break; | ||
2139 | default: | ||
2140 | break; | ||
2141 | } | ||
2142 | */ | ||
2143 | |||
2144 | return; | ||
2145 | } | ||
2146 | } | ||
2147 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseOutputStreamHandler.cs b/OpenSim/Framework/Servers/HttpServer/BaseOutputStreamHandler.cs new file mode 100644 index 0000000..72b3065 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BaseOutputStreamHandler.cs | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.IO; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | /// <summary> | ||
33 | /// Base handler for writing to an output stream | ||
34 | /// </summary> | ||
35 | /// <remarks> | ||
36 | /// Inheriting classes should override ProcessRequest() rather than Handle() | ||
37 | /// </remarks> | ||
38 | public abstract class BaseOutputStreamHandler : BaseRequestHandler, IRequestHandler | ||
39 | { | ||
40 | protected BaseOutputStreamHandler(string httpMethod, string path) : this(httpMethod, path, null, null) {} | ||
41 | |||
42 | protected BaseOutputStreamHandler(string httpMethod, string path, string name, string description) | ||
43 | : base(httpMethod, path, name, description) {} | ||
44 | |||
45 | public virtual void Handle( | ||
46 | string path, Stream request, Stream response, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
47 | { | ||
48 | RequestsReceived++; | ||
49 | |||
50 | ProcessRequest(path, request, response, httpRequest, httpResponse); | ||
51 | |||
52 | RequestsHandled++; | ||
53 | } | ||
54 | |||
55 | protected virtual void ProcessRequest( | ||
56 | string path, Stream request, Stream response, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
57 | { | ||
58 | } | ||
59 | } | ||
60 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseRequestHandler.cs b/OpenSim/Framework/Servers/HttpServer/BaseRequestHandler.cs new file mode 100644 index 0000000..d4a1ec3 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BaseRequestHandler.cs | |||
@@ -0,0 +1,117 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using OpenSim.Framework.Monitoring; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public abstract class BaseRequestHandler | ||
34 | { | ||
35 | public int RequestsReceived { get; protected set; } | ||
36 | |||
37 | public int RequestsHandled { get; protected set; } | ||
38 | |||
39 | public virtual string ContentType | ||
40 | { | ||
41 | get { return "application/xml"; } | ||
42 | } | ||
43 | |||
44 | private readonly string m_httpMethod; | ||
45 | |||
46 | public virtual string HttpMethod | ||
47 | { | ||
48 | get { return m_httpMethod; } | ||
49 | } | ||
50 | |||
51 | private readonly string m_path; | ||
52 | |||
53 | public string Name { get; private set; } | ||
54 | |||
55 | public string Description { get; private set; } | ||
56 | |||
57 | protected BaseRequestHandler(string httpMethod, string path) : this(httpMethod, path, null, null) {} | ||
58 | |||
59 | protected BaseRequestHandler(string httpMethod, string path, string name, string description) | ||
60 | { | ||
61 | Name = name; | ||
62 | Description = description; | ||
63 | m_httpMethod = httpMethod; | ||
64 | m_path = path; | ||
65 | |||
66 | // FIXME: A very temporary measure to stop the simulator stats being overwhelmed with user CAPS info. | ||
67 | // Needs to be fixed properly in stats display | ||
68 | if (!path.StartsWith("/CAPS/")) | ||
69 | { | ||
70 | StatsManager.RegisterStat( | ||
71 | new Stat( | ||
72 | "requests", | ||
73 | "requests", | ||
74 | "Number of requests received by this service endpoint", | ||
75 | "requests", | ||
76 | "service", | ||
77 | string.Format("{0}:{1}", httpMethod, path), | ||
78 | StatType.Pull, | ||
79 | MeasuresOfInterest.AverageChangeOverTime, | ||
80 | s => s.Value = RequestsReceived, | ||
81 | StatVerbosity.Debug)); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | public virtual string Path | ||
86 | { | ||
87 | get { return m_path; } | ||
88 | } | ||
89 | |||
90 | public string GetParam(string path) | ||
91 | { | ||
92 | if (CheckParam(path)) | ||
93 | { | ||
94 | return path.Substring(m_path.Length); | ||
95 | } | ||
96 | |||
97 | return String.Empty; | ||
98 | } | ||
99 | |||
100 | protected bool CheckParam(string path) | ||
101 | { | ||
102 | if (String.IsNullOrEmpty(path)) | ||
103 | { | ||
104 | return false; | ||
105 | } | ||
106 | |||
107 | return path.StartsWith(Path); | ||
108 | } | ||
109 | |||
110 | public string[] SplitParams(string path) | ||
111 | { | ||
112 | string param = GetParam(path); | ||
113 | |||
114 | return param.Split(new char[] { '/', '?', '&' }, StringSplitOptions.RemoveEmptyEntries); | ||
115 | } | ||
116 | } | ||
117 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandler.cs b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandler.cs new file mode 100644 index 0000000..41aa19b --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandler.cs | |||
@@ -0,0 +1,85 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.IO; | ||
29 | using System.Net; | ||
30 | using OpenSim.Framework.ServiceAuth; | ||
31 | |||
32 | namespace OpenSim.Framework.Servers.HttpServer | ||
33 | { | ||
34 | /// <summary> | ||
35 | /// Base streamed request handler. | ||
36 | /// </summary> | ||
37 | /// <remarks> | ||
38 | /// Inheriting classes should override ProcessRequest() rather than Handle() | ||
39 | /// </remarks> | ||
40 | public abstract class BaseStreamHandler : BaseRequestHandler, IStreamedRequestHandler | ||
41 | { | ||
42 | protected IServiceAuth m_Auth; | ||
43 | |||
44 | protected BaseStreamHandler(string httpMethod, string path) : this(httpMethod, path, null, null) { } | ||
45 | |||
46 | protected BaseStreamHandler(string httpMethod, string path, string name, string description) | ||
47 | : base(httpMethod, path, name, description) {} | ||
48 | |||
49 | protected BaseStreamHandler(string httpMethod, string path, IServiceAuth auth) | ||
50 | : base(httpMethod, path, null, null) | ||
51 | { | ||
52 | m_Auth = auth; | ||
53 | } | ||
54 | |||
55 | public virtual byte[] Handle( | ||
56 | string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
57 | { | ||
58 | RequestsReceived++; | ||
59 | |||
60 | if (m_Auth != null) | ||
61 | { | ||
62 | HttpStatusCode statusCode; | ||
63 | |||
64 | if (!m_Auth.Authenticate(httpRequest.Headers, httpResponse.AddHeader, out statusCode)) | ||
65 | { | ||
66 | httpResponse.StatusCode = (int)statusCode; | ||
67 | httpResponse.ContentType = "text/plain"; | ||
68 | return new byte[0]; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | byte[] result = ProcessRequest(path, request, httpRequest, httpResponse); | ||
73 | |||
74 | RequestsHandled++; | ||
75 | |||
76 | return result; | ||
77 | } | ||
78 | |||
79 | protected virtual byte[] ProcessRequest( | ||
80 | string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
81 | { | ||
82 | return null; | ||
83 | } | ||
84 | } | ||
85 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs new file mode 100644 index 0000000..1b88545 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BaseStreamHandlerBasicDOSProtector.cs | |||
@@ -0,0 +1,107 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | using OpenSim.Framework; | ||
28 | using System.IO; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | /// <summary> | ||
33 | /// BaseStreamHandlerBasicDOSProtector Base streamed request handler. | ||
34 | /// </summary> | ||
35 | /// <remarks> | ||
36 | /// Inheriting classes should override ProcessRequest() rather than Handle() | ||
37 | /// </remarks> | ||
38 | public abstract class BaseStreamHandlerBasicDOSProtector : BaseRequestHandler, IStreamedRequestHandler | ||
39 | { | ||
40 | |||
41 | private readonly BasicDosProtectorOptions _options; | ||
42 | private readonly BasicDOSProtector _dosProtector; | ||
43 | |||
44 | protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, BasicDosProtectorOptions options) : this(httpMethod, path, null, null, options) {} | ||
45 | |||
46 | protected BaseStreamHandlerBasicDOSProtector(string httpMethod, string path, string name, string description, BasicDosProtectorOptions options) | ||
47 | : base(httpMethod, path, name, description) | ||
48 | { | ||
49 | _options = options; | ||
50 | _dosProtector = new BasicDOSProtector(_options); | ||
51 | } | ||
52 | |||
53 | public virtual byte[] Handle( | ||
54 | string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
55 | { | ||
56 | byte[] result; | ||
57 | RequestsReceived++; | ||
58 | string clientstring = GetClientString(httpRequest); | ||
59 | string endpoint = GetRemoteAddr(httpRequest); | ||
60 | if (_dosProtector.Process(clientstring, endpoint)) | ||
61 | result = ProcessRequest(path, request, httpRequest, httpResponse); | ||
62 | else | ||
63 | result = ThrottledRequest(path, request, httpRequest, httpResponse); | ||
64 | if (_options.MaxConcurrentSessions > 0) | ||
65 | _dosProtector.ProcessEnd(clientstring, endpoint); | ||
66 | |||
67 | RequestsHandled++; | ||
68 | |||
69 | return result; | ||
70 | } | ||
71 | |||
72 | protected virtual byte[] ProcessRequest( | ||
73 | string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
74 | { | ||
75 | return null; | ||
76 | } | ||
77 | |||
78 | protected virtual byte[] ThrottledRequest( | ||
79 | string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
80 | { | ||
81 | return new byte[0]; | ||
82 | } | ||
83 | |||
84 | |||
85 | private string GetRemoteAddr(IOSHttpRequest httpRequest) | ||
86 | { | ||
87 | string remoteaddr = string.Empty; | ||
88 | if (httpRequest.Headers["remote_addr"] != null) | ||
89 | remoteaddr = httpRequest.Headers["remote_addr"]; | ||
90 | |||
91 | return remoteaddr; | ||
92 | } | ||
93 | |||
94 | private string GetClientString(IOSHttpRequest httpRequest) | ||
95 | { | ||
96 | string clientstring = string.Empty; | ||
97 | |||
98 | if (_options.AllowXForwardedFor && httpRequest.Headers["x-forwarded-for"] != null) | ||
99 | clientstring = httpRequest.Headers["x-forwarded-for"]; | ||
100 | else | ||
101 | clientstring = GetRemoteAddr(httpRequest); | ||
102 | |||
103 | return clientstring; | ||
104 | |||
105 | } | ||
106 | } | ||
107 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/BinaryStreamHandler.cs b/OpenSim/Framework/Servers/HttpServer/BinaryStreamHandler.cs new file mode 100644 index 0000000..1b03f54 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/BinaryStreamHandler.cs | |||
@@ -0,0 +1,76 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.IO; | ||
29 | using System.Text; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public delegate string BinaryMethod(byte[] data, string path, string param); | ||
34 | |||
35 | public class BinaryStreamHandler : BaseStreamHandler | ||
36 | { | ||
37 | private BinaryMethod m_method; | ||
38 | |||
39 | public BinaryStreamHandler(string httpMethod, string path, BinaryMethod binaryMethod) | ||
40 | : this(httpMethod, path, binaryMethod, null, null) {} | ||
41 | |||
42 | public BinaryStreamHandler(string httpMethod, string path, BinaryMethod binaryMethod, string name, string description) | ||
43 | : base(httpMethod, path, name, description) | ||
44 | { | ||
45 | m_method = binaryMethod; | ||
46 | } | ||
47 | |||
48 | protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
49 | { | ||
50 | byte[] data = ReadFully(request); | ||
51 | string param = GetParam(path); | ||
52 | string responseString = m_method(data, path, param); | ||
53 | |||
54 | return Encoding.UTF8.GetBytes(responseString); | ||
55 | } | ||
56 | |||
57 | private static byte[] ReadFully(Stream stream) | ||
58 | { | ||
59 | byte[] buffer = new byte[1024]; | ||
60 | using (MemoryStream ms = new MemoryStream(1024*256)) | ||
61 | { | ||
62 | while (true) | ||
63 | { | ||
64 | int read = stream.Read(buffer, 0, buffer.Length); | ||
65 | |||
66 | if (read <= 0) | ||
67 | { | ||
68 | return ms.ToArray(); | ||
69 | } | ||
70 | |||
71 | ms.Write(buffer, 0, read); | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs new file mode 100644 index 0000000..cd4b8ff --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/GenericHTTPBasicDOSProtector.cs | |||
@@ -0,0 +1,119 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Collections; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | public class GenericHTTPDOSProtector | ||
33 | { | ||
34 | private readonly GenericHTTPMethod _normalMethod; | ||
35 | private readonly GenericHTTPMethod _throttledMethod; | ||
36 | |||
37 | private readonly BasicDosProtectorOptions _options; | ||
38 | private readonly BasicDOSProtector _dosProtector; | ||
39 | |||
40 | public GenericHTTPDOSProtector(GenericHTTPMethod normalMethod, GenericHTTPMethod throttledMethod, BasicDosProtectorOptions options) | ||
41 | { | ||
42 | _normalMethod = normalMethod; | ||
43 | _throttledMethod = throttledMethod; | ||
44 | |||
45 | _options = options; | ||
46 | _dosProtector = new BasicDOSProtector(_options); | ||
47 | } | ||
48 | public Hashtable Process(Hashtable request) | ||
49 | { | ||
50 | Hashtable process = null; | ||
51 | string clientstring= GetClientString(request); | ||
52 | string endpoint = GetRemoteAddr(request); | ||
53 | if (_dosProtector.Process(clientstring, endpoint)) | ||
54 | process = _normalMethod(request); | ||
55 | else | ||
56 | process = _throttledMethod(request); | ||
57 | |||
58 | if (_options.MaxConcurrentSessions>0) | ||
59 | _dosProtector.ProcessEnd(clientstring, endpoint); | ||
60 | |||
61 | return process; | ||
62 | } | ||
63 | |||
64 | private string GetRemoteAddr(Hashtable request) | ||
65 | { | ||
66 | string remoteaddr = ""; | ||
67 | if (!request.ContainsKey("headers")) | ||
68 | return remoteaddr; | ||
69 | Hashtable requestinfo = (Hashtable)request["headers"]; | ||
70 | if (!requestinfo.ContainsKey("remote_addr")) | ||
71 | return remoteaddr; | ||
72 | object remote_addrobj = requestinfo["remote_addr"]; | ||
73 | if (remote_addrobj != null) | ||
74 | { | ||
75 | if (!string.IsNullOrEmpty(remote_addrobj.ToString())) | ||
76 | { | ||
77 | remoteaddr = remote_addrobj.ToString(); | ||
78 | } | ||
79 | |||
80 | } | ||
81 | return remoteaddr; | ||
82 | } | ||
83 | |||
84 | private string GetClientString(Hashtable request) | ||
85 | { | ||
86 | string clientstring = ""; | ||
87 | if (!request.ContainsKey("headers")) | ||
88 | return clientstring; | ||
89 | |||
90 | Hashtable requestinfo = (Hashtable)request["headers"]; | ||
91 | if (_options.AllowXForwardedFor && requestinfo.ContainsKey("x-forwarded-for")) | ||
92 | { | ||
93 | object str = requestinfo["x-forwarded-for"]; | ||
94 | if (str != null) | ||
95 | { | ||
96 | if (!string.IsNullOrEmpty(str.ToString())) | ||
97 | { | ||
98 | return str.ToString(); | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | if (!requestinfo.ContainsKey("remote_addr")) | ||
103 | return clientstring; | ||
104 | |||
105 | object remote_addrobj = requestinfo["remote_addr"]; | ||
106 | if (remote_addrobj != null) | ||
107 | { | ||
108 | if (!string.IsNullOrEmpty(remote_addrobj.ToString())) | ||
109 | { | ||
110 | clientstring = remote_addrobj.ToString(); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | return clientstring; | ||
115 | |||
116 | } | ||
117 | |||
118 | } | ||
119 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/GenericHTTPMethod.cs b/OpenSim/Framework/Servers/HttpServer/GenericHTTPMethod.cs new file mode 100644 index 0000000..456f3a4 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/GenericHTTPMethod.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Collections; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | public delegate Hashtable GenericHTTPMethod(Hashtable request); | ||
33 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpAgentHandler.cs b/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpAgentHandler.cs new file mode 100644 index 0000000..7078895 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpAgentHandler.cs | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | namespace OpenSim.Framework.Servers.HttpServer | ||
29 | { | ||
30 | public interface IHttpAgentHandler | ||
31 | { | ||
32 | bool Handle(OSHttpRequest req, OSHttpResponse resp); | ||
33 | bool Match(OSHttpRequest req, OSHttpResponse resp); | ||
34 | } | ||
35 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpServer.cs new file mode 100644 index 0000000..d162bc1 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/Interfaces/IHttpServer.cs | |||
@@ -0,0 +1,150 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using Nwc.XmlRpc; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | /// <summary> | ||
33 | /// Interface to OpenSimulator's built in HTTP server. Use this to register handlers (http, llsd, xmlrpc, etc.) | ||
34 | /// for given URLs. | ||
35 | /// </summary> | ||
36 | public interface IHttpServer | ||
37 | { | ||
38 | uint SSLPort { get; } | ||
39 | string SSLCommonName { get; } | ||
40 | |||
41 | uint Port { get; } | ||
42 | bool UseSSL { get; } | ||
43 | |||
44 | // // Note that the agent string is provided simply to differentiate | ||
45 | // // the handlers - it is NOT required to be an actual agent header | ||
46 | // // value. | ||
47 | // bool AddAgentHandler(string agent, IHttpAgentHandler handler); | ||
48 | |||
49 | /// <summary> | ||
50 | /// Add a handler for an HTTP request. | ||
51 | /// </summary> | ||
52 | /// <remarks> | ||
53 | /// This handler can actually be invoked either as | ||
54 | /// | ||
55 | /// http://<hostname>:<port>/?method=<methodName> | ||
56 | /// | ||
57 | /// or | ||
58 | /// | ||
59 | /// http://<hostname>:<port><method> | ||
60 | /// | ||
61 | /// if the method name starts with a slash. For example, AddHTTPHandler("/object/", ...) on a standalone region | ||
62 | /// server will register a handler that can be invoked with either | ||
63 | /// | ||
64 | /// http://localhost:9000/?method=/object/ | ||
65 | /// | ||
66 | /// or | ||
67 | /// | ||
68 | /// http://localhost:9000/object/ | ||
69 | /// | ||
70 | /// In addition, the handler invoked by the HTTP server for any request is the one when best matches the request | ||
71 | /// URI. So if a handler for "/myapp/" is registered and a request for "/myapp/page" is received, then | ||
72 | /// the "/myapp/" handler is invoked if no "/myapp/page" handler exists. | ||
73 | /// </remarks> | ||
74 | /// <param name="methodName"></param> | ||
75 | /// <param name="handler"></param> | ||
76 | /// <returns> | ||
77 | /// true if the handler was successfully registered, false if a handler with the same name already existed. | ||
78 | /// </returns> | ||
79 | bool AddHTTPHandler(string methodName, GenericHTTPMethod handler); | ||
80 | |||
81 | bool AddPollServiceHTTPHandler(string methodName, PollServiceEventArgs args); | ||
82 | |||
83 | /// <summary> | ||
84 | /// Adds a LLSD handler, yay. | ||
85 | /// </summary> | ||
86 | /// <param name="path">/resource/ path</param> | ||
87 | /// <param name="handler">handle the LLSD response</param> | ||
88 | /// <returns></returns> | ||
89 | bool AddLLSDHandler(string path, LLSDMethod handler); | ||
90 | |||
91 | /// <summary> | ||
92 | /// Add a stream handler to the http server. If the handler already exists, then nothing happens. | ||
93 | /// </summary> | ||
94 | /// <param name="handler"></param> | ||
95 | void AddStreamHandler(IRequestHandler handler); | ||
96 | |||
97 | bool AddXmlRPCHandler(string method, XmlRpcMethod handler); | ||
98 | bool AddXmlRPCHandler(string method, XmlRpcMethod handler, bool keepAlive); | ||
99 | |||
100 | bool AddJsonRPCHandler(string method, JsonRPCMethod handler); | ||
101 | |||
102 | /// <summary> | ||
103 | /// Websocket HTTP server handlers. | ||
104 | /// </summary> | ||
105 | /// <param name="servicepath"></param> | ||
106 | /// <param name="handler"></param> | ||
107 | void AddWebSocketHandler(string servicepath, BaseHttpServer.WebSocketRequestDelegate handler); | ||
108 | |||
109 | |||
110 | void RemoveWebSocketHandler(string servicepath); | ||
111 | |||
112 | /// <summary> | ||
113 | /// Gets the XML RPC handler for given method name | ||
114 | /// </summary> | ||
115 | /// <param name="method">Name of the method</param> | ||
116 | /// <returns>Returns null if not found</returns> | ||
117 | XmlRpcMethod GetXmlRPCHandler(string method); | ||
118 | |||
119 | bool SetDefaultLLSDHandler(DefaultLLSDMethod handler); | ||
120 | |||
121 | // /// <summary> | ||
122 | // /// Remove the agent if it is registered. | ||
123 | // /// </summary> | ||
124 | // /// <param name="agent"></param> | ||
125 | // /// <param name="handler"></param> | ||
126 | // /// <returns></returns> | ||
127 | // bool RemoveAgentHandler(string agent, IHttpAgentHandler handler); | ||
128 | |||
129 | /// <summary> | ||
130 | /// Remove an HTTP handler | ||
131 | /// </summary> | ||
132 | /// <param name="httpMethod"></param> | ||
133 | /// <param name="path"></param> | ||
134 | void RemoveHTTPHandler(string httpMethod, string path); | ||
135 | |||
136 | void RemovePollServiceHTTPHandler(string httpMethod, string path); | ||
137 | |||
138 | bool RemoveLLSDHandler(string path, LLSDMethod handler); | ||
139 | |||
140 | void RemoveStreamHandler(string httpMethod, string path); | ||
141 | |||
142 | void RemoveXmlRPCHandler(string method); | ||
143 | |||
144 | void RemoveJsonRPCHandler(string method); | ||
145 | |||
146 | string GetHTTP404(string host); | ||
147 | |||
148 | string GetHTTP500(); | ||
149 | } | ||
150 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpRequest.cs b/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpRequest.cs new file mode 100644 index 0000000..caf0edd --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpRequest.cs | |||
@@ -0,0 +1,59 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Specialized; | ||
31 | using System.IO; | ||
32 | using System.Net; | ||
33 | using System.Text; | ||
34 | using System.Web; | ||
35 | |||
36 | namespace OpenSim.Framework.Servers.HttpServer | ||
37 | { | ||
38 | public interface IOSHttpRequest | ||
39 | { | ||
40 | string[] AcceptTypes { get; } | ||
41 | Encoding ContentEncoding { get; } | ||
42 | long ContentLength { get; } | ||
43 | long ContentLength64 { get; } | ||
44 | string ContentType { get; } | ||
45 | HttpCookieCollection Cookies { get; } | ||
46 | bool HasEntityBody { get; } | ||
47 | NameValueCollection Headers { get; } | ||
48 | string HttpMethod { get; } | ||
49 | Stream InputStream { get; } | ||
50 | bool IsSecured { get; } | ||
51 | bool KeepAlive { get; } | ||
52 | NameValueCollection QueryString { get; } | ||
53 | Hashtable Query { get; } | ||
54 | string RawUrl { get; } | ||
55 | IPEndPoint RemoteIPEndPoint { get; } | ||
56 | Uri Url { get; } | ||
57 | string UserAgent { get; } | ||
58 | } | ||
59 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpResponse.cs b/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpResponse.cs new file mode 100644 index 0000000..f61b090 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/Interfaces/IOSHttpResponse.cs | |||
@@ -0,0 +1,132 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Specialized; | ||
31 | using System.IO; | ||
32 | using System.Net; | ||
33 | using System.Text; | ||
34 | using System.Web; | ||
35 | |||
36 | namespace OpenSim.Framework.Servers.HttpServer | ||
37 | { | ||
38 | public interface IOSHttpResponse | ||
39 | { | ||
40 | /// <summary> | ||
41 | /// Content type property. | ||
42 | /// </summary> | ||
43 | /// <remarks> | ||
44 | /// Setting this property will also set IsContentTypeSet to | ||
45 | /// true. | ||
46 | /// </remarks> | ||
47 | string ContentType { get; set; } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Boolean property indicating whether the content type | ||
51 | /// property actively has been set. | ||
52 | /// </summary> | ||
53 | /// <remarks> | ||
54 | /// IsContentTypeSet will go away together with .NET base. | ||
55 | /// </remarks> | ||
56 | // public bool IsContentTypeSet | ||
57 | // { | ||
58 | // get { return _contentTypeSet; } | ||
59 | // } | ||
60 | // private bool _contentTypeSet; | ||
61 | |||
62 | /// <summary> | ||
63 | /// Length of the body content; 0 if there is no body. | ||
64 | /// </summary> | ||
65 | long ContentLength { get; set; } | ||
66 | |||
67 | /// <summary> | ||
68 | /// Alias for ContentLength. | ||
69 | /// </summary> | ||
70 | long ContentLength64 { get; set; } | ||
71 | |||
72 | /// <summary> | ||
73 | /// Encoding of the body content. | ||
74 | /// </summary> | ||
75 | Encoding ContentEncoding { get; set; } | ||
76 | |||
77 | bool KeepAlive { get; set; } | ||
78 | |||
79 | /// <summary> | ||
80 | /// Get or set the keep alive timeout property (default is | ||
81 | /// 20). Setting this to 0 also disables KeepAlive. Setting | ||
82 | /// this to something else but 0 also enable KeepAlive. | ||
83 | /// </summary> | ||
84 | int KeepAliveTimeout { get; set; } | ||
85 | |||
86 | /// <summary> | ||
87 | /// Return the output stream feeding the body. | ||
88 | /// </summary> | ||
89 | /// <remarks> | ||
90 | /// On its way out... | ||
91 | /// </remarks> | ||
92 | Stream OutputStream { get; } | ||
93 | |||
94 | string ProtocolVersion { get; set; } | ||
95 | |||
96 | /// <summary> | ||
97 | /// Return the output stream feeding the body. | ||
98 | /// </summary> | ||
99 | Stream Body { get; } | ||
100 | |||
101 | /// <summary> | ||
102 | /// Set a redirct location. | ||
103 | /// </summary> | ||
104 | string RedirectLocation { set; } | ||
105 | |||
106 | /// <summary> | ||
107 | /// Chunk transfers. | ||
108 | /// </summary> | ||
109 | bool SendChunked { get; set; } | ||
110 | |||
111 | /// <summary> | ||
112 | /// HTTP status code. | ||
113 | /// </summary> | ||
114 | int StatusCode { get; set; } | ||
115 | |||
116 | /// <summary> | ||
117 | /// HTTP status description. | ||
118 | /// </summary> | ||
119 | string StatusDescription { get; set; } | ||
120 | |||
121 | bool ReuseContext { get; set; } | ||
122 | |||
123 | /// <summary> | ||
124 | /// Add a header field and content to the response. | ||
125 | /// </summary> | ||
126 | /// <param name="key">string containing the header field | ||
127 | /// name</param> | ||
128 | /// <param name="value">string containing the header field | ||
129 | /// value</param> | ||
130 | void AddHeader(string key, string value); | ||
131 | } | ||
132 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/Interfaces/IStreamHandler.cs b/OpenSim/Framework/Servers/HttpServer/Interfaces/IStreamHandler.cs new file mode 100644 index 0000000..b8541cb --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/Interfaces/IStreamHandler.cs | |||
@@ -0,0 +1,91 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Collections; | ||
29 | using System.IO; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public interface IRequestHandler | ||
34 | { | ||
35 | /// <summary> | ||
36 | /// Name for this handler. | ||
37 | /// </summary> | ||
38 | /// <remarks> | ||
39 | /// Used for diagnostics. The path doesn't always describe what the handler does. Can be null if none | ||
40 | /// specified. | ||
41 | /// </remarks> | ||
42 | string Name { get; } | ||
43 | |||
44 | /// <summary> | ||
45 | /// Description for this handler. | ||
46 | /// </summary> | ||
47 | /// <remarks> | ||
48 | /// Used for diagnostics. The path doesn't always describe what the handler does. Can be null if none | ||
49 | /// specified. | ||
50 | /// </remarks> | ||
51 | string Description { get; } | ||
52 | |||
53 | // Return response content type | ||
54 | string ContentType { get; } | ||
55 | |||
56 | // Return required http method | ||
57 | string HttpMethod { get; } | ||
58 | |||
59 | // Return path | ||
60 | string Path { get; } | ||
61 | |||
62 | /// <summary> | ||
63 | /// Number of requests received by this handler | ||
64 | /// </summary> | ||
65 | int RequestsReceived { get; } | ||
66 | |||
67 | /// <summary> | ||
68 | /// Number of requests handled. | ||
69 | /// </summary> | ||
70 | /// <remarks> | ||
71 | /// Should be equal to RequestsReceived unless requested are being handled slowly or there is deadlock. | ||
72 | /// </remarks> | ||
73 | int RequestsHandled { get; } | ||
74 | } | ||
75 | |||
76 | public interface IStreamedRequestHandler : IRequestHandler | ||
77 | { | ||
78 | // Handle request stream, return byte array | ||
79 | byte[] Handle(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse); | ||
80 | } | ||
81 | |||
82 | public interface IStreamHandler : IRequestHandler | ||
83 | { | ||
84 | void Handle(string path, Stream request, Stream response, IOSHttpRequest httpReqbuest, IOSHttpResponse httpResponse); | ||
85 | } | ||
86 | |||
87 | public interface IGenericHTTPHandler : IRequestHandler | ||
88 | { | ||
89 | Hashtable Handle(string path, Hashtable request); | ||
90 | } | ||
91 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/JsonRPCMethod.cs b/OpenSim/Framework/Servers/HttpServer/JsonRPCMethod.cs new file mode 100644 index 0000000..5bab508 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/JsonRPCMethod.cs | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Net; | ||
29 | using OpenMetaverse.StructuredData; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public delegate bool JsonRPCMethod(OSDMap jsonRpcRequest, ref JsonRpcResponse response); | ||
34 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/JsonRpcRequestManager.cs b/OpenSim/Framework/Servers/HttpServer/JsonRpcRequestManager.cs new file mode 100644 index 0000000..2fe1a7d --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/JsonRpcRequestManager.cs | |||
@@ -0,0 +1,190 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Net; | ||
30 | using System.Net.Sockets; | ||
31 | using System.Reflection; | ||
32 | using System.Text; | ||
33 | using System.IO; | ||
34 | using OpenMetaverse.StructuredData; | ||
35 | using OpenMetaverse; | ||
36 | using log4net; | ||
37 | |||
38 | namespace OpenSim.Framework.Servers.HttpServer | ||
39 | { | ||
40 | /// <summary> | ||
41 | /// Json rpc request manager. | ||
42 | /// </summary> | ||
43 | public class JsonRpcRequestManager | ||
44 | { | ||
45 | static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
46 | |||
47 | public JsonRpcRequestManager() | ||
48 | { | ||
49 | } | ||
50 | |||
51 | /// <summary> | ||
52 | /// Sends json-rpc request with a serializable type. | ||
53 | /// </summary> | ||
54 | /// <returns> | ||
55 | /// OSD Map. | ||
56 | /// </returns> | ||
57 | /// <param name='parameters'> | ||
58 | /// Serializable type . | ||
59 | /// </param> | ||
60 | /// <param name='method'> | ||
61 | /// Json-rpc method to call. | ||
62 | /// </param> | ||
63 | /// <param name='uri'> | ||
64 | /// URI of json-rpc service. | ||
65 | /// </param> | ||
66 | /// <param name='jsonId'> | ||
67 | /// Id for our call. | ||
68 | /// </param> | ||
69 | public bool JsonRpcRequest(ref object parameters, string method, string uri, string jsonId) | ||
70 | { | ||
71 | if (jsonId == null) | ||
72 | throw new ArgumentNullException("jsonId"); | ||
73 | if (uri == null) | ||
74 | throw new ArgumentNullException("uri"); | ||
75 | if (method == null) | ||
76 | throw new ArgumentNullException("method"); | ||
77 | if (parameters == null) | ||
78 | throw new ArgumentNullException("parameters"); | ||
79 | |||
80 | OSDMap request = new OSDMap(); | ||
81 | request.Add("jsonrpc", OSD.FromString("2.0")); | ||
82 | request.Add("id", OSD.FromString(jsonId)); | ||
83 | request.Add("method", OSD.FromString(method)); | ||
84 | request.Add("params", OSD.SerializeMembers(parameters)); | ||
85 | |||
86 | OSDMap response; | ||
87 | try | ||
88 | { | ||
89 | response = WebUtil.PostToService(uri, request, 10000, true); | ||
90 | } | ||
91 | catch (Exception e) | ||
92 | { | ||
93 | m_log.Debug(string.Format("JsonRpc request '{0}' to {1} failed", method, uri), e); | ||
94 | return false; | ||
95 | } | ||
96 | |||
97 | if (!response.ContainsKey("_Result")) | ||
98 | { | ||
99 | m_log.DebugFormat("JsonRpc request '{0}' to {1} returned an invalid response: {2}", | ||
100 | method, uri, OSDParser.SerializeJsonString(response)); | ||
101 | return false; | ||
102 | } | ||
103 | response = (OSDMap)response["_Result"]; | ||
104 | |||
105 | OSD data; | ||
106 | |||
107 | if (response.ContainsKey("error")) | ||
108 | { | ||
109 | data = response["error"]; | ||
110 | m_log.DebugFormat("JsonRpc request '{0}' to {1} returned an error: {2}", | ||
111 | method, uri, OSDParser.SerializeJsonString(data)); | ||
112 | return false; | ||
113 | } | ||
114 | |||
115 | if (!response.ContainsKey("result")) | ||
116 | { | ||
117 | m_log.DebugFormat("JsonRpc request '{0}' to {1} returned an invalid response: {2}", | ||
118 | method, uri, OSDParser.SerializeJsonString(response)); | ||
119 | return false; | ||
120 | } | ||
121 | |||
122 | data = response["result"]; | ||
123 | OSD.DeserializeMembers(ref parameters, (OSDMap)data); | ||
124 | |||
125 | return true; | ||
126 | } | ||
127 | |||
128 | /// <summary> | ||
129 | /// Sends json-rpc request with OSD parameter. | ||
130 | /// </summary> | ||
131 | /// <returns> | ||
132 | /// The rpc request. | ||
133 | /// </returns> | ||
134 | /// <param name='data'> | ||
135 | /// data - incoming as parameters, outgoing as result/error | ||
136 | /// </param> | ||
137 | /// <param name='method'> | ||
138 | /// Json-rpc method to call. | ||
139 | /// </param> | ||
140 | /// <param name='uri'> | ||
141 | /// URI of json-rpc service. | ||
142 | /// </param> | ||
143 | /// <param name='jsonId'> | ||
144 | /// If set to <c>true</c> json identifier. | ||
145 | /// </param> | ||
146 | public bool JsonRpcRequest(ref OSD data, string method, string uri, string jsonId) | ||
147 | { | ||
148 | if (string.IsNullOrEmpty(jsonId)) | ||
149 | jsonId = UUID.Random().ToString(); | ||
150 | |||
151 | OSDMap request = new OSDMap(); | ||
152 | request.Add("jsonrpc", OSD.FromString("2.0")); | ||
153 | request.Add("id", OSD.FromString(jsonId)); | ||
154 | request.Add("method", OSD.FromString(method)); | ||
155 | request.Add("params", data); | ||
156 | |||
157 | OSDMap response; | ||
158 | try | ||
159 | { | ||
160 | response = WebUtil.PostToService(uri, request, 10000, true); | ||
161 | } | ||
162 | catch (Exception e) | ||
163 | { | ||
164 | m_log.Debug(string.Format("JsonRpc request '{0}' to {1} failed", method, uri), e); | ||
165 | return false; | ||
166 | } | ||
167 | |||
168 | if (!response.ContainsKey("_Result")) | ||
169 | { | ||
170 | m_log.DebugFormat("JsonRpc request '{0}' to {1} returned an invalid response: {2}", | ||
171 | method, uri, OSDParser.SerializeJsonString(response)); | ||
172 | return false; | ||
173 | } | ||
174 | response = (OSDMap)response["_Result"]; | ||
175 | |||
176 | if (response.ContainsKey("error")) | ||
177 | { | ||
178 | data = response["error"]; | ||
179 | m_log.DebugFormat("JsonRpc request '{0}' to {1} returned an error: {2}", | ||
180 | method, uri, OSDParser.SerializeJsonString(data)); | ||
181 | return false; | ||
182 | } | ||
183 | |||
184 | data = response; | ||
185 | |||
186 | return true; | ||
187 | } | ||
188 | |||
189 | } | ||
190 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/JsonRpcResponse.cs b/OpenSim/Framework/Servers/HttpServer/JsonRpcResponse.cs new file mode 100644 index 0000000..2c50587 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/JsonRpcResponse.cs | |||
@@ -0,0 +1,150 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | using System; | ||
28 | using System.Net; | ||
29 | using OpenMetaverse.StructuredData; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public sealed class ErrorCode | ||
34 | { | ||
35 | private ErrorCode() {} | ||
36 | |||
37 | public const int ParseError = -32700; | ||
38 | public const int InvalidRequest = -32600; | ||
39 | public const int MethodNotFound = -32601; | ||
40 | public const int InvalidParams = -32602; | ||
41 | public const int InternalError = -32604; | ||
42 | |||
43 | } | ||
44 | |||
45 | public class JsonRpcError | ||
46 | { | ||
47 | internal OSDMap Error = new OSDMap(); | ||
48 | |||
49 | public int Code | ||
50 | { | ||
51 | get | ||
52 | { | ||
53 | if (Error.ContainsKey("code")) | ||
54 | return Error["code"].AsInteger(); | ||
55 | else | ||
56 | return 0; | ||
57 | } | ||
58 | set | ||
59 | { | ||
60 | Error["code"] = OSD.FromInteger(value); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | public string Message | ||
65 | { | ||
66 | get | ||
67 | { | ||
68 | if (Error.ContainsKey("message")) | ||
69 | return Error["message"].AsString(); | ||
70 | else | ||
71 | return null; | ||
72 | } | ||
73 | set | ||
74 | { | ||
75 | Error["message"] = OSD.FromString(value); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | public OSD Data | ||
80 | { | ||
81 | get; set; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | public class JsonRpcResponse | ||
86 | { | ||
87 | public string JsonRpc | ||
88 | { | ||
89 | get | ||
90 | { | ||
91 | return Reply["jsonrpc"].AsString(); | ||
92 | } | ||
93 | set | ||
94 | { | ||
95 | Reply["jsonrpc"] = OSD.FromString(value); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | public string Id | ||
100 | { | ||
101 | get | ||
102 | { | ||
103 | return Reply["id"].AsString(); | ||
104 | } | ||
105 | set | ||
106 | { | ||
107 | Reply["id"] = OSD.FromString(value); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | public OSD Result | ||
112 | { | ||
113 | get; set; | ||
114 | } | ||
115 | |||
116 | public JsonRpcError Error | ||
117 | { | ||
118 | get; set; | ||
119 | } | ||
120 | |||
121 | public OSDMap Reply = new OSDMap(); | ||
122 | |||
123 | public JsonRpcResponse() | ||
124 | { | ||
125 | Error = new JsonRpcError(); | ||
126 | } | ||
127 | |||
128 | public string Serialize() | ||
129 | { | ||
130 | if (Result != null) | ||
131 | Reply["result"] = Result; | ||
132 | |||
133 | if (Error.Code != 0) | ||
134 | { | ||
135 | Reply["error"] = (OSD)Error.Error; | ||
136 | } | ||
137 | |||
138 | string result = string.Empty; | ||
139 | try | ||
140 | { | ||
141 | result = OSDParser.SerializeJsonString(Reply); | ||
142 | } | ||
143 | catch | ||
144 | { | ||
145 | |||
146 | } | ||
147 | return result; | ||
148 | } | ||
149 | } | ||
150 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/LLSDMethod.cs b/OpenSim/Framework/Servers/HttpServer/LLSDMethod.cs new file mode 100644 index 0000000..35655c6 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/LLSDMethod.cs | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Net; | ||
29 | using OpenMetaverse.StructuredData; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public delegate OSD LLSDMethod(string path, OSD request, string endpoint); | ||
34 | public delegate OSD DefaultLLSDMethod(OSD request, IPEndPoint client); | ||
35 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/LLSDMethodString.cs b/OpenSim/Framework/Servers/HttpServer/LLSDMethodString.cs new file mode 100644 index 0000000..563b88f --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/LLSDMethodString.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using OpenMetaverse.StructuredData; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | public delegate OSD LLSDMethodString(OSD request, string thePath); | ||
33 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpHandler.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpHandler.cs new file mode 100644 index 0000000..2c2b47d --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpHandler.cs | |||
@@ -0,0 +1,183 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Text.RegularExpressions; | ||
32 | |||
33 | namespace OpenSim.Framework.Servers.HttpServer | ||
34 | { | ||
35 | /// <sumary> | ||
36 | /// Any OSHttpHandler must return one of the following results: | ||
37 | /// <list type = "table"> | ||
38 | /// <listheader> | ||
39 | /// <term>result code</term> | ||
40 | /// <description>meaning</description> | ||
41 | /// </listheader> | ||
42 | /// <item> | ||
43 | /// <term>Pass</term> | ||
44 | /// <description>handler did not process the request</request> | ||
45 | /// </item> | ||
46 | /// <item> | ||
47 | /// <term>Done</term> | ||
48 | /// <description>handler did process the request, OSHttpServer | ||
49 | /// can clean up and close the request</request> | ||
50 | /// </item> | ||
51 | /// </list> | ||
52 | /// </summary> | ||
53 | public enum OSHttpHandlerResult | ||
54 | { | ||
55 | Unprocessed, | ||
56 | Pass, | ||
57 | Done, | ||
58 | } | ||
59 | |||
60 | /// <summary> | ||
61 | /// An OSHttpHandler that matches on the "content-type" header can | ||
62 | /// supply an OSHttpContentTypeChecker delegate which will be | ||
63 | /// invoked by the request matcher in OSHttpRequestPump. | ||
64 | /// </summary> | ||
65 | /// <returns>true if the handler is interested in the content; | ||
66 | /// false otherwise</returns> | ||
67 | public delegate bool OSHttpContentTypeChecker(OSHttpRequest req); | ||
68 | |||
69 | public abstract class OSHttpHandler | ||
70 | { | ||
71 | /// <summary> | ||
72 | /// Regular expression used to match against method of | ||
73 | /// the incoming HTTP request. If you want to match any string | ||
74 | /// either use '.*' or null. To match on the empty string use | ||
75 | /// '^$'. | ||
76 | /// </summary> | ||
77 | public virtual Regex Method | ||
78 | { | ||
79 | get { return _method; } | ||
80 | } | ||
81 | protected Regex _method; | ||
82 | |||
83 | /// <summary> | ||
84 | /// Regular expression used to match against path of the | ||
85 | /// incoming HTTP request. If you want to match any string | ||
86 | /// either use '.*' or null. To match on the empty string use | ||
87 | /// '^$'. | ||
88 | /// </summary> | ||
89 | public virtual Regex Path | ||
90 | { | ||
91 | get { return _path; } | ||
92 | } | ||
93 | protected Regex _path; | ||
94 | |||
95 | /// <summary> | ||
96 | /// Dictionary of (query name, regular expression) tuples, | ||
97 | /// allowing us to match on URI query fields. | ||
98 | /// </summary> | ||
99 | public virtual Dictionary<string, Regex> Query | ||
100 | { | ||
101 | get { return _query; } | ||
102 | } | ||
103 | protected Dictionary<string, Regex> _query; | ||
104 | |||
105 | /// <summary> | ||
106 | /// Dictionary of (header name, regular expression) tuples, | ||
107 | /// allowing us to match on HTTP header fields. | ||
108 | /// </summary> | ||
109 | public virtual Dictionary<string, Regex> Headers | ||
110 | { | ||
111 | get { return _headers; } | ||
112 | } | ||
113 | protected Dictionary<string, Regex> _headers; | ||
114 | |||
115 | /// <summary> | ||
116 | /// Dictionary of (header name, regular expression) tuples, | ||
117 | /// allowing us to match on HTTP header fields. | ||
118 | /// </summary> | ||
119 | /// <remarks> | ||
120 | /// This feature is currently not implemented as it requires | ||
121 | /// (trivial) changes to HttpServer.HttpListener that have not | ||
122 | /// been implemented. | ||
123 | /// </remarks> | ||
124 | public virtual Regex IPEndPointWhitelist | ||
125 | { | ||
126 | get { return _ipEndPointRegex; } | ||
127 | } | ||
128 | protected Regex _ipEndPointRegex; | ||
129 | |||
130 | |||
131 | /// <summary> | ||
132 | /// Base class constructor. | ||
133 | /// </summary> | ||
134 | /// <param name="path">null or path regex</param> | ||
135 | /// <param name="headers">null or dictionary of header | ||
136 | /// regexs</param> | ||
137 | /// <param name="contentType">null or content type | ||
138 | /// regex</param> | ||
139 | /// <param name="whitelist">null or IP address regex</param> | ||
140 | public OSHttpHandler(Regex method, Regex path, Dictionary<string, Regex> query, | ||
141 | Dictionary<string, Regex> headers, Regex contentType, Regex whitelist) | ||
142 | { | ||
143 | _method = method; | ||
144 | _path = path; | ||
145 | _query = query; | ||
146 | _ipEndPointRegex = whitelist; | ||
147 | |||
148 | if (null == _headers && null != contentType) | ||
149 | { | ||
150 | _headers = new Dictionary<string, Regex>(); | ||
151 | _headers.Add("content-type", contentType); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | |||
156 | /// <summary> | ||
157 | /// Process an incoming OSHttpRequest that matched our | ||
158 | /// requirements. | ||
159 | /// </summary> | ||
160 | /// <returns> | ||
161 | /// OSHttpHandlerResult.Pass if we are after all not | ||
162 | /// interested in the request; OSHttpHandlerResult.Done if we | ||
163 | /// did process the request. | ||
164 | /// </returns> | ||
165 | public abstract OSHttpHandlerResult Process(OSHttpRequest request); | ||
166 | |||
167 | public override string ToString() | ||
168 | { | ||
169 | StringWriter sw = new StringWriter(); | ||
170 | sw.WriteLine("{0}", base.ToString()); | ||
171 | sw.WriteLine(" method regex {0}", null == Method ? "null" : Method.ToString()); | ||
172 | sw.WriteLine(" path regex {0}", null == Path ? "null": Path.ToString()); | ||
173 | foreach (string tag in Headers.Keys) | ||
174 | { | ||
175 | sw.WriteLine(" header {0} : {1}", tag, Headers[tag].ToString()); | ||
176 | } | ||
177 | sw.WriteLine(" IP whitelist {0}", null == IPEndPointWhitelist ? "null" : IPEndPointWhitelist.ToString()); | ||
178 | sw.WriteLine(); | ||
179 | sw.Close(); | ||
180 | return sw.ToString(); | ||
181 | } | ||
182 | } | ||
183 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpHttpHandler.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpHttpHandler.cs new file mode 100644 index 0000000..95dafcd --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpHttpHandler.cs | |||
@@ -0,0 +1,145 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Generic; | ||
31 | using System.IO; | ||
32 | using System.Net; | ||
33 | using System.Reflection; | ||
34 | using System.Text; | ||
35 | using System.Text.RegularExpressions; | ||
36 | using System.Xml; | ||
37 | using log4net; | ||
38 | using Nwc.XmlRpc; | ||
39 | |||
40 | namespace OpenSim.Framework.Servers.HttpServer | ||
41 | { | ||
42 | public delegate XmlRpcResponse OSHttpHttpProcessor(XmlRpcRequest request); | ||
43 | |||
44 | public class OSHttpHttpHandler: OSHttpHandler | ||
45 | { | ||
46 | private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
47 | |||
48 | // contains handler for processing HTTP Request | ||
49 | private GenericHTTPMethod _handler; | ||
50 | |||
51 | /// <summary> | ||
52 | /// Instantiate an HTTP handler. | ||
53 | /// </summary> | ||
54 | /// <param name="handler">a GenericHTTPMethod</param> | ||
55 | /// <param name="method">null or HTTP method regex</param> | ||
56 | /// <param name="path">null or path regex</param> | ||
57 | /// <param name="query">null or dictionary with query regexs</param> | ||
58 | /// <param name="headers">null or dictionary with header | ||
59 | /// regexs</param> | ||
60 | /// <param name="whitelist">null or IP address whitelist</param> | ||
61 | public OSHttpHttpHandler(GenericHTTPMethod handler, Regex method, Regex path, | ||
62 | Dictionary<string, Regex> query, | ||
63 | Dictionary<string, Regex> headers, Regex whitelist) | ||
64 | : base(method, path, query, headers, new Regex(@"^text/html", RegexOptions.IgnoreCase | RegexOptions.Compiled), | ||
65 | whitelist) | ||
66 | { | ||
67 | _handler = handler; | ||
68 | } | ||
69 | |||
70 | /// <summary> | ||
71 | /// Instantiate an HTTP handler. | ||
72 | /// </summary> | ||
73 | /// <param name="handler">a GenericHTTPMethod</param> | ||
74 | public OSHttpHttpHandler(GenericHTTPMethod handler) | ||
75 | : this(handler, new Regex(@"^GET$", RegexOptions.IgnoreCase | RegexOptions.Compiled), null, null, null, null) | ||
76 | { | ||
77 | } | ||
78 | |||
79 | /// <summary> | ||
80 | /// Invoked by OSHttpRequestPump. | ||
81 | /// </summary> | ||
82 | public override OSHttpHandlerResult Process(OSHttpRequest request) | ||
83 | { | ||
84 | // call handler method | ||
85 | Hashtable responseData = _handler(request.Query); | ||
86 | |||
87 | int responseCode = (int)responseData["int_response_code"]; | ||
88 | string responseString = (string)responseData["str_response_string"]; | ||
89 | string contentType = (string)responseData["content_type"]; | ||
90 | |||
91 | //Even though only one other part of the entire code uses HTTPHandlers, we shouldn't expect this | ||
92 | //and should check for NullReferenceExceptions | ||
93 | |||
94 | if (string.IsNullOrEmpty(contentType)) | ||
95 | { | ||
96 | contentType = "text/html"; | ||
97 | } | ||
98 | |||
99 | OSHttpResponse response = new OSHttpResponse(request); | ||
100 | |||
101 | // We're forgoing the usual error status codes here because the client | ||
102 | // ignores anything but 200 and 301 | ||
103 | |||
104 | response.StatusCode = (int)OSHttpStatusCode.SuccessOk; | ||
105 | |||
106 | if (responseCode == (int)OSHttpStatusCode.RedirectMovedPermanently) | ||
107 | { | ||
108 | response.RedirectLocation = (string)responseData["str_redirect_location"]; | ||
109 | response.StatusCode = responseCode; | ||
110 | } | ||
111 | |||
112 | response.AddHeader("Content-type", contentType); | ||
113 | |||
114 | byte[] buffer; | ||
115 | |||
116 | if (!contentType.Contains("image")) | ||
117 | { | ||
118 | buffer = Encoding.UTF8.GetBytes(responseString); | ||
119 | } | ||
120 | else | ||
121 | { | ||
122 | buffer = Convert.FromBase64String(responseString); | ||
123 | } | ||
124 | |||
125 | response.SendChunked = false; | ||
126 | response.ContentLength64 = buffer.Length; | ||
127 | response.ContentEncoding = Encoding.UTF8; | ||
128 | |||
129 | try | ||
130 | { | ||
131 | response.Body.Write(buffer, 0, buffer.Length); | ||
132 | } | ||
133 | catch (Exception ex) | ||
134 | { | ||
135 | _log.ErrorFormat("[OSHttpHttpHandler]: Error: {0}", ex.Message); | ||
136 | } | ||
137 | finally | ||
138 | { | ||
139 | response.Send(); | ||
140 | } | ||
141 | |||
142 | return OSHttpHandlerResult.Done; | ||
143 | } | ||
144 | } | ||
145 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpRequest.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpRequest.cs new file mode 100644 index 0000000..05ec6dc --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpRequest.cs | |||
@@ -0,0 +1,271 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Collections.Specialized; | ||
32 | using System.IO; | ||
33 | using System.Net; | ||
34 | using System.Reflection; | ||
35 | using System.Text; | ||
36 | using System.Web; | ||
37 | using HttpServer; | ||
38 | using log4net; | ||
39 | |||
40 | namespace OpenSim.Framework.Servers.HttpServer | ||
41 | { | ||
42 | public class OSHttpRequest : IOSHttpRequest | ||
43 | { | ||
44 | private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
45 | |||
46 | protected IHttpRequest _request = null; | ||
47 | protected IHttpClientContext _context = null; | ||
48 | |||
49 | public string[] AcceptTypes | ||
50 | { | ||
51 | get { return _request.AcceptTypes; } | ||
52 | } | ||
53 | |||
54 | public Encoding ContentEncoding | ||
55 | { | ||
56 | get { return _contentEncoding; } | ||
57 | } | ||
58 | private Encoding _contentEncoding; | ||
59 | |||
60 | public long ContentLength | ||
61 | { | ||
62 | get { return _request.ContentLength; } | ||
63 | } | ||
64 | |||
65 | public long ContentLength64 | ||
66 | { | ||
67 | get { return ContentLength; } | ||
68 | } | ||
69 | |||
70 | public string ContentType | ||
71 | { | ||
72 | get { return _contentType; } | ||
73 | } | ||
74 | private string _contentType; | ||
75 | |||
76 | public HttpCookieCollection Cookies | ||
77 | { | ||
78 | get | ||
79 | { | ||
80 | RequestCookies cookies = _request.Cookies; | ||
81 | HttpCookieCollection httpCookies = new HttpCookieCollection(); | ||
82 | foreach (RequestCookie cookie in cookies) | ||
83 | httpCookies.Add(new HttpCookie(cookie.Name, cookie.Value)); | ||
84 | return httpCookies; | ||
85 | } | ||
86 | } | ||
87 | |||
88 | public bool HasEntityBody | ||
89 | { | ||
90 | get { return _request.ContentLength != 0; } | ||
91 | } | ||
92 | |||
93 | public NameValueCollection Headers | ||
94 | { | ||
95 | get { return _request.Headers; } | ||
96 | } | ||
97 | |||
98 | public string HttpMethod | ||
99 | { | ||
100 | get { return _request.Method; } | ||
101 | } | ||
102 | |||
103 | public Stream InputStream | ||
104 | { | ||
105 | get { return _request.Body; } | ||
106 | } | ||
107 | |||
108 | public bool IsSecured | ||
109 | { | ||
110 | get { return _context.IsSecured; } | ||
111 | } | ||
112 | |||
113 | public bool KeepAlive | ||
114 | { | ||
115 | get { return ConnectionType.KeepAlive == _request.Connection; } | ||
116 | } | ||
117 | |||
118 | public NameValueCollection QueryString | ||
119 | { | ||
120 | get { return _queryString; } | ||
121 | } | ||
122 | private NameValueCollection _queryString; | ||
123 | |||
124 | public Hashtable Query | ||
125 | { | ||
126 | get { return _query; } | ||
127 | } | ||
128 | private Hashtable _query; | ||
129 | |||
130 | /// <value> | ||
131 | /// POST request values, if applicable | ||
132 | /// </value> | ||
133 | // public Hashtable Form { get; private set; } | ||
134 | |||
135 | public string RawUrl | ||
136 | { | ||
137 | get { return _request.Uri.AbsolutePath; } | ||
138 | } | ||
139 | |||
140 | public IPEndPoint RemoteIPEndPoint | ||
141 | { | ||
142 | get { return _remoteIPEndPoint; } | ||
143 | } | ||
144 | private IPEndPoint _remoteIPEndPoint; | ||
145 | |||
146 | public Uri Url | ||
147 | { | ||
148 | get { return _request.Uri; } | ||
149 | } | ||
150 | |||
151 | public string UserAgent | ||
152 | { | ||
153 | get { return _userAgent; } | ||
154 | } | ||
155 | private string _userAgent; | ||
156 | |||
157 | internal IHttpRequest IHttpRequest | ||
158 | { | ||
159 | get { return _request; } | ||
160 | } | ||
161 | |||
162 | internal IHttpClientContext IHttpClientContext | ||
163 | { | ||
164 | get { return _context; } | ||
165 | } | ||
166 | |||
167 | /// <summary> | ||
168 | /// Internal whiteboard for handlers to store temporary stuff | ||
169 | /// into. | ||
170 | /// </summary> | ||
171 | internal Dictionary<string, object> Whiteboard | ||
172 | { | ||
173 | get { return _whiteboard; } | ||
174 | } | ||
175 | private Dictionary<string, object> _whiteboard = new Dictionary<string, object>(); | ||
176 | |||
177 | public OSHttpRequest() {} | ||
178 | |||
179 | public OSHttpRequest(IHttpClientContext context, IHttpRequest req) | ||
180 | { | ||
181 | _request = req; | ||
182 | _context = context; | ||
183 | |||
184 | if (null != req.Headers["content-encoding"]) | ||
185 | { | ||
186 | try | ||
187 | { | ||
188 | _contentEncoding = Encoding.GetEncoding(_request.Headers["content-encoding"]); | ||
189 | } | ||
190 | catch (Exception) | ||
191 | { | ||
192 | // ignore | ||
193 | } | ||
194 | } | ||
195 | |||
196 | if (null != req.Headers["content-type"]) | ||
197 | _contentType = _request.Headers["content-type"]; | ||
198 | if (null != req.Headers["user-agent"]) | ||
199 | _userAgent = req.Headers["user-agent"]; | ||
200 | |||
201 | if (null != req.Headers["remote_addr"]) | ||
202 | { | ||
203 | try | ||
204 | { | ||
205 | IPAddress addr = IPAddress.Parse(req.Headers["remote_addr"]); | ||
206 | // sometimes req.Headers["remote_port"] returns a comma separated list, so use | ||
207 | // the first one in the list and log it | ||
208 | string[] strPorts = req.Headers["remote_port"].Split(new char[] { ',' }); | ||
209 | if (strPorts.Length > 1) | ||
210 | { | ||
211 | _log.ErrorFormat("[OSHttpRequest]: format exception on addr/port {0}:{1}, ignoring", | ||
212 | req.Headers["remote_addr"], req.Headers["remote_port"]); | ||
213 | } | ||
214 | int port = Int32.Parse(strPorts[0]); | ||
215 | _remoteIPEndPoint = new IPEndPoint(addr, port); | ||
216 | } | ||
217 | catch (FormatException) | ||
218 | { | ||
219 | _log.ErrorFormat("[OSHttpRequest]: format exception on addr/port {0}:{1}, ignoring", | ||
220 | req.Headers["remote_addr"], req.Headers["remote_port"]); | ||
221 | } | ||
222 | } | ||
223 | |||
224 | _queryString = new NameValueCollection(); | ||
225 | _query = new Hashtable(); | ||
226 | try | ||
227 | { | ||
228 | foreach (HttpInputItem item in req.QueryString) | ||
229 | { | ||
230 | try | ||
231 | { | ||
232 | _queryString.Add(item.Name, item.Value); | ||
233 | _query[item.Name] = item.Value; | ||
234 | } | ||
235 | catch (InvalidCastException) | ||
236 | { | ||
237 | _log.DebugFormat("[OSHttpRequest]: error parsing {0} query item, skipping it", item.Name); | ||
238 | continue; | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | catch (Exception) | ||
243 | { | ||
244 | _log.ErrorFormat("[OSHttpRequest]: Error parsing querystring"); | ||
245 | } | ||
246 | |||
247 | // Form = new Hashtable(); | ||
248 | // foreach (HttpInputItem item in req.Form) | ||
249 | // { | ||
250 | // _log.DebugFormat("[OSHttpRequest]: Got form item {0}={1}", item.Name, item.Value); | ||
251 | // Form.Add(item.Name, item.Value); | ||
252 | // } | ||
253 | } | ||
254 | |||
255 | public override string ToString() | ||
256 | { | ||
257 | StringBuilder me = new StringBuilder(); | ||
258 | me.Append(String.Format("OSHttpRequest: {0} {1}\n", HttpMethod, RawUrl)); | ||
259 | foreach (string k in Headers.AllKeys) | ||
260 | { | ||
261 | me.Append(String.Format(" {0}: {1}\n", k, Headers[k])); | ||
262 | } | ||
263 | if (null != RemoteIPEndPoint) | ||
264 | { | ||
265 | me.Append(String.Format(" IP: {0}\n", RemoteIPEndPoint)); | ||
266 | } | ||
267 | |||
268 | return me.ToString(); | ||
269 | } | ||
270 | } | ||
271 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpRequestPump.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpRequestPump.cs new file mode 100644 index 0000000..bdea278 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpRequestPump.cs | |||
@@ -0,0 +1,298 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | // #define DEBUGGING | ||
29 | |||
30 | using System; | ||
31 | using System.Collections.Generic; | ||
32 | using System.Collections.Specialized; | ||
33 | using System.Diagnostics; | ||
34 | using System.IO; | ||
35 | using System.Net; | ||
36 | using System.Reflection; | ||
37 | using System.Text.RegularExpressions; | ||
38 | using System.Threading; | ||
39 | using log4net; | ||
40 | using HttpServer; | ||
41 | |||
42 | namespace OpenSim.Framework.Servers.HttpServer | ||
43 | { | ||
44 | /// <summary> | ||
45 | /// An OSHttpRequestPump fetches incoming OSHttpRequest objects | ||
46 | /// from the OSHttpRequestQueue and feeds them to all subscribed | ||
47 | /// parties. Each OSHttpRequestPump encapsulates one thread to do | ||
48 | /// the work and there is a fixed number of pumps for each | ||
49 | /// OSHttpServer object. | ||
50 | /// </summary> | ||
51 | public class OSHttpRequestPump | ||
52 | { | ||
53 | private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
54 | |||
55 | protected OSHttpServer _server; | ||
56 | protected OSHttpRequestQueue _queue; | ||
57 | protected Thread _engine; | ||
58 | |||
59 | private int _id; | ||
60 | |||
61 | public string EngineID | ||
62 | { | ||
63 | get { return String.Format("{0} pump {1}", _server.EngineID, _id); } | ||
64 | } | ||
65 | |||
66 | public OSHttpRequestPump(OSHttpServer server, OSHttpRequestQueue queue, int id) | ||
67 | { | ||
68 | _server = server; | ||
69 | _queue = queue; | ||
70 | _id = id; | ||
71 | |||
72 | _engine = new Thread(new ThreadStart(Engine)); | ||
73 | _engine.IsBackground = true; | ||
74 | _engine.Start(); | ||
75 | _engine.Name = string.Format ("Engine:{0}",EngineID); | ||
76 | |||
77 | ThreadTracker.Add(_engine); | ||
78 | } | ||
79 | |||
80 | public static OSHttpRequestPump[] Pumps(OSHttpServer server, OSHttpRequestQueue queue, int poolSize) | ||
81 | { | ||
82 | OSHttpRequestPump[] pumps = new OSHttpRequestPump[poolSize]; | ||
83 | for (int i = 0; i < pumps.Length; i++) | ||
84 | { | ||
85 | pumps[i] = new OSHttpRequestPump(server, queue, i); | ||
86 | } | ||
87 | |||
88 | return pumps; | ||
89 | } | ||
90 | |||
91 | public void Start() | ||
92 | { | ||
93 | _engine = new Thread(new ThreadStart(Engine)); | ||
94 | _engine.IsBackground = true; | ||
95 | _engine.Start(); | ||
96 | _engine.Name = string.Format ("Engine:{0}",EngineID); | ||
97 | |||
98 | ThreadTracker.Add(_engine); | ||
99 | } | ||
100 | |||
101 | public void Engine() | ||
102 | { | ||
103 | OSHttpRequest req = null; | ||
104 | |||
105 | while (true) | ||
106 | { | ||
107 | try | ||
108 | { | ||
109 | // dequeue an OSHttpRequest from OSHttpServer's | ||
110 | // request queue | ||
111 | req = _queue.Dequeue(); | ||
112 | |||
113 | // get a copy of the list of registered handlers | ||
114 | List<OSHttpHandler> handlers = _server.OSHttpHandlers; | ||
115 | |||
116 | // prune list and have it sorted from most | ||
117 | // specific to least specific | ||
118 | handlers = MatchHandlers(req, handlers); | ||
119 | |||
120 | // process req: we try each handler in turn until | ||
121 | // we are either out of handlers or get back a | ||
122 | // Pass or Done | ||
123 | OSHttpHandlerResult rc = OSHttpHandlerResult.Unprocessed; | ||
124 | foreach (OSHttpHandler h in handlers) | ||
125 | { | ||
126 | rc = h.Process(req); | ||
127 | |||
128 | // Pass: handler did not process the request, | ||
129 | // try next handler | ||
130 | if (OSHttpHandlerResult.Pass == rc) continue; | ||
131 | |||
132 | // Handled: handler has processed the request | ||
133 | if (OSHttpHandlerResult.Done == rc) break; | ||
134 | |||
135 | // hmm, something went wrong | ||
136 | throw new Exception(String.Format("[{0}] got unexpected OSHttpHandlerResult {1}", EngineID, rc)); | ||
137 | } | ||
138 | |||
139 | if (OSHttpHandlerResult.Unprocessed == rc) | ||
140 | { | ||
141 | _log.InfoFormat("[{0}] OSHttpHandler: no handler registered for {1}", EngineID, req); | ||
142 | |||
143 | // set up response header | ||
144 | OSHttpResponse resp = new OSHttpResponse(req); | ||
145 | resp.StatusCode = (int)OSHttpStatusCode.ClientErrorNotFound; | ||
146 | resp.StatusDescription = String.Format("no handler on call for {0}", req); | ||
147 | resp.ContentType = "text/html"; | ||
148 | |||
149 | // add explanatory message | ||
150 | StreamWriter body = new StreamWriter(resp.Body); | ||
151 | body.WriteLine("<html>"); | ||
152 | body.WriteLine("<header><title>Ooops...</title><header>"); | ||
153 | body.WriteLine(String.Format("<body><p>{0}</p></body>", resp.StatusDescription)); | ||
154 | body.WriteLine("</html>"); | ||
155 | body.Flush(); | ||
156 | |||
157 | // and ship it back | ||
158 | resp.Send(); | ||
159 | } | ||
160 | } | ||
161 | catch (Exception e) | ||
162 | { | ||
163 | _log.DebugFormat("[{0}] OSHttpHandler problem: {1}", EngineID, e.ToString()); | ||
164 | _log.ErrorFormat("[{0}] OSHttpHandler problem: {1}", EngineID, e.Message); | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | protected List<OSHttpHandler> MatchHandlers(OSHttpRequest req, List<OSHttpHandler> handlers) | ||
170 | { | ||
171 | Dictionary<OSHttpHandler, int> scoredHandlers = new Dictionary<OSHttpHandler, int>(); | ||
172 | |||
173 | _log.DebugFormat("[{0}] MatchHandlers for {1}", EngineID, req); | ||
174 | foreach (OSHttpHandler h in handlers) | ||
175 | { | ||
176 | // initial anchor | ||
177 | scoredHandlers[h] = 0; | ||
178 | |||
179 | // first, check whether IPEndPointWhitelist applies | ||
180 | // and, if it does, whether client is on that white | ||
181 | // list. | ||
182 | if (null != h.IPEndPointWhitelist) | ||
183 | { | ||
184 | // TODO: following code requires code changes to | ||
185 | // HttpServer.HttpRequest to become functional | ||
186 | |||
187 | IPEndPoint remote = req.RemoteIPEndPoint; | ||
188 | if (null != remote) | ||
189 | { | ||
190 | Match epm = h.IPEndPointWhitelist.Match(remote.ToString()); | ||
191 | if (!epm.Success) | ||
192 | { | ||
193 | scoredHandlers.Remove(h); | ||
194 | continue; | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | |||
199 | if (null != h.Method) | ||
200 | { | ||
201 | Match m = h.Method.Match(req.HttpMethod); | ||
202 | if (!m.Success) | ||
203 | { | ||
204 | scoredHandlers.Remove(h); | ||
205 | continue; | ||
206 | } | ||
207 | scoredHandlers[h]++; | ||
208 | } | ||
209 | |||
210 | // whitelist ok, now check path | ||
211 | if (null != h.Path) | ||
212 | { | ||
213 | Match m = h.Path.Match(req.RawUrl); | ||
214 | if (!m.Success) | ||
215 | { | ||
216 | scoredHandlers.Remove(h); | ||
217 | continue; | ||
218 | } | ||
219 | scoredHandlers[h] += m.ToString().Length; | ||
220 | } | ||
221 | |||
222 | // whitelist & path ok, now check query string | ||
223 | if (null != h.Query) | ||
224 | { | ||
225 | int queriesMatch = MatchOnNameValueCollection(req.QueryString, h.Query); | ||
226 | if (0 == queriesMatch) | ||
227 | { | ||
228 | _log.DebugFormat("[{0}] request {1}", EngineID, req); | ||
229 | _log.DebugFormat("[{0}] dropping handler {1}", EngineID, h); | ||
230 | |||
231 | scoredHandlers.Remove(h); | ||
232 | continue; | ||
233 | } | ||
234 | scoredHandlers[h] += queriesMatch; | ||
235 | } | ||
236 | |||
237 | // whitelist, path, query string ok, now check headers | ||
238 | if (null != h.Headers) | ||
239 | { | ||
240 | int headersMatch = MatchOnNameValueCollection(req.Headers, h.Headers); | ||
241 | if (0 == headersMatch) | ||
242 | { | ||
243 | _log.DebugFormat("[{0}] request {1}", EngineID, req); | ||
244 | _log.DebugFormat("[{0}] dropping handler {1}", EngineID, h); | ||
245 | |||
246 | scoredHandlers.Remove(h); | ||
247 | continue; | ||
248 | } | ||
249 | scoredHandlers[h] += headersMatch; | ||
250 | } | ||
251 | } | ||
252 | |||
253 | List<OSHttpHandler> matchingHandlers = new List<OSHttpHandler>(scoredHandlers.Keys); | ||
254 | matchingHandlers.Sort(delegate(OSHttpHandler x, OSHttpHandler y) | ||
255 | { | ||
256 | return scoredHandlers[x] - scoredHandlers[y]; | ||
257 | }); | ||
258 | LogDumpHandlerList(matchingHandlers); | ||
259 | return matchingHandlers; | ||
260 | } | ||
261 | |||
262 | protected int MatchOnNameValueCollection(NameValueCollection collection, Dictionary<string, Regex> regexs) | ||
263 | { | ||
264 | int matched = 0; | ||
265 | |||
266 | foreach (string tag in regexs.Keys) | ||
267 | { | ||
268 | // do we have a header "tag"? | ||
269 | if (null == collection[tag]) | ||
270 | { | ||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | // does the content of collection[tag] match | ||
275 | // the supplied regex? | ||
276 | Match cm = regexs[tag].Match(collection[tag]); | ||
277 | if (!cm.Success) | ||
278 | { | ||
279 | return 0; | ||
280 | } | ||
281 | |||
282 | // ok: matches | ||
283 | matched++; | ||
284 | continue; | ||
285 | } | ||
286 | |||
287 | return matched; | ||
288 | } | ||
289 | |||
290 | [ConditionalAttribute("DEBUGGING")] | ||
291 | private void LogDumpHandlerList(List<OSHttpHandler> l) | ||
292 | { | ||
293 | _log.DebugFormat("[{0}] OSHttpHandlerList dump:", EngineID); | ||
294 | foreach (OSHttpHandler h in l) | ||
295 | _log.DebugFormat(" ", h.ToString()); | ||
296 | } | ||
297 | } | ||
298 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpRequestQueue.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpRequestQueue.cs new file mode 100644 index 0000000..881c5d1 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpRequestQueue.cs | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Threading; | ||
31 | using HttpServer; | ||
32 | |||
33 | namespace OpenSim.Framework.Servers.HttpServer | ||
34 | { | ||
35 | /// <summary> | ||
36 | /// OSHttpRequestQueues are used to hand over incoming HTTP | ||
37 | /// requests to OSHttpRequestPump objects. | ||
38 | /// </summary> | ||
39 | public class OSHttpRequestQueue : Queue<OSHttpRequest> | ||
40 | { | ||
41 | private object _syncObject = new object(); | ||
42 | |||
43 | new public void Enqueue(OSHttpRequest req) | ||
44 | { | ||
45 | lock (_syncObject) | ||
46 | { | ||
47 | base.Enqueue(req); | ||
48 | Monitor.Pulse(_syncObject); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | new public OSHttpRequest Dequeue() | ||
53 | { | ||
54 | OSHttpRequest req = null; | ||
55 | |||
56 | lock (_syncObject) | ||
57 | { | ||
58 | while (null == req) | ||
59 | { | ||
60 | Monitor.Wait(_syncObject); | ||
61 | if (0 != this.Count) req = base.Dequeue(); | ||
62 | } | ||
63 | } | ||
64 | |||
65 | return req; | ||
66 | } | ||
67 | } | ||
68 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpResponse.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpResponse.cs new file mode 100644 index 0000000..89fb5d4 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpResponse.cs | |||
@@ -0,0 +1,332 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.IO; | ||
29 | using System.Net; | ||
30 | using System.Text; | ||
31 | using HttpServer; | ||
32 | |||
33 | namespace OpenSim.Framework.Servers.HttpServer | ||
34 | { | ||
35 | /// <summary> | ||
36 | /// OSHttpResponse is the OpenSim representation of an HTTP | ||
37 | /// response. | ||
38 | /// </summary> | ||
39 | public class OSHttpResponse : IOSHttpResponse | ||
40 | { | ||
41 | /// <summary> | ||
42 | /// Content type property. | ||
43 | /// </summary> | ||
44 | /// <remarks> | ||
45 | /// Setting this property will also set IsContentTypeSet to | ||
46 | /// true. | ||
47 | /// </remarks> | ||
48 | public virtual string ContentType | ||
49 | { | ||
50 | get | ||
51 | { | ||
52 | return _httpResponse.ContentType; | ||
53 | } | ||
54 | |||
55 | set | ||
56 | { | ||
57 | _httpResponse.ContentType = value; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Boolean property indicating whether the content type | ||
63 | /// property actively has been set. | ||
64 | /// </summary> | ||
65 | /// <remarks> | ||
66 | /// IsContentTypeSet will go away together with .NET base. | ||
67 | /// </remarks> | ||
68 | // public bool IsContentTypeSet | ||
69 | // { | ||
70 | // get { return _contentTypeSet; } | ||
71 | // } | ||
72 | // private bool _contentTypeSet; | ||
73 | |||
74 | |||
75 | /// <summary> | ||
76 | /// Length of the body content; 0 if there is no body. | ||
77 | /// </summary> | ||
78 | public long ContentLength | ||
79 | { | ||
80 | get | ||
81 | { | ||
82 | return _httpResponse.ContentLength; | ||
83 | } | ||
84 | |||
85 | set | ||
86 | { | ||
87 | _httpResponse.ContentLength = value; | ||
88 | } | ||
89 | } | ||
90 | |||
91 | /// <summary> | ||
92 | /// Alias for ContentLength. | ||
93 | /// </summary> | ||
94 | public long ContentLength64 | ||
95 | { | ||
96 | get { return ContentLength; } | ||
97 | set { ContentLength = value; } | ||
98 | } | ||
99 | |||
100 | /// <summary> | ||
101 | /// Encoding of the body content. | ||
102 | /// </summary> | ||
103 | public Encoding ContentEncoding | ||
104 | { | ||
105 | get | ||
106 | { | ||
107 | return _httpResponse.Encoding; | ||
108 | } | ||
109 | |||
110 | set | ||
111 | { | ||
112 | _httpResponse.Encoding = value; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | public bool KeepAlive | ||
117 | { | ||
118 | get | ||
119 | { | ||
120 | return _httpResponse.Connection == ConnectionType.KeepAlive; | ||
121 | } | ||
122 | |||
123 | set | ||
124 | { | ||
125 | if (value) | ||
126 | _httpResponse.Connection = ConnectionType.KeepAlive; | ||
127 | else | ||
128 | _httpResponse.Connection = ConnectionType.Close; | ||
129 | } | ||
130 | } | ||
131 | |||
132 | /// <summary> | ||
133 | /// Get or set the keep alive timeout property (default is | ||
134 | /// 20). Setting this to 0 also disables KeepAlive. Setting | ||
135 | /// this to something else but 0 also enable KeepAlive. | ||
136 | /// </summary> | ||
137 | public int KeepAliveTimeout | ||
138 | { | ||
139 | get | ||
140 | { | ||
141 | return _httpResponse.KeepAlive; | ||
142 | } | ||
143 | |||
144 | set | ||
145 | { | ||
146 | if (value == 0) | ||
147 | { | ||
148 | _httpResponse.Connection = ConnectionType.Close; | ||
149 | _httpResponse.KeepAlive = 0; | ||
150 | } | ||
151 | else | ||
152 | { | ||
153 | _httpResponse.Connection = ConnectionType.KeepAlive; | ||
154 | _httpResponse.KeepAlive = value; | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | |||
159 | /// <summary> | ||
160 | /// Return the output stream feeding the body. | ||
161 | /// </summary> | ||
162 | /// <remarks> | ||
163 | /// On its way out... | ||
164 | /// </remarks> | ||
165 | public Stream OutputStream | ||
166 | { | ||
167 | get | ||
168 | { | ||
169 | return _httpResponse.Body; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | public string ProtocolVersion | ||
174 | { | ||
175 | get | ||
176 | { | ||
177 | return _httpResponse.ProtocolVersion; | ||
178 | } | ||
179 | |||
180 | set | ||
181 | { | ||
182 | _httpResponse.ProtocolVersion = value; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | /// <summary> | ||
187 | /// Return the output stream feeding the body. | ||
188 | /// </summary> | ||
189 | public Stream Body | ||
190 | { | ||
191 | get | ||
192 | { | ||
193 | return _httpResponse.Body; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | /// <summary> | ||
198 | /// Set a redirct location. | ||
199 | /// </summary> | ||
200 | public string RedirectLocation | ||
201 | { | ||
202 | // get { return _redirectLocation; } | ||
203 | set | ||
204 | { | ||
205 | _httpResponse.Redirect(value); | ||
206 | } | ||
207 | } | ||
208 | |||
209 | |||
210 | /// <summary> | ||
211 | /// Chunk transfers. | ||
212 | /// </summary> | ||
213 | public bool SendChunked | ||
214 | { | ||
215 | get | ||
216 | { | ||
217 | return _httpResponse.Chunked; | ||
218 | } | ||
219 | |||
220 | set | ||
221 | { | ||
222 | _httpResponse.Chunked = value; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | /// <summary> | ||
227 | /// HTTP status code. | ||
228 | /// </summary> | ||
229 | public virtual int StatusCode | ||
230 | { | ||
231 | get | ||
232 | { | ||
233 | return (int)_httpResponse.Status; | ||
234 | } | ||
235 | |||
236 | set | ||
237 | { | ||
238 | _httpResponse.Status = (HttpStatusCode)value; | ||
239 | } | ||
240 | } | ||
241 | |||
242 | |||
243 | /// <summary> | ||
244 | /// HTTP status description. | ||
245 | /// </summary> | ||
246 | public string StatusDescription | ||
247 | { | ||
248 | get | ||
249 | { | ||
250 | return _httpResponse.Reason; | ||
251 | } | ||
252 | |||
253 | set | ||
254 | { | ||
255 | _httpResponse.Reason = value; | ||
256 | } | ||
257 | } | ||
258 | |||
259 | public bool ReuseContext | ||
260 | { | ||
261 | get | ||
262 | { | ||
263 | if (_httpClientContext != null) | ||
264 | { | ||
265 | return !_httpClientContext.EndWhenDone; | ||
266 | } | ||
267 | return true; | ||
268 | } | ||
269 | set | ||
270 | { | ||
271 | if (_httpClientContext != null) | ||
272 | { | ||
273 | _httpClientContext.EndWhenDone = !value; | ||
274 | } | ||
275 | } | ||
276 | } | ||
277 | |||
278 | protected IHttpResponse _httpResponse; | ||
279 | private IHttpClientContext _httpClientContext; | ||
280 | |||
281 | public OSHttpResponse() {} | ||
282 | |||
283 | public OSHttpResponse(IHttpResponse resp) | ||
284 | { | ||
285 | _httpResponse = resp; | ||
286 | } | ||
287 | |||
288 | /// <summary> | ||
289 | /// Instantiate an OSHttpResponse object from an OSHttpRequest | ||
290 | /// object. | ||
291 | /// </summary | ||
292 | /// <param name="req">Incoming OSHttpRequest to which we are | ||
293 | /// replying</param> | ||
294 | public OSHttpResponse(OSHttpRequest req) | ||
295 | { | ||
296 | _httpResponse = new HttpResponse(req.IHttpClientContext, req.IHttpRequest); | ||
297 | _httpClientContext = req.IHttpClientContext; | ||
298 | } | ||
299 | public OSHttpResponse(HttpResponse resp, IHttpClientContext clientContext) | ||
300 | { | ||
301 | _httpResponse = resp; | ||
302 | _httpClientContext = clientContext; | ||
303 | } | ||
304 | |||
305 | /// <summary> | ||
306 | /// Add a header field and content to the response. | ||
307 | /// </summary> | ||
308 | /// <param name="key">string containing the header field | ||
309 | /// name</param> | ||
310 | /// <param name="value">string containing the header field | ||
311 | /// value</param> | ||
312 | public void AddHeader(string key, string value) | ||
313 | { | ||
314 | _httpResponse.AddHeader(key, value); | ||
315 | } | ||
316 | |||
317 | /// <summary> | ||
318 | /// Send the response back to the remote client | ||
319 | /// </summary> | ||
320 | public void Send() | ||
321 | { | ||
322 | _httpResponse.Body.Flush(); | ||
323 | _httpResponse.Send(); | ||
324 | } | ||
325 | |||
326 | public void FreeContext() | ||
327 | { | ||
328 | if (_httpClientContext != null) | ||
329 | _httpClientContext.Close(); | ||
330 | } | ||
331 | } | ||
332 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpServer.cs new file mode 100644 index 0000000..cd62842 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpServer.cs | |||
@@ -0,0 +1,210 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Net.Sockets; | ||
32 | using System.Reflection; | ||
33 | using System.Text.RegularExpressions; | ||
34 | using System.Threading; | ||
35 | using System.Security.Cryptography.X509Certificates; | ||
36 | using log4net; | ||
37 | using HttpServer; | ||
38 | |||
39 | using HttpListener = HttpServer.HttpListener; | ||
40 | |||
41 | namespace OpenSim.Framework.Servers.HttpServer | ||
42 | { | ||
43 | /// <summary> | ||
44 | /// OSHttpServer provides an HTTP server bound to a specific | ||
45 | /// port. When instantiated with just address and port it uses | ||
46 | /// normal HTTP, when instantiated with address, port, and X509 | ||
47 | /// certificate, it uses HTTPS. | ||
48 | /// </summary> | ||
49 | public class OSHttpServer | ||
50 | { | ||
51 | private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
52 | |||
53 | private object _syncObject = new object(); | ||
54 | |||
55 | // underlying HttpServer.HttpListener | ||
56 | protected HttpListener _listener; | ||
57 | // underlying core/engine thread | ||
58 | protected Thread _engine; | ||
59 | |||
60 | // Queue containing (OS)HttpRequests | ||
61 | protected OSHttpRequestQueue _queue; | ||
62 | |||
63 | // OSHttpRequestPumps "pumping" incoming OSHttpRequests | ||
64 | // upwards | ||
65 | protected OSHttpRequestPump[] _pumps; | ||
66 | |||
67 | // thread identifier | ||
68 | protected string _engineId; | ||
69 | public string EngineID | ||
70 | { | ||
71 | get { return _engineId; } | ||
72 | } | ||
73 | |||
74 | /// <summary> | ||
75 | /// True if this is an HTTPS connection; false otherwise. | ||
76 | /// </summary> | ||
77 | protected bool _isSecure; | ||
78 | public bool IsSecure | ||
79 | { | ||
80 | get { return _isSecure; } | ||
81 | } | ||
82 | |||
83 | public int QueueSize | ||
84 | { | ||
85 | get { return _pumps.Length; } | ||
86 | } | ||
87 | |||
88 | /// <summary> | ||
89 | /// List of registered OSHttpHandlers for this OSHttpServer instance. | ||
90 | /// </summary> | ||
91 | protected List<OSHttpHandler> _httpHandlers = new List<OSHttpHandler>(); | ||
92 | public List<OSHttpHandler> OSHttpHandlers | ||
93 | { | ||
94 | get | ||
95 | { | ||
96 | lock (_httpHandlers) | ||
97 | { | ||
98 | return new List<OSHttpHandler>(_httpHandlers); | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | |||
103 | |||
104 | /// <summary> | ||
105 | /// Instantiate an HTTP server. | ||
106 | /// </summary> | ||
107 | public OSHttpServer(IPAddress address, int port, int poolSize) | ||
108 | { | ||
109 | _engineId = String.Format("OSHttpServer (HTTP:{0})", port); | ||
110 | _isSecure = false; | ||
111 | _log.DebugFormat("[{0}] HTTP server instantiated", EngineID); | ||
112 | |||
113 | _listener = new HttpListener(address, port); | ||
114 | _queue = new OSHttpRequestQueue(); | ||
115 | _pumps = OSHttpRequestPump.Pumps(this, _queue, poolSize); | ||
116 | } | ||
117 | |||
118 | /// <summary> | ||
119 | /// Instantiate an HTTPS server. | ||
120 | /// </summary> | ||
121 | public OSHttpServer(IPAddress address, int port, X509Certificate certificate, int poolSize) | ||
122 | { | ||
123 | _engineId = String.Format("OSHttpServer [HTTPS:{0}/ps:{1}]", port, poolSize); | ||
124 | _isSecure = true; | ||
125 | _log.DebugFormat("[{0}] HTTPS server instantiated", EngineID); | ||
126 | |||
127 | _listener = new HttpListener(address, port, certificate); | ||
128 | _queue = new OSHttpRequestQueue(); | ||
129 | _pumps = OSHttpRequestPump.Pumps(this, _queue, poolSize); | ||
130 | } | ||
131 | |||
132 | /// <summary> | ||
133 | /// Turn an HttpRequest into an OSHttpRequestItem and place it | ||
134 | /// in the queue. The OSHttpRequestQueue object will pulse the | ||
135 | /// next available idle pump. | ||
136 | /// </summary> | ||
137 | protected void OnHttpRequest(HttpClientContext client, HttpRequest request) | ||
138 | { | ||
139 | // turn request into OSHttpRequest | ||
140 | OSHttpRequest req = new OSHttpRequest(client, request); | ||
141 | |||
142 | // place OSHttpRequest into _httpRequestQueue, will | ||
143 | // trigger Pulse to idle waiting pumps | ||
144 | _queue.Enqueue(req); | ||
145 | } | ||
146 | |||
147 | /// <summary> | ||
148 | /// Start the HTTP server engine. | ||
149 | /// </summary> | ||
150 | public void Start() | ||
151 | { | ||
152 | _engine = new Thread(new ThreadStart(Engine)); | ||
153 | _engine.IsBackground = true; | ||
154 | _engine.Start(); | ||
155 | _engine.Name = string.Format ("Engine:{0}",_engineId); | ||
156 | |||
157 | ThreadTracker.Add(_engine); | ||
158 | |||
159 | // start the pumps... | ||
160 | for (int i = 0; i < _pumps.Length; i++) | ||
161 | _pumps[i].Start(); | ||
162 | } | ||
163 | |||
164 | public void Stop() | ||
165 | { | ||
166 | lock (_syncObject) Monitor.Pulse(_syncObject); | ||
167 | } | ||
168 | |||
169 | /// <summary> | ||
170 | /// Engine keeps the HTTP server running. | ||
171 | /// </summary> | ||
172 | private void Engine() | ||
173 | { | ||
174 | try { | ||
175 | _listener.RequestHandler += OnHttpRequest; | ||
176 | _listener.Start(QueueSize); | ||
177 | _log.InfoFormat("[{0}] HTTP server started", EngineID); | ||
178 | |||
179 | lock (_syncObject) Monitor.Wait(_syncObject); | ||
180 | } | ||
181 | catch (Exception ex) | ||
182 | { | ||
183 | _log.DebugFormat("[{0}] HTTP server startup failed: {1}", EngineID, ex.ToString()); | ||
184 | } | ||
185 | |||
186 | _log.InfoFormat("[{0}] HTTP server terminated", EngineID); | ||
187 | } | ||
188 | |||
189 | |||
190 | /// <summary> | ||
191 | /// Add an HTTP request handler. | ||
192 | /// </summary> | ||
193 | /// <param name="handler">OSHttpHandler delegate</param> | ||
194 | /// <param name="path">regex object for path matching</parm> | ||
195 | /// <param name="headers">dictionary containing header names | ||
196 | /// and regular expressions to match against header values</param> | ||
197 | public void AddHandler(OSHttpHandler handler) | ||
198 | { | ||
199 | lock (_httpHandlers) | ||
200 | { | ||
201 | if (_httpHandlers.Contains(handler)) | ||
202 | { | ||
203 | _log.DebugFormat("[OSHttpServer] attempt to add already existing handler ignored"); | ||
204 | return; | ||
205 | } | ||
206 | _httpHandlers.Add(handler); | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpStatusCodes.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpStatusCodes.cs new file mode 100644 index 0000000..a736c8b --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpStatusCodes.cs | |||
@@ -0,0 +1,279 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | namespace OpenSim.Framework.Servers.HttpServer | ||
29 | { | ||
30 | /// <summary> | ||
31 | /// HTTP status codes (almost) as defined by W3C in http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html and IETF in http://tools.ietf.org/html/rfc6585 | ||
32 | /// </summary> | ||
33 | public enum OSHttpStatusCode : int | ||
34 | { | ||
35 | #region 1xx Informational status codes providing a provisional response. | ||
36 | |||
37 | /// <summary> | ||
38 | /// 100 Tells client that to keep on going sending its request | ||
39 | /// </summary> | ||
40 | InfoContinue = 100, | ||
41 | |||
42 | /// <summary> | ||
43 | /// 101 Server understands request, proposes to switch to different application level protocol | ||
44 | /// </summary> | ||
45 | InfoSwitchingProtocols = 101, | ||
46 | |||
47 | #endregion | ||
48 | |||
49 | #region 2xx Success codes | ||
50 | |||
51 | /// <summary> | ||
52 | /// 200 Request successful | ||
53 | /// </summary> | ||
54 | SuccessOk = 200, | ||
55 | |||
56 | /// <summary> | ||
57 | /// 201 Request successful, new resource created | ||
58 | /// </summary> | ||
59 | SuccessOkCreated = 201, | ||
60 | |||
61 | /// <summary> | ||
62 | /// 202 Request accepted, processing still on-going | ||
63 | /// </summary> | ||
64 | SuccessOkAccepted = 202, | ||
65 | |||
66 | /// <summary> | ||
67 | /// 203 Request successful, meta information not authoritative | ||
68 | /// </summary> | ||
69 | SuccessOkNonAuthoritativeInformation = 203, | ||
70 | |||
71 | /// <summary> | ||
72 | /// 204 Request successful, nothing to return in the body | ||
73 | /// </summary> | ||
74 | SuccessOkNoContent = 204, | ||
75 | |||
76 | /// <summary> | ||
77 | /// 205 Request successful, reset displayed content | ||
78 | /// </summary> | ||
79 | SuccessOkResetContent = 205, | ||
80 | |||
81 | /// <summary> | ||
82 | /// 206 Request successful, partial content returned | ||
83 | /// </summary> | ||
84 | SuccessOkPartialContent = 206, | ||
85 | |||
86 | #endregion | ||
87 | |||
88 | #region 3xx Redirect code: user agent needs to go somewhere else | ||
89 | |||
90 | /// <summary> | ||
91 | /// 300 Redirect: different presentation forms available, take a pick | ||
92 | /// </summary> | ||
93 | RedirectMultipleChoices = 300, | ||
94 | |||
95 | /// <summary> | ||
96 | /// 301 Redirect: requested resource has moved and now lives somewhere else | ||
97 | /// </summary> | ||
98 | RedirectMovedPermanently = 301, | ||
99 | |||
100 | /// <summary> | ||
101 | /// 302 Redirect: Resource temporarily somewhere else, location might change | ||
102 | /// </summary> | ||
103 | RedirectFound = 302, | ||
104 | |||
105 | /// <summary> | ||
106 | /// 303 Redirect: See other as result of a POST | ||
107 | /// </summary> | ||
108 | RedirectSeeOther = 303, | ||
109 | |||
110 | /// <summary> | ||
111 | /// 304 Redirect: Resource still the same as before | ||
112 | /// </summary> | ||
113 | RedirectNotModified = 304, | ||
114 | |||
115 | /// <summary> | ||
116 | /// 305 Redirect: Resource must be accessed via proxy provided in location field | ||
117 | /// </summary> | ||
118 | RedirectUseProxy = 305, | ||
119 | |||
120 | /// <summary> | ||
121 | /// 307 Redirect: Resource temporarily somewhere else, location might change | ||
122 | /// </summary> | ||
123 | RedirectMovedTemporarily = 307, | ||
124 | |||
125 | #endregion | ||
126 | |||
127 | #region 4xx Client error: the client borked the request | ||
128 | |||
129 | /// <summary> | ||
130 | /// 400 Client error: bad request, server does not grok what the client wants | ||
131 | /// </summary> | ||
132 | ClientErrorBadRequest = 400, | ||
133 | |||
134 | /// <summary> | ||
135 | /// 401 Client error: the client is not authorized, response provides WWW-Authenticate header field with a challenge | ||
136 | /// </summary> | ||
137 | ClientErrorUnauthorized = 401, | ||
138 | |||
139 | /// <summary> | ||
140 | /// 402 Client error: Payment required (reserved for future use) | ||
141 | /// </summary> | ||
142 | ClientErrorPaymentRequired = 402, | ||
143 | |||
144 | /// <summary> | ||
145 | /// 403 Client error: Server understood request, will not deliver, do not try again. | ||
146 | ClientErrorForbidden = 403, | ||
147 | |||
148 | /// <summary> | ||
149 | /// 404 Client error: Server cannot find anything matching the client request. | ||
150 | /// </summary> | ||
151 | ClientErrorNotFound = 404, | ||
152 | |||
153 | /// <summary> | ||
154 | /// 405 Client error: The method specified by the client in the request is not allowed for the resource requested | ||
155 | /// </summary> | ||
156 | ClientErrorMethodNotAllowed = 405, | ||
157 | |||
158 | /// <summary> | ||
159 | /// 406 Client error: Server cannot generate suitable response for the resource and content characteristics requested by the client | ||
160 | /// </summary> | ||
161 | ClientErrorNotAcceptable = 406, | ||
162 | |||
163 | /// <summary> | ||
164 | /// 407 Client error: Similar to 401, Server requests that client authenticate itself with the proxy first | ||
165 | /// </summary> | ||
166 | ClientErrorProxyAuthRequired = 407, | ||
167 | |||
168 | /// <summary> | ||
169 | /// 408 Client error: Server got impatient with client and decided to give up waiting for the client's request to arrive | ||
170 | /// </summary> | ||
171 | ClientErrorRequestTimeout = 408, | ||
172 | |||
173 | /// <summary> | ||
174 | /// 409 Client error: Server could not fulfill the request for a resource as there is a conflict with the current state of the resource but thinks client can do something about this | ||
175 | /// </summary> | ||
176 | ClientErrorConflict = 409, | ||
177 | |||
178 | /// <summary> | ||
179 | /// 410 Client error: The resource has moved somewhere else, but server has no clue where. | ||
180 | /// </summary> | ||
181 | ClientErrorGone = 410, | ||
182 | |||
183 | /// <summary> | ||
184 | /// 411 Client error: The server is picky again and insists on having a content-length header field in the request | ||
185 | /// </summary> | ||
186 | ClientErrorLengthRequired = 411, | ||
187 | |||
188 | /// <summary> | ||
189 | /// 412 Client error: one or more preconditions supplied in the client's request is false | ||
190 | /// </summary> | ||
191 | ClientErrorPreconditionFailed = 412, | ||
192 | |||
193 | /// <summary> | ||
194 | /// 413 Client error: For fear of reflux, the server refuses to swallow that much data. | ||
195 | /// </summary> | ||
196 | ClientErrorRequestEntityToLarge = 413, | ||
197 | |||
198 | /// <summary> | ||
199 | /// 414 Client error: The server considers the Request-URI to be indecently long and refuses to even look at it. | ||
200 | /// </summary> | ||
201 | ClientErrorRequestURITooLong = 414, | ||
202 | |||
203 | /// <summary> | ||
204 | /// 415 Client error: The server has no clue about the media type requested by the client (contrary to popular belief it is not a warez server) | ||
205 | /// </summary> | ||
206 | ClientErrorUnsupportedMediaType = 415, | ||
207 | |||
208 | /// <summary> | ||
209 | /// 416 Client error: The requested range cannot be delivered by the server. | ||
210 | /// </summary> | ||
211 | ClientErrorRequestRangeNotSatisfiable = 416, | ||
212 | |||
213 | /// <summary> | ||
214 | /// 417 Client error: The expectations of the client as expressed in one or more Expect header fields cannot be met by the server, the server is awfully sorry about this. | ||
215 | /// </summary> | ||
216 | ClientErrorExpectationFailed = 417, | ||
217 | |||
218 | /// <summary> | ||
219 | /// 428 Client error :The 428 status code indicates that the origin server requires the request to be conditional. | ||
220 | /// </summary> | ||
221 | ClientErrorPreconditionRequired = 428, | ||
222 | |||
223 | /// <summary> | ||
224 | /// 429 Client error: The 429 status code indicates that the user has sent too many requests in a given amount of time ("rate limiting"). | ||
225 | /// </summary> | ||
226 | ClientErrorTooManyRequests = 429, | ||
227 | |||
228 | /// <summary> | ||
229 | /// 431 Client error: The 431 status code indicates that the server is unwilling to process the request because its header fields are too large. The request MAY be resubmitted after reducing the size of the request header fields. | ||
230 | /// </summary> | ||
231 | ClientErrorRequestHeaderFieldsTooLarge = 431, | ||
232 | |||
233 | /// <summary> | ||
234 | /// 499 Client error: Wildcard error. | ||
235 | /// </summary> | ||
236 | ClientErrorJoker = 499, | ||
237 | |||
238 | #endregion | ||
239 | |||
240 | #region 5xx Server errors (rare) | ||
241 | |||
242 | /// <summary> | ||
243 | /// 500 Server error: something really strange and unexpected happened | ||
244 | /// </summary> | ||
245 | ServerErrorInternalError = 500, | ||
246 | |||
247 | /// <summary> | ||
248 | /// 501 Server error: The server does not do the functionality required to carry out the client request. not at all. certainly not before breakfast. but also not after breakfast. | ||
249 | /// </summary> | ||
250 | ServerErrorNotImplemented = 501, | ||
251 | |||
252 | /// <summary> | ||
253 | /// 502 Server error: While acting as a proxy or a gateway, the server got ditched by the upstream server and as a consequence regretfully cannot fulfill the client's request | ||
254 | /// </summary> | ||
255 | ServerErrorBadGateway = 502, | ||
256 | |||
257 | /// <summary> | ||
258 | /// 503 Server error: Due to unforseen circumstances the server cannot currently deliver the service requested. Retry-After header might indicate when to try again. | ||
259 | /// </summary> | ||
260 | ServerErrorServiceUnavailable = 503, | ||
261 | |||
262 | /// <summary> | ||
263 | /// 504 Server error: The server blames the upstream server for not being able to deliver the service requested and claims that the upstream server is too slow delivering the goods. | ||
264 | /// </summary> | ||
265 | ServerErrorGatewayTimeout = 504, | ||
266 | |||
267 | /// <summary> | ||
268 | /// 505 Server error: The server does not support the HTTP version conveyed in the client's request. | ||
269 | /// </summary> | ||
270 | ServerErrorHttpVersionNotSupported = 505, | ||
271 | |||
272 | /// <summary> | ||
273 | /// 511 Server error: The 511 status code indicates that the client needs to authenticate to gain network access. | ||
274 | /// </summary> | ||
275 | ServerErrorNetworkAuthenticationRequired = 511, | ||
276 | |||
277 | #endregion | ||
278 | } | ||
279 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/OSHttpXmlRpcHandler.cs b/OpenSim/Framework/Servers/HttpServer/OSHttpXmlRpcHandler.cs new file mode 100644 index 0000000..ef573a4 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/OSHttpXmlRpcHandler.cs | |||
@@ -0,0 +1,180 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Net; | ||
32 | using System.Reflection; | ||
33 | using System.Text; | ||
34 | using System.Text.RegularExpressions; | ||
35 | using System.Xml; | ||
36 | using log4net; | ||
37 | using Nwc.XmlRpc; | ||
38 | |||
39 | namespace OpenSim.Framework.Servers.HttpServer | ||
40 | { | ||
41 | public delegate XmlRpcResponse OSHttpXmlRpcProcessor(XmlRpcRequest request); | ||
42 | |||
43 | public class OSHttpXmlRpcHandler: OSHttpHandler | ||
44 | { | ||
45 | private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
46 | |||
47 | /// <summary> | ||
48 | /// XmlRpcMethodMatch tries to reify (deserialize) an incoming | ||
49 | /// XmlRpc request (and posts it to the "whiteboard") and | ||
50 | /// checks whether the method name is one we are interested | ||
51 | /// in. | ||
52 | /// </summary> | ||
53 | /// <returns>true if the handler is interested in the content; | ||
54 | /// false otherwise</returns> | ||
55 | protected bool XmlRpcMethodMatch(OSHttpRequest req) | ||
56 | { | ||
57 | XmlRpcRequest xmlRpcRequest = null; | ||
58 | |||
59 | // check whether req is already reified | ||
60 | // if not: reify (and post to whiteboard) | ||
61 | try | ||
62 | { | ||
63 | if (req.Whiteboard.ContainsKey("xmlrequest")) | ||
64 | { | ||
65 | xmlRpcRequest = req.Whiteboard["xmlrequest"] as XmlRpcRequest; | ||
66 | } | ||
67 | else | ||
68 | { | ||
69 | StreamReader body = new StreamReader(req.InputStream); | ||
70 | string requestBody = body.ReadToEnd(); | ||
71 | xmlRpcRequest = (XmlRpcRequest)(new XmlRpcRequestDeserializer()).Deserialize(requestBody); | ||
72 | req.Whiteboard["xmlrequest"] = xmlRpcRequest; | ||
73 | } | ||
74 | } | ||
75 | catch (XmlException) | ||
76 | { | ||
77 | _log.ErrorFormat("[OSHttpXmlRpcHandler] failed to deserialize XmlRpcRequest from {0}", req.ToString()); | ||
78 | return false; | ||
79 | } | ||
80 | |||
81 | // check against methodName | ||
82 | if ((null != xmlRpcRequest) | ||
83 | && !String.IsNullOrEmpty(xmlRpcRequest.MethodName) | ||
84 | && xmlRpcRequest.MethodName == _methodName) | ||
85 | { | ||
86 | _log.DebugFormat("[OSHttpXmlRpcHandler] located handler {0} for {1}", _methodName, req.ToString()); | ||
87 | return true; | ||
88 | } | ||
89 | |||
90 | return false; | ||
91 | } | ||
92 | |||
93 | // contains handler for processing XmlRpc Request | ||
94 | private XmlRpcMethod _handler; | ||
95 | |||
96 | // contains XmlRpc method name | ||
97 | private string _methodName; | ||
98 | |||
99 | |||
100 | /// <summary> | ||
101 | /// Instantiate an XmlRpc handler. | ||
102 | /// </summary> | ||
103 | /// <param name="handler">XmlRpcMethod | ||
104 | /// delegate</param> | ||
105 | /// <param name="methodName">XmlRpc method name</param> | ||
106 | /// <param name="path">XmlRpc path prefix (regular expression)</param> | ||
107 | /// <param name="headers">Dictionary with header names and | ||
108 | /// regular expressions to match content of headers</param> | ||
109 | /// <param name="whitelist">IP whitelist of remote end points | ||
110 | /// to accept (regular expression)</param> | ||
111 | /// <remarks> | ||
112 | /// Except for handler and methodName, all other parameters | ||
113 | /// can be null, in which case they are not taken into account | ||
114 | /// when the handler is being looked up. | ||
115 | /// </remarks> | ||
116 | public OSHttpXmlRpcHandler(XmlRpcMethod handler, string methodName, Regex path, | ||
117 | Dictionary<string, Regex> headers, Regex whitelist) | ||
118 | : base(new Regex(@"^POST$", RegexOptions.IgnoreCase | RegexOptions.Compiled), path, null, headers, | ||
119 | new Regex(@"^(text|application)/xml", RegexOptions.IgnoreCase | RegexOptions.Compiled), | ||
120 | whitelist) | ||
121 | { | ||
122 | _handler = handler; | ||
123 | _methodName = methodName; | ||
124 | } | ||
125 | |||
126 | |||
127 | /// <summary> | ||
128 | /// Instantiate an XmlRpc handler. | ||
129 | /// </summary> | ||
130 | /// <param name="handler">XmlRpcMethod | ||
131 | /// delegate</param> | ||
132 | /// <param name="methodName">XmlRpc method name</param> | ||
133 | public OSHttpXmlRpcHandler(XmlRpcMethod handler, string methodName) | ||
134 | : this(handler, methodName, null, null, null) | ||
135 | { | ||
136 | } | ||
137 | |||
138 | |||
139 | /// <summary> | ||
140 | /// Invoked by OSHttpRequestPump. | ||
141 | /// </summary> | ||
142 | public override OSHttpHandlerResult Process(OSHttpRequest request) | ||
143 | { | ||
144 | XmlRpcResponse xmlRpcResponse; | ||
145 | string responseString; | ||
146 | |||
147 | // check whether we are interested in this request | ||
148 | if (!XmlRpcMethodMatch(request)) return OSHttpHandlerResult.Pass; | ||
149 | |||
150 | |||
151 | OSHttpResponse resp = new OSHttpResponse(request); | ||
152 | try | ||
153 | { | ||
154 | // reified XmlRpcRequest must still be on the whiteboard | ||
155 | XmlRpcRequest xmlRpcRequest = request.Whiteboard["xmlrequest"] as XmlRpcRequest; | ||
156 | xmlRpcResponse = _handler(xmlRpcRequest); | ||
157 | responseString = XmlRpcResponseSerializer.Singleton.Serialize(xmlRpcResponse); | ||
158 | |||
159 | resp.ContentType = "text/xml"; | ||
160 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); | ||
161 | |||
162 | resp.SendChunked = false; | ||
163 | resp.ContentLength = buffer.Length; | ||
164 | resp.ContentEncoding = Encoding.UTF8; | ||
165 | |||
166 | resp.Body.Write(buffer, 0, buffer.Length); | ||
167 | resp.Body.Flush(); | ||
168 | |||
169 | resp.Send(); | ||
170 | |||
171 | } | ||
172 | catch (Exception ex) | ||
173 | { | ||
174 | _log.WarnFormat("[OSHttpXmlRpcHandler]: Error: {0}", ex.Message); | ||
175 | return OSHttpHandlerResult.Pass; | ||
176 | } | ||
177 | return OSHttpHandlerResult.Done; | ||
178 | } | ||
179 | } | ||
180 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/PollServiceEventArgs.cs b/OpenSim/Framework/Servers/HttpServer/PollServiceEventArgs.cs new file mode 100644 index 0000000..9477100 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/PollServiceEventArgs.cs | |||
@@ -0,0 +1,86 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using OpenMetaverse; | ||
31 | |||
32 | namespace OpenSim.Framework.Servers.HttpServer | ||
33 | { | ||
34 | public delegate void RequestMethod(UUID requestID, Hashtable request); | ||
35 | public delegate bool HasEventsMethod(UUID requestID, UUID pId); | ||
36 | |||
37 | public delegate Hashtable GetEventsMethod(UUID requestID, UUID pId); | ||
38 | |||
39 | public delegate Hashtable NoEventsMethod(UUID requestID, UUID pId); | ||
40 | |||
41 | public class PollServiceEventArgs : EventArgs | ||
42 | { | ||
43 | public HasEventsMethod HasEvents; | ||
44 | public GetEventsMethod GetEvents; | ||
45 | public NoEventsMethod NoEvents; | ||
46 | public RequestMethod Request; | ||
47 | public UUID Id; | ||
48 | public int TimeOutms; | ||
49 | public EventType Type; | ||
50 | |||
51 | public enum EventType : int | ||
52 | { | ||
53 | LongPoll = 0, | ||
54 | LslHttp = 1, | ||
55 | Inventory = 2 | ||
56 | } | ||
57 | |||
58 | public string Url { get; set; } | ||
59 | |||
60 | /// <summary> | ||
61 | /// Number of requests received for this poll service. | ||
62 | /// </summary> | ||
63 | public int RequestsReceived { get; set; } | ||
64 | |||
65 | /// <summary> | ||
66 | /// Number of requests handled by this poll service. | ||
67 | /// </summary> | ||
68 | public int RequestsHandled { get; set; } | ||
69 | |||
70 | public PollServiceEventArgs( | ||
71 | RequestMethod pRequest, | ||
72 | string pUrl, | ||
73 | HasEventsMethod pHasEvents, GetEventsMethod pGetEvents, NoEventsMethod pNoEvents, | ||
74 | UUID pId, int pTimeOutms) | ||
75 | { | ||
76 | Request = pRequest; | ||
77 | Url = pUrl; | ||
78 | HasEvents = pHasEvents; | ||
79 | GetEvents = pGetEvents; | ||
80 | NoEvents = pNoEvents; | ||
81 | Id = pId; | ||
82 | TimeOutms = pTimeOutms; | ||
83 | Type = EventType.LongPoll; | ||
84 | } | ||
85 | } | ||
86 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/PollServiceHttpRequest.cs b/OpenSim/Framework/Servers/HttpServer/PollServiceHttpRequest.cs new file mode 100644 index 0000000..caf0e98 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/PollServiceHttpRequest.cs | |||
@@ -0,0 +1,97 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Reflection; | ||
31 | using System.Text; | ||
32 | using HttpServer; | ||
33 | using log4net; | ||
34 | using OpenMetaverse; | ||
35 | |||
36 | namespace OpenSim.Framework.Servers.HttpServer | ||
37 | { | ||
38 | public class PollServiceHttpRequest | ||
39 | { | ||
40 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
41 | |||
42 | public readonly PollServiceEventArgs PollServiceArgs; | ||
43 | public readonly IHttpClientContext HttpContext; | ||
44 | public readonly IHttpRequest Request; | ||
45 | public readonly int RequestTime; | ||
46 | public readonly UUID RequestID; | ||
47 | |||
48 | public PollServiceHttpRequest( | ||
49 | PollServiceEventArgs pPollServiceArgs, IHttpClientContext pHttpContext, IHttpRequest pRequest) | ||
50 | { | ||
51 | PollServiceArgs = pPollServiceArgs; | ||
52 | HttpContext = pHttpContext; | ||
53 | Request = pRequest; | ||
54 | RequestTime = System.Environment.TickCount; | ||
55 | RequestID = UUID.Random(); | ||
56 | } | ||
57 | |||
58 | internal void DoHTTPGruntWork(BaseHttpServer server, Hashtable responsedata) | ||
59 | { | ||
60 | OSHttpResponse response | ||
61 | = new OSHttpResponse(new HttpResponse(HttpContext, Request), HttpContext); | ||
62 | |||
63 | byte[] buffer = server.DoHTTPGruntWork(responsedata, response); | ||
64 | |||
65 | response.SendChunked = false; | ||
66 | response.ContentLength64 = buffer.Length; | ||
67 | response.ContentEncoding = Encoding.UTF8; | ||
68 | |||
69 | try | ||
70 | { | ||
71 | response.OutputStream.Write(buffer, 0, buffer.Length); | ||
72 | } | ||
73 | catch (Exception ex) | ||
74 | { | ||
75 | m_log.Warn("[POLL SERVICE WORKER THREAD]: Error ", ex); | ||
76 | } | ||
77 | finally | ||
78 | { | ||
79 | //response.OutputStream.Close(); | ||
80 | try | ||
81 | { | ||
82 | response.OutputStream.Flush(); | ||
83 | response.Send(); | ||
84 | |||
85 | //if (!response.KeepAlive && response.ReuseContext) | ||
86 | // response.FreeContext(); | ||
87 | } | ||
88 | catch (Exception e) | ||
89 | { | ||
90 | m_log.Warn("[POLL SERVICE WORKER THREAD]: Error ", e); | ||
91 | } | ||
92 | |||
93 | PollServiceArgs.RequestsHandled++; | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs new file mode 100644 index 0000000..28bba70 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs | |||
@@ -0,0 +1,331 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Threading; | ||
31 | using System.Reflection; | ||
32 | using log4net; | ||
33 | using HttpServer; | ||
34 | using OpenSim.Framework; | ||
35 | using OpenSim.Framework.Monitoring; | ||
36 | using Amib.Threading; | ||
37 | using System.IO; | ||
38 | using System.Text; | ||
39 | using System.Collections.Generic; | ||
40 | |||
41 | namespace OpenSim.Framework.Servers.HttpServer | ||
42 | { | ||
43 | public class PollServiceRequestManager | ||
44 | { | ||
45 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
46 | |||
47 | /// <summary> | ||
48 | /// Is the poll service request manager running? | ||
49 | /// </summary> | ||
50 | /// <remarks> | ||
51 | /// Can be running either synchronously or asynchronously | ||
52 | /// </remarks> | ||
53 | public bool IsRunning { get; private set; } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Is the poll service performing responses asynchronously (with its own threads) or synchronously (via | ||
57 | /// external calls)? | ||
58 | /// </summary> | ||
59 | public bool PerformResponsesAsync { get; private set; } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Number of responses actually processed and sent to viewer (or aborted due to error). | ||
63 | /// </summary> | ||
64 | public int ResponsesProcessed { get; private set; } | ||
65 | |||
66 | private readonly BaseHttpServer m_server; | ||
67 | |||
68 | private BlockingQueue<PollServiceHttpRequest> m_requests = new BlockingQueue<PollServiceHttpRequest>(); | ||
69 | private static List<PollServiceHttpRequest> m_longPollRequests = new List<PollServiceHttpRequest>(); | ||
70 | |||
71 | private uint m_WorkerThreadCount = 0; | ||
72 | private Thread[] m_workerThreads; | ||
73 | |||
74 | private SmartThreadPool m_threadPool = new SmartThreadPool(20000, 12, 2); | ||
75 | |||
76 | // private int m_timeout = 1000; // increase timeout 250; now use the event one | ||
77 | |||
78 | public PollServiceRequestManager( | ||
79 | BaseHttpServer pSrv, bool performResponsesAsync, uint pWorkerThreadCount, int pTimeout) | ||
80 | { | ||
81 | m_server = pSrv; | ||
82 | PerformResponsesAsync = performResponsesAsync; | ||
83 | m_WorkerThreadCount = pWorkerThreadCount; | ||
84 | m_workerThreads = new Thread[m_WorkerThreadCount]; | ||
85 | |||
86 | StatsManager.RegisterStat( | ||
87 | new Stat( | ||
88 | "QueuedPollResponses", | ||
89 | "Number of poll responses queued for processing.", | ||
90 | "", | ||
91 | "", | ||
92 | "httpserver", | ||
93 | m_server.Port.ToString(), | ||
94 | StatType.Pull, | ||
95 | MeasuresOfInterest.AverageChangeOverTime, | ||
96 | stat => stat.Value = m_requests.Count(), | ||
97 | StatVerbosity.Debug)); | ||
98 | |||
99 | StatsManager.RegisterStat( | ||
100 | new Stat( | ||
101 | "ProcessedPollResponses", | ||
102 | "Number of poll responses processed.", | ||
103 | "", | ||
104 | "", | ||
105 | "httpserver", | ||
106 | m_server.Port.ToString(), | ||
107 | StatType.Pull, | ||
108 | MeasuresOfInterest.AverageChangeOverTime, | ||
109 | stat => stat.Value = ResponsesProcessed, | ||
110 | StatVerbosity.Debug)); | ||
111 | } | ||
112 | |||
113 | public void Start() | ||
114 | { | ||
115 | IsRunning = true; | ||
116 | |||
117 | if (PerformResponsesAsync) | ||
118 | { | ||
119 | //startup worker threads | ||
120 | for (uint i = 0; i < m_WorkerThreadCount; i++) | ||
121 | { | ||
122 | m_workerThreads[i] | ||
123 | = WorkManager.StartThread( | ||
124 | PoolWorkerJob, | ||
125 | string.Format("PollServiceWorkerThread{0}:{1}", i, m_server.Port), | ||
126 | ThreadPriority.Normal, | ||
127 | false, | ||
128 | false, | ||
129 | null, | ||
130 | int.MaxValue); | ||
131 | } | ||
132 | |||
133 | WorkManager.StartThread( | ||
134 | this.CheckLongPollThreads, | ||
135 | string.Format("LongPollServiceWatcherThread:{0}", m_server.Port), | ||
136 | ThreadPriority.Normal, | ||
137 | false, | ||
138 | true, | ||
139 | null, | ||
140 | 1000 * 60 * 10); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | private void ReQueueEvent(PollServiceHttpRequest req) | ||
145 | { | ||
146 | if (IsRunning) | ||
147 | { | ||
148 | // delay the enqueueing for 100ms. There's no need to have the event | ||
149 | // actively on the queue | ||
150 | Timer t = new Timer(self => { | ||
151 | ((Timer)self).Dispose(); | ||
152 | m_requests.Enqueue(req); | ||
153 | }); | ||
154 | |||
155 | t.Change(100, Timeout.Infinite); | ||
156 | |||
157 | } | ||
158 | } | ||
159 | |||
160 | public void Enqueue(PollServiceHttpRequest req) | ||
161 | { | ||
162 | if (IsRunning) | ||
163 | { | ||
164 | if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) | ||
165 | { | ||
166 | lock (m_longPollRequests) | ||
167 | m_longPollRequests.Add(req); | ||
168 | } | ||
169 | else | ||
170 | m_requests.Enqueue(req); | ||
171 | } | ||
172 | } | ||
173 | |||
174 | private void CheckLongPollThreads() | ||
175 | { | ||
176 | // The only purpose of this thread is to check the EQs for events. | ||
177 | // If there are events, that thread will be placed in the "ready-to-serve" queue, m_requests. | ||
178 | // If there are no events, that thread will be back to its "waiting" queue, m_longPollRequests. | ||
179 | // All other types of tasks (Inventory handlers, http-in, etc) don't have the long-poll nature, | ||
180 | // so if they aren't ready to be served by a worker thread (no events), they are placed | ||
181 | // directly back in the "ready-to-serve" queue by the worker thread. | ||
182 | while (IsRunning) | ||
183 | { | ||
184 | Thread.Sleep(500); | ||
185 | Watchdog.UpdateThread(); | ||
186 | |||
187 | // List<PollServiceHttpRequest> not_ready = new List<PollServiceHttpRequest>(); | ||
188 | lock (m_longPollRequests) | ||
189 | { | ||
190 | if (m_longPollRequests.Count > 0 && IsRunning) | ||
191 | { | ||
192 | List<PollServiceHttpRequest> ready = m_longPollRequests.FindAll(req => | ||
193 | (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id) || // there are events in this EQ | ||
194 | (Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms) // no events, but timeout | ||
195 | ); | ||
196 | |||
197 | ready.ForEach(req => | ||
198 | { | ||
199 | m_requests.Enqueue(req); | ||
200 | m_longPollRequests.Remove(req); | ||
201 | }); | ||
202 | |||
203 | } | ||
204 | |||
205 | } | ||
206 | } | ||
207 | } | ||
208 | |||
209 | public void Stop() | ||
210 | { | ||
211 | IsRunning = false; | ||
212 | // m_timeout = -10000; // cause all to expire | ||
213 | Thread.Sleep(1000); // let the world move | ||
214 | |||
215 | foreach (Thread t in m_workerThreads) | ||
216 | Watchdog.AbortThread(t.ManagedThreadId); | ||
217 | |||
218 | PollServiceHttpRequest wreq; | ||
219 | |||
220 | lock (m_longPollRequests) | ||
221 | { | ||
222 | if (m_longPollRequests.Count > 0 && IsRunning) | ||
223 | m_longPollRequests.ForEach(req => m_requests.Enqueue(req)); | ||
224 | } | ||
225 | |||
226 | while (m_requests.Count() > 0) | ||
227 | { | ||
228 | try | ||
229 | { | ||
230 | wreq = m_requests.Dequeue(0); | ||
231 | ResponsesProcessed++; | ||
232 | wreq.DoHTTPGruntWork( | ||
233 | m_server, wreq.PollServiceArgs.NoEvents(wreq.RequestID, wreq.PollServiceArgs.Id)); | ||
234 | } | ||
235 | catch | ||
236 | { | ||
237 | } | ||
238 | } | ||
239 | |||
240 | m_longPollRequests.Clear(); | ||
241 | m_requests.Clear(); | ||
242 | } | ||
243 | |||
244 | // work threads | ||
245 | |||
246 | private void PoolWorkerJob() | ||
247 | { | ||
248 | while (IsRunning) | ||
249 | { | ||
250 | Watchdog.UpdateThread(); | ||
251 | WaitPerformResponse(); | ||
252 | } | ||
253 | } | ||
254 | |||
255 | public void WaitPerformResponse() | ||
256 | { | ||
257 | PollServiceHttpRequest req = m_requests.Dequeue(5000); | ||
258 | // m_log.DebugFormat("[YYY]: Dequeued {0}", (req == null ? "null" : req.PollServiceArgs.Type.ToString())); | ||
259 | |||
260 | if (req != null) | ||
261 | { | ||
262 | try | ||
263 | { | ||
264 | if (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id)) | ||
265 | { | ||
266 | Hashtable responsedata = req.PollServiceArgs.GetEvents(req.RequestID, req.PollServiceArgs.Id); | ||
267 | |||
268 | if (responsedata == null) | ||
269 | return; | ||
270 | |||
271 | // This is the event queue. | ||
272 | // Even if we're not running we can still perform responses by explicit request. | ||
273 | if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll | ||
274 | || !PerformResponsesAsync) | ||
275 | { | ||
276 | try | ||
277 | { | ||
278 | ResponsesProcessed++; | ||
279 | req.DoHTTPGruntWork(m_server, responsedata); | ||
280 | } | ||
281 | catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream | ||
282 | { | ||
283 | // Ignore it, no need to reply | ||
284 | m_log.Error(e); | ||
285 | } | ||
286 | } | ||
287 | else | ||
288 | { | ||
289 | m_threadPool.QueueWorkItem(x => | ||
290 | { | ||
291 | try | ||
292 | { | ||
293 | ResponsesProcessed++; | ||
294 | req.DoHTTPGruntWork(m_server, responsedata); | ||
295 | } | ||
296 | catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream | ||
297 | { | ||
298 | // Ignore it, no need to reply | ||
299 | m_log.Error(e); | ||
300 | } | ||
301 | catch (Exception e) | ||
302 | { | ||
303 | m_log.Error(e); | ||
304 | } | ||
305 | |||
306 | return null; | ||
307 | }, null); | ||
308 | } | ||
309 | } | ||
310 | else | ||
311 | { | ||
312 | if ((Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms) | ||
313 | { | ||
314 | ResponsesProcessed++; | ||
315 | req.DoHTTPGruntWork( | ||
316 | m_server, req.PollServiceArgs.NoEvents(req.RequestID, req.PollServiceArgs.Id)); | ||
317 | } | ||
318 | else | ||
319 | { | ||
320 | ReQueueEvent(req); | ||
321 | } | ||
322 | } | ||
323 | } | ||
324 | catch (Exception e) | ||
325 | { | ||
326 | m_log.ErrorFormat("Exception in poll service thread: " + e.ToString()); | ||
327 | } | ||
328 | } | ||
329 | } | ||
330 | } | ||
331 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/Properties/AssemblyInfo.cs b/OpenSim/Framework/Servers/HttpServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..63335bd --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/Properties/AssemblyInfo.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | using System.Reflection; | ||
2 | using System.Runtime.CompilerServices; | ||
3 | using System.Runtime.InteropServices; | ||
4 | |||
5 | // General Information about an assembly is controlled through the following | ||
6 | // set of attributes. Change these attribute values to modify the information | ||
7 | // associated with an assembly. | ||
8 | [assembly: AssemblyTitle("OpenSim.Framework.Servers.HttpServer")] | ||
9 | [assembly: AssemblyDescription("")] | ||
10 | [assembly: AssemblyConfiguration("")] | ||
11 | [assembly: AssemblyCompany("http://opensimulator.org")] | ||
12 | [assembly: AssemblyProduct("OpenSim")] | ||
13 | [assembly: AssemblyCopyright("OpenSimulator developers")] | ||
14 | [assembly: AssemblyTrademark("")] | ||
15 | [assembly: AssemblyCulture("")] | ||
16 | |||
17 | // Setting ComVisible to false makes the types in this assembly not visible | ||
18 | // to COM components. If you need to access a type in this assembly from | ||
19 | // COM, set the ComVisible attribute to true on that type. | ||
20 | [assembly: ComVisible(false)] | ||
21 | |||
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM | ||
23 | [assembly: Guid("c4ea5baa-81c4-4867-a645-1ec360c1f164")] | ||
24 | |||
25 | // Version information for an assembly consists of the following four values: | ||
26 | // | ||
27 | // Major Version | ||
28 | // Minor Version | ||
29 | // Build Number | ||
30 | // Revision | ||
31 | // | ||
32 | [assembly: AssemblyVersion("0.8.2.*")] | ||
33 | |||
diff --git a/OpenSim/Framework/Servers/HttpServer/RestDeserialiseHandler.cs b/OpenSim/Framework/Servers/HttpServer/RestDeserialiseHandler.cs new file mode 100644 index 0000000..bd55657 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/RestDeserialiseHandler.cs | |||
@@ -0,0 +1,70 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.IO; | ||
29 | using System.Xml; | ||
30 | using System.Xml.Serialization; | ||
31 | |||
32 | namespace OpenSim.Framework.Servers.HttpServer | ||
33 | { | ||
34 | public delegate TResponse RestDeserialiseMethod<TRequest, TResponse>(TRequest request); | ||
35 | |||
36 | public class RestDeserialiseHandler<TRequest, TResponse> : BaseOutputStreamHandler, IStreamHandler | ||
37 | where TRequest : new() | ||
38 | { | ||
39 | private RestDeserialiseMethod<TRequest, TResponse> m_method; | ||
40 | |||
41 | public RestDeserialiseHandler(string httpMethod, string path, RestDeserialiseMethod<TRequest, TResponse> method) | ||
42 | : this(httpMethod, path, method, null, null) {} | ||
43 | |||
44 | public RestDeserialiseHandler( | ||
45 | string httpMethod, string path, RestDeserialiseMethod<TRequest, TResponse> method, string name, string description) | ||
46 | : base(httpMethod, path, name, description) | ||
47 | { | ||
48 | m_method = method; | ||
49 | } | ||
50 | |||
51 | protected override void ProcessRequest(string path, Stream request, Stream responseStream, | ||
52 | IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
53 | { | ||
54 | TRequest deserial; | ||
55 | using (XmlTextReader xmlReader = new XmlTextReader(request)) | ||
56 | { | ||
57 | XmlSerializer deserializer = new XmlSerializer(typeof (TRequest)); | ||
58 | deserial = (TRequest) deserializer.Deserialize(xmlReader); | ||
59 | } | ||
60 | |||
61 | TResponse response = m_method(deserial); | ||
62 | |||
63 | using (XmlWriter xmlWriter = XmlTextWriter.Create(responseStream)) | ||
64 | { | ||
65 | XmlSerializer serializer = new XmlSerializer(typeof (TResponse)); | ||
66 | serializer.Serialize(xmlWriter, response); | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/RestHTTPHandler.cs b/OpenSim/Framework/Servers/HttpServer/RestHTTPHandler.cs new file mode 100644 index 0000000..7f89839 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/RestHTTPHandler.cs | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Collections; | ||
29 | |||
30 | namespace OpenSim.Framework.Servers.HttpServer | ||
31 | { | ||
32 | public class RestHTTPHandler : BaseHTTPHandler | ||
33 | { | ||
34 | private GenericHTTPMethod m_dhttpMethod; | ||
35 | |||
36 | public GenericHTTPMethod Method | ||
37 | { | ||
38 | get { return m_dhttpMethod; } | ||
39 | } | ||
40 | |||
41 | public RestHTTPHandler(string httpMethod, string path, GenericHTTPMethod dhttpMethod) | ||
42 | : base(httpMethod, path) | ||
43 | { | ||
44 | m_dhttpMethod = dhttpMethod; | ||
45 | } | ||
46 | |||
47 | public RestHTTPHandler( | ||
48 | string httpMethod, string path, GenericHTTPMethod dhttpMethod, string name, string description) | ||
49 | : base(httpMethod, path, name, description) | ||
50 | { | ||
51 | m_dhttpMethod = dhttpMethod; | ||
52 | } | ||
53 | |||
54 | public override Hashtable Handle(string path, Hashtable request) | ||
55 | { | ||
56 | string param = GetParam(path); | ||
57 | request.Add("param", param); | ||
58 | request.Add("path", path); | ||
59 | return m_dhttpMethod(request); | ||
60 | } | ||
61 | } | ||
62 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/RestMethod.cs b/OpenSim/Framework/Servers/HttpServer/RestMethod.cs new file mode 100644 index 0000000..80bc7ef --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/RestMethod.cs | |||
@@ -0,0 +1,32 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | namespace OpenSim.Framework.Servers.HttpServer | ||
29 | { | ||
30 | public delegate string RestMethod(string request, string path, string param, | ||
31 | IOSHttpRequest httpRequest, IOSHttpResponse httpResponse); | ||
32 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/RestObjectPoster.cs b/OpenSim/Framework/Servers/HttpServer/RestObjectPoster.cs new file mode 100644 index 0000000..afe052f --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/RestObjectPoster.cs | |||
@@ -0,0 +1,86 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.IO; | ||
30 | using System.Net; | ||
31 | using System.Text; | ||
32 | using System.Xml; | ||
33 | using System.Xml.Serialization; | ||
34 | |||
35 | namespace OpenSim.Framework.Servers.HttpServer | ||
36 | { | ||
37 | /// <summary> | ||
38 | /// Makes an asynchronous REST request which doesn't require us to do anything with the response. | ||
39 | /// </summary> | ||
40 | public class RestObjectPoster | ||
41 | { | ||
42 | public static void BeginPostObject<TRequest>(string requestUrl, TRequest obj) | ||
43 | { | ||
44 | BeginPostObject("POST", requestUrl, obj); | ||
45 | } | ||
46 | |||
47 | public static void BeginPostObject<TRequest>(string verb, string requestUrl, TRequest obj) | ||
48 | { | ||
49 | Type type = typeof (TRequest); | ||
50 | |||
51 | WebRequest request = WebRequest.Create(requestUrl); | ||
52 | request.Method = verb; | ||
53 | request.ContentType = "text/xml"; | ||
54 | |||
55 | using (MemoryStream buffer = new MemoryStream()) | ||
56 | { | ||
57 | XmlWriterSettings settings = new XmlWriterSettings(); | ||
58 | settings.Encoding = Encoding.UTF8; | ||
59 | |||
60 | using (XmlWriter writer = XmlWriter.Create(buffer, settings)) | ||
61 | { | ||
62 | XmlSerializer serializer = new XmlSerializer(type); | ||
63 | serializer.Serialize(writer, obj); | ||
64 | writer.Flush(); | ||
65 | } | ||
66 | |||
67 | int length = (int)buffer.Length; | ||
68 | request.ContentLength = length; | ||
69 | |||
70 | using (Stream requestStream = request.GetRequestStream()) | ||
71 | requestStream.Write(buffer.ToArray(), 0, length); | ||
72 | } | ||
73 | |||
74 | // IAsyncResult result = request.BeginGetResponse(AsyncCallback, request); | ||
75 | request.BeginGetResponse(AsyncCallback, request); | ||
76 | } | ||
77 | |||
78 | private static void AsyncCallback(IAsyncResult result) | ||
79 | { | ||
80 | WebRequest request = (WebRequest) result.AsyncState; | ||
81 | using (WebResponse resp = request.EndGetResponse(result)) | ||
82 | { | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/RestObjectPosterResponse.cs b/OpenSim/Framework/Servers/HttpServer/RestObjectPosterResponse.cs new file mode 100644 index 0000000..a911b9f --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/RestObjectPosterResponse.cs | |||
@@ -0,0 +1,108 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.IO; | ||
30 | using System.Net; | ||
31 | using System.Text; | ||
32 | using System.Xml; | ||
33 | using System.Xml.Serialization; | ||
34 | |||
35 | namespace OpenSim.Framework.Servers.HttpServer | ||
36 | { | ||
37 | public delegate void ReturnResponse<T>(T reponse); | ||
38 | |||
39 | /// <summary> | ||
40 | /// Makes an asynchronous REST request with a callback to invoke with the response. | ||
41 | /// </summary> | ||
42 | public class RestObjectPosterResponse<TResponse> | ||
43 | { | ||
44 | // private static readonly log4net.ILog m_log | ||
45 | // = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); | ||
46 | |||
47 | public ReturnResponse<TResponse> ResponseCallback; | ||
48 | |||
49 | public void BeginPostObject<TRequest>(string requestUrl, TRequest obj) | ||
50 | { | ||
51 | BeginPostObject("POST", requestUrl, obj); | ||
52 | } | ||
53 | |||
54 | public void BeginPostObject<TRequest>(string verb, string requestUrl, TRequest obj) | ||
55 | { | ||
56 | Type type = typeof (TRequest); | ||
57 | |||
58 | WebRequest request = WebRequest.Create(requestUrl); | ||
59 | request.Method = verb; | ||
60 | request.ContentType = "text/xml"; | ||
61 | request.Timeout = 10000; | ||
62 | |||
63 | using (MemoryStream buffer = new MemoryStream()) | ||
64 | { | ||
65 | XmlWriterSettings settings = new XmlWriterSettings(); | ||
66 | settings.Encoding = Encoding.UTF8; | ||
67 | |||
68 | using (XmlWriter writer = XmlWriter.Create(buffer, settings)) | ||
69 | { | ||
70 | XmlSerializer serializer = new XmlSerializer(type); | ||
71 | serializer.Serialize(writer, obj); | ||
72 | writer.Flush(); | ||
73 | } | ||
74 | |||
75 | int length = (int)buffer.Length; | ||
76 | request.ContentLength = length; | ||
77 | |||
78 | using (Stream requestStream = request.GetRequestStream()) | ||
79 | requestStream.Write(buffer.ToArray(), 0, length); | ||
80 | } | ||
81 | |||
82 | // IAsyncResult result = request.BeginGetResponse(AsyncCallback, request); | ||
83 | request.BeginGetResponse(AsyncCallback, request); | ||
84 | } | ||
85 | |||
86 | private void AsyncCallback(IAsyncResult result) | ||
87 | { | ||
88 | WebRequest request = (WebRequest) result.AsyncState; | ||
89 | using (WebResponse resp = request.EndGetResponse(result)) | ||
90 | { | ||
91 | TResponse deserial; | ||
92 | XmlSerializer deserializer = new XmlSerializer(typeof (TResponse)); | ||
93 | Stream stream = resp.GetResponseStream(); | ||
94 | |||
95 | // This is currently a bad debug stanza since it gobbles us the response... | ||
96 | // StreamReader reader = new StreamReader(stream); | ||
97 | // m_log.DebugFormat("[REST OBJECT POSTER RESPONSE]: Received {0}", reader.ReadToEnd()); | ||
98 | |||
99 | deserial = (TResponse) deserializer.Deserialize(stream); | ||
100 | |||
101 | if (deserial != null && ResponseCallback != null) | ||
102 | { | ||
103 | ResponseCallback(deserial); | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/RestSessionService.cs b/OpenSim/Framework/Servers/HttpServer/RestSessionService.cs new file mode 100644 index 0000000..ad69cd2 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/RestSessionService.cs | |||
@@ -0,0 +1,295 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.IO; | ||
30 | using System.Net; | ||
31 | using System.Reflection; | ||
32 | using System.Text; | ||
33 | using System.Xml; | ||
34 | using System.Xml.Serialization; | ||
35 | using log4net; | ||
36 | |||
37 | namespace OpenSim.Framework.Servers.HttpServer | ||
38 | { | ||
39 | public class RestSessionObject<TRequest> | ||
40 | { | ||
41 | private string sid; | ||
42 | private string aid; | ||
43 | private TRequest request_body; | ||
44 | |||
45 | public string SessionID | ||
46 | { | ||
47 | get { return sid; } | ||
48 | set { sid = value; } | ||
49 | } | ||
50 | |||
51 | public string AvatarID | ||
52 | { | ||
53 | get { return aid; } | ||
54 | set { aid = value; } | ||
55 | } | ||
56 | |||
57 | public TRequest Body | ||
58 | { | ||
59 | get { return request_body; } | ||
60 | set { request_body = value; } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | public class SynchronousRestSessionObjectPoster<TRequest, TResponse> | ||
65 | { | ||
66 | public static TResponse BeginPostObject(string verb, string requestUrl, TRequest obj, string sid, string aid) | ||
67 | { | ||
68 | RestSessionObject<TRequest> sobj = new RestSessionObject<TRequest>(); | ||
69 | sobj.SessionID = sid; | ||
70 | sobj.AvatarID = aid; | ||
71 | sobj.Body = obj; | ||
72 | |||
73 | Type type = typeof(RestSessionObject<TRequest>); | ||
74 | |||
75 | WebRequest request = WebRequest.Create(requestUrl); | ||
76 | request.Method = verb; | ||
77 | request.ContentType = "text/xml"; | ||
78 | request.Timeout = 20000; | ||
79 | |||
80 | using (MemoryStream buffer = new MemoryStream()) | ||
81 | { | ||
82 | XmlWriterSettings settings = new XmlWriterSettings(); | ||
83 | settings.Encoding = Encoding.UTF8; | ||
84 | |||
85 | using (XmlWriter writer = XmlWriter.Create(buffer, settings)) | ||
86 | { | ||
87 | XmlSerializer serializer = new XmlSerializer(type); | ||
88 | serializer.Serialize(writer, sobj); | ||
89 | writer.Flush(); | ||
90 | } | ||
91 | |||
92 | int length = (int)buffer.Length; | ||
93 | request.ContentLength = length; | ||
94 | |||
95 | using (Stream requestStream = request.GetRequestStream()) | ||
96 | requestStream.Write(buffer.ToArray(), 0, length); | ||
97 | } | ||
98 | |||
99 | TResponse deserial = default(TResponse); | ||
100 | using (WebResponse resp = request.GetResponse()) | ||
101 | { | ||
102 | XmlSerializer deserializer = new XmlSerializer(typeof(TResponse)); | ||
103 | |||
104 | using (Stream respStream = resp.GetResponseStream()) | ||
105 | deserial = (TResponse)deserializer.Deserialize(respStream); | ||
106 | } | ||
107 | |||
108 | return deserial; | ||
109 | } | ||
110 | } | ||
111 | |||
112 | public class RestSessionObjectPosterResponse<TRequest, TResponse> | ||
113 | { | ||
114 | public ReturnResponse<TResponse> ResponseCallback; | ||
115 | |||
116 | public void BeginPostObject(string requestUrl, TRequest obj, string sid, string aid) | ||
117 | { | ||
118 | BeginPostObject("POST", requestUrl, obj, sid, aid); | ||
119 | } | ||
120 | |||
121 | public void BeginPostObject(string verb, string requestUrl, TRequest obj, string sid, string aid) | ||
122 | { | ||
123 | RestSessionObject<TRequest> sobj = new RestSessionObject<TRequest>(); | ||
124 | sobj.SessionID = sid; | ||
125 | sobj.AvatarID = aid; | ||
126 | sobj.Body = obj; | ||
127 | |||
128 | Type type = typeof(RestSessionObject<TRequest>); | ||
129 | |||
130 | WebRequest request = WebRequest.Create(requestUrl); | ||
131 | request.Method = verb; | ||
132 | request.ContentType = "text/xml"; | ||
133 | request.Timeout = 10000; | ||
134 | |||
135 | using (MemoryStream buffer = new MemoryStream()) | ||
136 | { | ||
137 | XmlWriterSettings settings = new XmlWriterSettings(); | ||
138 | settings.Encoding = Encoding.UTF8; | ||
139 | |||
140 | using (XmlWriter writer = XmlWriter.Create(buffer, settings)) | ||
141 | { | ||
142 | XmlSerializer serializer = new XmlSerializer(type); | ||
143 | serializer.Serialize(writer, sobj); | ||
144 | writer.Flush(); | ||
145 | } | ||
146 | |||
147 | int length = (int)buffer.Length; | ||
148 | request.ContentLength = length; | ||
149 | |||
150 | using (Stream requestStream = request.GetRequestStream()) | ||
151 | requestStream.Write(buffer.ToArray(), 0, length); | ||
152 | } | ||
153 | |||
154 | // IAsyncResult result = request.BeginGetResponse(AsyncCallback, request); | ||
155 | request.BeginGetResponse(AsyncCallback, request); | ||
156 | } | ||
157 | |||
158 | private void AsyncCallback(IAsyncResult result) | ||
159 | { | ||
160 | WebRequest request = (WebRequest)result.AsyncState; | ||
161 | using (WebResponse resp = request.EndGetResponse(result)) | ||
162 | { | ||
163 | TResponse deserial; | ||
164 | XmlSerializer deserializer = new XmlSerializer(typeof(TResponse)); | ||
165 | Stream stream = resp.GetResponseStream(); | ||
166 | |||
167 | // This is currently a bad debug stanza since it gobbles us the response... | ||
168 | // StreamReader reader = new StreamReader(stream); | ||
169 | // m_log.DebugFormat("[REST OBJECT POSTER RESPONSE]: Received {0}", reader.ReadToEnd()); | ||
170 | |||
171 | deserial = (TResponse)deserializer.Deserialize(stream); | ||
172 | if (stream != null) | ||
173 | stream.Close(); | ||
174 | |||
175 | if (deserial != null && ResponseCallback != null) | ||
176 | { | ||
177 | ResponseCallback(deserial); | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | |||
183 | public delegate bool CheckIdentityMethod(string sid, string aid); | ||
184 | |||
185 | public class RestDeserialiseSecureHandler<TRequest, TResponse> : BaseOutputStreamHandler, IStreamHandler | ||
186 | where TRequest : new() | ||
187 | { | ||
188 | private static readonly ILog m_log | ||
189 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
190 | |||
191 | private RestDeserialiseMethod<TRequest, TResponse> m_method; | ||
192 | private CheckIdentityMethod m_smethod; | ||
193 | |||
194 | public RestDeserialiseSecureHandler( | ||
195 | string httpMethod, string path, | ||
196 | RestDeserialiseMethod<TRequest, TResponse> method, CheckIdentityMethod smethod) | ||
197 | : base(httpMethod, path) | ||
198 | { | ||
199 | m_smethod = smethod; | ||
200 | m_method = method; | ||
201 | } | ||
202 | |||
203 | protected override void ProcessRequest(string path, Stream request, Stream responseStream, | ||
204 | IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
205 | { | ||
206 | RestSessionObject<TRequest> deserial = default(RestSessionObject<TRequest>); | ||
207 | bool fail = false; | ||
208 | |||
209 | using (XmlTextReader xmlReader = new XmlTextReader(request)) | ||
210 | { | ||
211 | try | ||
212 | { | ||
213 | XmlSerializer deserializer = new XmlSerializer(typeof(RestSessionObject<TRequest>)); | ||
214 | deserial = (RestSessionObject<TRequest>)deserializer.Deserialize(xmlReader); | ||
215 | } | ||
216 | catch (Exception e) | ||
217 | { | ||
218 | m_log.Error("[REST]: Deserialization problem. Ignoring request. " + e); | ||
219 | fail = true; | ||
220 | } | ||
221 | } | ||
222 | |||
223 | TResponse response = default(TResponse); | ||
224 | if (!fail && m_smethod(deserial.SessionID, deserial.AvatarID)) | ||
225 | { | ||
226 | response = m_method(deserial.Body); | ||
227 | } | ||
228 | |||
229 | using (XmlWriter xmlWriter = XmlTextWriter.Create(responseStream)) | ||
230 | { | ||
231 | XmlSerializer serializer = new XmlSerializer(typeof(TResponse)); | ||
232 | serializer.Serialize(xmlWriter, response); | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | public delegate bool CheckTrustedSourceMethod(IPEndPoint peer); | ||
238 | |||
239 | public class RestDeserialiseTrustedHandler<TRequest, TResponse> : BaseOutputStreamHandler, IStreamHandler | ||
240 | where TRequest : new() | ||
241 | { | ||
242 | private static readonly ILog m_log | ||
243 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
244 | |||
245 | /// <summary> | ||
246 | /// The operation to perform once trust has been established. | ||
247 | /// </summary> | ||
248 | private RestDeserialiseMethod<TRequest, TResponse> m_method; | ||
249 | |||
250 | /// <summary> | ||
251 | /// The method used to check whether a request is trusted. | ||
252 | /// </summary> | ||
253 | private CheckTrustedSourceMethod m_tmethod; | ||
254 | |||
255 | public RestDeserialiseTrustedHandler(string httpMethod, string path, RestDeserialiseMethod<TRequest, TResponse> method, CheckTrustedSourceMethod tmethod) | ||
256 | : base(httpMethod, path) | ||
257 | { | ||
258 | m_tmethod = tmethod; | ||
259 | m_method = method; | ||
260 | } | ||
261 | |||
262 | protected override void ProcessRequest(string path, Stream request, Stream responseStream, | ||
263 | IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
264 | { | ||
265 | TRequest deserial = default(TRequest); | ||
266 | bool fail = false; | ||
267 | |||
268 | using (XmlTextReader xmlReader = new XmlTextReader(request)) | ||
269 | { | ||
270 | try | ||
271 | { | ||
272 | XmlSerializer deserializer = new XmlSerializer(typeof(TRequest)); | ||
273 | deserial = (TRequest)deserializer.Deserialize(xmlReader); | ||
274 | } | ||
275 | catch (Exception e) | ||
276 | { | ||
277 | m_log.Error("[REST]: Deserialization problem. Ignoring request. " + e); | ||
278 | fail = true; | ||
279 | } | ||
280 | } | ||
281 | |||
282 | TResponse response = default(TResponse); | ||
283 | if (!fail && m_tmethod(httpRequest.RemoteIPEndPoint)) | ||
284 | { | ||
285 | response = m_method(deserial); | ||
286 | } | ||
287 | |||
288 | using (XmlWriter xmlWriter = XmlTextWriter.Create(responseStream)) | ||
289 | { | ||
290 | XmlSerializer serializer = new XmlSerializer(typeof(TResponse)); | ||
291 | serializer.Serialize(xmlWriter, response); | ||
292 | } | ||
293 | } | ||
294 | } | ||
295 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/RestStreamHandler.cs b/OpenSim/Framework/Servers/HttpServer/RestStreamHandler.cs new file mode 100644 index 0000000..0305dee --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/RestStreamHandler.cs | |||
@@ -0,0 +1,65 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.IO; | ||
29 | using System.Text; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public class RestStreamHandler : BaseStreamHandler | ||
34 | { | ||
35 | private RestMethod m_restMethod; | ||
36 | |||
37 | public RestMethod Method | ||
38 | { | ||
39 | get { return m_restMethod; } | ||
40 | } | ||
41 | |||
42 | public RestStreamHandler(string httpMethod, string path, RestMethod restMethod) | ||
43 | : this(httpMethod, path, restMethod, null, null) {} | ||
44 | |||
45 | public RestStreamHandler(string httpMethod, string path, RestMethod restMethod, string name, string description) | ||
46 | : base(httpMethod, path, name, description) | ||
47 | { | ||
48 | m_restMethod = restMethod; | ||
49 | } | ||
50 | |||
51 | protected override byte[] ProcessRequest(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) | ||
52 | { | ||
53 | Encoding encoding = Encoding.UTF8; | ||
54 | StreamReader streamReader = new StreamReader(request, encoding); | ||
55 | |||
56 | string requestBody = streamReader.ReadToEnd(); | ||
57 | streamReader.Close(); | ||
58 | |||
59 | string param = GetParam(path); | ||
60 | string responseString = m_restMethod(requestBody, path, param, httpRequest, httpResponse); | ||
61 | |||
62 | return Encoding.UTF8.GetBytes(responseString); | ||
63 | } | ||
64 | } | ||
65 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs new file mode 100644 index 0000000..c2925e3 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs | |||
@@ -0,0 +1,1159 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Net; | ||
32 | using System.Security.Cryptography; | ||
33 | using System.Text; | ||
34 | using System.Threading; | ||
35 | using HttpServer; | ||
36 | |||
37 | namespace OpenSim.Framework.Servers.HttpServer | ||
38 | { | ||
39 | // Sealed class. If you're going to unseal it, implement IDisposable. | ||
40 | /// <summary> | ||
41 | /// This class implements websockets. It grabs the network context from C#Webserver and utilizes it directly as a tcp streaming service | ||
42 | /// </summary> | ||
43 | public sealed class WebSocketHttpServerHandler : BaseRequestHandler | ||
44 | { | ||
45 | |||
46 | private class WebSocketState | ||
47 | { | ||
48 | public List<byte> ReceivedBytes; | ||
49 | public int ExpectedBytes; | ||
50 | public WebsocketFrameHeader Header; | ||
51 | public bool FrameComplete; | ||
52 | public WebSocketFrame ContinuationFrame; | ||
53 | } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Binary Data will trigger this event | ||
57 | /// </summary> | ||
58 | public event DataDelegate OnData; | ||
59 | |||
60 | /// <summary> | ||
61 | /// Textual Data will trigger this event | ||
62 | /// </summary> | ||
63 | public event TextDelegate OnText; | ||
64 | |||
65 | /// <summary> | ||
66 | /// A ping request form the other side will trigger this event. | ||
67 | /// This class responds to the ping automatically. You shouldn't send a pong. | ||
68 | /// it's informational. | ||
69 | /// </summary> | ||
70 | public event PingDelegate OnPing; | ||
71 | |||
72 | /// <summary> | ||
73 | /// This is a response to a ping you sent. | ||
74 | /// </summary> | ||
75 | public event PongDelegate OnPong; | ||
76 | |||
77 | /// <summary> | ||
78 | /// This is a regular HTTP Request... This may be removed in the future. | ||
79 | /// </summary> | ||
80 | // public event RegularHttpRequestDelegate OnRegularHttpRequest; | ||
81 | |||
82 | /// <summary> | ||
83 | /// When the upgrade from a HTTP request to a Websocket is completed, this will be fired | ||
84 | /// </summary> | ||
85 | public event UpgradeCompletedDelegate OnUpgradeCompleted; | ||
86 | |||
87 | /// <summary> | ||
88 | /// If the upgrade failed, this will be fired | ||
89 | /// </summary> | ||
90 | public event UpgradeFailedDelegate OnUpgradeFailed; | ||
91 | |||
92 | /// <summary> | ||
93 | /// When the websocket is closed, this will be fired. | ||
94 | /// </summary> | ||
95 | public event CloseDelegate OnClose; | ||
96 | |||
97 | /// <summary> | ||
98 | /// Set this delegate to allow your module to validate the origin of the | ||
99 | /// Websocket request. Primary line of defense against cross site scripting | ||
100 | /// </summary> | ||
101 | public ValidateHandshake HandshakeValidateMethodOverride = null; | ||
102 | |||
103 | private ManualResetEvent _receiveDone = new ManualResetEvent(false); | ||
104 | |||
105 | private OSHttpRequest _request; | ||
106 | private HTTPNetworkContext _networkContext; | ||
107 | private IHttpClientContext _clientContext; | ||
108 | |||
109 | private int _pingtime = 0; | ||
110 | private byte[] _buffer; | ||
111 | private int _bufferPosition; | ||
112 | private int _bufferLength; | ||
113 | private bool _closing; | ||
114 | private bool _upgraded; | ||
115 | private int _maxPayloadBytes = 41943040; | ||
116 | private int _initialMsgTimeout = 0; | ||
117 | private int _defaultReadTimeout = 10000; | ||
118 | |||
119 | private const string HandshakeAcceptText = | ||
120 | "HTTP/1.1 101 Switching Protocols\r\n" + | ||
121 | "upgrade: websocket\r\n" + | ||
122 | "Connection: Upgrade\r\n" + | ||
123 | "sec-websocket-accept: {0}\r\n\r\n";// + | ||
124 | //"{1}"; | ||
125 | |||
126 | private const string HandshakeDeclineText = | ||
127 | "HTTP/1.1 {0} {1}\r\n" + | ||
128 | "Connection: close\r\n\r\n"; | ||
129 | |||
130 | /// <summary> | ||
131 | /// Mysterious constant defined in RFC6455 to append to the client provided security key | ||
132 | /// </summary> | ||
133 | private const string WebsocketHandshakeAcceptHashConstant = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | ||
134 | |||
135 | public WebSocketHttpServerHandler(OSHttpRequest preq, IHttpClientContext pContext, int bufferlen) | ||
136 | : base(preq.HttpMethod, preq.Url.OriginalString) | ||
137 | { | ||
138 | _request = preq; | ||
139 | _networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing(); | ||
140 | _networkContext.Stream.ReadTimeout = _defaultReadTimeout; | ||
141 | _clientContext = pContext; | ||
142 | _bufferLength = bufferlen; | ||
143 | _buffer = new byte[_bufferLength]; | ||
144 | } | ||
145 | |||
146 | // Sealed class implments destructor and an internal dispose method. complies with C# unmanaged resource best practices. | ||
147 | ~WebSocketHttpServerHandler() | ||
148 | { | ||
149 | Dispose(); | ||
150 | |||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Sets the length of the stream buffer | ||
155 | /// </summary> | ||
156 | /// <param name="pChunk">Byte length.</param> | ||
157 | public void SetChunksize(int pChunk) | ||
158 | { | ||
159 | if (!_upgraded) | ||
160 | { | ||
161 | _buffer = new byte[pChunk]; | ||
162 | } | ||
163 | else | ||
164 | { | ||
165 | throw new InvalidOperationException("You must set the chunksize before the connection is upgraded"); | ||
166 | } | ||
167 | } | ||
168 | |||
169 | /// <summary> | ||
170 | /// This is the famous nagle. | ||
171 | /// </summary> | ||
172 | public bool NoDelay_TCP_Nagle | ||
173 | { | ||
174 | get | ||
175 | { | ||
176 | if (_networkContext != null && _networkContext.Socket != null) | ||
177 | { | ||
178 | return _networkContext.Socket.NoDelay; | ||
179 | } | ||
180 | else | ||
181 | { | ||
182 | throw new InvalidOperationException("The socket has been shutdown"); | ||
183 | } | ||
184 | } | ||
185 | set | ||
186 | { | ||
187 | if (_networkContext != null && _networkContext.Socket != null) | ||
188 | _networkContext.Socket.NoDelay = value; | ||
189 | else | ||
190 | { | ||
191 | throw new InvalidOperationException("The socket has been shutdown"); | ||
192 | } | ||
193 | } | ||
194 | } | ||
195 | |||
196 | /// <summary> | ||
197 | /// This triggers the websocket to start the upgrade process... | ||
198 | /// This is a Generalized Networking 'common sense' helper method. Some people expect to call Start() instead | ||
199 | /// of the more context appropriate HandshakeAndUpgrade() | ||
200 | /// </summary> | ||
201 | public void Start() | ||
202 | { | ||
203 | HandshakeAndUpgrade(); | ||
204 | } | ||
205 | |||
206 | /// <summary> | ||
207 | /// Max Payload Size in bytes. Defaults to 40MB, but could be set upon connection before calling handshake and upgrade. | ||
208 | /// </summary> | ||
209 | public int MaxPayloadSize | ||
210 | { | ||
211 | get { return _maxPayloadBytes; } | ||
212 | set { _maxPayloadBytes = value; } | ||
213 | } | ||
214 | |||
215 | /// <summary> | ||
216 | /// Set this to the maximum amount of milliseconds to wait for the first complete message to be sent or received on the websocket after upgrading | ||
217 | /// Default, it waits forever. Use this when your Websocket consuming code opens a connection and waits for a message from the other side to avoid a Denial of Service vector. | ||
218 | /// </summary> | ||
219 | public int InitialMsgTimeout | ||
220 | { | ||
221 | get { return _initialMsgTimeout; } | ||
222 | set { _initialMsgTimeout = value; } | ||
223 | } | ||
224 | |||
225 | /// <summary> | ||
226 | /// This triggers the websocket start the upgrade process | ||
227 | /// </summary> | ||
228 | public void HandshakeAndUpgrade() | ||
229 | { | ||
230 | string webOrigin = string.Empty; | ||
231 | string websocketKey = string.Empty; | ||
232 | string acceptKey = string.Empty; | ||
233 | string accepthost = string.Empty; | ||
234 | if (!string.IsNullOrEmpty(_request.Headers["origin"])) | ||
235 | webOrigin = _request.Headers["origin"]; | ||
236 | |||
237 | if (!string.IsNullOrEmpty(_request.Headers["sec-websocket-key"])) | ||
238 | websocketKey = _request.Headers["sec-websocket-key"]; | ||
239 | |||
240 | if (!string.IsNullOrEmpty(_request.Headers["host"])) | ||
241 | accepthost = _request.Headers["host"]; | ||
242 | |||
243 | if (string.IsNullOrEmpty(_request.Headers["upgrade"])) | ||
244 | { | ||
245 | FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no upgrade request submitted"); | ||
246 | } | ||
247 | |||
248 | string connectionheader = _request.Headers["upgrade"]; | ||
249 | if (connectionheader.ToLower() != "websocket") | ||
250 | { | ||
251 | FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no connection upgrade request submitted"); | ||
252 | } | ||
253 | |||
254 | // If the object consumer provided a method to validate the origin, we should call it and give the client a success or fail. | ||
255 | // If not.. we should accept any. The assumption here is that there would be no Websocket handlers registered in baseHTTPServer unless | ||
256 | // Something asked for it... | ||
257 | if (HandshakeValidateMethodOverride != null) | ||
258 | { | ||
259 | if (HandshakeValidateMethodOverride(webOrigin, websocketKey, accepthost)) | ||
260 | { | ||
261 | acceptKey = GenerateAcceptKey(websocketKey); | ||
262 | string rawaccept = string.Format(HandshakeAcceptText, acceptKey); | ||
263 | SendUpgradeSuccess(rawaccept); | ||
264 | |||
265 | |||
266 | } | ||
267 | else | ||
268 | { | ||
269 | FailUpgrade(OSHttpStatusCode.ClientErrorForbidden, "Origin Validation Failed"); | ||
270 | } | ||
271 | } | ||
272 | else | ||
273 | { | ||
274 | acceptKey = GenerateAcceptKey(websocketKey); | ||
275 | string rawaccept = string.Format(HandshakeAcceptText, acceptKey); | ||
276 | SendUpgradeSuccess(rawaccept); | ||
277 | } | ||
278 | } | ||
279 | public IPEndPoint GetRemoteIPEndpoint() | ||
280 | { | ||
281 | return _request.RemoteIPEndPoint; | ||
282 | } | ||
283 | |||
284 | /// <summary> | ||
285 | /// Generates a handshake response key string based on the client's | ||
286 | /// provided key to prove to the client that we're allowing the Websocket | ||
287 | /// upgrade of our own free will and we were not coerced into doing it. | ||
288 | /// </summary> | ||
289 | /// <param name="key">Client provided security key</param> | ||
290 | /// <returns></returns> | ||
291 | private static string GenerateAcceptKey(string key) | ||
292 | { | ||
293 | if (string.IsNullOrEmpty(key)) | ||
294 | return string.Empty; | ||
295 | |||
296 | string acceptkey = key + WebsocketHandshakeAcceptHashConstant; | ||
297 | |||
298 | SHA1 hashobj = SHA1.Create(); | ||
299 | string ret = Convert.ToBase64String(hashobj.ComputeHash(Encoding.UTF8.GetBytes(acceptkey))); | ||
300 | hashobj.Clear(); | ||
301 | |||
302 | return ret; | ||
303 | } | ||
304 | |||
305 | /// <summary> | ||
306 | /// Informs the otherside that we accepted their upgrade request | ||
307 | /// </summary> | ||
308 | /// <param name="pHandshakeResponse">The HTTP 1.1 101 response that says Yay \o/ </param> | ||
309 | private void SendUpgradeSuccess(string pHandshakeResponse) | ||
310 | { | ||
311 | // Create a new websocket state so we can keep track of data in between network reads. | ||
312 | WebSocketState socketState = new WebSocketState() { ReceivedBytes = new List<byte>(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete = true}; | ||
313 | |||
314 | byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse); | ||
315 | |||
316 | |||
317 | |||
318 | |||
319 | try | ||
320 | { | ||
321 | if (_initialMsgTimeout > 0) | ||
322 | { | ||
323 | _receiveDone.Reset(); | ||
324 | } | ||
325 | // Begin reading the TCP stream before writing the Upgrade success message to the other side of the stream. | ||
326 | _networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState); | ||
327 | |||
328 | // Write the upgrade handshake success message | ||
329 | _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length); | ||
330 | _networkContext.Stream.Flush(); | ||
331 | _upgraded = true; | ||
332 | UpgradeCompletedDelegate d = OnUpgradeCompleted; | ||
333 | if (d != null) | ||
334 | d(this, new UpgradeCompletedEventArgs()); | ||
335 | if (_initialMsgTimeout > 0) | ||
336 | { | ||
337 | if (!_receiveDone.WaitOne(TimeSpan.FromMilliseconds(_initialMsgTimeout))) | ||
338 | Close(string.Empty); | ||
339 | } | ||
340 | } | ||
341 | catch (IOException) | ||
342 | { | ||
343 | Close(string.Empty); | ||
344 | } | ||
345 | catch (ObjectDisposedException) | ||
346 | { | ||
347 | Close(string.Empty); | ||
348 | } | ||
349 | } | ||
350 | |||
351 | /// <summary> | ||
352 | /// The server has decided not to allow the upgrade to a websocket for some reason. The Http 1.1 response that says Nay >:( | ||
353 | /// </summary> | ||
354 | /// <param name="pCode">HTTP Status reflecting the reason why</param> | ||
355 | /// <param name="pMessage">Textual reason for the upgrade fail</param> | ||
356 | private void FailUpgrade(OSHttpStatusCode pCode, string pMessage ) | ||
357 | { | ||
358 | string handshakeResponse = string.Format(HandshakeDeclineText, (int)pCode, pMessage.Replace("\n", string.Empty).Replace("\r", string.Empty)); | ||
359 | byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(handshakeResponse); | ||
360 | _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length); | ||
361 | _networkContext.Stream.Flush(); | ||
362 | _networkContext.Stream.Dispose(); | ||
363 | |||
364 | UpgradeFailedDelegate d = OnUpgradeFailed; | ||
365 | if (d != null) | ||
366 | d(this,new UpgradeFailedEventArgs()); | ||
367 | } | ||
368 | |||
369 | |||
370 | /// <summary> | ||
371 | /// This is our ugly Async OnReceive event handler. | ||
372 | /// This chunks the input stream based on the length of the provided buffer and processes out | ||
373 | /// as many frames as it can. It then moves the unprocessed data to the beginning of the buffer. | ||
374 | /// </summary> | ||
375 | /// <param name="ar">Our Async State from beginread</param> | ||
376 | private void OnReceive(IAsyncResult ar) | ||
377 | { | ||
378 | WebSocketState _socketState = ar.AsyncState as WebSocketState; | ||
379 | try | ||
380 | { | ||
381 | int bytesRead = _networkContext.Stream.EndRead(ar); | ||
382 | if (bytesRead == 0) | ||
383 | { | ||
384 | // Do Disconnect | ||
385 | _networkContext.Stream.Dispose(); | ||
386 | _networkContext = null; | ||
387 | return; | ||
388 | } | ||
389 | _bufferPosition += bytesRead; | ||
390 | |||
391 | if (_bufferPosition > _bufferLength) | ||
392 | { | ||
393 | // Message too big for chunksize.. not sure how this happened... | ||
394 | //Close(string.Empty); | ||
395 | } | ||
396 | |||
397 | int offset = 0; | ||
398 | bool headerread = true; | ||
399 | int headerforwardposition = 0; | ||
400 | while (headerread && offset < bytesRead) | ||
401 | { | ||
402 | if (_socketState.FrameComplete) | ||
403 | { | ||
404 | WebsocketFrameHeader pheader = WebsocketFrameHeader.ZeroHeader; | ||
405 | |||
406 | headerread = WebSocketReader.TryReadHeader(_buffer, offset, _bufferPosition - offset, out pheader, | ||
407 | out headerforwardposition); | ||
408 | offset += headerforwardposition; | ||
409 | |||
410 | if (headerread) | ||
411 | { | ||
412 | _socketState.FrameComplete = false; | ||
413 | if (pheader.PayloadLen > (ulong) _maxPayloadBytes) | ||
414 | { | ||
415 | Close("Invalid Payload size"); | ||
416 | |||
417 | return; | ||
418 | } | ||
419 | if (pheader.PayloadLen > 0) | ||
420 | { | ||
421 | if ((int) pheader.PayloadLen > _bufferPosition - offset) | ||
422 | { | ||
423 | byte[] writebytes = new byte[_bufferPosition - offset]; | ||
424 | |||
425 | Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition - offset); | ||
426 | _socketState.ExpectedBytes = (int) pheader.PayloadLen; | ||
427 | _socketState.ReceivedBytes.AddRange(writebytes); | ||
428 | _socketState.Header = pheader; // We need to add the header so that we can unmask it | ||
429 | offset += (int) _bufferPosition - offset; | ||
430 | } | ||
431 | else | ||
432 | { | ||
433 | byte[] writebytes = new byte[pheader.PayloadLen]; | ||
434 | Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) pheader.PayloadLen); | ||
435 | WebSocketReader.Mask(pheader.Mask, writebytes); | ||
436 | pheader.IsMasked = false; | ||
437 | _socketState.FrameComplete = true; | ||
438 | _socketState.ReceivedBytes.AddRange(writebytes); | ||
439 | _socketState.Header = pheader; | ||
440 | offset += (int) pheader.PayloadLen; | ||
441 | } | ||
442 | } | ||
443 | else | ||
444 | { | ||
445 | pheader.Mask = 0; | ||
446 | _socketState.FrameComplete = true; | ||
447 | _socketState.Header = pheader; | ||
448 | } | ||
449 | |||
450 | if (_socketState.FrameComplete) | ||
451 | { | ||
452 | ProcessFrame(_socketState); | ||
453 | _socketState.Header.SetDefault(); | ||
454 | _socketState.ReceivedBytes.Clear(); | ||
455 | _socketState.ExpectedBytes = 0; | ||
456 | |||
457 | } | ||
458 | } | ||
459 | } | ||
460 | else | ||
461 | { | ||
462 | WebsocketFrameHeader frameHeader = _socketState.Header; | ||
463 | int bytesleft = _socketState.ExpectedBytes - _socketState.ReceivedBytes.Count; | ||
464 | |||
465 | if (bytesleft > _bufferPosition) | ||
466 | { | ||
467 | byte[] writebytes = new byte[_bufferPosition]; | ||
468 | |||
469 | Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition); | ||
470 | _socketState.ReceivedBytes.AddRange(writebytes); | ||
471 | _socketState.Header = frameHeader; // We need to add the header so that we can unmask it | ||
472 | offset += (int) _bufferPosition; | ||
473 | } | ||
474 | else | ||
475 | { | ||
476 | byte[] writebytes = new byte[_bufferPosition]; | ||
477 | Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition); | ||
478 | _socketState.FrameComplete = true; | ||
479 | _socketState.ReceivedBytes.AddRange(writebytes); | ||
480 | _socketState.Header = frameHeader; | ||
481 | offset += (int) _bufferPosition; | ||
482 | } | ||
483 | if (_socketState.FrameComplete) | ||
484 | { | ||
485 | ProcessFrame(_socketState); | ||
486 | _socketState.Header.SetDefault(); | ||
487 | _socketState.ReceivedBytes.Clear(); | ||
488 | _socketState.ExpectedBytes = 0; | ||
489 | // do some processing | ||
490 | } | ||
491 | } | ||
492 | } | ||
493 | if (offset > 0) | ||
494 | { | ||
495 | // If the buffer is maxed out.. we can just move the cursor. Nothing to move to the beginning. | ||
496 | if (offset <_buffer.Length) | ||
497 | Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bufferPosition - offset); | ||
498 | _bufferPosition -= offset; | ||
499 | } | ||
500 | if (_networkContext.Stream != null && _networkContext.Stream.CanRead && !_closing) | ||
501 | { | ||
502 | _networkContext.Stream.BeginRead(_buffer, _bufferPosition, _bufferLength - _bufferPosition, OnReceive, | ||
503 | _socketState); | ||
504 | } | ||
505 | else | ||
506 | { | ||
507 | // We can't read the stream anymore... | ||
508 | } | ||
509 | } | ||
510 | catch (IOException) | ||
511 | { | ||
512 | Close(string.Empty); | ||
513 | } | ||
514 | catch (ObjectDisposedException) | ||
515 | { | ||
516 | Close(string.Empty); | ||
517 | } | ||
518 | } | ||
519 | |||
520 | /// <summary> | ||
521 | /// Sends a string to the other side | ||
522 | /// </summary> | ||
523 | /// <param name="message">the string message that is to be sent</param> | ||
524 | public void SendMessage(string message) | ||
525 | { | ||
526 | if (_initialMsgTimeout > 0) | ||
527 | { | ||
528 | _receiveDone.Set(); | ||
529 | _initialMsgTimeout = 0; | ||
530 | } | ||
531 | byte[] messagedata = Encoding.UTF8.GetBytes(message); | ||
532 | WebSocketFrame textMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = messagedata }; | ||
533 | textMessageFrame.Header.Opcode = WebSocketReader.OpCode.Text; | ||
534 | textMessageFrame.Header.IsEnd = true; | ||
535 | SendSocket(textMessageFrame.ToBytes()); | ||
536 | |||
537 | } | ||
538 | |||
539 | public void SendData(byte[] data) | ||
540 | { | ||
541 | if (_initialMsgTimeout > 0) | ||
542 | { | ||
543 | _receiveDone.Set(); | ||
544 | _initialMsgTimeout = 0; | ||
545 | } | ||
546 | WebSocketFrame dataMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = data}; | ||
547 | dataMessageFrame.Header.IsEnd = true; | ||
548 | dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary; | ||
549 | SendSocket(dataMessageFrame.ToBytes()); | ||
550 | |||
551 | } | ||
552 | |||
553 | /// <summary> | ||
554 | /// Writes raw bytes to the websocket. Unframed data will cause disconnection | ||
555 | /// </summary> | ||
556 | /// <param name="data"></param> | ||
557 | private void SendSocket(byte[] data) | ||
558 | { | ||
559 | if (!_closing) | ||
560 | { | ||
561 | try | ||
562 | { | ||
563 | |||
564 | _networkContext.Stream.Write(data, 0, data.Length); | ||
565 | } | ||
566 | catch (IOException) | ||
567 | { | ||
568 | |||
569 | } | ||
570 | } | ||
571 | } | ||
572 | |||
573 | /// <summary> | ||
574 | /// Sends a Ping check to the other side. The other side SHOULD respond as soon as possible with a pong frame. This interleaves with incoming fragmented frames. | ||
575 | /// </summary> | ||
576 | public void SendPingCheck() | ||
577 | { | ||
578 | if (_initialMsgTimeout > 0) | ||
579 | { | ||
580 | _receiveDone.Set(); | ||
581 | _initialMsgTimeout = 0; | ||
582 | } | ||
583 | WebSocketFrame pingFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = new byte[0] }; | ||
584 | pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping; | ||
585 | pingFrame.Header.IsEnd = true; | ||
586 | _pingtime = Util.EnvironmentTickCount(); | ||
587 | SendSocket(pingFrame.ToBytes()); | ||
588 | } | ||
589 | |||
590 | /// <summary> | ||
591 | /// Closes the websocket connection. Sends a close message to the other side if it hasn't already done so. | ||
592 | /// </summary> | ||
593 | /// <param name="message"></param> | ||
594 | public void Close(string message) | ||
595 | { | ||
596 | if (_initialMsgTimeout > 0) | ||
597 | { | ||
598 | _receiveDone.Set(); | ||
599 | _initialMsgTimeout = 0; | ||
600 | } | ||
601 | if (_networkContext == null) | ||
602 | return; | ||
603 | if (_networkContext.Stream != null) | ||
604 | { | ||
605 | if (_networkContext.Stream.CanWrite) | ||
606 | { | ||
607 | byte[] messagedata = Encoding.UTF8.GetBytes(message); | ||
608 | WebSocketFrame closeResponseFrame = new WebSocketFrame() | ||
609 | { | ||
610 | Header = WebsocketFrameHeader.HeaderDefault(), | ||
611 | WebSocketPayload = messagedata | ||
612 | }; | ||
613 | closeResponseFrame.Header.Opcode = WebSocketReader.OpCode.Close; | ||
614 | closeResponseFrame.Header.PayloadLen = (ulong) messagedata.Length; | ||
615 | closeResponseFrame.Header.IsEnd = true; | ||
616 | SendSocket(closeResponseFrame.ToBytes()); | ||
617 | } | ||
618 | } | ||
619 | CloseDelegate closeD = OnClose; | ||
620 | if (closeD != null) | ||
621 | { | ||
622 | closeD(this, new CloseEventArgs()); | ||
623 | } | ||
624 | |||
625 | _closing = true; | ||
626 | } | ||
627 | |||
628 | /// <summary> | ||
629 | /// Processes a websocket frame and triggers consumer events | ||
630 | /// </summary> | ||
631 | /// <param name="psocketState">We need to modify the websocket state here depending on the frame</param> | ||
632 | private void ProcessFrame(WebSocketState psocketState) | ||
633 | { | ||
634 | if (psocketState.Header.IsMasked) | ||
635 | { | ||
636 | byte[] unmask = psocketState.ReceivedBytes.ToArray(); | ||
637 | WebSocketReader.Mask(psocketState.Header.Mask, unmask); | ||
638 | psocketState.ReceivedBytes = new List<byte>(unmask); | ||
639 | } | ||
640 | if (psocketState.Header.Opcode != WebSocketReader.OpCode.Continue && _initialMsgTimeout > 0) | ||
641 | { | ||
642 | _receiveDone.Set(); | ||
643 | _initialMsgTimeout = 0; | ||
644 | } | ||
645 | switch (psocketState.Header.Opcode) | ||
646 | { | ||
647 | case WebSocketReader.OpCode.Ping: | ||
648 | PingDelegate pingD = OnPing; | ||
649 | if (pingD != null) | ||
650 | { | ||
651 | pingD(this, new PingEventArgs()); | ||
652 | } | ||
653 | |||
654 | WebSocketFrame pongFrame = new WebSocketFrame(){Header = WebsocketFrameHeader.HeaderDefault(),WebSocketPayload = new byte[0]}; | ||
655 | pongFrame.Header.Opcode = WebSocketReader.OpCode.Pong; | ||
656 | pongFrame.Header.IsEnd = true; | ||
657 | SendSocket(pongFrame.ToBytes()); | ||
658 | break; | ||
659 | case WebSocketReader.OpCode.Pong: | ||
660 | |||
661 | PongDelegate pongD = OnPong; | ||
662 | if (pongD != null) | ||
663 | { | ||
664 | pongD(this, new PongEventArgs(){PingResponseMS = Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),_pingtime)}); | ||
665 | } | ||
666 | break; | ||
667 | case WebSocketReader.OpCode.Binary: | ||
668 | if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame. | ||
669 | { | ||
670 | psocketState.ContinuationFrame = new WebSocketFrame | ||
671 | { | ||
672 | Header = psocketState.Header, | ||
673 | WebSocketPayload = | ||
674 | psocketState.ReceivedBytes.ToArray() | ||
675 | }; | ||
676 | } | ||
677 | else | ||
678 | { | ||
679 | // Send Done Event! | ||
680 | DataDelegate dataD = OnData; | ||
681 | if (dataD != null) | ||
682 | { | ||
683 | dataD(this,new WebsocketDataEventArgs(){Data = psocketState.ReceivedBytes.ToArray()}); | ||
684 | } | ||
685 | } | ||
686 | break; | ||
687 | case WebSocketReader.OpCode.Text: | ||
688 | if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame. | ||
689 | { | ||
690 | psocketState.ContinuationFrame = new WebSocketFrame | ||
691 | { | ||
692 | Header = psocketState.Header, | ||
693 | WebSocketPayload = | ||
694 | psocketState.ReceivedBytes.ToArray() | ||
695 | }; | ||
696 | } | ||
697 | else | ||
698 | { | ||
699 | TextDelegate textD = OnText; | ||
700 | if (textD != null) | ||
701 | { | ||
702 | textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(psocketState.ReceivedBytes.ToArray()) }); | ||
703 | } | ||
704 | |||
705 | // Send Done Event! | ||
706 | } | ||
707 | break; | ||
708 | case WebSocketReader.OpCode.Continue: // Continuation. Multiple frames worth of data for one message. Only valid when not using Control Opcodes | ||
709 | //Console.WriteLine("currhead " + psocketState.Header.IsEnd); | ||
710 | //Console.WriteLine("Continuation! " + psocketState.ContinuationFrame.Header.IsEnd); | ||
711 | byte[] combineddata = new byte[psocketState.ReceivedBytes.Count+psocketState.ContinuationFrame.WebSocketPayload.Length]; | ||
712 | byte[] newdata = psocketState.ReceivedBytes.ToArray(); | ||
713 | Buffer.BlockCopy(psocketState.ContinuationFrame.WebSocketPayload, 0, combineddata, 0, psocketState.ContinuationFrame.WebSocketPayload.Length); | ||
714 | Buffer.BlockCopy(newdata, 0, combineddata, | ||
715 | psocketState.ContinuationFrame.WebSocketPayload.Length, newdata.Length); | ||
716 | psocketState.ContinuationFrame.WebSocketPayload = combineddata; | ||
717 | psocketState.Header.PayloadLen = (ulong)combineddata.Length; | ||
718 | if (psocketState.Header.IsEnd) | ||
719 | { | ||
720 | if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Text) | ||
721 | { | ||
722 | // Send Done event | ||
723 | TextDelegate textD = OnText; | ||
724 | if (textD != null) | ||
725 | { | ||
726 | textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(combineddata) }); | ||
727 | } | ||
728 | } | ||
729 | else if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Binary) | ||
730 | { | ||
731 | // Send Done event | ||
732 | DataDelegate dataD = OnData; | ||
733 | if (dataD != null) | ||
734 | { | ||
735 | dataD(this, new WebsocketDataEventArgs() { Data = combineddata }); | ||
736 | } | ||
737 | } | ||
738 | else | ||
739 | { | ||
740 | // protocol violation | ||
741 | } | ||
742 | psocketState.ContinuationFrame = null; | ||
743 | } | ||
744 | break; | ||
745 | case WebSocketReader.OpCode.Close: | ||
746 | Close(string.Empty); | ||
747 | |||
748 | break; | ||
749 | |||
750 | } | ||
751 | psocketState.Header.SetDefault(); | ||
752 | psocketState.ReceivedBytes.Clear(); | ||
753 | psocketState.ExpectedBytes = 0; | ||
754 | } | ||
755 | public void Dispose() | ||
756 | { | ||
757 | if (_initialMsgTimeout > 0) | ||
758 | { | ||
759 | _receiveDone.Set(); | ||
760 | _initialMsgTimeout = 0; | ||
761 | } | ||
762 | if (_networkContext != null && _networkContext.Stream != null) | ||
763 | { | ||
764 | if (_networkContext.Stream.CanWrite) | ||
765 | _networkContext.Stream.Flush(); | ||
766 | _networkContext.Stream.Close(); | ||
767 | _networkContext.Stream.Dispose(); | ||
768 | _networkContext.Stream = null; | ||
769 | } | ||
770 | |||
771 | if (_request != null && _request.InputStream != null) | ||
772 | { | ||
773 | _request.InputStream.Close(); | ||
774 | _request.InputStream.Dispose(); | ||
775 | _request = null; | ||
776 | } | ||
777 | |||
778 | if (_clientContext != null) | ||
779 | { | ||
780 | _clientContext.Close(); | ||
781 | _clientContext = null; | ||
782 | } | ||
783 | } | ||
784 | } | ||
785 | |||
786 | /// <summary> | ||
787 | /// Reads a byte stream and returns Websocket frames. | ||
788 | /// </summary> | ||
789 | public class WebSocketReader | ||
790 | { | ||
791 | /// <summary> | ||
792 | /// Bit to determine if the frame read on the stream is the last frame in a sequence of fragmented frames | ||
793 | /// </summary> | ||
794 | private const byte EndBit = 0x80; | ||
795 | |||
796 | /// <summary> | ||
797 | /// These are the Frame Opcodes | ||
798 | /// </summary> | ||
799 | public enum OpCode | ||
800 | { | ||
801 | // Data Opcodes | ||
802 | Continue = 0x0, | ||
803 | Text = 0x1, | ||
804 | Binary = 0x2, | ||
805 | |||
806 | // Control flow Opcodes | ||
807 | Close = 0x8, | ||
808 | Ping = 0x9, | ||
809 | Pong = 0xA | ||
810 | } | ||
811 | |||
812 | /// <summary> | ||
813 | /// Masks and Unmasks data using the frame mask. Mask is applied per octal | ||
814 | /// Note: Frames from clients MUST be masked | ||
815 | /// Note: Frames from servers MUST NOT be masked | ||
816 | /// </summary> | ||
817 | /// <param name="pMask">Int representing 32 bytes of mask data. Mask is applied per octal</param> | ||
818 | /// <param name="pBuffer"></param> | ||
819 | public static void Mask(int pMask, byte[] pBuffer) | ||
820 | { | ||
821 | byte[] maskKey = BitConverter.GetBytes(pMask); | ||
822 | int currentMaskIndex = 0; | ||
823 | for (int i = 0; i < pBuffer.Length; i++) | ||
824 | { | ||
825 | pBuffer[i] = (byte)(pBuffer[i] ^ maskKey[currentMaskIndex]); | ||
826 | if (currentMaskIndex == 3) | ||
827 | { | ||
828 | currentMaskIndex = 0; | ||
829 | } | ||
830 | else | ||
831 | { | ||
832 | currentMaskIndex++; | ||
833 | |||
834 | } | ||
835 | |||
836 | } | ||
837 | } | ||
838 | |||
839 | /// <summary> | ||
840 | /// Attempts to read a header off the provided buffer. Returns true, exports a WebSocketFrameheader, | ||
841 | /// and an int to move the buffer forward when it reads a header. False when it can't read a header | ||
842 | /// </summary> | ||
843 | /// <param name="pBuffer">Bytes read from the stream</param> | ||
844 | /// <param name="pOffset">Starting place in the stream to begin trying to read from</param> | ||
845 | /// <param name="length">Lenth in the stream to try and read from. Provided for cases where the | ||
846 | /// buffer's length is larger then the data in it</param> | ||
847 | /// <param name="oHeader">Outputs the read WebSocket frame header</param> | ||
848 | /// <param name="moveBuffer">Informs the calling stream to move the buffer forward</param> | ||
849 | /// <returns>True if it got a header, False if it didn't get a header</returns> | ||
850 | public static bool TryReadHeader(byte[] pBuffer, int pOffset, int length, out WebsocketFrameHeader oHeader, | ||
851 | out int moveBuffer) | ||
852 | { | ||
853 | oHeader = WebsocketFrameHeader.ZeroHeader; | ||
854 | int minumheadersize = 2; | ||
855 | if (length > pBuffer.Length - pOffset) | ||
856 | throw new ArgumentOutOfRangeException("The Length specified was larger the byte array supplied"); | ||
857 | if (length < minumheadersize) | ||
858 | { | ||
859 | moveBuffer = 0; | ||
860 | return false; | ||
861 | } | ||
862 | |||
863 | byte nibble1 = (byte)(pBuffer[pOffset] & 0xF0); //FIN/RSV1/RSV2/RSV3 | ||
864 | byte nibble2 = (byte)(pBuffer[pOffset] & 0x0F); // Opcode block | ||
865 | |||
866 | oHeader = new WebsocketFrameHeader(); | ||
867 | oHeader.SetDefault(); | ||
868 | |||
869 | if ((nibble1 & WebSocketReader.EndBit) == WebSocketReader.EndBit) | ||
870 | { | ||
871 | oHeader.IsEnd = true; | ||
872 | } | ||
873 | else | ||
874 | { | ||
875 | oHeader.IsEnd = false; | ||
876 | } | ||
877 | //Opcode | ||
878 | oHeader.Opcode = (WebSocketReader.OpCode)nibble2; | ||
879 | //Mask | ||
880 | oHeader.IsMasked = Convert.ToBoolean((pBuffer[pOffset + 1] & 0x80) >> 7); | ||
881 | |||
882 | // Payload length | ||
883 | oHeader.PayloadLen = (byte)(pBuffer[pOffset + 1] & 0x7F); | ||
884 | |||
885 | int index = 2; // LargerPayload length starts at byte 3 | ||
886 | |||
887 | switch (oHeader.PayloadLen) | ||
888 | { | ||
889 | case 126: | ||
890 | minumheadersize += 2; | ||
891 | if (length < minumheadersize) | ||
892 | { | ||
893 | moveBuffer = 0; | ||
894 | return false; | ||
895 | } | ||
896 | Array.Reverse(pBuffer, pOffset + index, 2); // two bytes | ||
897 | oHeader.PayloadLen = BitConverter.ToUInt16(pBuffer, pOffset + index); | ||
898 | index += 2; | ||
899 | break; | ||
900 | case 127: // we got more this is a bigger frame | ||
901 | // 8 bytes - uint64 - most significant bit 0 network byte order | ||
902 | minumheadersize += 8; | ||
903 | if (length < minumheadersize) | ||
904 | { | ||
905 | moveBuffer = 0; | ||
906 | return false; | ||
907 | } | ||
908 | Array.Reverse(pBuffer, pOffset + index, 8); | ||
909 | oHeader.PayloadLen = BitConverter.ToUInt64(pBuffer, pOffset + index); | ||
910 | index += 8; | ||
911 | break; | ||
912 | |||
913 | } | ||
914 | //oHeader.PayloadLeft = oHeader.PayloadLen; // Start the count in case it's chunked over the network. This is different then frame fragmentation | ||
915 | if (oHeader.IsMasked) | ||
916 | { | ||
917 | minumheadersize += 4; | ||
918 | if (length < minumheadersize) | ||
919 | { | ||
920 | moveBuffer = 0; | ||
921 | return false; | ||
922 | } | ||
923 | oHeader.Mask = BitConverter.ToInt32(pBuffer, pOffset + index); | ||
924 | index += 4; | ||
925 | } | ||
926 | moveBuffer = index; | ||
927 | return true; | ||
928 | |||
929 | } | ||
930 | } | ||
931 | |||
932 | /// <summary> | ||
933 | /// RFC6455 Websocket Frame | ||
934 | /// </summary> | ||
935 | public class WebSocketFrame | ||
936 | { | ||
937 | /* | ||
938 | * RFC6455 | ||
939 | nib 0 1 2 3 4 5 6 7 | ||
940 | byt 0 1 2 3 | ||
941 | dec 0 1 2 3 | ||
942 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
943 | +-+-+-+-+-------+-+-------------+-------------------------------+ | ||
944 | |F|R|R|R| opcode|M| Payload len | Extended payload length | | ||
945 | |I|S|S|S| (4) |A| (7) | (16/64) + | ||
946 | |N|V|V|V| |S| | (if payload len==126/127) | | ||
947 | | |1|2|3| |K| | + | ||
948 | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | ||
949 | | Extended payload length continued, if payload len == 127 | | ||
950 | + - - - - - - - - - - - - - - - +-------------------------------+ | ||
951 | | |Masking-key, if MASK set to 1 | | ||
952 | +-------------------------------+-------------------------------+ | ||
953 | | Masking-key (continued) | Payload Data | | ||
954 | +-------------------------------- - - - - - - - - - - - - - - - + | ||
955 | : Payload Data continued ... : | ||
956 | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | ||
957 | | Payload Data continued ... | | ||
958 | +---------------------------------------------------------------+ | ||
959 | |||
960 | * When reading these, the frames are possibly fragmented and interleaved with control frames | ||
961 | * the fragmented frames are not interleaved with data frames. Just control frames | ||
962 | */ | ||
963 | public static readonly WebSocketFrame DefaultFrame = new WebSocketFrame(){Header = new WebsocketFrameHeader(),WebSocketPayload = new byte[0]}; | ||
964 | public WebsocketFrameHeader Header; | ||
965 | public byte[] WebSocketPayload; | ||
966 | |||
967 | public byte[] ToBytes() | ||
968 | { | ||
969 | Header.PayloadLen = (ulong)WebSocketPayload.Length; | ||
970 | return Header.ToBytes(WebSocketPayload); | ||
971 | } | ||
972 | |||
973 | } | ||
974 | |||
975 | public struct WebsocketFrameHeader | ||
976 | { | ||
977 | //public byte CurrentMaskIndex; | ||
978 | /// <summary> | ||
979 | /// The last frame in a sequence of fragmented frames or the one and only frame for this message. | ||
980 | /// </summary> | ||
981 | public bool IsEnd; | ||
982 | |||
983 | /// <summary> | ||
984 | /// Returns whether the payload data is masked or not. Data from Clients MUST be masked, Data from Servers MUST NOT be masked | ||
985 | /// </summary> | ||
986 | public bool IsMasked; | ||
987 | |||
988 | /// <summary> | ||
989 | /// A set of cryptologically sound random bytes XoR-ed against the payload octally. Looped | ||
990 | /// </summary> | ||
991 | public int Mask; | ||
992 | /* | ||
993 | byt 0 1 2 3 | ||
994 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | ||
995 | +---------------+---------------+---------------+---------------+ | ||
996 | | Octal 1 | Octal 2 | Octal 3 | Octal 4 | | ||
997 | +---------------+---------------+---------------+---------------+ | ||
998 | */ | ||
999 | |||
1000 | |||
1001 | public WebSocketReader.OpCode Opcode; | ||
1002 | |||
1003 | public UInt64 PayloadLen; | ||
1004 | //public UInt64 PayloadLeft; | ||
1005 | // Payload is X + Y | ||
1006 | //public UInt64 ExtensionDataLength; | ||
1007 | //public UInt64 ApplicationDataLength; | ||
1008 | public static readonly WebsocketFrameHeader ZeroHeader = WebsocketFrameHeader.HeaderDefault(); | ||
1009 | |||
1010 | public void SetDefault() | ||
1011 | { | ||
1012 | |||
1013 | //CurrentMaskIndex = 0; | ||
1014 | IsEnd = true; | ||
1015 | IsMasked = true; | ||
1016 | Mask = 0; | ||
1017 | Opcode = WebSocketReader.OpCode.Close; | ||
1018 | // PayloadLeft = 0; | ||
1019 | PayloadLen = 0; | ||
1020 | // ExtensionDataLength = 0; | ||
1021 | // ApplicationDataLength = 0; | ||
1022 | |||
1023 | } | ||
1024 | |||
1025 | /// <summary> | ||
1026 | /// Returns a byte array representing the Frame header | ||
1027 | /// </summary> | ||
1028 | /// <param name="payload">This is the frame data payload. The header describes the size of the payload. | ||
1029 | /// If payload is null, a Zero sized payload is assumed</param> | ||
1030 | /// <returns>Returns a byte array representing the frame header</returns> | ||
1031 | public byte[] ToBytes(byte[] payload) | ||
1032 | { | ||
1033 | List<byte> result = new List<byte>(); | ||
1034 | |||
1035 | // Squeeze in our opcode and our ending bit. | ||
1036 | result.Add((byte)((byte)Opcode | (IsEnd?0x80:0x00) )); | ||
1037 | |||
1038 | // Again with the three different byte interpretations of size.. | ||
1039 | |||
1040 | //bytesize | ||
1041 | if (PayloadLen <= 125) | ||
1042 | { | ||
1043 | result.Add((byte) PayloadLen); | ||
1044 | } //Uint16 | ||
1045 | else if (PayloadLen <= ushort.MaxValue) | ||
1046 | { | ||
1047 | result.Add(126); | ||
1048 | byte[] payloadLengthByte = BitConverter.GetBytes(Convert.ToUInt16(PayloadLen)); | ||
1049 | Array.Reverse(payloadLengthByte); | ||
1050 | result.AddRange(payloadLengthByte); | ||
1051 | } //UInt64 | ||
1052 | else | ||
1053 | { | ||
1054 | result.Add(127); | ||
1055 | byte[] payloadLengthByte = BitConverter.GetBytes(PayloadLen); | ||
1056 | Array.Reverse(payloadLengthByte); | ||
1057 | result.AddRange(payloadLengthByte); | ||
1058 | } | ||
1059 | |||
1060 | // Only add a payload if it's not null | ||
1061 | if (payload != null) | ||
1062 | { | ||
1063 | result.AddRange(payload); | ||
1064 | } | ||
1065 | return result.ToArray(); | ||
1066 | } | ||
1067 | |||
1068 | /// <summary> | ||
1069 | /// A Helper method to define the defaults | ||
1070 | /// </summary> | ||
1071 | /// <returns></returns> | ||
1072 | |||
1073 | public static WebsocketFrameHeader HeaderDefault() | ||
1074 | { | ||
1075 | return new WebsocketFrameHeader | ||
1076 | { | ||
1077 | //CurrentMaskIndex = 0, | ||
1078 | IsEnd = false, | ||
1079 | IsMasked = true, | ||
1080 | Mask = 0, | ||
1081 | Opcode = WebSocketReader.OpCode.Close, | ||
1082 | //PayloadLeft = 0, | ||
1083 | PayloadLen = 0, | ||
1084 | // ExtensionDataLength = 0, | ||
1085 | // ApplicationDataLength = 0 | ||
1086 | }; | ||
1087 | } | ||
1088 | } | ||
1089 | |||
1090 | public delegate void DataDelegate(object sender, WebsocketDataEventArgs data); | ||
1091 | |||
1092 | public delegate void TextDelegate(object sender, WebsocketTextEventArgs text); | ||
1093 | |||
1094 | public delegate void PingDelegate(object sender, PingEventArgs pingdata); | ||
1095 | |||
1096 | public delegate void PongDelegate(object sender, PongEventArgs pongdata); | ||
1097 | |||
1098 | public delegate void RegularHttpRequestDelegate(object sender, RegularHttpRequestEvnetArgs request); | ||
1099 | |||
1100 | public delegate void UpgradeCompletedDelegate(object sender, UpgradeCompletedEventArgs completeddata); | ||
1101 | |||
1102 | public delegate void UpgradeFailedDelegate(object sender, UpgradeFailedEventArgs faileddata); | ||
1103 | |||
1104 | public delegate void CloseDelegate(object sender, CloseEventArgs closedata); | ||
1105 | |||
1106 | public delegate bool ValidateHandshake(string pWebOrigin, string pWebSocketKey, string pHost); | ||
1107 | |||
1108 | |||
1109 | public class WebsocketDataEventArgs : EventArgs | ||
1110 | { | ||
1111 | public byte[] Data; | ||
1112 | } | ||
1113 | |||
1114 | public class WebsocketTextEventArgs : EventArgs | ||
1115 | { | ||
1116 | public string Data; | ||
1117 | } | ||
1118 | |||
1119 | public class PingEventArgs : EventArgs | ||
1120 | { | ||
1121 | /// <summary> | ||
1122 | /// The ping event can arbitrarily contain data | ||
1123 | /// </summary> | ||
1124 | public byte[] Data; | ||
1125 | } | ||
1126 | |||
1127 | public class PongEventArgs : EventArgs | ||
1128 | { | ||
1129 | /// <summary> | ||
1130 | /// The pong event can arbitrarily contain data | ||
1131 | /// </summary> | ||
1132 | public byte[] Data; | ||
1133 | |||
1134 | public int PingResponseMS; | ||
1135 | |||
1136 | } | ||
1137 | |||
1138 | public class RegularHttpRequestEvnetArgs : EventArgs | ||
1139 | { | ||
1140 | |||
1141 | } | ||
1142 | |||
1143 | public class UpgradeCompletedEventArgs : EventArgs | ||
1144 | { | ||
1145 | |||
1146 | } | ||
1147 | |||
1148 | public class UpgradeFailedEventArgs : EventArgs | ||
1149 | { | ||
1150 | |||
1151 | } | ||
1152 | |||
1153 | public class CloseEventArgs : EventArgs | ||
1154 | { | ||
1155 | |||
1156 | } | ||
1157 | |||
1158 | |||
1159 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs new file mode 100644 index 0000000..f212208 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/XmlRpcBasicDOSProtector.cs | |||
@@ -0,0 +1,91 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Net; | ||
29 | using Nwc.XmlRpc; | ||
30 | using OpenSim.Framework; | ||
31 | |||
32 | |||
33 | namespace OpenSim.Framework.Servers.HttpServer | ||
34 | { | ||
35 | public class XmlRpcBasicDOSProtector | ||
36 | { | ||
37 | private readonly XmlRpcMethod _normalMethod; | ||
38 | private readonly XmlRpcMethod _throttledMethod; | ||
39 | |||
40 | private readonly BasicDosProtectorOptions _options; | ||
41 | private readonly BasicDOSProtector _dosProtector; | ||
42 | |||
43 | public XmlRpcBasicDOSProtector(XmlRpcMethod normalMethod, XmlRpcMethod throttledMethod,BasicDosProtectorOptions options) | ||
44 | { | ||
45 | _normalMethod = normalMethod; | ||
46 | _throttledMethod = throttledMethod; | ||
47 | |||
48 | _options = options; | ||
49 | _dosProtector = new BasicDOSProtector(_options); | ||
50 | |||
51 | } | ||
52 | public XmlRpcResponse Process(XmlRpcRequest request, IPEndPoint client) | ||
53 | { | ||
54 | |||
55 | XmlRpcResponse resp = null; | ||
56 | string clientstring = GetClientString(request, client); | ||
57 | string endpoint = GetEndPoint(request, client); | ||
58 | if (_dosProtector.Process(clientstring, endpoint)) | ||
59 | resp = _normalMethod(request, client); | ||
60 | else | ||
61 | resp = _throttledMethod(request, client); | ||
62 | if (_options.MaxConcurrentSessions > 0) | ||
63 | _dosProtector.ProcessEnd(clientstring, endpoint); | ||
64 | return resp; | ||
65 | } | ||
66 | |||
67 | private string GetClientString(XmlRpcRequest request, IPEndPoint client) | ||
68 | { | ||
69 | string clientstring; | ||
70 | if (_options.AllowXForwardedFor && request.Params.Count > 3) | ||
71 | { | ||
72 | object headerstr = request.Params[3]; | ||
73 | if (headerstr != null && !string.IsNullOrEmpty(headerstr.ToString())) | ||
74 | clientstring = request.Params[3].ToString(); | ||
75 | else | ||
76 | clientstring = client.Address.ToString(); | ||
77 | } | ||
78 | else | ||
79 | clientstring = client.Address.ToString(); | ||
80 | return clientstring; | ||
81 | } | ||
82 | |||
83 | private string GetEndPoint(XmlRpcRequest request, IPEndPoint client) | ||
84 | { | ||
85 | return client.Address.ToString(); | ||
86 | } | ||
87 | |||
88 | } | ||
89 | |||
90 | |||
91 | } | ||
diff --git a/OpenSim/Framework/Servers/HttpServer/XmlRpcMethod.cs b/OpenSim/Framework/Servers/HttpServer/XmlRpcMethod.cs new file mode 100644 index 0000000..27cf3f3 --- /dev/null +++ b/OpenSim/Framework/Servers/HttpServer/XmlRpcMethod.cs | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Net; | ||
29 | using Nwc.XmlRpc; | ||
30 | |||
31 | namespace OpenSim.Framework.Servers.HttpServer | ||
32 | { | ||
33 | public delegate XmlRpcResponse XmlRpcMethod(XmlRpcRequest request, IPEndPoint client); | ||
34 | } | ||
diff --git a/OpenSim/Framework/Servers/MainServer.cs b/OpenSim/Framework/Servers/MainServer.cs new file mode 100644 index 0000000..57931d4 --- /dev/null +++ b/OpenSim/Framework/Servers/MainServer.cs | |||
@@ -0,0 +1,357 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Reflection; | ||
31 | using System.Net; | ||
32 | using System.Text; | ||
33 | using log4net; | ||
34 | using OpenSim.Framework; | ||
35 | using OpenSim.Framework.Console; | ||
36 | using OpenSim.Framework.Servers.HttpServer; | ||
37 | |||
38 | namespace OpenSim.Framework.Servers | ||
39 | { | ||
40 | public class MainServer | ||
41 | { | ||
42 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
43 | |||
44 | private static BaseHttpServer instance = null; | ||
45 | private static Dictionary<uint, BaseHttpServer> m_Servers = new Dictionary<uint, BaseHttpServer>(); | ||
46 | |||
47 | /// <summary> | ||
48 | /// Control the printing of certain debug messages. | ||
49 | /// </summary> | ||
50 | /// <remarks> | ||
51 | /// If DebugLevel >= 1 then short warnings are logged when receiving bad input data. | ||
52 | /// If DebugLevel >= 2 then long warnings are logged when receiving bad input data. | ||
53 | /// If DebugLevel >= 3 then short notices about all incoming non-poll HTTP requests are logged. | ||
54 | /// If DebugLevel >= 4 then the time taken to fulfill the request is logged. | ||
55 | /// If DebugLevel >= 5 then the start of the body of incoming non-poll HTTP requests will be logged. | ||
56 | /// If DebugLevel >= 6 then the entire body of incoming non-poll HTTP requests will be logged. | ||
57 | /// </remarks> | ||
58 | public static int DebugLevel | ||
59 | { | ||
60 | get { return s_debugLevel; } | ||
61 | set | ||
62 | { | ||
63 | s_debugLevel = value; | ||
64 | |||
65 | lock (m_Servers) | ||
66 | foreach (BaseHttpServer server in m_Servers.Values) | ||
67 | server.DebugLevel = s_debugLevel; | ||
68 | } | ||
69 | } | ||
70 | |||
71 | private static int s_debugLevel; | ||
72 | |||
73 | /// <summary> | ||
74 | /// Set the main HTTP server instance. | ||
75 | /// </summary> | ||
76 | /// <remarks> | ||
77 | /// This will be used to register all handlers that listen to the default port. | ||
78 | /// </remarks> | ||
79 | /// <exception cref='Exception'> | ||
80 | /// Thrown if the HTTP server has not already been registered via AddHttpServer() | ||
81 | /// </exception> | ||
82 | public static BaseHttpServer Instance | ||
83 | { | ||
84 | get { return instance; } | ||
85 | |||
86 | set | ||
87 | { | ||
88 | lock (m_Servers) | ||
89 | if (!m_Servers.ContainsValue(value)) | ||
90 | throw new Exception("HTTP server must already have been registered to be set as the main instance"); | ||
91 | |||
92 | instance = value; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | /// <summary> | ||
97 | /// Get all the registered servers. | ||
98 | /// </summary> | ||
99 | /// <remarks> | ||
100 | /// Returns a copy of the dictionary so this can be iterated through without locking. | ||
101 | /// </remarks> | ||
102 | /// <value></value> | ||
103 | public static Dictionary<uint, BaseHttpServer> Servers | ||
104 | { | ||
105 | get { return new Dictionary<uint, BaseHttpServer>(m_Servers); } | ||
106 | } | ||
107 | |||
108 | public static void RegisterHttpConsoleCommands(ICommandConsole console) | ||
109 | { | ||
110 | console.Commands.AddCommand( | ||
111 | "Comms", false, "show http-handlers", | ||
112 | "show http-handlers", | ||
113 | "Show all registered http handlers", HandleShowHttpHandlersCommand); | ||
114 | |||
115 | console.Commands.AddCommand( | ||
116 | "Debug", false, "debug http", "debug http <in|out|all> [<level>]", | ||
117 | "Turn on http request logging.", | ||
118 | "If in or all and\n" | ||
119 | + " level <= 0 then no extra logging is done.\n" | ||
120 | + " level >= 1 then short warnings are logged when receiving bad input data.\n" | ||
121 | + " level >= 2 then long warnings are logged when receiving bad input data.\n" | ||
122 | + " level >= 3 then short notices about all incoming non-poll HTTP requests are logged.\n" | ||
123 | + " level >= 4 then the time taken to fulfill the request is logged.\n" | ||
124 | + " level >= 5 then a sample from the beginning of the data is logged.\n" | ||
125 | + " level >= 6 then the entire data is logged.\n" | ||
126 | + " no level is specified then the current level is returned.\n\n" | ||
127 | + "If out or all and\n" | ||
128 | + " level >= 3 then short notices about all outgoing requests going through WebUtil are logged.\n" | ||
129 | + " level >= 4 then the time taken to fulfill the request is logged.\n" | ||
130 | + " level >= 5 then a sample from the beginning of the data is logged.\n" | ||
131 | + " level >= 6 then the entire data is logged.\n", | ||
132 | HandleDebugHttpCommand); | ||
133 | } | ||
134 | |||
135 | /// <summary> | ||
136 | /// Turn on some debugging values for OpenSim. | ||
137 | /// </summary> | ||
138 | /// <param name="args"></param> | ||
139 | private static void HandleDebugHttpCommand(string module, string[] cmdparams) | ||
140 | { | ||
141 | if (cmdparams.Length < 3) | ||
142 | { | ||
143 | MainConsole.Instance.Output("Usage: debug http <in|out|all> 0..6"); | ||
144 | return; | ||
145 | } | ||
146 | |||
147 | bool inReqs = false; | ||
148 | bool outReqs = false; | ||
149 | bool allReqs = false; | ||
150 | |||
151 | string subCommand = cmdparams[2]; | ||
152 | |||
153 | if (subCommand.ToLower() == "in") | ||
154 | { | ||
155 | inReqs = true; | ||
156 | } | ||
157 | else if (subCommand.ToLower() == "out") | ||
158 | { | ||
159 | outReqs = true; | ||
160 | } | ||
161 | else if (subCommand.ToLower() == "all") | ||
162 | { | ||
163 | allReqs = true; | ||
164 | } | ||
165 | else | ||
166 | { | ||
167 | MainConsole.Instance.Output("You must specify in, out or all"); | ||
168 | return; | ||
169 | } | ||
170 | |||
171 | if (cmdparams.Length >= 4) | ||
172 | { | ||
173 | string rawNewDebug = cmdparams[3]; | ||
174 | int newDebug; | ||
175 | |||
176 | if (!int.TryParse(rawNewDebug, out newDebug)) | ||
177 | { | ||
178 | MainConsole.Instance.OutputFormat("{0} is not a valid debug level", rawNewDebug); | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | if (newDebug < 0 || newDebug > 6) | ||
183 | { | ||
184 | MainConsole.Instance.OutputFormat("{0} is outside the valid debug level range of 0..6", newDebug); | ||
185 | return; | ||
186 | } | ||
187 | |||
188 | if (allReqs || inReqs) | ||
189 | { | ||
190 | MainServer.DebugLevel = newDebug; | ||
191 | MainConsole.Instance.OutputFormat("IN debug level set to {0}", newDebug); | ||
192 | } | ||
193 | |||
194 | if (allReqs || outReqs) | ||
195 | { | ||
196 | WebUtil.DebugLevel = newDebug; | ||
197 | MainConsole.Instance.OutputFormat("OUT debug level set to {0}", newDebug); | ||
198 | } | ||
199 | } | ||
200 | else | ||
201 | { | ||
202 | if (allReqs || inReqs) | ||
203 | MainConsole.Instance.OutputFormat("Current IN debug level is {0}", MainServer.DebugLevel); | ||
204 | |||
205 | if (allReqs || outReqs) | ||
206 | MainConsole.Instance.OutputFormat("Current OUT debug level is {0}", WebUtil.DebugLevel); | ||
207 | } | ||
208 | } | ||
209 | |||
210 | private static void HandleShowHttpHandlersCommand(string module, string[] args) | ||
211 | { | ||
212 | if (args.Length != 2) | ||
213 | { | ||
214 | MainConsole.Instance.Output("Usage: show http-handlers"); | ||
215 | return; | ||
216 | } | ||
217 | |||
218 | StringBuilder handlers = new StringBuilder(); | ||
219 | |||
220 | lock (m_Servers) | ||
221 | { | ||
222 | foreach (BaseHttpServer httpServer in m_Servers.Values) | ||
223 | { | ||
224 | handlers.AppendFormat( | ||
225 | "Registered HTTP Handlers for server at {0}:{1}\n", httpServer.ListenIPAddress, httpServer.Port); | ||
226 | |||
227 | handlers.AppendFormat("* XMLRPC:\n"); | ||
228 | foreach (String s in httpServer.GetXmlRpcHandlerKeys()) | ||
229 | handlers.AppendFormat("\t{0}\n", s); | ||
230 | |||
231 | handlers.AppendFormat("* HTTP:\n"); | ||
232 | foreach (String s in httpServer.GetHTTPHandlerKeys()) | ||
233 | handlers.AppendFormat("\t{0}\n", s); | ||
234 | |||
235 | handlers.AppendFormat("* HTTP (poll):\n"); | ||
236 | foreach (String s in httpServer.GetPollServiceHandlerKeys()) | ||
237 | handlers.AppendFormat("\t{0}\n", s); | ||
238 | |||
239 | handlers.AppendFormat("* JSONRPC:\n"); | ||
240 | foreach (String s in httpServer.GetJsonRpcHandlerKeys()) | ||
241 | handlers.AppendFormat("\t{0}\n", s); | ||
242 | |||
243 | // handlers.AppendFormat("* Agent:\n"); | ||
244 | // foreach (String s in httpServer.GetAgentHandlerKeys()) | ||
245 | // handlers.AppendFormat("\t{0}\n", s); | ||
246 | |||
247 | handlers.AppendFormat("* LLSD:\n"); | ||
248 | foreach (String s in httpServer.GetLLSDHandlerKeys()) | ||
249 | handlers.AppendFormat("\t{0}\n", s); | ||
250 | |||
251 | handlers.AppendFormat("* StreamHandlers ({0}):\n", httpServer.GetStreamHandlerKeys().Count); | ||
252 | foreach (String s in httpServer.GetStreamHandlerKeys()) | ||
253 | handlers.AppendFormat("\t{0}\n", s); | ||
254 | |||
255 | handlers.Append("\n"); | ||
256 | } | ||
257 | } | ||
258 | |||
259 | MainConsole.Instance.Output(handlers.ToString()); | ||
260 | } | ||
261 | |||
262 | /// <summary> | ||
263 | /// Register an already started HTTP server to the collection of known servers. | ||
264 | /// </summary> | ||
265 | /// <param name='server'></param> | ||
266 | public static void AddHttpServer(BaseHttpServer server) | ||
267 | { | ||
268 | lock (m_Servers) | ||
269 | { | ||
270 | if (m_Servers.ContainsKey(server.Port)) | ||
271 | throw new Exception(string.Format("HTTP server for port {0} already exists.", server.Port)); | ||
272 | |||
273 | m_Servers.Add(server.Port, server); | ||
274 | } | ||
275 | } | ||
276 | |||
277 | /// <summary> | ||
278 | /// Removes the http server listening on the given port. | ||
279 | /// </summary> | ||
280 | /// <remarks> | ||
281 | /// It is the responsibility of the caller to do clean up. | ||
282 | /// </remarks> | ||
283 | /// <param name='port'></param> | ||
284 | /// <returns></returns> | ||
285 | public static bool RemoveHttpServer(uint port) | ||
286 | { | ||
287 | lock (m_Servers) | ||
288 | { | ||
289 | if (instance != null && instance.Port == port) | ||
290 | instance = null; | ||
291 | |||
292 | return m_Servers.Remove(port); | ||
293 | } | ||
294 | } | ||
295 | |||
296 | /// <summary> | ||
297 | /// Does this collection of servers contain one with the given port? | ||
298 | /// </summary> | ||
299 | /// <remarks> | ||
300 | /// Unlike GetHttpServer, this will not instantiate a server if one does not exist on that port. | ||
301 | /// </remarks> | ||
302 | /// <param name='port'></param> | ||
303 | /// <returns>true if a server with the given port is registered, false otherwise.</returns> | ||
304 | public static bool ContainsHttpServer(uint port) | ||
305 | { | ||
306 | lock (m_Servers) | ||
307 | return m_Servers.ContainsKey(port); | ||
308 | } | ||
309 | |||
310 | /// <summary> | ||
311 | /// Get the default http server or an http server for a specific port. | ||
312 | /// </summary> | ||
313 | /// <remarks> | ||
314 | /// If the requested HTTP server doesn't already exist then a new one is instantiated and started. | ||
315 | /// </remarks> | ||
316 | /// <returns></returns> | ||
317 | /// <param name='port'>If 0 then the default HTTP server is returned.</param> | ||
318 | public static IHttpServer GetHttpServer(uint port) | ||
319 | { | ||
320 | return GetHttpServer(port, null); | ||
321 | } | ||
322 | |||
323 | /// <summary> | ||
324 | /// Get the default http server, an http server for a specific port | ||
325 | /// and/or an http server bound to a specific address | ||
326 | /// </summary> | ||
327 | /// <remarks> | ||
328 | /// If the requested HTTP server doesn't already exist then a new one is instantiated and started. | ||
329 | /// </remarks> | ||
330 | /// <returns></returns> | ||
331 | /// <param name='port'>If 0 then the default HTTP server is returned.</param> | ||
332 | /// <param name='ipaddr'>A specific IP address to bind to. If null then the default IP address is used.</param> | ||
333 | public static IHttpServer GetHttpServer(uint port, IPAddress ipaddr) | ||
334 | { | ||
335 | if (port == 0) | ||
336 | return Instance; | ||
337 | |||
338 | if (instance != null && port == Instance.Port) | ||
339 | return Instance; | ||
340 | |||
341 | lock (m_Servers) | ||
342 | { | ||
343 | if (m_Servers.ContainsKey(port)) | ||
344 | return m_Servers[port]; | ||
345 | |||
346 | m_Servers[port] = new BaseHttpServer(port); | ||
347 | |||
348 | if (ipaddr != null) | ||
349 | m_Servers[port].ListenIPAddress = ipaddr; | ||
350 | |||
351 | m_Servers[port].Start(); | ||
352 | |||
353 | return m_Servers[port]; | ||
354 | } | ||
355 | } | ||
356 | } | ||
357 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/Properties/AssemblyInfo.cs b/OpenSim/Framework/Servers/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..792c62e --- /dev/null +++ b/OpenSim/Framework/Servers/Properties/AssemblyInfo.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | using System.Reflection; | ||
2 | using System.Runtime.CompilerServices; | ||
3 | using System.Runtime.InteropServices; | ||
4 | |||
5 | // General Information about an assembly is controlled through the following | ||
6 | // set of attributes. Change these attribute values to modify the information | ||
7 | // associated with an assembly. | ||
8 | [assembly: AssemblyTitle("OpenSim.Framework.Servers")] | ||
9 | [assembly: AssemblyDescription("")] | ||
10 | [assembly: AssemblyConfiguration("")] | ||
11 | [assembly: AssemblyCompany("http://opensimulator.org")] | ||
12 | [assembly: AssemblyProduct("OpenSim")] | ||
13 | [assembly: AssemblyCopyright("OpenSimulator developers")] | ||
14 | [assembly: AssemblyTrademark("")] | ||
15 | [assembly: AssemblyCulture("")] | ||
16 | |||
17 | // Setting ComVisible to false makes the types in this assembly not visible | ||
18 | // to COM components. If you need to access a type in this assembly from | ||
19 | // COM, set the ComVisible attribute to true on that type. | ||
20 | [assembly: ComVisible(false)] | ||
21 | |||
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM | ||
23 | [assembly: Guid("b48e8b3e-5c5c-4673-b31f-21e13b8e568b")] | ||
24 | |||
25 | // Version information for an assembly consists of the following four values: | ||
26 | // | ||
27 | // Major Version | ||
28 | // Minor Version | ||
29 | // Build Number | ||
30 | // Revision | ||
31 | // | ||
32 | [assembly: AssemblyVersion("0.7.6.*")] | ||
33 | [assembly: AssemblyFileVersion("1.0.0.0")] | ||
diff --git a/OpenSim/Framework/Servers/ServerBase.cs b/OpenSim/Framework/Servers/ServerBase.cs new file mode 100644 index 0000000..e403ba0 --- /dev/null +++ b/OpenSim/Framework/Servers/ServerBase.cs | |||
@@ -0,0 +1,1047 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Diagnostics; | ||
31 | using System.IO; | ||
32 | using System.Linq; | ||
33 | using System.Reflection; | ||
34 | using System.Text; | ||
35 | using System.Text.RegularExpressions; | ||
36 | using System.Threading; | ||
37 | using log4net; | ||
38 | using log4net.Appender; | ||
39 | using log4net.Core; | ||
40 | using log4net.Repository; | ||
41 | using Nini.Config; | ||
42 | using OpenSim.Framework.Console; | ||
43 | using OpenSim.Framework.Monitoring; | ||
44 | |||
45 | namespace OpenSim.Framework.Servers | ||
46 | { | ||
47 | public class ServerBase | ||
48 | { | ||
49 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
50 | |||
51 | public IConfigSource Config { get; protected set; } | ||
52 | |||
53 | /// <summary> | ||
54 | /// Console to be used for any command line output. Can be null, in which case there should be no output. | ||
55 | /// </summary> | ||
56 | protected ICommandConsole m_console; | ||
57 | |||
58 | protected OpenSimAppender m_consoleAppender; | ||
59 | protected FileAppender m_logFileAppender; | ||
60 | |||
61 | protected DateTime m_startuptime; | ||
62 | protected string m_startupDirectory = Environment.CurrentDirectory; | ||
63 | |||
64 | protected string m_pidFile = String.Empty; | ||
65 | |||
66 | protected ServerStatsCollector m_serverStatsCollector; | ||
67 | |||
68 | /// <summary> | ||
69 | /// Server version information. Usually VersionInfo + information about git commit, operating system, etc. | ||
70 | /// </summary> | ||
71 | protected string m_version; | ||
72 | |||
73 | public ServerBase() | ||
74 | { | ||
75 | m_startuptime = DateTime.Now; | ||
76 | m_version = VersionInfo.Version; | ||
77 | EnhanceVersionInformation(); | ||
78 | } | ||
79 | |||
80 | protected void CreatePIDFile(string path) | ||
81 | { | ||
82 | if (File.Exists(path)) | ||
83 | m_log.ErrorFormat( | ||
84 | "[SERVER BASE]: Previous pid file {0} still exists on startup. Possibly previously unclean shutdown.", | ||
85 | path); | ||
86 | |||
87 | try | ||
88 | { | ||
89 | string pidstring = System.Diagnostics.Process.GetCurrentProcess().Id.ToString(); | ||
90 | |||
91 | using (FileStream fs = File.Create(path)) | ||
92 | { | ||
93 | Byte[] buf = Encoding.ASCII.GetBytes(pidstring); | ||
94 | fs.Write(buf, 0, buf.Length); | ||
95 | } | ||
96 | |||
97 | m_pidFile = path; | ||
98 | |||
99 | m_log.InfoFormat("[SERVER BASE]: Created pid file {0}", m_pidFile); | ||
100 | } | ||
101 | catch (Exception e) | ||
102 | { | ||
103 | m_log.Warn(string.Format("[SERVER BASE]: Could not create PID file at {0} ", path), e); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | protected void RemovePIDFile() | ||
108 | { | ||
109 | if (m_pidFile != String.Empty) | ||
110 | { | ||
111 | try | ||
112 | { | ||
113 | File.Delete(m_pidFile); | ||
114 | } | ||
115 | catch (Exception e) | ||
116 | { | ||
117 | m_log.Error(string.Format("[SERVER BASE]: Error whilst removing {0} ", m_pidFile), e); | ||
118 | } | ||
119 | |||
120 | m_pidFile = String.Empty; | ||
121 | } | ||
122 | } | ||
123 | |||
124 | /// <summary> | ||
125 | /// Log information about the circumstances in which we're running (OpenSimulator version number, CLR details, | ||
126 | /// etc.). | ||
127 | /// </summary> | ||
128 | public void LogEnvironmentInformation() | ||
129 | { | ||
130 | // FIXME: This should be done down in ServerBase but we need to sort out and refactor the log4net | ||
131 | // XmlConfigurator calls first accross servers. | ||
132 | m_log.InfoFormat("[SERVER BASE]: Starting in {0}", m_startupDirectory); | ||
133 | |||
134 | m_log.InfoFormat("[SERVER BASE]: OpenSimulator version: {0}", m_version); | ||
135 | |||
136 | // clr version potentially is more confusing than helpful, since it doesn't tell us if we're running under Mono/MS .NET and | ||
137 | // the clr version number doesn't match the project version number under Mono. | ||
138 | //m_log.Info("[STARTUP]: Virtual machine runtime version: " + Environment.Version + Environment.NewLine); | ||
139 | m_log.InfoFormat( | ||
140 | "[SERVER BASE]: Operating system version: {0}, .NET platform {1}, {2}-bit", | ||
141 | Environment.OSVersion, Environment.OSVersion.Platform, Util.Is64BitProcess() ? "64" : "32"); | ||
142 | } | ||
143 | |||
144 | public void RegisterCommonAppenders(IConfig startupConfig) | ||
145 | { | ||
146 | ILoggerRepository repository = LogManager.GetRepository(); | ||
147 | IAppender[] appenders = repository.GetAppenders(); | ||
148 | |||
149 | foreach (IAppender appender in appenders) | ||
150 | { | ||
151 | if (appender.Name == "Console") | ||
152 | { | ||
153 | m_consoleAppender = (OpenSimAppender)appender; | ||
154 | } | ||
155 | else if (appender.Name == "LogFileAppender") | ||
156 | { | ||
157 | m_logFileAppender = (FileAppender)appender; | ||
158 | } | ||
159 | } | ||
160 | |||
161 | if (null == m_consoleAppender) | ||
162 | { | ||
163 | Notice("No appender named Console found (see the log4net config file for this executable)!"); | ||
164 | } | ||
165 | else | ||
166 | { | ||
167 | // FIXME: This should be done through an interface rather than casting. | ||
168 | m_consoleAppender.Console = (ConsoleBase)m_console; | ||
169 | |||
170 | // If there is no threshold set then the threshold is effectively everything. | ||
171 | if (null == m_consoleAppender.Threshold) | ||
172 | m_consoleAppender.Threshold = Level.All; | ||
173 | |||
174 | Notice(String.Format("Console log level is {0}", m_consoleAppender.Threshold)); | ||
175 | } | ||
176 | |||
177 | if (m_logFileAppender != null && startupConfig != null) | ||
178 | { | ||
179 | string cfgFileName = startupConfig.GetString("LogFile", null); | ||
180 | if (cfgFileName != null) | ||
181 | { | ||
182 | m_logFileAppender.File = cfgFileName; | ||
183 | m_logFileAppender.ActivateOptions(); | ||
184 | } | ||
185 | |||
186 | m_log.InfoFormat("[SERVER BASE]: Logging started to file {0}", m_logFileAppender.File); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | /// <summary> | ||
191 | /// Register common commands once m_console has been set if it is going to be set | ||
192 | /// </summary> | ||
193 | public void RegisterCommonCommands() | ||
194 | { | ||
195 | if (m_console == null) | ||
196 | return; | ||
197 | |||
198 | m_console.Commands.AddCommand( | ||
199 | "General", false, "show info", "show info", "Show general information about the server", HandleShow); | ||
200 | |||
201 | m_console.Commands.AddCommand( | ||
202 | "General", false, "show version", "show version", "Show server version", HandleShow); | ||
203 | |||
204 | m_console.Commands.AddCommand( | ||
205 | "General", false, "show uptime", "show uptime", "Show server uptime", HandleShow); | ||
206 | |||
207 | m_console.Commands.AddCommand( | ||
208 | "General", false, "get log level", "get log level", "Get the current console logging level", | ||
209 | (mod, cmd) => ShowLogLevel()); | ||
210 | |||
211 | m_console.Commands.AddCommand( | ||
212 | "General", false, "set log level", "set log level <level>", | ||
213 | "Set the console logging level for this session.", HandleSetLogLevel); | ||
214 | |||
215 | m_console.Commands.AddCommand( | ||
216 | "General", false, "config set", | ||
217 | "config set <section> <key> <value>", | ||
218 | "Set a config option. In most cases this is not useful since changed parameters are not dynamically reloaded. Neither do changed parameters persist - you will have to change a config file manually and restart.", HandleConfig); | ||
219 | |||
220 | m_console.Commands.AddCommand( | ||
221 | "General", false, "config get", | ||
222 | "config get [<section>] [<key>]", | ||
223 | "Synonym for config show", | ||
224 | HandleConfig); | ||
225 | |||
226 | m_console.Commands.AddCommand( | ||
227 | "General", false, "config show", | ||
228 | "config show [<section>] [<key>]", | ||
229 | "Show config information", | ||
230 | "If neither section nor field are specified, then the whole current configuration is printed." + Environment.NewLine | ||
231 | + "If a section is given but not a field, then all fields in that section are printed.", | ||
232 | HandleConfig); | ||
233 | |||
234 | m_console.Commands.AddCommand( | ||
235 | "General", false, "config save", | ||
236 | "config save <path>", | ||
237 | "Save current configuration to a file at the given path", HandleConfig); | ||
238 | |||
239 | m_console.Commands.AddCommand( | ||
240 | "General", false, "command-script", | ||
241 | "command-script <script>", | ||
242 | "Run a command script from file", HandleScript); | ||
243 | |||
244 | m_console.Commands.AddCommand( | ||
245 | "General", false, "show threads", | ||
246 | "show threads", | ||
247 | "Show thread status", HandleShow); | ||
248 | |||
249 | m_console.Commands.AddCommand( | ||
250 | "Debug", false, "threads abort", | ||
251 | "threads abort <thread-id>", | ||
252 | "Abort a managed thread. Use \"show threads\" to find possible threads.", HandleThreadsAbort); | ||
253 | |||
254 | m_console.Commands.AddCommand( | ||
255 | "General", false, "threads show", | ||
256 | "threads show", | ||
257 | "Show thread status. Synonym for \"show threads\"", | ||
258 | (string module, string[] args) => Notice(GetThreadsReport())); | ||
259 | |||
260 | m_console.Commands.AddCommand ( | ||
261 | "Debug", false, "debug comms set", | ||
262 | "debug comms set serialosdreq true|false", | ||
263 | "Set comms parameters. For debug purposes.", | ||
264 | HandleDebugCommsSet); | ||
265 | |||
266 | m_console.Commands.AddCommand ( | ||
267 | "Debug", false, "debug comms status", | ||
268 | "debug comms status", | ||
269 | "Show current debug comms parameters.", | ||
270 | HandleDebugCommsStatus); | ||
271 | |||
272 | m_console.Commands.AddCommand ( | ||
273 | "Debug", false, "debug threadpool set", | ||
274 | "debug threadpool set worker|iocp min|max <n>", | ||
275 | "Set threadpool parameters. For debug purposes.", | ||
276 | HandleDebugThreadpoolSet); | ||
277 | |||
278 | m_console.Commands.AddCommand ( | ||
279 | "Debug", false, "debug threadpool status", | ||
280 | "debug threadpool status", | ||
281 | "Show current debug threadpool parameters.", | ||
282 | HandleDebugThreadpoolStatus); | ||
283 | |||
284 | m_console.Commands.AddCommand( | ||
285 | "Debug", false, "debug threadpool level", | ||
286 | "debug threadpool level 0.." + Util.MAX_THREADPOOL_LEVEL, | ||
287 | "Turn on logging of activity in the main thread pool.", | ||
288 | "Log levels:\n" | ||
289 | + " 0 = no logging\n" | ||
290 | + " 1 = only first line of stack trace; don't log common threads\n" | ||
291 | + " 2 = full stack trace; don't log common threads\n" | ||
292 | + " 3 = full stack trace, including common threads\n", | ||
293 | HandleDebugThreadpoolLevel); | ||
294 | |||
295 | // m_console.Commands.AddCommand( | ||
296 | // "Debug", false, "show threadpool calls active", | ||
297 | // "show threadpool calls active", | ||
298 | // "Show details about threadpool calls that are still active (currently waiting or in progress)", | ||
299 | // HandleShowThreadpoolCallsActive); | ||
300 | |||
301 | m_console.Commands.AddCommand( | ||
302 | "Debug", false, "show threadpool calls complete", | ||
303 | "show threadpool calls complete", | ||
304 | "Show details about threadpool calls that have been completed.", | ||
305 | HandleShowThreadpoolCallsComplete); | ||
306 | |||
307 | m_console.Commands.AddCommand( | ||
308 | "Debug", false, "force gc", | ||
309 | "force gc", | ||
310 | "Manually invoke runtime garbage collection. For debugging purposes", | ||
311 | HandleForceGc); | ||
312 | |||
313 | m_console.Commands.AddCommand( | ||
314 | "General", false, "quit", | ||
315 | "quit", | ||
316 | "Quit the application", (mod, args) => Shutdown()); | ||
317 | |||
318 | m_console.Commands.AddCommand( | ||
319 | "General", false, "shutdown", | ||
320 | "shutdown", | ||
321 | "Quit the application", (mod, args) => Shutdown()); | ||
322 | |||
323 | ChecksManager.RegisterConsoleCommands(m_console); | ||
324 | StatsManager.RegisterConsoleCommands(m_console); | ||
325 | } | ||
326 | |||
327 | public void RegisterCommonComponents(IConfigSource configSource) | ||
328 | { | ||
329 | IConfig networkConfig = configSource.Configs["Network"]; | ||
330 | |||
331 | if (networkConfig != null) | ||
332 | { | ||
333 | WebUtil.SerializeOSDRequestsPerEndpoint = networkConfig.GetBoolean("SerializeOSDRequests", false); | ||
334 | } | ||
335 | |||
336 | m_serverStatsCollector = new ServerStatsCollector(); | ||
337 | m_serverStatsCollector.Initialise(configSource); | ||
338 | m_serverStatsCollector.Start(); | ||
339 | } | ||
340 | |||
341 | private void HandleDebugCommsStatus(string module, string[] args) | ||
342 | { | ||
343 | Notice("serialosdreq is {0}", WebUtil.SerializeOSDRequestsPerEndpoint); | ||
344 | } | ||
345 | |||
346 | private void HandleDebugCommsSet(string module, string[] args) | ||
347 | { | ||
348 | if (args.Length != 5) | ||
349 | { | ||
350 | Notice("Usage: debug comms set serialosdreq true|false"); | ||
351 | return; | ||
352 | } | ||
353 | |||
354 | if (args[3] != "serialosdreq") | ||
355 | { | ||
356 | Notice("Usage: debug comms set serialosdreq true|false"); | ||
357 | return; | ||
358 | } | ||
359 | |||
360 | bool setSerializeOsdRequests; | ||
361 | |||
362 | if (!ConsoleUtil.TryParseConsoleBool(m_console, args[4], out setSerializeOsdRequests)) | ||
363 | return; | ||
364 | |||
365 | WebUtil.SerializeOSDRequestsPerEndpoint = setSerializeOsdRequests; | ||
366 | |||
367 | Notice("serialosdreq is now {0}", setSerializeOsdRequests); | ||
368 | } | ||
369 | |||
370 | private void HandleShowThreadpoolCallsActive(string module, string[] args) | ||
371 | { | ||
372 | List<KeyValuePair<string, int>> calls = Util.GetFireAndForgetCallsInProgress().ToList(); | ||
373 | calls.Sort((kvp1, kvp2) => kvp2.Value.CompareTo(kvp1.Value)); | ||
374 | int namedCalls = 0; | ||
375 | |||
376 | ConsoleDisplayList cdl = new ConsoleDisplayList(); | ||
377 | foreach (KeyValuePair<string, int> kvp in calls) | ||
378 | { | ||
379 | if (kvp.Value > 0) | ||
380 | { | ||
381 | cdl.AddRow(kvp.Key, kvp.Value); | ||
382 | namedCalls += kvp.Value; | ||
383 | } | ||
384 | } | ||
385 | |||
386 | cdl.AddRow("TOTAL NAMED", namedCalls); | ||
387 | |||
388 | long allQueuedCalls = Util.TotalQueuedFireAndForgetCalls; | ||
389 | long allRunningCalls = Util.TotalRunningFireAndForgetCalls; | ||
390 | |||
391 | cdl.AddRow("TOTAL QUEUED", allQueuedCalls); | ||
392 | cdl.AddRow("TOTAL RUNNING", allRunningCalls); | ||
393 | cdl.AddRow("TOTAL ANONYMOUS", allQueuedCalls + allRunningCalls - namedCalls); | ||
394 | cdl.AddRow("TOTAL ALL", allQueuedCalls + allRunningCalls); | ||
395 | |||
396 | MainConsole.Instance.Output(cdl.ToString()); | ||
397 | } | ||
398 | |||
399 | private void HandleShowThreadpoolCallsComplete(string module, string[] args) | ||
400 | { | ||
401 | List<KeyValuePair<string, int>> calls = Util.GetFireAndForgetCallsMade().ToList(); | ||
402 | calls.Sort((kvp1, kvp2) => kvp2.Value.CompareTo(kvp1.Value)); | ||
403 | int namedCallsMade = 0; | ||
404 | |||
405 | ConsoleDisplayList cdl = new ConsoleDisplayList(); | ||
406 | foreach (KeyValuePair<string, int> kvp in calls) | ||
407 | { | ||
408 | cdl.AddRow(kvp.Key, kvp.Value); | ||
409 | namedCallsMade += kvp.Value; | ||
410 | } | ||
411 | |||
412 | cdl.AddRow("TOTAL NAMED", namedCallsMade); | ||
413 | |||
414 | long allCallsMade = Util.TotalFireAndForgetCallsMade; | ||
415 | cdl.AddRow("TOTAL ANONYMOUS", allCallsMade - namedCallsMade); | ||
416 | cdl.AddRow("TOTAL ALL", allCallsMade); | ||
417 | |||
418 | MainConsole.Instance.Output(cdl.ToString()); | ||
419 | } | ||
420 | |||
421 | private void HandleDebugThreadpoolStatus(string module, string[] args) | ||
422 | { | ||
423 | int workerThreads, iocpThreads; | ||
424 | |||
425 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); | ||
426 | Notice("Min worker threads: {0}", workerThreads); | ||
427 | Notice("Min IOCP threads: {0}", iocpThreads); | ||
428 | |||
429 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); | ||
430 | Notice("Max worker threads: {0}", workerThreads); | ||
431 | Notice("Max IOCP threads: {0}", iocpThreads); | ||
432 | |||
433 | ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads); | ||
434 | Notice("Available worker threads: {0}", workerThreads); | ||
435 | Notice("Available IOCP threads: {0}", iocpThreads); | ||
436 | } | ||
437 | |||
438 | private void HandleDebugThreadpoolSet(string module, string[] args) | ||
439 | { | ||
440 | if (args.Length != 6) | ||
441 | { | ||
442 | Notice("Usage: debug threadpool set worker|iocp min|max <n>"); | ||
443 | return; | ||
444 | } | ||
445 | |||
446 | int newThreads; | ||
447 | |||
448 | if (!ConsoleUtil.TryParseConsoleInt(m_console, args[5], out newThreads)) | ||
449 | return; | ||
450 | |||
451 | string poolType = args[3]; | ||
452 | string bound = args[4]; | ||
453 | |||
454 | bool fail = false; | ||
455 | int workerThreads, iocpThreads; | ||
456 | |||
457 | if (poolType == "worker") | ||
458 | { | ||
459 | if (bound == "min") | ||
460 | { | ||
461 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); | ||
462 | |||
463 | if (!ThreadPool.SetMinThreads(newThreads, iocpThreads)) | ||
464 | fail = true; | ||
465 | } | ||
466 | else | ||
467 | { | ||
468 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); | ||
469 | |||
470 | if (!ThreadPool.SetMaxThreads(newThreads, iocpThreads)) | ||
471 | fail = true; | ||
472 | } | ||
473 | } | ||
474 | else | ||
475 | { | ||
476 | if (bound == "min") | ||
477 | { | ||
478 | ThreadPool.GetMinThreads(out workerThreads, out iocpThreads); | ||
479 | |||
480 | if (!ThreadPool.SetMinThreads(workerThreads, newThreads)) | ||
481 | fail = true; | ||
482 | } | ||
483 | else | ||
484 | { | ||
485 | ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads); | ||
486 | |||
487 | if (!ThreadPool.SetMaxThreads(workerThreads, newThreads)) | ||
488 | fail = true; | ||
489 | } | ||
490 | } | ||
491 | |||
492 | if (fail) | ||
493 | { | ||
494 | Notice("ERROR: Could not set {0} {1} threads to {2}", poolType, bound, newThreads); | ||
495 | } | ||
496 | else | ||
497 | { | ||
498 | int minWorkerThreads, maxWorkerThreads, minIocpThreads, maxIocpThreads; | ||
499 | |||
500 | ThreadPool.GetMinThreads(out minWorkerThreads, out minIocpThreads); | ||
501 | ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIocpThreads); | ||
502 | |||
503 | Notice("Min worker threads now {0}", minWorkerThreads); | ||
504 | Notice("Min IOCP threads now {0}", minIocpThreads); | ||
505 | Notice("Max worker threads now {0}", maxWorkerThreads); | ||
506 | Notice("Max IOCP threads now {0}", maxIocpThreads); | ||
507 | } | ||
508 | } | ||
509 | |||
510 | private static void HandleDebugThreadpoolLevel(string module, string[] cmdparams) | ||
511 | { | ||
512 | if (cmdparams.Length < 4) | ||
513 | { | ||
514 | MainConsole.Instance.Output("Usage: debug threadpool level 0.." + Util.MAX_THREADPOOL_LEVEL); | ||
515 | return; | ||
516 | } | ||
517 | |||
518 | string rawLevel = cmdparams[3]; | ||
519 | int newLevel; | ||
520 | |||
521 | if (!int.TryParse(rawLevel, out newLevel)) | ||
522 | { | ||
523 | MainConsole.Instance.OutputFormat("{0} is not a valid debug level", rawLevel); | ||
524 | return; | ||
525 | } | ||
526 | |||
527 | if (newLevel < 0 || newLevel > Util.MAX_THREADPOOL_LEVEL) | ||
528 | { | ||
529 | MainConsole.Instance.OutputFormat("{0} is outside the valid debug level range of 0.." + Util.MAX_THREADPOOL_LEVEL, newLevel); | ||
530 | return; | ||
531 | } | ||
532 | |||
533 | Util.LogThreadPool = newLevel; | ||
534 | MainConsole.Instance.OutputFormat("LogThreadPool set to {0}", newLevel); | ||
535 | } | ||
536 | |||
537 | private void HandleForceGc(string module, string[] args) | ||
538 | { | ||
539 | Notice("Manually invoking runtime garbage collection"); | ||
540 | GC.Collect(); | ||
541 | } | ||
542 | |||
543 | public virtual void HandleShow(string module, string[] cmd) | ||
544 | { | ||
545 | List<string> args = new List<string>(cmd); | ||
546 | |||
547 | args.RemoveAt(0); | ||
548 | |||
549 | string[] showParams = args.ToArray(); | ||
550 | |||
551 | switch (showParams[0]) | ||
552 | { | ||
553 | case "info": | ||
554 | ShowInfo(); | ||
555 | break; | ||
556 | |||
557 | case "version": | ||
558 | Notice(GetVersionText()); | ||
559 | break; | ||
560 | |||
561 | case "uptime": | ||
562 | Notice(GetUptimeReport()); | ||
563 | break; | ||
564 | |||
565 | case "threads": | ||
566 | Notice(GetThreadsReport()); | ||
567 | break; | ||
568 | } | ||
569 | } | ||
570 | |||
571 | /// <summary> | ||
572 | /// Change and load configuration file data. | ||
573 | /// </summary> | ||
574 | /// <param name="module"></param> | ||
575 | /// <param name="cmd"></param> | ||
576 | private void HandleConfig(string module, string[] cmd) | ||
577 | { | ||
578 | List<string> args = new List<string>(cmd); | ||
579 | args.RemoveAt(0); | ||
580 | string[] cmdparams = args.ToArray(); | ||
581 | |||
582 | if (cmdparams.Length > 0) | ||
583 | { | ||
584 | string firstParam = cmdparams[0].ToLower(); | ||
585 | |||
586 | switch (firstParam) | ||
587 | { | ||
588 | case "set": | ||
589 | if (cmdparams.Length < 4) | ||
590 | { | ||
591 | Notice("Syntax: config set <section> <key> <value>"); | ||
592 | Notice("Example: config set ScriptEngine.DotNetEngine NumberOfScriptThreads 5"); | ||
593 | } | ||
594 | else | ||
595 | { | ||
596 | IConfig c; | ||
597 | IConfigSource source = new IniConfigSource(); | ||
598 | c = source.AddConfig(cmdparams[1]); | ||
599 | if (c != null) | ||
600 | { | ||
601 | string _value = String.Join(" ", cmdparams, 3, cmdparams.Length - 3); | ||
602 | c.Set(cmdparams[2], _value); | ||
603 | Config.Merge(source); | ||
604 | |||
605 | Notice("In section [{0}], set {1} = {2}", c.Name, cmdparams[2], _value); | ||
606 | } | ||
607 | } | ||
608 | break; | ||
609 | |||
610 | case "get": | ||
611 | case "show": | ||
612 | if (cmdparams.Length == 1) | ||
613 | { | ||
614 | foreach (IConfig config in Config.Configs) | ||
615 | { | ||
616 | Notice("[{0}]", config.Name); | ||
617 | string[] keys = config.GetKeys(); | ||
618 | foreach (string key in keys) | ||
619 | Notice(" {0} = {1}", key, config.GetString(key)); | ||
620 | } | ||
621 | } | ||
622 | else if (cmdparams.Length == 2 || cmdparams.Length == 3) | ||
623 | { | ||
624 | IConfig config = Config.Configs[cmdparams[1]]; | ||
625 | if (config == null) | ||
626 | { | ||
627 | Notice("Section \"{0}\" does not exist.",cmdparams[1]); | ||
628 | break; | ||
629 | } | ||
630 | else | ||
631 | { | ||
632 | if (cmdparams.Length == 2) | ||
633 | { | ||
634 | Notice("[{0}]", config.Name); | ||
635 | foreach (string key in config.GetKeys()) | ||
636 | Notice(" {0} = {1}", key, config.GetString(key)); | ||
637 | } | ||
638 | else | ||
639 | { | ||
640 | Notice( | ||
641 | "config get {0} {1} : {2}", | ||
642 | cmdparams[1], cmdparams[2], config.GetString(cmdparams[2])); | ||
643 | } | ||
644 | } | ||
645 | } | ||
646 | else | ||
647 | { | ||
648 | Notice("Syntax: config {0} [<section>] [<key>]", firstParam); | ||
649 | Notice("Example: config {0} ScriptEngine.DotNetEngine NumberOfScriptThreads", firstParam); | ||
650 | } | ||
651 | |||
652 | break; | ||
653 | |||
654 | case "save": | ||
655 | if (cmdparams.Length < 2) | ||
656 | { | ||
657 | Notice("Syntax: config save <path>"); | ||
658 | return; | ||
659 | } | ||
660 | |||
661 | string path = cmdparams[1]; | ||
662 | Notice("Saving configuration file: {0}", path); | ||
663 | |||
664 | if (Config is IniConfigSource) | ||
665 | { | ||
666 | IniConfigSource iniCon = (IniConfigSource)Config; | ||
667 | iniCon.Save(path); | ||
668 | } | ||
669 | else if (Config is XmlConfigSource) | ||
670 | { | ||
671 | XmlConfigSource xmlCon = (XmlConfigSource)Config; | ||
672 | xmlCon.Save(path); | ||
673 | } | ||
674 | |||
675 | break; | ||
676 | } | ||
677 | } | ||
678 | } | ||
679 | |||
680 | private void HandleSetLogLevel(string module, string[] cmd) | ||
681 | { | ||
682 | if (cmd.Length != 4) | ||
683 | { | ||
684 | Notice("Usage: set log level <level>"); | ||
685 | return; | ||
686 | } | ||
687 | |||
688 | if (null == m_consoleAppender) | ||
689 | { | ||
690 | Notice("No appender named Console found (see the log4net config file for this executable)!"); | ||
691 | return; | ||
692 | } | ||
693 | |||
694 | string rawLevel = cmd[3]; | ||
695 | |||
696 | ILoggerRepository repository = LogManager.GetRepository(); | ||
697 | Level consoleLevel = repository.LevelMap[rawLevel]; | ||
698 | |||
699 | if (consoleLevel != null) | ||
700 | m_consoleAppender.Threshold = consoleLevel; | ||
701 | else | ||
702 | Notice( | ||
703 | "{0} is not a valid logging level. Valid logging levels are ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF", | ||
704 | rawLevel); | ||
705 | |||
706 | ShowLogLevel(); | ||
707 | } | ||
708 | |||
709 | private void ShowLogLevel() | ||
710 | { | ||
711 | Notice("Console log level is {0}", m_consoleAppender.Threshold); | ||
712 | } | ||
713 | |||
714 | protected virtual void HandleScript(string module, string[] parms) | ||
715 | { | ||
716 | if (parms.Length != 2) | ||
717 | { | ||
718 | Notice("Usage: command-script <path-to-script"); | ||
719 | return; | ||
720 | } | ||
721 | |||
722 | RunCommandScript(parms[1]); | ||
723 | } | ||
724 | |||
725 | /// <summary> | ||
726 | /// Run an optional startup list of commands | ||
727 | /// </summary> | ||
728 | /// <param name="fileName"></param> | ||
729 | protected void RunCommandScript(string fileName) | ||
730 | { | ||
731 | if (m_console == null) | ||
732 | return; | ||
733 | |||
734 | if (File.Exists(fileName)) | ||
735 | { | ||
736 | m_log.Info("[SERVER BASE]: Running " + fileName); | ||
737 | |||
738 | using (StreamReader readFile = File.OpenText(fileName)) | ||
739 | { | ||
740 | string currentCommand; | ||
741 | while ((currentCommand = readFile.ReadLine()) != null) | ||
742 | { | ||
743 | currentCommand = currentCommand.Trim(); | ||
744 | if (!(currentCommand == "" | ||
745 | || currentCommand.StartsWith(";") | ||
746 | || currentCommand.StartsWith("//") | ||
747 | || currentCommand.StartsWith("#"))) | ||
748 | { | ||
749 | m_log.Info("[SERVER BASE]: Running '" + currentCommand + "'"); | ||
750 | m_console.RunCommand(currentCommand); | ||
751 | } | ||
752 | } | ||
753 | } | ||
754 | } | ||
755 | } | ||
756 | |||
757 | /// <summary> | ||
758 | /// Return a report about the uptime of this server | ||
759 | /// </summary> | ||
760 | /// <returns></returns> | ||
761 | protected string GetUptimeReport() | ||
762 | { | ||
763 | StringBuilder sb = new StringBuilder(String.Format("Time now is {0}\n", DateTime.Now)); | ||
764 | sb.Append(String.Format("Server has been running since {0}, {1}\n", m_startuptime.DayOfWeek, m_startuptime)); | ||
765 | sb.Append(String.Format("That is an elapsed time of {0}\n", DateTime.Now - m_startuptime)); | ||
766 | |||
767 | return sb.ToString(); | ||
768 | } | ||
769 | |||
770 | protected void ShowInfo() | ||
771 | { | ||
772 | Notice(GetVersionText()); | ||
773 | Notice("Startup directory: " + m_startupDirectory); | ||
774 | if (null != m_consoleAppender) | ||
775 | Notice(String.Format("Console log level: {0}", m_consoleAppender.Threshold)); | ||
776 | } | ||
777 | |||
778 | /// <summary> | ||
779 | /// Enhance the version string with extra information if it's available. | ||
780 | /// </summary> | ||
781 | protected void EnhanceVersionInformation() | ||
782 | { | ||
783 | string buildVersion = string.Empty; | ||
784 | |||
785 | // The subversion information is deprecated and will be removed at a later date | ||
786 | // Add subversion revision information if available | ||
787 | // Try file "svn_revision" in the current directory first, then the .svn info. | ||
788 | // This allows to make the revision available in simulators not running from the source tree. | ||
789 | // FIXME: Making an assumption about the directory we're currently in - we do this all over the place | ||
790 | // elsewhere as well | ||
791 | string gitDir = "../.git/"; | ||
792 | string gitRefPointerPath = gitDir + "HEAD"; | ||
793 | |||
794 | string svnRevisionFileName = "svn_revision"; | ||
795 | string svnFileName = ".svn/entries"; | ||
796 | string manualVersionFileName = ".version"; | ||
797 | string inputLine; | ||
798 | int strcmp; | ||
799 | |||
800 | if (File.Exists(manualVersionFileName)) | ||
801 | { | ||
802 | using (StreamReader CommitFile = File.OpenText(manualVersionFileName)) | ||
803 | buildVersion = CommitFile.ReadLine(); | ||
804 | |||
805 | m_version += buildVersion ?? ""; | ||
806 | } | ||
807 | else if (File.Exists(gitRefPointerPath)) | ||
808 | { | ||
809 | // m_log.DebugFormat("[SERVER BASE]: Found {0}", gitRefPointerPath); | ||
810 | |||
811 | string rawPointer = ""; | ||
812 | |||
813 | using (StreamReader pointerFile = File.OpenText(gitRefPointerPath)) | ||
814 | rawPointer = pointerFile.ReadLine(); | ||
815 | |||
816 | // m_log.DebugFormat("[SERVER BASE]: rawPointer [{0}]", rawPointer); | ||
817 | |||
818 | Match m = Regex.Match(rawPointer, "^ref: (.+)$"); | ||
819 | |||
820 | if (m.Success) | ||
821 | { | ||
822 | // m_log.DebugFormat("[SERVER BASE]: Matched [{0}]", m.Groups[1].Value); | ||
823 | |||
824 | string gitRef = m.Groups[1].Value; | ||
825 | string gitRefPath = gitDir + gitRef; | ||
826 | if (File.Exists(gitRefPath)) | ||
827 | { | ||
828 | // m_log.DebugFormat("[SERVER BASE]: Found gitRefPath [{0}]", gitRefPath); | ||
829 | |||
830 | using (StreamReader refFile = File.OpenText(gitRefPath)) | ||
831 | { | ||
832 | string gitHash = refFile.ReadLine(); | ||
833 | m_version += gitHash.Substring(0, 7); | ||
834 | } | ||
835 | } | ||
836 | } | ||
837 | } | ||
838 | else | ||
839 | { | ||
840 | // Remove the else logic when subversion mirror is no longer used | ||
841 | if (File.Exists(svnRevisionFileName)) | ||
842 | { | ||
843 | StreamReader RevisionFile = File.OpenText(svnRevisionFileName); | ||
844 | buildVersion = RevisionFile.ReadLine(); | ||
845 | buildVersion.Trim(); | ||
846 | RevisionFile.Close(); | ||
847 | } | ||
848 | |||
849 | if (string.IsNullOrEmpty(buildVersion) && File.Exists(svnFileName)) | ||
850 | { | ||
851 | StreamReader EntriesFile = File.OpenText(svnFileName); | ||
852 | inputLine = EntriesFile.ReadLine(); | ||
853 | while (inputLine != null) | ||
854 | { | ||
855 | // using the dir svn revision at the top of entries file | ||
856 | strcmp = String.Compare(inputLine, "dir"); | ||
857 | if (strcmp == 0) | ||
858 | { | ||
859 | buildVersion = EntriesFile.ReadLine(); | ||
860 | break; | ||
861 | } | ||
862 | else | ||
863 | { | ||
864 | inputLine = EntriesFile.ReadLine(); | ||
865 | } | ||
866 | } | ||
867 | EntriesFile.Close(); | ||
868 | } | ||
869 | |||
870 | m_version += string.IsNullOrEmpty(buildVersion) ? " " : ("." + buildVersion + " ").Substring(0, 6); | ||
871 | } | ||
872 | } | ||
873 | |||
874 | protected string GetVersionText() | ||
875 | { | ||
876 | return String.Format("Version: {0} (interface version {1})", m_version, VersionInfo.MajorInterfaceVersion); | ||
877 | } | ||
878 | |||
879 | /// <summary> | ||
880 | /// Get a report about the registered threads in this server. | ||
881 | /// </summary> | ||
882 | protected string GetThreadsReport() | ||
883 | { | ||
884 | // This should be a constant field. | ||
885 | string reportFormat = "{0,6} {1,35} {2,16} {3,13} {4,10} {5,30}"; | ||
886 | |||
887 | StringBuilder sb = new StringBuilder(); | ||
888 | Watchdog.ThreadWatchdogInfo[] threads = Watchdog.GetThreadsInfo(); | ||
889 | |||
890 | sb.Append(threads.Length + " threads are being tracked:" + Environment.NewLine); | ||
891 | |||
892 | int timeNow = Environment.TickCount & Int32.MaxValue; | ||
893 | |||
894 | sb.AppendFormat(reportFormat, "ID", "NAME", "LAST UPDATE (MS)", "LIFETIME (MS)", "PRIORITY", "STATE"); | ||
895 | sb.Append(Environment.NewLine); | ||
896 | |||
897 | foreach (Watchdog.ThreadWatchdogInfo twi in threads) | ||
898 | { | ||
899 | Thread t = twi.Thread; | ||
900 | |||
901 | sb.AppendFormat( | ||
902 | reportFormat, | ||
903 | t.ManagedThreadId, | ||
904 | t.Name, | ||
905 | timeNow - twi.LastTick, | ||
906 | timeNow - twi.FirstTick, | ||
907 | t.Priority, | ||
908 | t.ThreadState); | ||
909 | |||
910 | sb.Append("\n"); | ||
911 | } | ||
912 | |||
913 | sb.Append("\n"); | ||
914 | |||
915 | // For some reason mono 2.6.7 returns an empty threads set! Not going to confuse people by reporting | ||
916 | // zero active threads. | ||
917 | int totalThreads = Process.GetCurrentProcess().Threads.Count; | ||
918 | if (totalThreads > 0) | ||
919 | sb.AppendFormat("Total threads active: {0}\n\n", totalThreads); | ||
920 | |||
921 | sb.Append("Main threadpool (excluding script engine pools)\n"); | ||
922 | sb.Append(GetThreadPoolReport()); | ||
923 | |||
924 | return sb.ToString(); | ||
925 | } | ||
926 | |||
927 | /// <summary> | ||
928 | /// Get a thread pool report. | ||
929 | /// </summary> | ||
930 | /// <returns></returns> | ||
931 | public static string GetThreadPoolReport() | ||
932 | { | ||
933 | string threadPoolUsed = null; | ||
934 | int maxThreads = 0; | ||
935 | int minThreads = 0; | ||
936 | int allocatedThreads = 0; | ||
937 | int inUseThreads = 0; | ||
938 | int waitingCallbacks = 0; | ||
939 | int completionPortThreads = 0; | ||
940 | |||
941 | StringBuilder sb = new StringBuilder(); | ||
942 | if (Util.FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool) | ||
943 | { | ||
944 | STPInfo stpi = Util.GetSmartThreadPoolInfo(); | ||
945 | |||
946 | // ROBUST currently leaves this the FireAndForgetMethod but never actually initializes the threadpool. | ||
947 | if (stpi != null) | ||
948 | { | ||
949 | threadPoolUsed = "SmartThreadPool"; | ||
950 | maxThreads = stpi.MaxThreads; | ||
951 | minThreads = stpi.MinThreads; | ||
952 | inUseThreads = stpi.InUseThreads; | ||
953 | allocatedThreads = stpi.ActiveThreads; | ||
954 | waitingCallbacks = stpi.WaitingCallbacks; | ||
955 | } | ||
956 | } | ||
957 | else if ( | ||
958 | Util.FireAndForgetMethod == FireAndForgetMethod.QueueUserWorkItem | ||
959 | || Util.FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem) | ||
960 | { | ||
961 | threadPoolUsed = "BuiltInThreadPool"; | ||
962 | ThreadPool.GetMaxThreads(out maxThreads, out completionPortThreads); | ||
963 | ThreadPool.GetMinThreads(out minThreads, out completionPortThreads); | ||
964 | int availableThreads; | ||
965 | ThreadPool.GetAvailableThreads(out availableThreads, out completionPortThreads); | ||
966 | inUseThreads = maxThreads - availableThreads; | ||
967 | allocatedThreads = -1; | ||
968 | waitingCallbacks = -1; | ||
969 | } | ||
970 | |||
971 | if (threadPoolUsed != null) | ||
972 | { | ||
973 | sb.AppendFormat("Thread pool used : {0}\n", threadPoolUsed); | ||
974 | sb.AppendFormat("Max threads : {0}\n", maxThreads); | ||
975 | sb.AppendFormat("Min threads : {0}\n", minThreads); | ||
976 | sb.AppendFormat("Allocated threads : {0}\n", allocatedThreads < 0 ? "not applicable" : allocatedThreads.ToString()); | ||
977 | sb.AppendFormat("In use threads : {0}\n", inUseThreads); | ||
978 | sb.AppendFormat("Work items waiting : {0}\n", waitingCallbacks < 0 ? "not available" : waitingCallbacks.ToString()); | ||
979 | } | ||
980 | else | ||
981 | { | ||
982 | sb.AppendFormat("Thread pool not used\n"); | ||
983 | } | ||
984 | |||
985 | return sb.ToString(); | ||
986 | } | ||
987 | |||
988 | public virtual void HandleThreadsAbort(string module, string[] cmd) | ||
989 | { | ||
990 | if (cmd.Length != 3) | ||
991 | { | ||
992 | MainConsole.Instance.Output("Usage: threads abort <thread-id>"); | ||
993 | return; | ||
994 | } | ||
995 | |||
996 | int threadId; | ||
997 | if (!int.TryParse(cmd[2], out threadId)) | ||
998 | { | ||
999 | MainConsole.Instance.Output("ERROR: Thread id must be an integer"); | ||
1000 | return; | ||
1001 | } | ||
1002 | |||
1003 | if (Watchdog.AbortThread(threadId)) | ||
1004 | MainConsole.Instance.OutputFormat("Aborted thread with id {0}", threadId); | ||
1005 | else | ||
1006 | MainConsole.Instance.OutputFormat("ERROR - Thread with id {0} not found in managed threads", threadId); | ||
1007 | } | ||
1008 | |||
1009 | /// <summary> | ||
1010 | /// Console output is only possible if a console has been established. | ||
1011 | /// That is something that cannot be determined within this class. So | ||
1012 | /// all attempts to use the console MUST be verified. | ||
1013 | /// </summary> | ||
1014 | /// <param name="msg"></param> | ||
1015 | protected void Notice(string msg) | ||
1016 | { | ||
1017 | if (m_console != null) | ||
1018 | { | ||
1019 | m_console.Output(msg); | ||
1020 | } | ||
1021 | } | ||
1022 | |||
1023 | /// <summary> | ||
1024 | /// Console output is only possible if a console has been established. | ||
1025 | /// That is something that cannot be determined within this class. So | ||
1026 | /// all attempts to use the console MUST be verified. | ||
1027 | /// </summary> | ||
1028 | /// <param name="format"></param> | ||
1029 | /// <param name="components"></param> | ||
1030 | protected void Notice(string format, params object[] components) | ||
1031 | { | ||
1032 | if (m_console != null) | ||
1033 | m_console.OutputFormat(format, components); | ||
1034 | } | ||
1035 | |||
1036 | public virtual void Shutdown() | ||
1037 | { | ||
1038 | m_serverStatsCollector.Close(); | ||
1039 | ShutdownSpecific(); | ||
1040 | } | ||
1041 | |||
1042 | /// <summary> | ||
1043 | /// Should be overriden and referenced by descendents if they need to perform extra shutdown processing | ||
1044 | /// </summary> | ||
1045 | protected virtual void ShutdownSpecific() {} | ||
1046 | } | ||
1047 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/Tests/OSHttpTests.cs b/OpenSim/Framework/Servers/Tests/OSHttpTests.cs new file mode 100644 index 0000000..5c0e0df --- /dev/null +++ b/OpenSim/Framework/Servers/Tests/OSHttpTests.cs | |||
@@ -0,0 +1,116 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Specialized; | ||
30 | using System.IO; | ||
31 | using System.Net; | ||
32 | using System.Net.Sockets; | ||
33 | using System.Text; | ||
34 | using HttpServer; | ||
35 | using HttpServer.FormDecoders; | ||
36 | using NUnit.Framework; | ||
37 | using OpenSim.Framework.Servers.HttpServer; | ||
38 | using OpenSim.Tests.Common; | ||
39 | |||
40 | namespace OpenSim.Framework.Servers.Tests | ||
41 | { | ||
42 | [TestFixture] | ||
43 | public class OSHttpTests : OpenSimTestCase | ||
44 | { | ||
45 | public OSHttpRequest req0; | ||
46 | public OSHttpRequest req1; | ||
47 | |||
48 | public OSHttpResponse rsp0; | ||
49 | |||
50 | public IPEndPoint ipEP0; | ||
51 | |||
52 | [TestFixtureSetUp] | ||
53 | public void Init() | ||
54 | { | ||
55 | TestHttpRequest threq0 = new TestHttpRequest("utf-8", "text/xml", "OpenSim Test Agent", "192.168.0.1", "4711", | ||
56 | new string[] {"text/xml"}, | ||
57 | ConnectionType.KeepAlive, 4711, | ||
58 | new Uri("http://127.0.0.1/admin/inventory/Dr+Who/Tardis")); | ||
59 | threq0.Method = "GET"; | ||
60 | threq0.HttpVersion = HttpHelper.HTTP10; | ||
61 | |||
62 | TestHttpRequest threq1 = new TestHttpRequest("utf-8", "text/xml", "OpenSim Test Agent", "192.168.0.1", "4711", | ||
63 | new string[] {"text/xml"}, | ||
64 | ConnectionType.KeepAlive, 4711, | ||
65 | new Uri("http://127.0.0.1/admin/inventory/Dr+Who/Tardis?a=0&b=1&c=2")); | ||
66 | threq1.Method = "POST"; | ||
67 | threq1.HttpVersion = HttpHelper.HTTP11; | ||
68 | threq1.Headers["x-wuff"] = "wuffwuff"; | ||
69 | threq1.Headers["www-authenticate"] = "go away"; | ||
70 | |||
71 | req0 = new OSHttpRequest(new TestHttpClientContext(false), threq0); | ||
72 | req1 = new OSHttpRequest(new TestHttpClientContext(false), threq1); | ||
73 | |||
74 | rsp0 = new OSHttpResponse(new TestHttpResponse()); | ||
75 | |||
76 | ipEP0 = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 4711); | ||
77 | |||
78 | } | ||
79 | |||
80 | [Test] | ||
81 | public void T000_OSHttpRequest() | ||
82 | { | ||
83 | Assert.That(req0.HttpMethod, Is.EqualTo("GET")); | ||
84 | Assert.That(req0.ContentType, Is.EqualTo("text/xml")); | ||
85 | Assert.That(req0.ContentLength, Is.EqualTo(4711)); | ||
86 | |||
87 | Assert.That(req1.HttpMethod, Is.EqualTo("POST")); | ||
88 | } | ||
89 | |||
90 | [Test] | ||
91 | public void T001_OSHttpRequestHeaderAccess() | ||
92 | { | ||
93 | Assert.That(req1.Headers["x-wuff"], Is.EqualTo("wuffwuff")); | ||
94 | Assert.That(req1.Headers.Get("x-wuff"), Is.EqualTo("wuffwuff")); | ||
95 | |||
96 | Assert.That(req1.Headers["www-authenticate"], Is.EqualTo("go away")); | ||
97 | Assert.That(req1.Headers.Get("www-authenticate"), Is.EqualTo("go away")); | ||
98 | |||
99 | Assert.That(req0.RemoteIPEndPoint, Is.EqualTo(ipEP0)); | ||
100 | } | ||
101 | |||
102 | [Test] | ||
103 | public void T002_OSHttpRequestUriParsing() | ||
104 | { | ||
105 | Assert.That(req0.RawUrl, Is.EqualTo("/admin/inventory/Dr+Who/Tardis")); | ||
106 | Assert.That(req1.Url.ToString(), Is.EqualTo("http://127.0.0.1/admin/inventory/Dr+Who/Tardis?a=0&b=1&c=2")); | ||
107 | } | ||
108 | |||
109 | [Test] | ||
110 | public void T100_OSHttpResponse() | ||
111 | { | ||
112 | rsp0.ContentType = "text/xml"; | ||
113 | Assert.That(rsp0.ContentType, Is.EqualTo("text/xml")); | ||
114 | } | ||
115 | } | ||
116 | } \ No newline at end of file | ||
diff --git a/OpenSim/Framework/Servers/Tests/VersionInfoTests.cs b/OpenSim/Framework/Servers/Tests/VersionInfoTests.cs new file mode 100644 index 0000000..480f2bb --- /dev/null +++ b/OpenSim/Framework/Servers/Tests/VersionInfoTests.cs | |||
@@ -0,0 +1,54 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Text; | ||
31 | using NUnit.Framework; | ||
32 | using OpenSim.Tests.Common; | ||
33 | |||
34 | namespace OpenSim.Framework.Servers.Tests | ||
35 | { | ||
36 | [TestFixture] | ||
37 | public class VersionInfoTests : OpenSimTestCase | ||
38 | { | ||
39 | [Test] | ||
40 | public void TestVersionLength() | ||
41 | { | ||
42 | Assert.AreEqual(VersionInfo.VERSIONINFO_VERSION_LENGTH, VersionInfo.Version.Length," VersionInfo.Version string not " + VersionInfo.VERSIONINFO_VERSION_LENGTH + " chars."); | ||
43 | } | ||
44 | |||
45 | [Test] | ||
46 | public void TestGetVersionStringLength() | ||
47 | { | ||
48 | foreach (VersionInfo.Flavour flavour in Enum.GetValues(typeof(VersionInfo.Flavour))) | ||
49 | { | ||
50 | Assert.AreEqual(VersionInfo.VERSIONINFO_VERSION_LENGTH, VersionInfo.GetVersionString("0.0.0", flavour).Length, "0.0.0/" + flavour + " failed"); | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | } | ||