clockwerk-opensim-stable – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 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 | |||
50 | namespace OpenSim.Framework.Servers.HttpServer |
||
51 | { |
||
52 | public class BaseHttpServer : IHttpServer |
||
53 | { |
||
54 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
55 | private HttpServerLogWriter httpserverlog = new HttpServerLogWriter(); |
||
56 | |||
57 | /// <summary> |
||
58 | /// This is a pending websocket request before it got an sucessful upgrade response. |
||
59 | /// The consumer must call handler.HandshakeAndUpgrade() to signal to the handler to |
||
60 | /// start the connection and optionally provide an origin authentication method. |
||
61 | /// </summary> |
||
62 | /// <param name="servicepath"></param> |
||
63 | /// <param name="handler"></param> |
||
64 | public delegate void WebSocketRequestDelegate(string servicepath, WebSocketHttpServerHandler handler); |
||
65 | |||
66 | /// <summary> |
||
67 | /// Gets or sets the debug level. |
||
68 | /// </summary> |
||
69 | /// <value> |
||
70 | /// See MainServer.DebugLevel. |
||
71 | /// </value> |
||
72 | public int DebugLevel { get; set; } |
||
73 | |||
74 | /// <summary> |
||
75 | /// Request number for diagnostic purposes. |
||
76 | /// </summary> |
||
77 | /// <remarks> |
||
78 | /// This is an internal number. In some debug situations an external number may also be supplied in the |
||
79 | /// opensim-request-id header but we are not currently logging this. |
||
80 | /// </remarks> |
||
81 | public int RequestNumber { get; private set; } |
||
82 | |||
83 | /// <summary> |
||
84 | /// Statistic for holding number of requests processed. |
||
85 | /// </summary> |
||
86 | private Stat m_requestsProcessedStat; |
||
87 | |||
88 | private volatile int NotSocketErrors = 0; |
||
89 | public volatile bool HTTPDRunning = false; |
||
90 | |||
91 | // protected HttpListener m_httpListener; |
||
92 | protected CoolHTTPListener m_httpListener2; |
||
93 | protected Dictionary<string, XmlRpcMethod> m_rpcHandlers = new Dictionary<string, XmlRpcMethod>(); |
||
94 | protected Dictionary<string, JsonRPCMethod> jsonRpcHandlers = new Dictionary<string, JsonRPCMethod>(); |
||
95 | protected Dictionary<string, bool> m_rpcHandlersKeepAlive = new Dictionary<string, bool>(); |
||
96 | protected DefaultLLSDMethod m_defaultLlsdHandler = null; // <-- Moving away from the monolithic.. and going to /registered/ |
||
97 | protected Dictionary<string, LLSDMethod> m_llsdHandlers = new Dictionary<string, LLSDMethod>(); |
||
98 | protected Dictionary<string, IRequestHandler> m_streamHandlers = new Dictionary<string, IRequestHandler>(); |
||
99 | protected Dictionary<string, GenericHTTPMethod> m_HTTPHandlers = new Dictionary<string, GenericHTTPMethod>(); |
||
100 | // protected Dictionary<string, IHttpAgentHandler> m_agentHandlers = new Dictionary<string, IHttpAgentHandler>(); |
||
101 | protected Dictionary<string, PollServiceEventArgs> m_pollHandlers = |
||
102 | new Dictionary<string, PollServiceEventArgs>(); |
||
103 | |||
104 | protected Dictionary<string, WebSocketRequestDelegate> m_WebSocketHandlers = |
||
105 | new Dictionary<string, WebSocketRequestDelegate>(); |
||
106 | |||
107 | protected uint m_port; |
||
108 | protected uint m_sslport; |
||
109 | protected bool m_ssl; |
||
110 | private X509Certificate2 m_cert; |
||
111 | protected bool m_firstcaps = true; |
||
112 | protected string m_SSLCommonName = ""; |
||
113 | |||
114 | protected IPAddress m_listenIPAddress = IPAddress.Any; |
||
115 | |||
116 | private PollServiceRequestManager m_PollServiceManager; |
||
117 | |||
118 | public uint SSLPort |
||
119 | { |
||
120 | get { return m_sslport; } |
||
121 | } |
||
122 | |||
123 | public string SSLCommonName |
||
124 | { |
||
125 | get { return m_SSLCommonName; } |
||
126 | } |
||
127 | |||
128 | public uint Port |
||
129 | { |
||
130 | get { return m_port; } |
||
131 | } |
||
132 | |||
133 | public bool UseSSL |
||
134 | { |
||
135 | get { return m_ssl; } |
||
136 | } |
||
137 | |||
138 | public IPAddress ListenIPAddress |
||
139 | { |
||
140 | get { return m_listenIPAddress; } |
||
141 | set { m_listenIPAddress = value; } |
||
142 | } |
||
143 | |||
144 | public BaseHttpServer(uint port) |
||
145 | { |
||
146 | m_port = port; |
||
147 | } |
||
148 | |||
149 | public BaseHttpServer(uint port, bool ssl) : this (port) |
||
150 | { |
||
151 | m_ssl = ssl; |
||
152 | } |
||
153 | |||
154 | public BaseHttpServer(uint port, bool ssl, uint sslport, string CN) : this (port, ssl) |
||
155 | { |
||
156 | if (m_ssl) |
||
157 | { |
||
158 | m_sslport = sslport; |
||
159 | } |
||
160 | } |
||
161 | |||
162 | public BaseHttpServer(uint port, bool ssl, string CPath, string CPass) : this (port, ssl) |
||
163 | { |
||
164 | if (m_ssl) |
||
165 | { |
||
166 | m_cert = new X509Certificate2(CPath, CPass); |
||
167 | } |
||
168 | } |
||
169 | |||
170 | /// <summary> |
||
171 | /// Add a stream handler to the http server. If the handler already exists, then nothing happens. |
||
172 | /// </summary> |
||
173 | /// <param name="handler"></param> |
||
174 | public void AddStreamHandler(IRequestHandler handler) |
||
175 | { |
||
176 | string httpMethod = handler.HttpMethod; |
||
177 | string path = handler.Path; |
||
178 | string handlerKey = GetHandlerKey(httpMethod, path); |
||
179 | |||
180 | lock (m_streamHandlers) |
||
181 | { |
||
182 | if (!m_streamHandlers.ContainsKey(handlerKey)) |
||
183 | { |
||
184 | // m_log.DebugFormat("[BASE HTTP SERVER]: Adding handler key {0}", handlerKey); |
||
185 | m_streamHandlers.Add(handlerKey, handler); |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | |||
190 | public void AddWebSocketHandler(string servicepath, WebSocketRequestDelegate handler) |
||
191 | { |
||
192 | lock (m_WebSocketHandlers) |
||
193 | { |
||
194 | if (!m_WebSocketHandlers.ContainsKey(servicepath)) |
||
195 | m_WebSocketHandlers.Add(servicepath, handler); |
||
196 | } |
||
197 | } |
||
198 | |||
199 | public void RemoveWebSocketHandler(string servicepath) |
||
200 | { |
||
201 | lock (m_WebSocketHandlers) |
||
202 | if (m_WebSocketHandlers.ContainsKey(servicepath)) |
||
203 | m_WebSocketHandlers.Remove(servicepath); |
||
204 | } |
||
205 | |||
206 | public List<string> GetStreamHandlerKeys() |
||
207 | { |
||
208 | lock (m_streamHandlers) |
||
209 | return new List<string>(m_streamHandlers.Keys); |
||
210 | } |
||
211 | |||
212 | private static string GetHandlerKey(string httpMethod, string path) |
||
213 | { |
||
214 | return httpMethod + ":" + path; |
||
215 | } |
||
216 | |||
217 | public bool AddXmlRPCHandler(string method, XmlRpcMethod handler) |
||
218 | { |
||
219 | return AddXmlRPCHandler(method, handler, true); |
||
220 | } |
||
221 | |||
222 | public bool AddXmlRPCHandler(string method, XmlRpcMethod handler, bool keepAlive) |
||
223 | { |
||
224 | lock (m_rpcHandlers) |
||
225 | { |
||
226 | m_rpcHandlers[method] = handler; |
||
227 | m_rpcHandlersKeepAlive[method] = keepAlive; // default |
||
228 | } |
||
229 | |||
230 | return true; |
||
231 | } |
||
232 | |||
233 | public XmlRpcMethod GetXmlRPCHandler(string method) |
||
234 | { |
||
235 | lock (m_rpcHandlers) |
||
236 | { |
||
237 | if (m_rpcHandlers.ContainsKey(method)) |
||
238 | { |
||
239 | return m_rpcHandlers[method]; |
||
240 | } |
||
241 | else |
||
242 | { |
||
243 | return null; |
||
244 | } |
||
245 | } |
||
246 | } |
||
247 | |||
248 | public List<string> GetXmlRpcHandlerKeys() |
||
249 | { |
||
250 | lock (m_rpcHandlers) |
||
251 | return new List<string>(m_rpcHandlers.Keys); |
||
252 | } |
||
253 | |||
254 | // JsonRPC |
||
255 | public bool AddJsonRPCHandler(string method, JsonRPCMethod handler) |
||
256 | { |
||
257 | lock(jsonRpcHandlers) |
||
258 | { |
||
259 | jsonRpcHandlers.Add(method, handler); |
||
260 | } |
||
261 | return true; |
||
262 | } |
||
263 | |||
264 | public JsonRPCMethod GetJsonRPCHandler(string method) |
||
265 | { |
||
266 | lock (jsonRpcHandlers) |
||
267 | { |
||
268 | if (jsonRpcHandlers.ContainsKey(method)) |
||
269 | { |
||
270 | return jsonRpcHandlers[method]; |
||
271 | } |
||
272 | else |
||
273 | { |
||
274 | return null; |
||
275 | } |
||
276 | } |
||
277 | } |
||
278 | |||
279 | public List<string> GetJsonRpcHandlerKeys() |
||
280 | { |
||
281 | lock (jsonRpcHandlers) |
||
282 | return new List<string>(jsonRpcHandlers.Keys); |
||
283 | } |
||
284 | |||
285 | public bool AddHTTPHandler(string methodName, GenericHTTPMethod handler) |
||
286 | { |
||
287 | //m_log.DebugFormat("[BASE HTTP SERVER]: Registering {0}", methodName); |
||
288 | |||
289 | lock (m_HTTPHandlers) |
||
290 | { |
||
291 | if (!m_HTTPHandlers.ContainsKey(methodName)) |
||
292 | { |
||
293 | m_HTTPHandlers.Add(methodName, handler); |
||
294 | return true; |
||
295 | } |
||
296 | } |
||
297 | |||
298 | //must already have a handler for that path so return false |
||
299 | return false; |
||
300 | } |
||
301 | |||
302 | public List<string> GetHTTPHandlerKeys() |
||
303 | { |
||
304 | lock (m_HTTPHandlers) |
||
305 | return new List<string>(m_HTTPHandlers.Keys); |
||
306 | } |
||
307 | |||
308 | public bool AddPollServiceHTTPHandler(string methodName, PollServiceEventArgs args) |
||
309 | { |
||
310 | lock (m_pollHandlers) |
||
311 | { |
||
312 | if (!m_pollHandlers.ContainsKey(methodName)) |
||
313 | { |
||
314 | m_pollHandlers.Add(methodName, args); |
||
315 | return true; |
||
316 | } |
||
317 | } |
||
318 | |||
319 | return false; |
||
320 | } |
||
321 | |||
322 | public List<string> GetPollServiceHandlerKeys() |
||
323 | { |
||
324 | lock (m_pollHandlers) |
||
325 | return new List<string>(m_pollHandlers.Keys); |
||
326 | } |
||
327 | |||
328 | // // Note that the agent string is provided simply to differentiate |
||
329 | // // the handlers - it is NOT required to be an actual agent header |
||
330 | // // value. |
||
331 | // public bool AddAgentHandler(string agent, IHttpAgentHandler handler) |
||
332 | // { |
||
333 | // lock (m_agentHandlers) |
||
334 | // { |
||
335 | // if (!m_agentHandlers.ContainsKey(agent)) |
||
336 | // { |
||
337 | // m_agentHandlers.Add(agent, handler); |
||
338 | // return true; |
||
339 | // } |
||
340 | // } |
||
341 | // |
||
342 | // //must already have a handler for that path so return false |
||
343 | // return false; |
||
344 | // } |
||
345 | // |
||
346 | // public List<string> GetAgentHandlerKeys() |
||
347 | // { |
||
348 | // lock (m_agentHandlers) |
||
349 | // return new List<string>(m_agentHandlers.Keys); |
||
350 | // } |
||
351 | |||
352 | public bool AddLLSDHandler(string path, LLSDMethod handler) |
||
353 | { |
||
354 | lock (m_llsdHandlers) |
||
355 | { |
||
356 | if (!m_llsdHandlers.ContainsKey(path)) |
||
357 | { |
||
358 | m_llsdHandlers.Add(path, handler); |
||
359 | return true; |
||
360 | } |
||
361 | } |
||
362 | return false; |
||
363 | } |
||
364 | |||
365 | public List<string> GetLLSDHandlerKeys() |
||
366 | { |
||
367 | lock (m_llsdHandlers) |
||
368 | return new List<string>(m_llsdHandlers.Keys); |
||
369 | } |
||
370 | |||
371 | public bool SetDefaultLLSDHandler(DefaultLLSDMethod handler) |
||
372 | { |
||
373 | m_defaultLlsdHandler = handler; |
||
374 | return true; |
||
375 | } |
||
376 | |||
377 | private void OnRequest(object source, RequestEventArgs args) |
||
378 | { |
||
379 | RequestNumber++; |
||
380 | |||
381 | try |
||
382 | { |
||
383 | IHttpClientContext context = (IHttpClientContext)source; |
||
384 | IHttpRequest request = args.Request; |
||
385 | |||
386 | PollServiceEventArgs psEvArgs; |
||
387 | |||
388 | if (TryGetPollServiceHTTPHandler(request.UriPath.ToString(), out psEvArgs)) |
||
389 | { |
||
390 | psEvArgs.RequestsReceived++; |
||
391 | |||
392 | PollServiceHttpRequest psreq = new PollServiceHttpRequest(psEvArgs, context, request); |
||
393 | |||
394 | if (psEvArgs.Request != null) |
||
395 | { |
||
396 | OSHttpRequest req = new OSHttpRequest(context, request); |
||
397 | |||
398 | Stream requestStream = req.InputStream; |
||
399 | |||
400 | Encoding encoding = Encoding.UTF8; |
||
401 | StreamReader reader = new StreamReader(requestStream, encoding); |
||
402 | |||
403 | string requestBody = reader.ReadToEnd(); |
||
404 | |||
405 | Hashtable keysvals = new Hashtable(); |
||
406 | Hashtable headervals = new Hashtable(); |
||
407 | |||
408 | string[] querystringkeys = req.QueryString.AllKeys; |
||
409 | string[] rHeaders = req.Headers.AllKeys; |
||
410 | |||
411 | keysvals.Add("body", requestBody); |
||
412 | keysvals.Add("uri", req.RawUrl); |
||
413 | keysvals.Add("content-type", req.ContentType); |
||
414 | keysvals.Add("http-method", req.HttpMethod); |
||
415 | |||
416 | foreach (string queryname in querystringkeys) |
||
417 | { |
||
418 | keysvals.Add(queryname, req.QueryString[queryname]); |
||
419 | } |
||
420 | |||
421 | foreach (string headername in rHeaders) |
||
422 | { |
||
423 | headervals[headername] = req.Headers[headername]; |
||
424 | } |
||
425 | |||
426 | keysvals.Add("headers", headervals); |
||
427 | keysvals.Add("querystringkeys", querystringkeys); |
||
428 | |||
429 | psEvArgs.Request(psreq.RequestID, keysvals); |
||
430 | } |
||
431 | |||
432 | m_PollServiceManager.Enqueue(psreq); |
||
433 | } |
||
434 | else |
||
435 | { |
||
436 | OnHandleRequestIOThread(context, request); |
||
437 | } |
||
438 | } |
||
439 | catch (Exception e) |
||
440 | { |
||
441 | m_log.Error(String.Format("[BASE HTTP SERVER]: OnRequest() failed: {0} ", e.Message), e); |
||
442 | } |
||
443 | } |
||
444 | |||
445 | private void OnHandleRequestIOThread(IHttpClientContext context, IHttpRequest request) |
||
446 | { |
||
447 | OSHttpRequest req = new OSHttpRequest(context, request); |
||
448 | WebSocketRequestDelegate dWebSocketRequestDelegate = null; |
||
449 | lock (m_WebSocketHandlers) |
||
450 | { |
||
451 | if (m_WebSocketHandlers.ContainsKey(req.RawUrl)) |
||
452 | dWebSocketRequestDelegate = m_WebSocketHandlers[req.RawUrl]; |
||
453 | } |
||
454 | if (dWebSocketRequestDelegate != null) |
||
455 | { |
||
456 | dWebSocketRequestDelegate(req.Url.AbsolutePath, new WebSocketHttpServerHandler(req, context, 8192)); |
||
457 | return; |
||
458 | } |
||
459 | |||
460 | OSHttpResponse resp = new OSHttpResponse(new HttpResponse(context, request),context); |
||
461 | resp.ReuseContext = true; |
||
462 | HandleRequest(req, resp); |
||
463 | |||
464 | // !!!HACK ALERT!!! |
||
465 | // There seems to be a bug in the underlying http code that makes subsequent requests |
||
466 | // come up with trash in Accept headers. Until that gets fixed, we're cleaning them up here. |
||
467 | if (request.AcceptTypes != null) |
||
468 | for (int i = 0; i < request.AcceptTypes.Length; i++) |
||
469 | request.AcceptTypes[i] = string.Empty; |
||
470 | } |
||
471 | |||
472 | // public void ConvertIHttpClientContextToOSHttp(object stateinfo) |
||
473 | // { |
||
474 | // HttpServerContextObj objstate = (HttpServerContextObj)stateinfo; |
||
475 | |||
476 | // OSHttpRequest request = objstate.oreq; |
||
477 | // OSHttpResponse resp = objstate.oresp; |
||
478 | |||
479 | // HandleRequest(request,resp); |
||
480 | // } |
||
481 | |||
482 | /// <summary> |
||
483 | /// This methods is the start of incoming HTTP request handling. |
||
484 | /// </summary> |
||
485 | /// <param name="request"></param> |
||
486 | /// <param name="response"></param> |
||
487 | public virtual void HandleRequest(OSHttpRequest request, OSHttpResponse response) |
||
488 | { |
||
489 | if (request.HttpMethod == String.Empty) // Can't handle empty requests, not wasting a thread |
||
490 | { |
||
491 | try |
||
492 | { |
||
493 | byte[] buffer500 = SendHTML500(response); |
||
494 | response.Body.Write(buffer500,0,buffer500.Length); |
||
495 | response.Body.Close(); |
||
496 | } |
||
497 | catch |
||
498 | { |
||
499 | } |
||
500 | |||
501 | return; |
||
502 | } |
||
503 | |||
504 | string requestMethod = request.HttpMethod; |
||
505 | string uriString = request.RawUrl; |
||
506 | |||
507 | int requestStartTick = Environment.TickCount; |
||
508 | |||
509 | // Will be adjusted later on. |
||
510 | int requestEndTick = requestStartTick; |
||
511 | |||
512 | IRequestHandler requestHandler = null; |
||
513 | |||
514 | try |
||
515 | { |
||
516 | // OpenSim.Framework.WebUtil.OSHeaderRequestID |
||
517 | // if (request.Headers["opensim-request-id"] != null) |
||
518 | // reqnum = String.Format("{0}:{1}",request.RemoteIPEndPoint,request.Headers["opensim-request-id"]); |
||
519 | //m_log.DebugFormat("[BASE HTTP SERVER]: <{0}> handle request for {1}",reqnum,request.RawUrl); |
||
520 | |||
521 | Culture.SetCurrentCulture(); |
||
522 | |||
523 | // // This is the REST agent interface. We require an agent to properly identify |
||
524 | // // itself. If the REST handler recognizes the prefix it will attempt to |
||
525 | // // satisfy the request. If it is not recognizable, and no damage has occurred |
||
526 | // // the request can be passed through to the other handlers. This is a low |
||
527 | // // probability event; if a request is matched it is normally expected to be |
||
528 | // // handled |
||
529 | // IHttpAgentHandler agentHandler; |
||
530 | // |
||
531 | // if (TryGetAgentHandler(request, response, out agentHandler)) |
||
532 | // { |
||
533 | // if (HandleAgentRequest(agentHandler, request, response)) |
||
534 | // { |
||
535 | // requestEndTick = Environment.TickCount; |
||
536 | // return; |
||
537 | // } |
||
538 | // } |
||
539 | |||
540 | //response.KeepAlive = true; |
||
541 | response.SendChunked = false; |
||
542 | |||
543 | string path = request.RawUrl; |
||
544 | string handlerKey = GetHandlerKey(request.HttpMethod, path); |
||
545 | byte[] buffer = null; |
||
546 | |||
547 | if (TryGetStreamHandler(handlerKey, out requestHandler)) |
||
548 | { |
||
549 | if (DebugLevel >= 3) |
||
550 | LogIncomingToStreamHandler(request, requestHandler); |
||
551 | |||
552 | response.ContentType = requestHandler.ContentType; // Lets do this defaulting before in case handler has varying content type. |
||
553 | |||
554 | if (requestHandler is IStreamedRequestHandler) |
||
555 | { |
||
556 | IStreamedRequestHandler streamedRequestHandler = requestHandler as IStreamedRequestHandler; |
||
557 | |||
558 | buffer = streamedRequestHandler.Handle(path, request.InputStream, request, response); |
||
559 | } |
||
560 | else if (requestHandler is IGenericHTTPHandler) |
||
561 | { |
||
562 | //m_log.Debug("[BASE HTTP SERVER]: Found Caps based HTTP Handler"); |
||
563 | IGenericHTTPHandler HTTPRequestHandler = requestHandler as IGenericHTTPHandler; |
||
564 | Stream requestStream = request.InputStream; |
||
565 | |||
566 | Encoding encoding = Encoding.UTF8; |
||
567 | StreamReader reader = new StreamReader(requestStream, encoding); |
||
568 | |||
569 | string requestBody = reader.ReadToEnd(); |
||
570 | |||
571 | reader.Close(); |
||
572 | //requestStream.Close(); |
||
573 | |||
574 | Hashtable keysvals = new Hashtable(); |
||
575 | Hashtable headervals = new Hashtable(); |
||
576 | //string host = String.Empty; |
||
577 | |||
578 | string[] querystringkeys = request.QueryString.AllKeys; |
||
579 | string[] rHeaders = request.Headers.AllKeys; |
||
580 | |||
581 | foreach (string queryname in querystringkeys) |
||
582 | { |
||
583 | keysvals.Add(queryname, request.QueryString[queryname]); |
||
584 | } |
||
585 | |||
586 | foreach (string headername in rHeaders) |
||
587 | { |
||
588 | //m_log.Warn("[HEADER]: " + headername + "=" + request.Headers[headername]); |
||
589 | headervals[headername] = request.Headers[headername]; |
||
590 | } |
||
591 | |||
592 | // if (headervals.Contains("Host")) |
||
593 | // { |
||
594 | // host = (string)headervals["Host"]; |
||
595 | // } |
||
596 | |||
597 | keysvals.Add("requestbody", requestBody); |
||
598 | keysvals.Add("headers",headervals); |
||
599 | if (keysvals.Contains("method")) |
||
600 | { |
||
601 | //m_log.Warn("[HTTP]: Contains Method"); |
||
602 | //string method = (string)keysvals["method"]; |
||
603 | //m_log.Warn("[HTTP]: " + requestBody); |
||
604 | |||
605 | } |
||
606 | |||
607 | buffer = DoHTTPGruntWork(HTTPRequestHandler.Handle(path, keysvals), response); |
||
608 | } |
||
609 | else |
||
610 | { |
||
611 | IStreamHandler streamHandler = (IStreamHandler)requestHandler; |
||
612 | |||
613 | using (MemoryStream memoryStream = new MemoryStream()) |
||
614 | { |
||
615 | streamHandler.Handle(path, request.InputStream, memoryStream, request, response); |
||
616 | memoryStream.Flush(); |
||
617 | buffer = memoryStream.ToArray(); |
||
618 | } |
||
619 | } |
||
620 | } |
||
621 | else |
||
622 | { |
||
623 | switch (request.ContentType) |
||
624 | { |
||
625 | case null: |
||
626 | case "text/html": |
||
627 | if (DebugLevel >= 3) |
||
628 | LogIncomingToContentTypeHandler(request); |
||
629 | |||
630 | buffer = HandleHTTPRequest(request, response); |
||
631 | break; |
||
632 | |||
633 | case "application/llsd+xml": |
||
634 | case "application/xml+llsd": |
||
635 | case "application/llsd+json": |
||
636 | if (DebugLevel >= 3) |
||
637 | LogIncomingToContentTypeHandler(request); |
||
638 | |||
639 | buffer = HandleLLSDRequests(request, response); |
||
640 | break; |
||
641 | |||
642 | case "application/json-rpc": |
||
643 | if (DebugLevel >= 3) |
||
644 | LogIncomingToContentTypeHandler(request); |
||
645 | |||
646 | buffer = HandleJsonRpcRequests(request, response); |
||
647 | break; |
||
648 | |||
649 | case "text/xml": |
||
650 | case "application/xml": |
||
651 | case "application/json": |
||
652 | |||
653 | default: |
||
654 | //m_log.Info("[Debug BASE HTTP SERVER]: in default handler"); |
||
655 | // Point of note.. the DoWeHaveA methods check for an EXACT path |
||
656 | // if (request.RawUrl.Contains("/CAPS/EQG")) |
||
657 | // { |
||
658 | // int i = 1; |
||
659 | // } |
||
660 | //m_log.Info("[Debug BASE HTTP SERVER]: Checking for LLSD Handler"); |
||
661 | if (DoWeHaveALLSDHandler(request.RawUrl)) |
||
662 | { |
||
663 | if (DebugLevel >= 3) |
||
664 | LogIncomingToContentTypeHandler(request); |
||
665 | |||
666 | buffer = HandleLLSDRequests(request, response); |
||
667 | } |
||
668 | // m_log.DebugFormat("[BASE HTTP SERVER]: Checking for HTTP Handler for request {0}", request.RawUrl); |
||
669 | else if (DoWeHaveAHTTPHandler(request.RawUrl)) |
||
670 | { |
||
671 | if (DebugLevel >= 3) |
||
672 | LogIncomingToContentTypeHandler(request); |
||
673 | |||
674 | buffer = HandleHTTPRequest(request, response); |
||
675 | } |
||
676 | else |
||
677 | { |
||
678 | if (DebugLevel >= 3) |
||
679 | LogIncomingToXmlRpcHandler(request); |
||
680 | |||
681 | // generic login request. |
||
682 | buffer = HandleXmlRpcRequests(request, response); |
||
683 | } |
||
684 | |||
685 | break; |
||
686 | } |
||
687 | } |
||
688 | |||
689 | request.InputStream.Close(); |
||
690 | |||
691 | if (buffer != null) |
||
692 | { |
||
693 | if (!response.SendChunked && response.ContentLength64 <= 0) |
||
694 | response.ContentLength64 = buffer.LongLength; |
||
695 | |||
696 | response.OutputStream.Write(buffer, 0, buffer.Length); |
||
697 | } |
||
698 | |||
699 | // Do not include the time taken to actually send the response to the caller in the measurement |
||
700 | // time. This is to avoid logging when it's the client that is slow to process rather than the |
||
701 | // server |
||
702 | requestEndTick = Environment.TickCount; |
||
703 | |||
704 | response.Send(); |
||
705 | |||
706 | //response.OutputStream.Close(); |
||
707 | |||
708 | //response.FreeContext(); |
||
709 | } |
||
710 | catch (SocketException e) |
||
711 | { |
||
712 | // At least on linux, it appears that if the client makes a request without requiring the response, |
||
713 | // an unconnected socket exception is thrown when we close the response output stream. There's no |
||
714 | // obvious way to tell if the client didn't require the response, so instead we'll catch and ignore |
||
715 | // the exception instead. |
||
716 | // |
||
717 | // An alternative may be to turn off all response write exceptions on the HttpListener, but let's go |
||
718 | // with the minimum first |
||
719 | m_log.Warn(String.Format("[BASE HTTP SERVER]: HandleRequest threw {0}.\nNOTE: this may be spurious on Linux ", e.Message), e); |
||
720 | } |
||
721 | catch (IOException e) |
||
722 | { |
||
723 | m_log.Error(String.Format("[BASE HTTP SERVER]: HandleRequest() threw {0} ", e.StackTrace), e); |
||
724 | } |
||
725 | catch (Exception e) |
||
726 | { |
||
727 | m_log.Error(String.Format("[BASE HTTP SERVER]: HandleRequest() threw {0} ", e.StackTrace), e); |
||
728 | try |
||
729 | { |
||
730 | byte[] buffer500 = SendHTML500(response); |
||
731 | response.Body.Write(buffer500, 0, buffer500.Length); |
||
732 | response.Body.Close(); |
||
733 | } |
||
734 | catch |
||
735 | { |
||
736 | } |
||
737 | } |
||
738 | finally |
||
739 | { |
||
740 | // Every month or so this will wrap and give bad numbers, not really a problem |
||
741 | // since its just for reporting |
||
742 | int tickdiff = requestEndTick - requestStartTick; |
||
743 | if (tickdiff > 3000 && requestHandler != null && requestHandler.Name != "GetTexture") |
||
744 | { |
||
745 | m_log.InfoFormat( |
||
746 | "[BASE HTTP SERVER]: Slow handling of {0} {1} {2} {3} {4} from {5} took {6}ms", |
||
747 | RequestNumber, |
||
748 | requestMethod, |
||
749 | uriString, |
||
750 | requestHandler != null ? requestHandler.Name : "", |
||
751 | requestHandler != null ? requestHandler.Description : "", |
||
752 | request.RemoteIPEndPoint, |
||
753 | tickdiff); |
||
754 | } |
||
755 | else if (DebugLevel >= 4) |
||
756 | { |
||
757 | m_log.DebugFormat( |
||
758 | "[BASE HTTP SERVER]: HTTP IN {0} :{1} took {2}ms", |
||
759 | RequestNumber, |
||
760 | Port, |
||
761 | tickdiff); |
||
762 | } |
||
763 | } |
||
764 | } |
||
765 | |||
766 | private void LogIncomingToStreamHandler(OSHttpRequest request, IRequestHandler requestHandler) |
||
767 | { |
||
768 | m_log.DebugFormat( |
||
769 | "[BASE HTTP SERVER]: HTTP IN {0} :{1} stream handler {2} {3} {4} {5} from {6}", |
||
770 | RequestNumber, |
||
771 | Port, |
||
772 | request.HttpMethod, |
||
773 | request.Url.PathAndQuery, |
||
774 | requestHandler.Name, |
||
775 | requestHandler.Description, |
||
776 | request.RemoteIPEndPoint); |
||
777 | |||
778 | if (DebugLevel >= 5) |
||
779 | LogIncomingInDetail(request); |
||
780 | } |
||
781 | |||
782 | private void LogIncomingToContentTypeHandler(OSHttpRequest request) |
||
783 | { |
||
784 | m_log.DebugFormat( |
||
785 | "[BASE HTTP SERVER]: HTTP IN {0} :{1} {2} content type handler {3} {4} from {5}", |
||
786 | RequestNumber, |
||
787 | Port, |
||
788 | (request.ContentType == null || request.ContentType == "") ? "not set" : request.ContentType, |
||
789 | request.HttpMethod, |
||
790 | request.Url.PathAndQuery, |
||
791 | request.RemoteIPEndPoint); |
||
792 | |||
793 | if (DebugLevel >= 5) |
||
794 | LogIncomingInDetail(request); |
||
795 | } |
||
796 | |||
797 | private void LogIncomingToXmlRpcHandler(OSHttpRequest request) |
||
798 | { |
||
799 | m_log.DebugFormat( |
||
800 | "[BASE HTTP SERVER]: HTTP IN {0} :{1} assumed generic XMLRPC request {2} {3} from {4}", |
||
801 | RequestNumber, |
||
802 | Port, |
||
803 | request.HttpMethod, |
||
804 | request.Url.PathAndQuery, |
||
805 | request.RemoteIPEndPoint); |
||
806 | |||
807 | if (DebugLevel >= 5) |
||
808 | LogIncomingInDetail(request); |
||
809 | } |
||
810 | |||
811 | private void LogIncomingInDetail(OSHttpRequest request) |
||
812 | { |
||
813 | using (StreamReader reader = new StreamReader(Util.Copy(request.InputStream), Encoding.UTF8)) |
||
814 | { |
||
815 | string output; |
||
816 | |||
817 | if (DebugLevel == 5) |
||
818 | { |
||
819 | const int sampleLength = 80; |
||
820 | char[] sampleChars = new char[sampleLength + 3]; |
||
821 | reader.Read(sampleChars, 0, sampleLength); |
||
822 | sampleChars[80] = '.'; |
||
823 | sampleChars[81] = '.'; |
||
824 | sampleChars[82] = '.'; |
||
825 | output = new string(sampleChars); |
||
826 | } |
||
827 | else |
||
828 | { |
||
829 | output = reader.ReadToEnd(); |
||
830 | } |
||
831 | |||
832 | m_log.DebugFormat("[BASE HTTP SERVER]: {0}", output.Replace("\n", @"\n")); |
||
833 | } |
||
834 | } |
||
835 | |||
836 | private bool TryGetStreamHandler(string handlerKey, out IRequestHandler streamHandler) |
||
837 | { |
||
838 | string bestMatch = null; |
||
839 | |||
840 | lock (m_streamHandlers) |
||
841 | { |
||
842 | foreach (string pattern in m_streamHandlers.Keys) |
||
843 | { |
||
844 | if (handlerKey.StartsWith(pattern)) |
||
845 | { |
||
846 | if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) |
||
847 | { |
||
848 | bestMatch = pattern; |
||
849 | } |
||
850 | } |
||
851 | } |
||
852 | |||
853 | if (String.IsNullOrEmpty(bestMatch)) |
||
854 | { |
||
855 | streamHandler = null; |
||
856 | return false; |
||
857 | } |
||
858 | else |
||
859 | { |
||
860 | streamHandler = m_streamHandlers[bestMatch]; |
||
861 | return true; |
||
862 | } |
||
863 | } |
||
864 | } |
||
865 | |||
866 | private bool TryGetPollServiceHTTPHandler(string handlerKey, out PollServiceEventArgs oServiceEventArgs) |
||
867 | { |
||
868 | string bestMatch = null; |
||
869 | |||
870 | lock (m_pollHandlers) |
||
871 | { |
||
872 | foreach (string pattern in m_pollHandlers.Keys) |
||
873 | { |
||
874 | if (handlerKey.StartsWith(pattern)) |
||
875 | { |
||
876 | if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) |
||
877 | { |
||
878 | bestMatch = pattern; |
||
879 | } |
||
880 | } |
||
881 | } |
||
882 | |||
883 | if (String.IsNullOrEmpty(bestMatch)) |
||
884 | { |
||
885 | oServiceEventArgs = null; |
||
886 | return false; |
||
887 | } |
||
888 | else |
||
889 | { |
||
890 | oServiceEventArgs = m_pollHandlers[bestMatch]; |
||
891 | return true; |
||
892 | } |
||
893 | } |
||
894 | } |
||
895 | |||
896 | private bool TryGetHTTPHandler(string handlerKey, out GenericHTTPMethod HTTPHandler) |
||
897 | { |
||
898 | // m_log.DebugFormat("[BASE HTTP HANDLER]: Looking for HTTP handler for {0}", handlerKey); |
||
899 | |||
900 | string bestMatch = null; |
||
901 | |||
902 | lock (m_HTTPHandlers) |
||
903 | { |
||
904 | foreach (string pattern in m_HTTPHandlers.Keys) |
||
905 | { |
||
906 | if (handlerKey.StartsWith(pattern)) |
||
907 | { |
||
908 | if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) |
||
909 | { |
||
910 | bestMatch = pattern; |
||
911 | } |
||
912 | } |
||
913 | } |
||
914 | |||
915 | if (String.IsNullOrEmpty(bestMatch)) |
||
916 | { |
||
917 | HTTPHandler = null; |
||
918 | return false; |
||
919 | } |
||
920 | else |
||
921 | { |
||
922 | HTTPHandler = m_HTTPHandlers[bestMatch]; |
||
923 | return true; |
||
924 | } |
||
925 | } |
||
926 | } |
||
927 | |||
928 | // private bool TryGetAgentHandler(OSHttpRequest request, OSHttpResponse response, out IHttpAgentHandler agentHandler) |
||
929 | // { |
||
930 | // agentHandler = null; |
||
931 | // |
||
932 | // lock (m_agentHandlers) |
||
933 | // { |
||
934 | // foreach (IHttpAgentHandler handler in m_agentHandlers.Values) |
||
935 | // { |
||
936 | // if (handler.Match(request, response)) |
||
937 | // { |
||
938 | // agentHandler = handler; |
||
939 | // return true; |
||
940 | // } |
||
941 | // } |
||
942 | // } |
||
943 | // |
||
944 | // return false; |
||
945 | // } |
||
946 | |||
947 | /// <summary> |
||
948 | /// Try all the registered xmlrpc handlers when an xmlrpc request is received. |
||
949 | /// Sends back an XMLRPC unknown request response if no handler is registered for the requested method. |
||
950 | /// </summary> |
||
951 | /// <param name="request"></param> |
||
952 | /// <param name="response"></param> |
||
953 | private byte[] HandleXmlRpcRequests(OSHttpRequest request, OSHttpResponse response) |
||
954 | { |
||
955 | Stream requestStream = request.InputStream; |
||
956 | |||
957 | Encoding encoding = Encoding.UTF8; |
||
958 | StreamReader reader = new StreamReader(requestStream, encoding); |
||
959 | |||
960 | string requestBody = reader.ReadToEnd(); |
||
961 | reader.Close(); |
||
962 | requestStream.Close(); |
||
963 | //m_log.Debug(requestBody); |
||
964 | requestBody = requestBody.Replace("<base64></base64>", ""); |
||
965 | string responseString = String.Empty; |
||
966 | XmlRpcRequest xmlRprcRequest = null; |
||
967 | |||
968 | try |
||
969 | { |
||
970 | xmlRprcRequest = (XmlRpcRequest) (new XmlRpcRequestDeserializer()).Deserialize(requestBody); |
||
971 | } |
||
972 | catch (XmlException e) |
||
973 | { |
||
974 | if (DebugLevel >= 1) |
||
975 | { |
||
976 | if (DebugLevel >= 2) |
||
977 | m_log.Warn( |
||
978 | string.Format( |
||
979 | "[BASE HTTP SERVER]: Got XMLRPC request with invalid XML from {0}. XML was '{1}'. Sending blank response. Exception ", |
||
980 | request.RemoteIPEndPoint, requestBody), |
||
981 | e); |
||
982 | else |
||
983 | { |
||
984 | m_log.WarnFormat( |
||
985 | "[BASE HTTP SERVER]: Got XMLRPC request with invalid XML from {0}, length {1}. Sending blank response.", |
||
986 | request.RemoteIPEndPoint, requestBody.Length); |
||
987 | } |
||
988 | } |
||
989 | } |
||
990 | |||
991 | if (xmlRprcRequest != null) |
||
992 | { |
||
993 | string methodName = xmlRprcRequest.MethodName; |
||
994 | if (methodName != null) |
||
995 | { |
||
996 | xmlRprcRequest.Params.Add(request.RemoteIPEndPoint); // Param[1] |
||
997 | XmlRpcResponse xmlRpcResponse; |
||
998 | |||
999 | XmlRpcMethod method; |
||
1000 | bool methodWasFound; |
||
1001 | bool keepAlive = false; |
||
1002 | lock (m_rpcHandlers) |
||
1003 | { |
||
1004 | methodWasFound = m_rpcHandlers.TryGetValue(methodName, out method); |
||
1005 | if (methodWasFound) |
||
1006 | keepAlive = m_rpcHandlersKeepAlive[methodName]; |
||
1007 | } |
||
1008 | |||
1009 | if (methodWasFound) |
||
1010 | { |
||
1011 | xmlRprcRequest.Params.Add(request.Url); // Param[2] |
||
1012 | |||
1013 | string xff = "X-Forwarded-For"; |
||
1014 | string xfflower = xff.ToLower(); |
||
1015 | foreach (string s in request.Headers.AllKeys) |
||
1016 | { |
||
1017 | if (s != null && s.Equals(xfflower)) |
||
1018 | { |
||
1019 | xff = xfflower; |
||
1020 | break; |
||
1021 | } |
||
1022 | } |
||
1023 | xmlRprcRequest.Params.Add(request.Headers.Get(xff)); // Param[3] |
||
1024 | |||
1025 | try |
||
1026 | { |
||
1027 | xmlRpcResponse = method(xmlRprcRequest, request.RemoteIPEndPoint); |
||
1028 | } |
||
1029 | catch(Exception e) |
||
1030 | { |
||
1031 | string errorMessage |
||
1032 | = String.Format( |
||
1033 | "Requested method [{0}] from {1} threw exception: {2} {3}", |
||
1034 | methodName, request.RemoteIPEndPoint.Address, e.Message, e.StackTrace); |
||
1035 | |||
1036 | m_log.ErrorFormat("[BASE HTTP SERVER]: {0}", errorMessage); |
||
1037 | |||
1038 | // if the registered XmlRpc method threw an exception, we pass a fault-code along |
||
1039 | xmlRpcResponse = new XmlRpcResponse(); |
||
1040 | |||
1041 | // Code probably set in accordance with http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php |
||
1042 | xmlRpcResponse.SetFault(-32603, errorMessage); |
||
1043 | } |
||
1044 | |||
1045 | // if the method wasn't found, we can't determine KeepAlive state anyway, so lets do it only here |
||
1046 | response.KeepAlive = keepAlive; |
||
1047 | } |
||
1048 | else |
||
1049 | { |
||
1050 | xmlRpcResponse = new XmlRpcResponse(); |
||
1051 | |||
1052 | // Code set in accordance with http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php |
||
1053 | xmlRpcResponse.SetFault( |
||
1054 | XmlRpcErrorCodes.SERVER_ERROR_METHOD, |
||
1055 | String.Format("Requested method [{0}] not found", methodName)); |
||
1056 | } |
||
1057 | |||
1058 | response.ContentType = "text/xml"; |
||
1059 | responseString = XmlRpcResponseSerializer.Singleton.Serialize(xmlRpcResponse); |
||
1060 | } |
||
1061 | else |
||
1062 | { |
||
1063 | //HandleLLSDRequests(request, response); |
||
1064 | response.ContentType = "text/plain"; |
||
1065 | response.StatusCode = 404; |
||
1066 | response.StatusDescription = "Not Found"; |
||
1067 | response.ProtocolVersion = "HTTP/1.0"; |
||
1068 | responseString = "Not found"; |
||
1069 | response.KeepAlive = false; |
||
1070 | |||
1071 | m_log.ErrorFormat( |
||
1072 | "[BASE HTTP SERVER]: Handler not found for http request {0} {1}", |
||
1073 | request.HttpMethod, request.Url.PathAndQuery); |
||
1074 | } |
||
1075 | } |
||
1076 | |||
1077 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); |
||
1078 | |||
1079 | response.SendChunked = false; |
||
1080 | response.ContentLength64 = buffer.Length; |
||
1081 | response.ContentEncoding = Encoding.UTF8; |
||
1082 | |||
1083 | return buffer; |
||
1084 | } |
||
1085 | |||
1086 | // JsonRpc (v2.0 only) |
||
1087 | // Batch requests not yet supported |
||
1088 | private byte[] HandleJsonRpcRequests(OSHttpRequest request, OSHttpResponse response) |
||
1089 | { |
||
1090 | Stream requestStream = request.InputStream; |
||
1091 | JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(); |
||
1092 | OSDMap jsonRpcRequest = null; |
||
1093 | |||
1094 | try |
||
1095 | { |
||
1096 | jsonRpcRequest = (OSDMap)OSDParser.DeserializeJson(requestStream); |
||
1097 | } |
||
1098 | catch (LitJson.JsonException e) |
||
1099 | { |
||
1100 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; |
||
1101 | jsonRpcResponse.Error.Message = e.Message; |
||
1102 | } |
||
1103 | |||
1104 | requestStream.Close(); |
||
1105 | |||
1106 | if (jsonRpcRequest != null) |
||
1107 | { |
||
1108 | if (jsonRpcRequest.ContainsKey("jsonrpc") || jsonRpcRequest["jsonrpc"].AsString() == "2.0") |
||
1109 | { |
||
1110 | jsonRpcResponse.JsonRpc = "2.0"; |
||
1111 | |||
1112 | // If we have no id, then it's a "notification" |
||
1113 | if (jsonRpcRequest.ContainsKey("id")) |
||
1114 | { |
||
1115 | jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); |
||
1116 | } |
||
1117 | |||
1118 | string methodname = jsonRpcRequest["method"]; |
||
1119 | JsonRPCMethod method; |
||
1120 | |||
1121 | if (jsonRpcHandlers.ContainsKey(methodname)) |
||
1122 | { |
||
1123 | lock(jsonRpcHandlers) |
||
1124 | { |
||
1125 | jsonRpcHandlers.TryGetValue(methodname, out method); |
||
1126 | } |
||
1127 | bool res = false; |
||
1128 | try |
||
1129 | { |
||
1130 | res = method(jsonRpcRequest, ref jsonRpcResponse); |
||
1131 | if(!res) |
||
1132 | { |
||
1133 | // The handler sent back an unspecified error |
||
1134 | if(jsonRpcResponse.Error.Code == 0) |
||
1135 | { |
||
1136 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; |
||
1137 | } |
||
1138 | } |
||
1139 | } |
||
1140 | catch (Exception e) |
||
1141 | { |
||
1142 | string ErrorMessage = string.Format("[BASE HTTP SERVER]: Json-Rpc Handler Error method {0} - {1}", methodname, e.Message); |
||
1143 | m_log.Error(ErrorMessage); |
||
1144 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; |
||
1145 | jsonRpcResponse.Error.Message = ErrorMessage; |
||
1146 | } |
||
1147 | } |
||
1148 | else // Error no hanlder defined for requested method |
||
1149 | { |
||
1150 | jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; |
||
1151 | jsonRpcResponse.Error.Message = string.Format ("No handler defined for {0}", methodname); |
||
1152 | } |
||
1153 | } |
||
1154 | else // not json-rpc 2.0 could be v1 |
||
1155 | { |
||
1156 | jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; |
||
1157 | jsonRpcResponse.Error.Message = "Must be valid json-rpc 2.0 see: http://www.jsonrpc.org/specification"; |
||
1158 | |||
1159 | if (jsonRpcRequest.ContainsKey("id")) |
||
1160 | jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); |
||
1161 | } |
||
1162 | } |
||
1163 | |||
1164 | response.KeepAlive = true; |
||
1165 | string responseData = string.Empty; |
||
1166 | |||
1167 | responseData = jsonRpcResponse.Serialize(); |
||
1168 | |||
1169 | byte[] buffer = Encoding.UTF8.GetBytes(responseData); |
||
1170 | return buffer; |
||
1171 | } |
||
1172 | |||
1173 | private byte[] HandleLLSDRequests(OSHttpRequest request, OSHttpResponse response) |
||
1174 | { |
||
1175 | //m_log.Warn("[BASE HTTP SERVER]: We've figured out it's a LLSD Request"); |
||
1176 | Stream requestStream = request.InputStream; |
||
1177 | |||
1178 | Encoding encoding = Encoding.UTF8; |
||
1179 | StreamReader reader = new StreamReader(requestStream, encoding); |
||
1180 | |||
1181 | string requestBody = reader.ReadToEnd(); |
||
1182 | reader.Close(); |
||
1183 | requestStream.Close(); |
||
1184 | |||
1185 | //m_log.DebugFormat("[OGP]: {0}:{1}", request.RawUrl, requestBody); |
||
1186 | response.KeepAlive = true; |
||
1187 | |||
1188 | OSD llsdRequest = null; |
||
1189 | OSD llsdResponse = null; |
||
1190 | |||
1191 | bool LegacyLLSDLoginLibOMV = (requestBody.Contains("passwd") && requestBody.Contains("mac") && requestBody.Contains("viewer_digest")); |
||
1192 | |||
1193 | if (requestBody.Length == 0) |
||
1194 | // Get Request |
||
1195 | { |
||
1196 | requestBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><llsd><map><key>request</key><string>get</string></map></llsd>"; |
||
1197 | } |
||
1198 | try |
||
1199 | { |
||
1200 | llsdRequest = OSDParser.Deserialize(requestBody); |
||
1201 | } |
||
1202 | catch (Exception ex) |
||
1203 | { |
||
1204 | m_log.Warn("[BASE HTTP SERVER]: Error - " + ex.Message); |
||
1205 | } |
||
1206 | |||
1207 | if (llsdRequest != null)// && m_defaultLlsdHandler != null) |
||
1208 | { |
||
1209 | LLSDMethod llsdhandler = null; |
||
1210 | |||
1211 | if (TryGetLLSDHandler(request.RawUrl, out llsdhandler) && !LegacyLLSDLoginLibOMV) |
||
1212 | { |
||
1213 | // we found a registered llsd handler to service this request |
||
1214 | llsdResponse = llsdhandler(request.RawUrl, llsdRequest, request.RemoteIPEndPoint.ToString()); |
||
1215 | } |
||
1216 | else |
||
1217 | { |
||
1218 | // we didn't find a registered llsd handler to service this request |
||
1219 | // check if we have a default llsd handler |
||
1220 | |||
1221 | if (m_defaultLlsdHandler != null) |
||
1222 | { |
||
1223 | // LibOMV path |
||
1224 | llsdResponse = m_defaultLlsdHandler(llsdRequest, request.RemoteIPEndPoint); |
||
1225 | } |
||
1226 | else |
||
1227 | { |
||
1228 | // Oops, no handler for this.. give em the failed message |
||
1229 | llsdResponse = GenerateNoLLSDHandlerResponse(); |
||
1230 | } |
||
1231 | } |
||
1232 | } |
||
1233 | else |
||
1234 | { |
||
1235 | llsdResponse = GenerateNoLLSDHandlerResponse(); |
||
1236 | } |
||
1237 | |||
1238 | byte[] buffer = new byte[0]; |
||
1239 | |||
1240 | if (llsdResponse.ToString() == "shutdown404!") |
||
1241 | { |
||
1242 | response.ContentType = "text/plain"; |
||
1243 | response.StatusCode = 404; |
||
1244 | response.StatusDescription = "Not Found"; |
||
1245 | response.ProtocolVersion = "HTTP/1.0"; |
||
1246 | buffer = Encoding.UTF8.GetBytes("Not found"); |
||
1247 | } |
||
1248 | else |
||
1249 | { |
||
1250 | // Select an appropriate response format |
||
1251 | buffer = BuildLLSDResponse(request, response, llsdResponse); |
||
1252 | } |
||
1253 | |||
1254 | response.SendChunked = false; |
||
1255 | response.ContentLength64 = buffer.Length; |
||
1256 | response.ContentEncoding = Encoding.UTF8; |
||
1257 | response.KeepAlive = true; |
||
1258 | |||
1259 | return buffer; |
||
1260 | } |
||
1261 | |||
1262 | private byte[] BuildLLSDResponse(OSHttpRequest request, OSHttpResponse response, OSD llsdResponse) |
||
1263 | { |
||
1264 | if (request.AcceptTypes != null && request.AcceptTypes.Length > 0) |
||
1265 | { |
||
1266 | foreach (string strAccept in request.AcceptTypes) |
||
1267 | { |
||
1268 | switch (strAccept) |
||
1269 | { |
||
1270 | case "application/llsd+xml": |
||
1271 | case "application/xml": |
||
1272 | case "text/xml": |
||
1273 | response.ContentType = strAccept; |
||
1274 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); |
||
1275 | case "application/llsd+json": |
||
1276 | case "application/json": |
||
1277 | response.ContentType = strAccept; |
||
1278 | return Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); |
||
1279 | } |
||
1280 | } |
||
1281 | } |
||
1282 | |||
1283 | if (!String.IsNullOrEmpty(request.ContentType)) |
||
1284 | { |
||
1285 | switch (request.ContentType) |
||
1286 | { |
||
1287 | case "application/llsd+xml": |
||
1288 | case "application/xml": |
||
1289 | case "text/xml": |
||
1290 | response.ContentType = request.ContentType; |
||
1291 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); |
||
1292 | case "application/llsd+json": |
||
1293 | case "application/json": |
||
1294 | response.ContentType = request.ContentType; |
||
1295 | return Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); |
||
1296 | } |
||
1297 | } |
||
1298 | |||
1299 | // response.ContentType = "application/llsd+json"; |
||
1300 | // return Util.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); |
||
1301 | response.ContentType = "application/llsd+xml"; |
||
1302 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); |
||
1303 | } |
||
1304 | |||
1305 | /// <summary> |
||
1306 | /// Checks if we have an Exact path in the LLSD handlers for the path provided |
||
1307 | /// </summary> |
||
1308 | /// <param name="path">URI of the request</param> |
||
1309 | /// <returns>true if we have one, false if not</returns> |
||
1310 | private bool DoWeHaveALLSDHandler(string path) |
||
1311 | { |
||
1312 | string[] pathbase = path.Split('/'); |
||
1313 | string searchquery = "/"; |
||
1314 | |||
1315 | if (pathbase.Length < 1) |
||
1316 | return false; |
||
1317 | |||
1318 | for (int i = 1; i < pathbase.Length; i++) |
||
1319 | { |
||
1320 | searchquery += pathbase[i]; |
||
1321 | if (pathbase.Length - 1 != i) |
||
1322 | searchquery += "/"; |
||
1323 | } |
||
1324 | |||
1325 | string bestMatch = null; |
||
1326 | |||
1327 | lock (m_llsdHandlers) |
||
1328 | { |
||
1329 | foreach (string pattern in m_llsdHandlers.Keys) |
||
1330 | { |
||
1331 | if (searchquery.StartsWith(pattern) && searchquery.Length >= pattern.Length) |
||
1332 | bestMatch = pattern; |
||
1333 | } |
||
1334 | } |
||
1335 | |||
1336 | // extra kicker to remove the default XMLRPC login case.. just in case.. |
||
1337 | if (path != "/" && bestMatch == "/" && searchquery != "/") |
||
1338 | return false; |
||
1339 | |||
1340 | if (path == "/") |
||
1341 | return false; |
||
1342 | |||
1343 | if (String.IsNullOrEmpty(bestMatch)) |
||
1344 | { |
||
1345 | return false; |
||
1346 | } |
||
1347 | else |
||
1348 | { |
||
1349 | return true; |
||
1350 | } |
||
1351 | } |
||
1352 | |||
1353 | /// <summary> |
||
1354 | /// Checks if we have an Exact path in the HTTP handlers for the path provided |
||
1355 | /// </summary> |
||
1356 | /// <param name="path">URI of the request</param> |
||
1357 | /// <returns>true if we have one, false if not</returns> |
||
1358 | private bool DoWeHaveAHTTPHandler(string path) |
||
1359 | { |
||
1360 | string[] pathbase = path.Split('/'); |
||
1361 | string searchquery = "/"; |
||
1362 | |||
1363 | if (pathbase.Length < 1) |
||
1364 | return false; |
||
1365 | |||
1366 | for (int i = 1; i < pathbase.Length; i++) |
||
1367 | { |
||
1368 | searchquery += pathbase[i]; |
||
1369 | if (pathbase.Length - 1 != i) |
||
1370 | searchquery += "/"; |
||
1371 | } |
||
1372 | |||
1373 | string bestMatch = null; |
||
1374 | |||
1375 | //m_log.DebugFormat("[BASE HTTP HANDLER]: Checking if we have an HTTP handler for {0}", searchquery); |
||
1376 | |||
1377 | lock (m_HTTPHandlers) |
||
1378 | { |
||
1379 | foreach (string pattern in m_HTTPHandlers.Keys) |
||
1380 | { |
||
1381 | if (searchquery.StartsWith(pattern) && searchquery.Length >= pattern.Length) |
||
1382 | { |
||
1383 | bestMatch = pattern; |
||
1384 | } |
||
1385 | } |
||
1386 | |||
1387 | // extra kicker to remove the default XMLRPC login case.. just in case.. |
||
1388 | if (path == "/") |
||
1389 | return false; |
||
1390 | |||
1391 | if (String.IsNullOrEmpty(bestMatch)) |
||
1392 | { |
||
1393 | return false; |
||
1394 | } |
||
1395 | else |
||
1396 | { |
||
1397 | return true; |
||
1398 | } |
||
1399 | } |
||
1400 | } |
||
1401 | |||
1402 | private bool TryGetLLSDHandler(string path, out LLSDMethod llsdHandler) |
||
1403 | { |
||
1404 | llsdHandler = null; |
||
1405 | // Pull out the first part of the path |
||
1406 | // splitting the path by '/' means we'll get the following return.. |
||
1407 | // {0}/{1}/{2} |
||
1408 | // where {0} isn't something we really control 100% |
||
1409 | |||
1410 | string[] pathbase = path.Split('/'); |
||
1411 | string searchquery = "/"; |
||
1412 | |||
1413 | if (pathbase.Length < 1) |
||
1414 | return false; |
||
1415 | |||
1416 | for (int i=1; i<pathbase.Length; i++) |
||
1417 | { |
||
1418 | searchquery += pathbase[i]; |
||
1419 | if (pathbase.Length-1 != i) |
||
1420 | searchquery += "/"; |
||
1421 | } |
||
1422 | |||
1423 | // while the matching algorithm below doesn't require it, we're expecting a query in the form |
||
1424 | // |
||
1425 | // [] = optional |
||
1426 | // /resource/UUID/action[/action] |
||
1427 | // |
||
1428 | // now try to get the closest match to the reigstered path |
||
1429 | // at least for OGP, registered path would probably only consist of the /resource/ |
||
1430 | |||
1431 | string bestMatch = null; |
||
1432 | |||
1433 | lock (m_llsdHandlers) |
||
1434 | { |
||
1435 | foreach (string pattern in m_llsdHandlers.Keys) |
||
1436 | { |
||
1437 | if (searchquery.ToLower().StartsWith(pattern.ToLower())) |
||
1438 | { |
||
1439 | if (String.IsNullOrEmpty(bestMatch) || searchquery.Length > bestMatch.Length) |
||
1440 | { |
||
1441 | // You have to specifically register for '/' and to get it, you must specificaly request it |
||
1442 | // |
||
1443 | if (pattern == "/" && searchquery == "/" || pattern != "/") |
||
1444 | bestMatch = pattern; |
||
1445 | } |
||
1446 | } |
||
1447 | } |
||
1448 | |||
1449 | if (String.IsNullOrEmpty(bestMatch)) |
||
1450 | { |
||
1451 | llsdHandler = null; |
||
1452 | return false; |
||
1453 | } |
||
1454 | else |
||
1455 | { |
||
1456 | llsdHandler = m_llsdHandlers[bestMatch]; |
||
1457 | return true; |
||
1458 | } |
||
1459 | } |
||
1460 | } |
||
1461 | |||
1462 | private OSDMap GenerateNoLLSDHandlerResponse() |
||
1463 | { |
||
1464 | OSDMap map = new OSDMap(); |
||
1465 | map["reason"] = OSD.FromString("LLSDRequest"); |
||
1466 | map["message"] = OSD.FromString("No handler registered for LLSD Requests"); |
||
1467 | map["login"] = OSD.FromString("false"); |
||
1468 | return map; |
||
1469 | } |
||
1470 | |||
1471 | public byte[] HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response) |
||
1472 | { |
||
1473 | // m_log.DebugFormat( |
||
1474 | // "[BASE HTTP SERVER]: HandleHTTPRequest for request to {0}, method {1}", |
||
1475 | // request.RawUrl, request.HttpMethod); |
||
1476 | |||
1477 | switch (request.HttpMethod) |
||
1478 | { |
||
1479 | case "OPTIONS": |
||
1480 | response.StatusCode = (int)OSHttpStatusCode.SuccessOk; |
||
1481 | return null; |
||
1482 | |||
1483 | default: |
||
1484 | return HandleContentVerbs(request, response); |
||
1485 | } |
||
1486 | } |
||
1487 | |||
1488 | private byte[] HandleContentVerbs(OSHttpRequest request, OSHttpResponse response) |
||
1489 | { |
||
1490 | // m_log.DebugFormat("[BASE HTTP SERVER]: HandleContentVerbs for request to {0}", request.RawUrl); |
||
1491 | |||
1492 | // This is a test. There's a workable alternative.. as this way sucks. |
||
1493 | // We'd like to put this into a text file parhaps that's easily editable. |
||
1494 | // |
||
1495 | // For this test to work, I used the following secondlife.exe parameters |
||
1496 | // "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 |
||
1497 | // |
||
1498 | // Even after all that, there's still an error, but it's a start. |
||
1499 | // |
||
1500 | // I depend on show_login_form being in the secondlife.exe parameters to figure out |
||
1501 | // to display the form, or process it. |
||
1502 | // a better way would be nifty. |
||
1503 | |||
1504 | byte[] buffer; |
||
1505 | |||
1506 | Stream requestStream = request.InputStream; |
||
1507 | |||
1508 | Encoding encoding = Encoding.UTF8; |
||
1509 | StreamReader reader = new StreamReader(requestStream, encoding); |
||
1510 | |||
1511 | string requestBody = reader.ReadToEnd(); |
||
1512 | // avoid warning for now |
||
1513 | reader.ReadToEnd(); |
||
1514 | reader.Close(); |
||
1515 | requestStream.Close(); |
||
1516 | |||
1517 | Hashtable keysvals = new Hashtable(); |
||
1518 | Hashtable headervals = new Hashtable(); |
||
1519 | |||
1520 | Hashtable requestVars = new Hashtable(); |
||
1521 | |||
1522 | string host = String.Empty; |
||
1523 | |||
1524 | string[] querystringkeys = request.QueryString.AllKeys; |
||
1525 | string[] rHeaders = request.Headers.AllKeys; |
||
1526 | |||
1527 | keysvals.Add("body", requestBody); |
||
1528 | keysvals.Add("uri", request.RawUrl); |
||
1529 | keysvals.Add("content-type", request.ContentType); |
||
1530 | keysvals.Add("http-method", request.HttpMethod); |
||
1531 | |||
1532 | foreach (string queryname in querystringkeys) |
||
1533 | { |
||
1534 | // m_log.DebugFormat( |
||
1535 | // "[BASE HTTP SERVER]: Got query paremeter {0}={1}", queryname, request.QueryString[queryname]); |
||
1536 | keysvals.Add(queryname, request.QueryString[queryname]); |
||
1537 | requestVars.Add(queryname, keysvals[queryname]); |
||
1538 | } |
||
1539 | |||
1540 | foreach (string headername in rHeaders) |
||
1541 | { |
||
1542 | // m_log.Debug("[BASE HTTP SERVER]: " + headername + "=" + request.Headers[headername]); |
||
1543 | headervals[headername] = request.Headers[headername]; |
||
1544 | } |
||
1545 | |||
1546 | if (headervals.Contains("Host")) |
||
1547 | { |
||
1548 | host = (string)headervals["Host"]; |
||
1549 | } |
||
1550 | |||
1551 | keysvals.Add("headers", headervals); |
||
1552 | keysvals.Add("querystringkeys", querystringkeys); |
||
1553 | keysvals.Add("requestvars", requestVars); |
||
1554 | // keysvals.Add("form", request.Form); |
||
1555 | |||
1556 | if (keysvals.Contains("method")) |
||
1557 | { |
||
1558 | // m_log.Debug("[BASE HTTP SERVER]: Contains Method"); |
||
1559 | string method = (string) keysvals["method"]; |
||
1560 | // m_log.Debug("[BASE HTTP SERVER]: " + requestBody); |
||
1561 | GenericHTTPMethod requestprocessor; |
||
1562 | bool foundHandler = TryGetHTTPHandler(method, out requestprocessor); |
||
1563 | if (foundHandler) |
||
1564 | { |
||
1565 | Hashtable responsedata1 = requestprocessor(keysvals); |
||
1566 | buffer = DoHTTPGruntWork(responsedata1,response); |
||
1567 | |||
1568 | //SendHTML500(response); |
||
1569 | } |
||
1570 | else |
||
1571 | { |
||
1572 | // m_log.Warn("[BASE HTTP SERVER]: Handler Not Found"); |
||
1573 | buffer = SendHTML404(response, host); |
||
1574 | } |
||
1575 | } |
||
1576 | else |
||
1577 | { |
||
1578 | GenericHTTPMethod requestprocessor; |
||
1579 | bool foundHandler = TryGetHTTPHandlerPathBased(request.RawUrl, out requestprocessor); |
||
1580 | if (foundHandler) |
||
1581 | { |
||
1582 | Hashtable responsedata2 = requestprocessor(keysvals); |
||
1583 | buffer = DoHTTPGruntWork(responsedata2, response); |
||
1584 | |||
1585 | //SendHTML500(response); |
||
1586 | } |
||
1587 | else |
||
1588 | { |
||
1589 | // m_log.Warn("[BASE HTTP SERVER]: Handler Not Found2"); |
||
1590 | buffer = SendHTML404(response, host); |
||
1591 | } |
||
1592 | } |
||
1593 | |||
1594 | return buffer; |
||
1595 | } |
||
1596 | |||
1597 | private bool TryGetHTTPHandlerPathBased(string path, out GenericHTTPMethod httpHandler) |
||
1598 | { |
||
1599 | httpHandler = null; |
||
1600 | // Pull out the first part of the path |
||
1601 | // splitting the path by '/' means we'll get the following return.. |
||
1602 | // {0}/{1}/{2} |
||
1603 | // where {0} isn't something we really control 100% |
||
1604 | |||
1605 | string[] pathbase = path.Split('/'); |
||
1606 | string searchquery = "/"; |
||
1607 | |||
1608 | if (pathbase.Length < 1) |
||
1609 | return false; |
||
1610 | |||
1611 | for (int i = 1; i < pathbase.Length; i++) |
||
1612 | { |
||
1613 | searchquery += pathbase[i]; |
||
1614 | if (pathbase.Length - 1 != i) |
||
1615 | searchquery += "/"; |
||
1616 | } |
||
1617 | |||
1618 | // while the matching algorithm below doesn't require it, we're expecting a query in the form |
||
1619 | // |
||
1620 | // [] = optional |
||
1621 | // /resource/UUID/action[/action] |
||
1622 | // |
||
1623 | // now try to get the closest match to the reigstered path |
||
1624 | // at least for OGP, registered path would probably only consist of the /resource/ |
||
1625 | |||
1626 | string bestMatch = null; |
||
1627 | |||
1628 | // m_log.DebugFormat( |
||
1629 | // "[BASE HTTP HANDLER]: TryGetHTTPHandlerPathBased() looking for HTTP handler to match {0}", searchquery); |
||
1630 | |||
1631 | lock (m_HTTPHandlers) |
||
1632 | { |
||
1633 | foreach (string pattern in m_HTTPHandlers.Keys) |
||
1634 | { |
||
1635 | if (searchquery.ToLower().StartsWith(pattern.ToLower())) |
||
1636 | { |
||
1637 | if (String.IsNullOrEmpty(bestMatch) || searchquery.Length > bestMatch.Length) |
||
1638 | { |
||
1639 | // You have to specifically register for '/' and to get it, you must specifically request it |
||
1640 | if (pattern == "/" && searchquery == "/" || pattern != "/") |
||
1641 | bestMatch = pattern; |
||
1642 | } |
||
1643 | } |
||
1644 | } |
||
1645 | |||
1646 | if (String.IsNullOrEmpty(bestMatch)) |
||
1647 | { |
||
1648 | httpHandler = null; |
||
1649 | return false; |
||
1650 | } |
||
1651 | else |
||
1652 | { |
||
1653 | if (bestMatch == "/" && searchquery != "/") |
||
1654 | return false; |
||
1655 | |||
1656 | httpHandler = m_HTTPHandlers[bestMatch]; |
||
1657 | return true; |
||
1658 | } |
||
1659 | } |
||
1660 | } |
||
1661 | |||
1662 | internal byte[] DoHTTPGruntWork(Hashtable responsedata, OSHttpResponse response) |
||
1663 | { |
||
1664 | //m_log.Info("[BASE HTTP SERVER]: Doing HTTP Grunt work with response"); |
||
1665 | int responsecode = (int)responsedata["int_response_code"]; |
||
1666 | string responseString = (string)responsedata["str_response_string"]; |
||
1667 | string contentType = (string)responsedata["content_type"]; |
||
1668 | |||
1669 | if (responsedata.ContainsKey("error_status_text")) |
||
1670 | { |
||
1671 | response.StatusDescription = (string)responsedata["error_status_text"]; |
||
1672 | } |
||
1673 | if (responsedata.ContainsKey("http_protocol_version")) |
||
1674 | { |
||
1675 | response.ProtocolVersion = (string)responsedata["http_protocol_version"]; |
||
1676 | } |
||
1677 | |||
1678 | if (responsedata.ContainsKey("keepalive")) |
||
1679 | { |
||
1680 | bool keepalive = (bool)responsedata["keepalive"]; |
||
1681 | response.KeepAlive = keepalive; |
||
1682 | |||
1683 | } |
||
1684 | |||
1685 | if (responsedata.ContainsKey("reusecontext")) |
||
1686 | response.ReuseContext = (bool) responsedata["reusecontext"]; |
||
1687 | |||
1688 | // Cross-Origin Resource Sharing with simple requests |
||
1689 | if (responsedata.ContainsKey("access_control_allow_origin")) |
||
1690 | response.AddHeader("Access-Control-Allow-Origin", (string)responsedata["access_control_allow_origin"]); |
||
1691 | |||
1692 | //Even though only one other part of the entire code uses HTTPHandlers, we shouldn't expect this |
||
1693 | //and should check for NullReferenceExceptions |
||
1694 | |||
1695 | if (string.IsNullOrEmpty(contentType)) |
||
1696 | { |
||
1697 | contentType = "text/html"; |
||
1698 | } |
||
1699 | |||
1700 | // The client ignores anything but 200 here for web login, so ensure that this is 200 for that |
||
1701 | |||
1702 | response.StatusCode = responsecode; |
||
1703 | |||
1704 | if (responsecode == (int)OSHttpStatusCode.RedirectMovedPermanently) |
||
1705 | { |
||
1706 | response.RedirectLocation = (string)responsedata["str_redirect_location"]; |
||
1707 | response.StatusCode = responsecode; |
||
1708 | } |
||
1709 | |||
1710 | response.AddHeader("Content-Type", contentType); |
||
1711 | |||
1712 | byte[] buffer; |
||
1713 | |||
1714 | if (!(contentType.Contains("image") |
||
1715 | || contentType.Contains("x-shockwave-flash") |
||
1716 | || contentType.Contains("application/x-oar") |
||
1717 | || contentType.Contains("application/vnd.ll.mesh"))) |
||
1718 | { |
||
1719 | // Text |
||
1720 | buffer = Encoding.UTF8.GetBytes(responseString); |
||
1721 | } |
||
1722 | else |
||
1723 | { |
||
1724 | // Binary! |
||
1725 | buffer = Convert.FromBase64String(responseString); |
||
1726 | } |
||
1727 | |||
1728 | response.SendChunked = false; |
||
1729 | response.ContentLength64 = buffer.Length; |
||
1730 | response.ContentEncoding = Encoding.UTF8; |
||
1731 | |||
1732 | return buffer; |
||
1733 | } |
||
1734 | |||
1735 | public byte[] SendHTML404(OSHttpResponse response, string host) |
||
1736 | { |
||
1737 | // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s |
||
1738 | response.StatusCode = 404; |
||
1739 | response.AddHeader("Content-type", "text/html"); |
||
1740 | |||
1741 | string responseString = GetHTTP404(host); |
||
1742 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); |
||
1743 | |||
1744 | response.SendChunked = false; |
||
1745 | response.ContentLength64 = buffer.Length; |
||
1746 | response.ContentEncoding = Encoding.UTF8; |
||
1747 | |||
1748 | return buffer; |
||
1749 | } |
||
1750 | |||
1751 | public byte[] SendHTML500(OSHttpResponse response) |
||
1752 | { |
||
1753 | // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s |
||
1754 | response.StatusCode = (int)OSHttpStatusCode.SuccessOk; |
||
1755 | response.AddHeader("Content-type", "text/html"); |
||
1756 | |||
1757 | string responseString = GetHTTP500(); |
||
1758 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); |
||
1759 | |||
1760 | response.SendChunked = false; |
||
1761 | response.ContentLength64 = buffer.Length; |
||
1762 | response.ContentEncoding = Encoding.UTF8; |
||
1763 | |||
1764 | |||
1765 | return buffer; |
||
1766 | } |
||
1767 | |||
1768 | public void Start() |
||
1769 | { |
||
1770 | StartHTTP(); |
||
1771 | } |
||
1772 | |||
1773 | private void StartHTTP() |
||
1774 | { |
||
1775 | m_log.InfoFormat( |
||
1776 | "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); |
||
1777 | |||
1778 | try |
||
1779 | { |
||
1780 | //m_httpListener = new HttpListener(); |
||
1781 | |||
1782 | NotSocketErrors = 0; |
||
1783 | if (!m_ssl) |
||
1784 | { |
||
1785 | //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); |
||
1786 | //m_httpListener.Prefixes.Add("http://10.1.1.5:" + m_port + "/"); |
||
1787 | m_httpListener2 = CoolHTTPListener.Create(m_listenIPAddress, (int)m_port); |
||
1788 | m_httpListener2.ExceptionThrown += httpServerException; |
||
1789 | m_httpListener2.LogWriter = httpserverlog; |
||
1790 | |||
1791 | // Uncomment this line in addition to those in HttpServerLogWriter |
||
1792 | // if you want more detailed trace information from the HttpServer |
||
1793 | //m_httpListener2.UseTraceLogs = true; |
||
1794 | |||
1795 | //m_httpListener2.DisconnectHandler = httpServerDisconnectMonitor; |
||
1796 | } |
||
1797 | else |
||
1798 | { |
||
1799 | //m_httpListener.Prefixes.Add("https://+:" + (m_sslport) + "/"); |
||
1800 | //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); |
||
1801 | m_httpListener2 = CoolHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert); |
||
1802 | m_httpListener2.ExceptionThrown += httpServerException; |
||
1803 | m_httpListener2.LogWriter = httpserverlog; |
||
1804 | } |
||
1805 | |||
1806 | m_httpListener2.RequestReceived += OnRequest; |
||
1807 | //m_httpListener.Start(); |
||
1808 | m_httpListener2.Start(64); |
||
1809 | |||
1810 | // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events |
||
1811 | m_PollServiceManager = new PollServiceRequestManager(this, 3, 25000); |
||
1812 | m_PollServiceManager.Start(); |
||
1813 | HTTPDRunning = true; |
||
1814 | |||
1815 | //HttpListenerContext context; |
||
1816 | //while (true) |
||
1817 | //{ |
||
1818 | // context = m_httpListener.GetContext(); |
||
1819 | // ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(HandleRequest), context); |
||
1820 | // } |
||
1821 | } |
||
1822 | catch (Exception e) |
||
1823 | { |
||
1824 | m_log.Error("[BASE HTTP SERVER]: Error - " + e.Message); |
||
1825 | m_log.Error("[BASE HTTP SERVER]: Tip: Do you have permission to listen on port " + m_port + ", " + m_sslport + "?"); |
||
1826 | |||
1827 | // We want this exception to halt the entire server since in current configurations we aren't too |
||
1828 | // useful without inbound HTTP. |
||
1829 | throw e; |
||
1830 | } |
||
1831 | |||
1832 | m_requestsProcessedStat |
||
1833 | = new Stat( |
||
1834 | "HTTPRequestsServed", |
||
1835 | "Number of inbound HTTP requests processed", |
||
1836 | "", |
||
1837 | "requests", |
||
1838 | "httpserver", |
||
1839 | Port.ToString(), |
||
1840 | StatType.Pull, |
||
1841 | MeasuresOfInterest.AverageChangeOverTime, |
||
1842 | stat => stat.Value = RequestNumber, |
||
1843 | StatVerbosity.Debug); |
||
1844 | |||
1845 | StatsManager.RegisterStat(m_requestsProcessedStat); |
||
1846 | } |
||
1847 | |||
1848 | public void httpServerDisconnectMonitor(IHttpClientContext source, SocketError err) |
||
1849 | { |
||
1850 | switch (err) |
||
1851 | { |
||
1852 | case SocketError.NotSocket: |
||
1853 | NotSocketErrors++; |
||
1854 | |||
1855 | break; |
||
1856 | } |
||
1857 | } |
||
1858 | |||
1859 | public void httpServerException(object source, Exception exception) |
||
1860 | { |
||
1861 | m_log.Error(String.Format("[BASE HTTP SERVER]: {0} had an exception: {1} ", source.ToString(), exception.Message), exception); |
||
1862 | /* |
||
1863 | if (HTTPDRunning)// && NotSocketErrors > 5) |
||
1864 | { |
||
1865 | Stop(); |
||
1866 | Thread.Sleep(200); |
||
1867 | StartHTTP(); |
||
1868 | m_log.Warn("[HTTPSERVER]: Died. Trying to kick....."); |
||
1869 | } |
||
1870 | */ |
||
1871 | } |
||
1872 | |||
1873 | public void Stop() |
||
1874 | { |
||
1875 | HTTPDRunning = false; |
||
1876 | |||
1877 | StatsManager.DeregisterStat(m_requestsProcessedStat); |
||
1878 | |||
1879 | try |
||
1880 | { |
||
1881 | m_PollServiceManager.Stop(); |
||
1882 | |||
1883 | m_httpListener2.ExceptionThrown -= httpServerException; |
||
1884 | //m_httpListener2.DisconnectHandler = null; |
||
1885 | |||
1886 | m_httpListener2.LogWriter = null; |
||
1887 | m_httpListener2.RequestReceived -= OnRequest; |
||
1888 | m_httpListener2.Stop(); |
||
1889 | } |
||
1890 | catch (NullReferenceException) |
||
1891 | { |
||
1892 | m_log.Warn("[BASE HTTP SERVER]: Null Reference when stopping HttpServer."); |
||
1893 | } |
||
1894 | } |
||
1895 | |||
1896 | public void RemoveStreamHandler(string httpMethod, string path) |
||
1897 | { |
||
1898 | string handlerKey = GetHandlerKey(httpMethod, path); |
||
1899 | |||
1900 | //m_log.DebugFormat("[BASE HTTP SERVER]: Removing handler key {0}", handlerKey); |
||
1901 | |||
1902 | lock (m_streamHandlers) |
||
1903 | m_streamHandlers.Remove(handlerKey); |
||
1904 | } |
||
1905 | |||
1906 | public void RemoveHTTPHandler(string httpMethod, string path) |
||
1907 | { |
||
1908 | lock (m_HTTPHandlers) |
||
1909 | { |
||
1910 | if (httpMethod != null && httpMethod.Length == 0) |
||
1911 | { |
||
1912 | m_HTTPHandlers.Remove(path); |
||
1913 | return; |
||
1914 | } |
||
1915 | |||
1916 | m_HTTPHandlers.Remove(GetHandlerKey(httpMethod, path)); |
||
1917 | } |
||
1918 | } |
||
1919 | |||
1920 | public void RemovePollServiceHTTPHandler(string httpMethod, string path) |
||
1921 | { |
||
1922 | lock (m_pollHandlers) |
||
1923 | m_pollHandlers.Remove(path); |
||
1924 | } |
||
1925 | |||
1926 | // public bool RemoveAgentHandler(string agent, IHttpAgentHandler handler) |
||
1927 | // { |
||
1928 | // lock (m_agentHandlers) |
||
1929 | // { |
||
1930 | // IHttpAgentHandler foundHandler; |
||
1931 | // |
||
1932 | // if (m_agentHandlers.TryGetValue(agent, out foundHandler) && foundHandler == handler) |
||
1933 | // { |
||
1934 | // m_agentHandlers.Remove(agent); |
||
1935 | // return true; |
||
1936 | // } |
||
1937 | // } |
||
1938 | // |
||
1939 | // return false; |
||
1940 | // } |
||
1941 | |||
1942 | public void RemoveXmlRPCHandler(string method) |
||
1943 | { |
||
1944 | lock (m_rpcHandlers) |
||
1945 | m_rpcHandlers.Remove(method); |
||
1946 | } |
||
1947 | |||
1948 | public void RemoveJsonRPCHandler(string method) |
||
1949 | { |
||
1950 | lock(jsonRpcHandlers) |
||
1951 | jsonRpcHandlers.Remove(method); |
||
1952 | } |
||
1953 | |||
1954 | public bool RemoveLLSDHandler(string path, LLSDMethod handler) |
||
1955 | { |
||
1956 | lock (m_llsdHandlers) |
||
1957 | { |
||
1958 | LLSDMethod foundHandler; |
||
1959 | |||
1960 | if (m_llsdHandlers.TryGetValue(path, out foundHandler) && foundHandler == handler) |
||
1961 | { |
||
1962 | m_llsdHandlers.Remove(path); |
||
1963 | return true; |
||
1964 | } |
||
1965 | } |
||
1966 | |||
1967 | return false; |
||
1968 | } |
||
1969 | |||
1970 | public string GetHTTP404(string host) |
||
1971 | { |
||
1972 | string file = Path.Combine(".", "http_404.html"); |
||
1973 | if (!File.Exists(file)) |
||
1974 | return getDefaultHTTP404(host); |
||
1975 | |||
1976 | StreamReader sr = File.OpenText(file); |
||
1977 | string result = sr.ReadToEnd(); |
||
1978 | sr.Close(); |
||
1979 | return result; |
||
1980 | } |
||
1981 | |||
1982 | public string GetHTTP500() |
||
1983 | { |
||
1984 | string file = Path.Combine(".", "http_500.html"); |
||
1985 | if (!File.Exists(file)) |
||
1986 | return getDefaultHTTP500(); |
||
1987 | |||
1988 | StreamReader sr = File.OpenText(file); |
||
1989 | string result = sr.ReadToEnd(); |
||
1990 | sr.Close(); |
||
1991 | return result; |
||
1992 | } |
||
1993 | |||
1994 | // Fallback HTTP responses in case the HTTP error response files don't exist |
||
1995 | private static string getDefaultHTTP404(string host) |
||
1996 | { |
||
1997 | 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>"; |
||
1998 | } |
||
1999 | |||
2000 | private static string getDefaultHTTP500() |
||
2001 | { |
||
2002 | 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>"; |
||
2003 | } |
||
2004 | } |
||
2005 | |||
2006 | public class HttpServerContextObj |
||
2007 | { |
||
2008 | public IHttpClientContext context = null; |
||
2009 | public IHttpRequest req = null; |
||
2010 | public OSHttpRequest oreq = null; |
||
2011 | public OSHttpResponse oresp = null; |
||
2012 | |||
2013 | public HttpServerContextObj(IHttpClientContext contxt, IHttpRequest reqs) |
||
2014 | { |
||
2015 | context = contxt; |
||
2016 | req = reqs; |
||
2017 | } |
||
2018 | |||
2019 | public HttpServerContextObj(OSHttpRequest osreq, OSHttpResponse osresp) |
||
2020 | { |
||
2021 | oreq = osreq; |
||
2022 | oresp = osresp; |
||
2023 | } |
||
2024 | } |
||
2025 | |||
2026 | /// <summary> |
||
2027 | /// Relays HttpServer log messages to our own logging mechanism. |
||
2028 | /// </summary> |
||
2029 | /// To use this you must uncomment the switch section |
||
2030 | /// |
||
2031 | /// You may also be able to get additional trace information from HttpServer if you uncomment the UseTraceLogs |
||
2032 | /// property in StartHttp() for the HttpListener |
||
2033 | public class HttpServerLogWriter : ILogWriter |
||
2034 | { |
||
2035 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
2036 | |||
2037 | public void Write(object source, LogPrio priority, string message) |
||
2038 | { |
||
2039 | /* |
||
2040 | switch (priority) |
||
2041 | { |
||
2042 | case LogPrio.Trace: |
||
2043 | m_log.DebugFormat("[{0}]: {1}", source, message); |
||
2044 | break; |
||
2045 | case LogPrio.Debug: |
||
2046 | m_log.DebugFormat("[{0}]: {1}", source, message); |
||
2047 | break; |
||
2048 | case LogPrio.Error: |
||
2049 | m_log.ErrorFormat("[{0}]: {1}", source, message); |
||
2050 | break; |
||
2051 | case LogPrio.Info: |
||
2052 | m_log.InfoFormat("[{0}]: {1}", source, message); |
||
2053 | break; |
||
2054 | case LogPrio.Warning: |
||
2055 | m_log.WarnFormat("[{0}]: {1}", source, message); |
||
2056 | break; |
||
2057 | case LogPrio.Fatal: |
||
2058 | m_log.ErrorFormat("[{0}]: FATAL! - {1}", source, message); |
||
2059 | break; |
||
2060 | default: |
||
2061 | break; |
||
2062 | } |
||
2063 | */ |
||
2064 | |||
2065 | return; |
||
2066 | } |
||
2067 | } |
||
2068 | } |