opensim-development – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 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 | string.IsNullOrEmpty(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 | using (MemoryStream outs = new MemoryStream()) |
||
1060 | { |
||
1061 | using (XmlTextWriter writer = new XmlTextWriter(outs, Encoding.UTF8)) |
||
1062 | { |
||
1063 | writer.Formatting = Formatting.None; |
||
1064 | XmlRpcResponseSerializer.Singleton.Serialize(writer, xmlRpcResponse); |
||
1065 | writer.Flush(); |
||
1066 | outs.Flush(); |
||
1067 | outs.Position = 0; |
||
1068 | using (StreamReader sr = new StreamReader(outs)) |
||
1069 | { |
||
1070 | responseString = sr.ReadToEnd(); |
||
1071 | } |
||
1072 | } |
||
1073 | } |
||
1074 | } |
||
1075 | else |
||
1076 | { |
||
1077 | //HandleLLSDRequests(request, response); |
||
1078 | response.ContentType = "text/plain"; |
||
1079 | response.StatusCode = 404; |
||
1080 | response.StatusDescription = "Not Found"; |
||
1081 | response.ProtocolVersion = "HTTP/1.0"; |
||
1082 | responseString = "Not found"; |
||
1083 | response.KeepAlive = false; |
||
1084 | |||
1085 | m_log.ErrorFormat( |
||
1086 | "[BASE HTTP SERVER]: Handler not found for http request {0} {1}", |
||
1087 | request.HttpMethod, request.Url.PathAndQuery); |
||
1088 | } |
||
1089 | } |
||
1090 | |||
1091 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); |
||
1092 | |||
1093 | response.SendChunked = false; |
||
1094 | response.ContentLength64 = buffer.Length; |
||
1095 | response.ContentEncoding = Encoding.UTF8; |
||
1096 | |||
1097 | return buffer; |
||
1098 | } |
||
1099 | |||
1100 | // JsonRpc (v2.0 only) |
||
1101 | // Batch requests not yet supported |
||
1102 | private byte[] HandleJsonRpcRequests(OSHttpRequest request, OSHttpResponse response) |
||
1103 | { |
||
1104 | Stream requestStream = request.InputStream; |
||
1105 | JsonRpcResponse jsonRpcResponse = new JsonRpcResponse(); |
||
1106 | OSDMap jsonRpcRequest = null; |
||
1107 | |||
1108 | try |
||
1109 | { |
||
1110 | jsonRpcRequest = (OSDMap)OSDParser.DeserializeJson(requestStream); |
||
1111 | } |
||
1112 | catch (LitJson.JsonException e) |
||
1113 | { |
||
1114 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; |
||
1115 | jsonRpcResponse.Error.Message = e.Message; |
||
1116 | } |
||
1117 | |||
1118 | requestStream.Close(); |
||
1119 | |||
1120 | if (jsonRpcRequest != null) |
||
1121 | { |
||
1122 | if (jsonRpcRequest.ContainsKey("jsonrpc") || jsonRpcRequest["jsonrpc"].AsString() == "2.0") |
||
1123 | { |
||
1124 | jsonRpcResponse.JsonRpc = "2.0"; |
||
1125 | |||
1126 | // If we have no id, then it's a "notification" |
||
1127 | if (jsonRpcRequest.ContainsKey("id")) |
||
1128 | { |
||
1129 | jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); |
||
1130 | } |
||
1131 | |||
1132 | string methodname = jsonRpcRequest["method"]; |
||
1133 | JsonRPCMethod method; |
||
1134 | |||
1135 | if (jsonRpcHandlers.ContainsKey(methodname)) |
||
1136 | { |
||
1137 | lock(jsonRpcHandlers) |
||
1138 | { |
||
1139 | jsonRpcHandlers.TryGetValue(methodname, out method); |
||
1140 | } |
||
1141 | bool res = false; |
||
1142 | try |
||
1143 | { |
||
1144 | res = method(jsonRpcRequest, ref jsonRpcResponse); |
||
1145 | if(!res) |
||
1146 | { |
||
1147 | // The handler sent back an unspecified error |
||
1148 | if(jsonRpcResponse.Error.Code == 0) |
||
1149 | { |
||
1150 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; |
||
1151 | } |
||
1152 | } |
||
1153 | } |
||
1154 | catch (Exception e) |
||
1155 | { |
||
1156 | string ErrorMessage = string.Format("[BASE HTTP SERVER]: Json-Rpc Handler Error method {0} - {1}", methodname, e.Message); |
||
1157 | m_log.Error(ErrorMessage); |
||
1158 | jsonRpcResponse.Error.Code = ErrorCode.InternalError; |
||
1159 | jsonRpcResponse.Error.Message = ErrorMessage; |
||
1160 | } |
||
1161 | } |
||
1162 | else // Error no hanlder defined for requested method |
||
1163 | { |
||
1164 | jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; |
||
1165 | jsonRpcResponse.Error.Message = string.Format ("No handler defined for {0}", methodname); |
||
1166 | } |
||
1167 | } |
||
1168 | else // not json-rpc 2.0 could be v1 |
||
1169 | { |
||
1170 | jsonRpcResponse.Error.Code = ErrorCode.InvalidRequest; |
||
1171 | jsonRpcResponse.Error.Message = "Must be valid json-rpc 2.0 see: http://www.jsonrpc.org/specification"; |
||
1172 | |||
1173 | if (jsonRpcRequest.ContainsKey("id")) |
||
1174 | jsonRpcResponse.Id = jsonRpcRequest["id"].AsString(); |
||
1175 | } |
||
1176 | } |
||
1177 | |||
1178 | response.KeepAlive = true; |
||
1179 | string responseData = string.Empty; |
||
1180 | |||
1181 | responseData = jsonRpcResponse.Serialize(); |
||
1182 | |||
1183 | byte[] buffer = Encoding.UTF8.GetBytes(responseData); |
||
1184 | return buffer; |
||
1185 | } |
||
1186 | |||
1187 | private byte[] HandleLLSDRequests(OSHttpRequest request, OSHttpResponse response) |
||
1188 | { |
||
1189 | //m_log.Warn("[BASE HTTP SERVER]: We've figured out it's a LLSD Request"); |
||
1190 | Stream requestStream = request.InputStream; |
||
1191 | |||
1192 | Encoding encoding = Encoding.UTF8; |
||
1193 | StreamReader reader = new StreamReader(requestStream, encoding); |
||
1194 | |||
1195 | string requestBody = reader.ReadToEnd(); |
||
1196 | reader.Close(); |
||
1197 | requestStream.Close(); |
||
1198 | |||
1199 | //m_log.DebugFormat("[OGP]: {0}:{1}", request.RawUrl, requestBody); |
||
1200 | response.KeepAlive = true; |
||
1201 | |||
1202 | OSD llsdRequest = null; |
||
1203 | OSD llsdResponse = null; |
||
1204 | |||
1205 | bool LegacyLLSDLoginLibOMV = (requestBody.Contains("passwd") && requestBody.Contains("mac") && requestBody.Contains("viewer_digest")); |
||
1206 | |||
1207 | if (requestBody.Length == 0) |
||
1208 | // Get Request |
||
1209 | { |
||
1210 | requestBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><llsd><map><key>request</key><string>get</string></map></llsd>"; |
||
1211 | } |
||
1212 | try |
||
1213 | { |
||
1214 | llsdRequest = OSDParser.Deserialize(requestBody); |
||
1215 | } |
||
1216 | catch (Exception ex) |
||
1217 | { |
||
1218 | m_log.Warn("[BASE HTTP SERVER]: Error - " + ex.Message); |
||
1219 | } |
||
1220 | |||
1221 | if (llsdRequest != null)// && m_defaultLlsdHandler != null) |
||
1222 | { |
||
1223 | LLSDMethod llsdhandler = null; |
||
1224 | |||
1225 | if (TryGetLLSDHandler(request.RawUrl, out llsdhandler) && !LegacyLLSDLoginLibOMV) |
||
1226 | { |
||
1227 | // we found a registered llsd handler to service this request |
||
1228 | llsdResponse = llsdhandler(request.RawUrl, llsdRequest, request.RemoteIPEndPoint.ToString()); |
||
1229 | } |
||
1230 | else |
||
1231 | { |
||
1232 | // we didn't find a registered llsd handler to service this request |
||
1233 | // check if we have a default llsd handler |
||
1234 | |||
1235 | if (m_defaultLlsdHandler != null) |
||
1236 | { |
||
1237 | // LibOMV path |
||
1238 | llsdResponse = m_defaultLlsdHandler(llsdRequest, request.RemoteIPEndPoint); |
||
1239 | } |
||
1240 | else |
||
1241 | { |
||
1242 | // Oops, no handler for this.. give em the failed message |
||
1243 | llsdResponse = GenerateNoLLSDHandlerResponse(); |
||
1244 | } |
||
1245 | } |
||
1246 | } |
||
1247 | else |
||
1248 | { |
||
1249 | llsdResponse = GenerateNoLLSDHandlerResponse(); |
||
1250 | } |
||
1251 | |||
1252 | byte[] buffer = new byte[0]; |
||
1253 | |||
1254 | if (llsdResponse.ToString() == "shutdown404!") |
||
1255 | { |
||
1256 | response.ContentType = "text/plain"; |
||
1257 | response.StatusCode = 404; |
||
1258 | response.StatusDescription = "Not Found"; |
||
1259 | response.ProtocolVersion = "HTTP/1.0"; |
||
1260 | buffer = Encoding.UTF8.GetBytes("Not found"); |
||
1261 | } |
||
1262 | else |
||
1263 | { |
||
1264 | // Select an appropriate response format |
||
1265 | buffer = BuildLLSDResponse(request, response, llsdResponse); |
||
1266 | } |
||
1267 | |||
1268 | response.SendChunked = false; |
||
1269 | response.ContentLength64 = buffer.Length; |
||
1270 | response.ContentEncoding = Encoding.UTF8; |
||
1271 | response.KeepAlive = true; |
||
1272 | |||
1273 | return buffer; |
||
1274 | } |
||
1275 | |||
1276 | private byte[] BuildLLSDResponse(OSHttpRequest request, OSHttpResponse response, OSD llsdResponse) |
||
1277 | { |
||
1278 | if (request.AcceptTypes != null && request.AcceptTypes.Length > 0) |
||
1279 | { |
||
1280 | foreach (string strAccept in request.AcceptTypes) |
||
1281 | { |
||
1282 | switch (strAccept) |
||
1283 | { |
||
1284 | case "application/llsd+xml": |
||
1285 | case "application/xml": |
||
1286 | case "text/xml": |
||
1287 | response.ContentType = strAccept; |
||
1288 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); |
||
1289 | case "application/llsd+json": |
||
1290 | case "application/json": |
||
1291 | response.ContentType = strAccept; |
||
1292 | return Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); |
||
1293 | } |
||
1294 | } |
||
1295 | } |
||
1296 | |||
1297 | if (!String.IsNullOrEmpty(request.ContentType)) |
||
1298 | { |
||
1299 | switch (request.ContentType) |
||
1300 | { |
||
1301 | case "application/llsd+xml": |
||
1302 | case "application/xml": |
||
1303 | case "text/xml": |
||
1304 | response.ContentType = request.ContentType; |
||
1305 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); |
||
1306 | case "application/llsd+json": |
||
1307 | case "application/json": |
||
1308 | response.ContentType = request.ContentType; |
||
1309 | return Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); |
||
1310 | } |
||
1311 | } |
||
1312 | |||
1313 | // response.ContentType = "application/llsd+json"; |
||
1314 | // return Util.UTF8.GetBytes(OSDParser.SerializeJsonString(llsdResponse)); |
||
1315 | response.ContentType = "application/llsd+xml"; |
||
1316 | return OSDParser.SerializeLLSDXmlBytes(llsdResponse); |
||
1317 | } |
||
1318 | |||
1319 | /// <summary> |
||
1320 | /// Checks if we have an Exact path in the LLSD handlers for the path provided |
||
1321 | /// </summary> |
||
1322 | /// <param name="path">URI of the request</param> |
||
1323 | /// <returns>true if we have one, false if not</returns> |
||
1324 | private bool DoWeHaveALLSDHandler(string path) |
||
1325 | { |
||
1326 | string[] pathbase = path.Split('/'); |
||
1327 | string searchquery = "/"; |
||
1328 | |||
1329 | if (pathbase.Length < 1) |
||
1330 | return false; |
||
1331 | |||
1332 | for (int i = 1; i < pathbase.Length; i++) |
||
1333 | { |
||
1334 | searchquery += pathbase[i]; |
||
1335 | if (pathbase.Length - 1 != i) |
||
1336 | searchquery += "/"; |
||
1337 | } |
||
1338 | |||
1339 | string bestMatch = null; |
||
1340 | |||
1341 | lock (m_llsdHandlers) |
||
1342 | { |
||
1343 | foreach (string pattern in m_llsdHandlers.Keys) |
||
1344 | { |
||
1345 | if (searchquery.StartsWith(pattern) && searchquery.Length >= pattern.Length) |
||
1346 | bestMatch = pattern; |
||
1347 | } |
||
1348 | } |
||
1349 | |||
1350 | // extra kicker to remove the default XMLRPC login case.. just in case.. |
||
1351 | if (path != "/" && bestMatch == "/" && searchquery != "/") |
||
1352 | return false; |
||
1353 | |||
1354 | if (path == "/") |
||
1355 | return false; |
||
1356 | |||
1357 | if (String.IsNullOrEmpty(bestMatch)) |
||
1358 | { |
||
1359 | return false; |
||
1360 | } |
||
1361 | else |
||
1362 | { |
||
1363 | return true; |
||
1364 | } |
||
1365 | } |
||
1366 | |||
1367 | /// <summary> |
||
1368 | /// Checks if we have an Exact path in the HTTP handlers for the path provided |
||
1369 | /// </summary> |
||
1370 | /// <param name="path">URI of the request</param> |
||
1371 | /// <returns>true if we have one, false if not</returns> |
||
1372 | private bool DoWeHaveAHTTPHandler(string path) |
||
1373 | { |
||
1374 | string[] pathbase = path.Split('/'); |
||
1375 | string searchquery = "/"; |
||
1376 | |||
1377 | if (pathbase.Length < 1) |
||
1378 | return false; |
||
1379 | |||
1380 | for (int i = 1; i < pathbase.Length; i++) |
||
1381 | { |
||
1382 | searchquery += pathbase[i]; |
||
1383 | if (pathbase.Length - 1 != i) |
||
1384 | searchquery += "/"; |
||
1385 | } |
||
1386 | |||
1387 | string bestMatch = null; |
||
1388 | |||
1389 | //m_log.DebugFormat("[BASE HTTP HANDLER]: Checking if we have an HTTP handler for {0}", searchquery); |
||
1390 | |||
1391 | lock (m_HTTPHandlers) |
||
1392 | { |
||
1393 | foreach (string pattern in m_HTTPHandlers.Keys) |
||
1394 | { |
||
1395 | if (searchquery.StartsWith(pattern) && searchquery.Length >= pattern.Length) |
||
1396 | { |
||
1397 | bestMatch = pattern; |
||
1398 | } |
||
1399 | } |
||
1400 | |||
1401 | // extra kicker to remove the default XMLRPC login case.. just in case.. |
||
1402 | if (path == "/") |
||
1403 | return false; |
||
1404 | |||
1405 | if (String.IsNullOrEmpty(bestMatch)) |
||
1406 | { |
||
1407 | return false; |
||
1408 | } |
||
1409 | else |
||
1410 | { |
||
1411 | return true; |
||
1412 | } |
||
1413 | } |
||
1414 | } |
||
1415 | |||
1416 | private bool TryGetLLSDHandler(string path, out LLSDMethod llsdHandler) |
||
1417 | { |
||
1418 | llsdHandler = null; |
||
1419 | // Pull out the first part of the path |
||
1420 | // splitting the path by '/' means we'll get the following return.. |
||
1421 | // {0}/{1}/{2} |
||
1422 | // where {0} isn't something we really control 100% |
||
1423 | |||
1424 | string[] pathbase = path.Split('/'); |
||
1425 | string searchquery = "/"; |
||
1426 | |||
1427 | if (pathbase.Length < 1) |
||
1428 | return false; |
||
1429 | |||
1430 | for (int i=1; i<pathbase.Length; i++) |
||
1431 | { |
||
1432 | searchquery += pathbase[i]; |
||
1433 | if (pathbase.Length-1 != i) |
||
1434 | searchquery += "/"; |
||
1435 | } |
||
1436 | |||
1437 | // while the matching algorithm below doesn't require it, we're expecting a query in the form |
||
1438 | // |
||
1439 | // [] = optional |
||
1440 | // /resource/UUID/action[/action] |
||
1441 | // |
||
1442 | // now try to get the closest match to the reigstered path |
||
1443 | // at least for OGP, registered path would probably only consist of the /resource/ |
||
1444 | |||
1445 | string bestMatch = null; |
||
1446 | |||
1447 | lock (m_llsdHandlers) |
||
1448 | { |
||
1449 | foreach (string pattern in m_llsdHandlers.Keys) |
||
1450 | { |
||
1451 | if (searchquery.ToLower().StartsWith(pattern.ToLower())) |
||
1452 | { |
||
1453 | if (String.IsNullOrEmpty(bestMatch) || searchquery.Length > bestMatch.Length) |
||
1454 | { |
||
1455 | // You have to specifically register for '/' and to get it, you must specificaly request it |
||
1456 | // |
||
1457 | if (pattern == "/" && searchquery == "/" || pattern != "/") |
||
1458 | bestMatch = pattern; |
||
1459 | } |
||
1460 | } |
||
1461 | } |
||
1462 | |||
1463 | if (String.IsNullOrEmpty(bestMatch)) |
||
1464 | { |
||
1465 | llsdHandler = null; |
||
1466 | return false; |
||
1467 | } |
||
1468 | else |
||
1469 | { |
||
1470 | llsdHandler = m_llsdHandlers[bestMatch]; |
||
1471 | return true; |
||
1472 | } |
||
1473 | } |
||
1474 | } |
||
1475 | |||
1476 | private OSDMap GenerateNoLLSDHandlerResponse() |
||
1477 | { |
||
1478 | OSDMap map = new OSDMap(); |
||
1479 | map["reason"] = OSD.FromString("LLSDRequest"); |
||
1480 | map["message"] = OSD.FromString("No handler registered for LLSD Requests"); |
||
1481 | map["login"] = OSD.FromString("false"); |
||
1482 | return map; |
||
1483 | } |
||
1484 | |||
1485 | public byte[] HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response) |
||
1486 | { |
||
1487 | // m_log.DebugFormat( |
||
1488 | // "[BASE HTTP SERVER]: HandleHTTPRequest for request to {0}, method {1}", |
||
1489 | // request.RawUrl, request.HttpMethod); |
||
1490 | |||
1491 | switch (request.HttpMethod) |
||
1492 | { |
||
1493 | case "OPTIONS": |
||
1494 | response.StatusCode = (int)OSHttpStatusCode.SuccessOk; |
||
1495 | return null; |
||
1496 | |||
1497 | default: |
||
1498 | return HandleContentVerbs(request, response); |
||
1499 | } |
||
1500 | } |
||
1501 | |||
1502 | private byte[] HandleContentVerbs(OSHttpRequest request, OSHttpResponse response) |
||
1503 | { |
||
1504 | // m_log.DebugFormat("[BASE HTTP SERVER]: HandleContentVerbs for request to {0}", request.RawUrl); |
||
1505 | |||
1506 | // This is a test. There's a workable alternative.. as this way sucks. |
||
1507 | // We'd like to put this into a text file parhaps that's easily editable. |
||
1508 | // |
||
1509 | // For this test to work, I used the following secondlife.exe parameters |
||
1510 | // "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 |
||
1511 | // |
||
1512 | // Even after all that, there's still an error, but it's a start. |
||
1513 | // |
||
1514 | // I depend on show_login_form being in the secondlife.exe parameters to figure out |
||
1515 | // to display the form, or process it. |
||
1516 | // a better way would be nifty. |
||
1517 | |||
1518 | byte[] buffer; |
||
1519 | |||
1520 | Stream requestStream = request.InputStream; |
||
1521 | |||
1522 | Encoding encoding = Encoding.UTF8; |
||
1523 | StreamReader reader = new StreamReader(requestStream, encoding); |
||
1524 | |||
1525 | string requestBody = reader.ReadToEnd(); |
||
1526 | // avoid warning for now |
||
1527 | reader.ReadToEnd(); |
||
1528 | reader.Close(); |
||
1529 | requestStream.Close(); |
||
1530 | |||
1531 | Hashtable keysvals = new Hashtable(); |
||
1532 | Hashtable headervals = new Hashtable(); |
||
1533 | |||
1534 | Hashtable requestVars = new Hashtable(); |
||
1535 | |||
1536 | string host = String.Empty; |
||
1537 | |||
1538 | string[] querystringkeys = request.QueryString.AllKeys; |
||
1539 | string[] rHeaders = request.Headers.AllKeys; |
||
1540 | |||
1541 | keysvals.Add("body", requestBody); |
||
1542 | keysvals.Add("uri", request.RawUrl); |
||
1543 | keysvals.Add("content-type", request.ContentType); |
||
1544 | keysvals.Add("http-method", request.HttpMethod); |
||
1545 | |||
1546 | foreach (string queryname in querystringkeys) |
||
1547 | { |
||
1548 | // m_log.DebugFormat( |
||
1549 | // "[BASE HTTP SERVER]: Got query paremeter {0}={1}", queryname, request.QueryString[queryname]); |
||
1550 | keysvals.Add(queryname, request.QueryString[queryname]); |
||
1551 | requestVars.Add(queryname, keysvals[queryname]); |
||
1552 | } |
||
1553 | |||
1554 | foreach (string headername in rHeaders) |
||
1555 | { |
||
1556 | // m_log.Debug("[BASE HTTP SERVER]: " + headername + "=" + request.Headers[headername]); |
||
1557 | headervals[headername] = request.Headers[headername]; |
||
1558 | } |
||
1559 | |||
1560 | if (headervals.Contains("Host")) |
||
1561 | { |
||
1562 | host = (string)headervals["Host"]; |
||
1563 | } |
||
1564 | |||
1565 | keysvals.Add("headers", headervals); |
||
1566 | keysvals.Add("querystringkeys", querystringkeys); |
||
1567 | keysvals.Add("requestvars", requestVars); |
||
1568 | // keysvals.Add("form", request.Form); |
||
1569 | |||
1570 | if (keysvals.Contains("method")) |
||
1571 | { |
||
1572 | // m_log.Debug("[BASE HTTP SERVER]: Contains Method"); |
||
1573 | string method = (string) keysvals["method"]; |
||
1574 | // m_log.Debug("[BASE HTTP SERVER]: " + requestBody); |
||
1575 | GenericHTTPMethod requestprocessor; |
||
1576 | bool foundHandler = TryGetHTTPHandler(method, out requestprocessor); |
||
1577 | if (foundHandler) |
||
1578 | { |
||
1579 | Hashtable responsedata1 = requestprocessor(keysvals); |
||
1580 | buffer = DoHTTPGruntWork(responsedata1,response); |
||
1581 | |||
1582 | //SendHTML500(response); |
||
1583 | } |
||
1584 | else |
||
1585 | { |
||
1586 | // m_log.Warn("[BASE HTTP SERVER]: Handler Not Found"); |
||
1587 | buffer = SendHTML404(response, host); |
||
1588 | } |
||
1589 | } |
||
1590 | else |
||
1591 | { |
||
1592 | GenericHTTPMethod requestprocessor; |
||
1593 | bool foundHandler = TryGetHTTPHandlerPathBased(request.RawUrl, out requestprocessor); |
||
1594 | if (foundHandler) |
||
1595 | { |
||
1596 | Hashtable responsedata2 = requestprocessor(keysvals); |
||
1597 | buffer = DoHTTPGruntWork(responsedata2, response); |
||
1598 | |||
1599 | //SendHTML500(response); |
||
1600 | } |
||
1601 | else |
||
1602 | { |
||
1603 | // m_log.Warn("[BASE HTTP SERVER]: Handler Not Found2"); |
||
1604 | buffer = SendHTML404(response, host); |
||
1605 | } |
||
1606 | } |
||
1607 | |||
1608 | return buffer; |
||
1609 | } |
||
1610 | |||
1611 | private bool TryGetHTTPHandlerPathBased(string path, out GenericHTTPMethod httpHandler) |
||
1612 | { |
||
1613 | httpHandler = null; |
||
1614 | // Pull out the first part of the path |
||
1615 | // splitting the path by '/' means we'll get the following return.. |
||
1616 | // {0}/{1}/{2} |
||
1617 | // where {0} isn't something we really control 100% |
||
1618 | |||
1619 | string[] pathbase = path.Split('/'); |
||
1620 | string searchquery = "/"; |
||
1621 | |||
1622 | if (pathbase.Length < 1) |
||
1623 | return false; |
||
1624 | |||
1625 | for (int i = 1; i < pathbase.Length; i++) |
||
1626 | { |
||
1627 | searchquery += pathbase[i]; |
||
1628 | if (pathbase.Length - 1 != i) |
||
1629 | searchquery += "/"; |
||
1630 | } |
||
1631 | |||
1632 | // while the matching algorithm below doesn't require it, we're expecting a query in the form |
||
1633 | // |
||
1634 | // [] = optional |
||
1635 | // /resource/UUID/action[/action] |
||
1636 | // |
||
1637 | // now try to get the closest match to the reigstered path |
||
1638 | // at least for OGP, registered path would probably only consist of the /resource/ |
||
1639 | |||
1640 | string bestMatch = null; |
||
1641 | |||
1642 | // m_log.DebugFormat( |
||
1643 | // "[BASE HTTP HANDLER]: TryGetHTTPHandlerPathBased() looking for HTTP handler to match {0}", searchquery); |
||
1644 | |||
1645 | lock (m_HTTPHandlers) |
||
1646 | { |
||
1647 | foreach (string pattern in m_HTTPHandlers.Keys) |
||
1648 | { |
||
1649 | if (searchquery.ToLower().StartsWith(pattern.ToLower())) |
||
1650 | { |
||
1651 | if (String.IsNullOrEmpty(bestMatch) || searchquery.Length > bestMatch.Length) |
||
1652 | { |
||
1653 | // You have to specifically register for '/' and to get it, you must specifically request it |
||
1654 | if (pattern == "/" && searchquery == "/" || pattern != "/") |
||
1655 | bestMatch = pattern; |
||
1656 | } |
||
1657 | } |
||
1658 | } |
||
1659 | |||
1660 | if (String.IsNullOrEmpty(bestMatch)) |
||
1661 | { |
||
1662 | httpHandler = null; |
||
1663 | return false; |
||
1664 | } |
||
1665 | else |
||
1666 | { |
||
1667 | if (bestMatch == "/" && searchquery != "/") |
||
1668 | return false; |
||
1669 | |||
1670 | httpHandler = m_HTTPHandlers[bestMatch]; |
||
1671 | return true; |
||
1672 | } |
||
1673 | } |
||
1674 | } |
||
1675 | |||
1676 | internal byte[] DoHTTPGruntWork(Hashtable responsedata, OSHttpResponse response) |
||
1677 | { |
||
1678 | //m_log.Info("[BASE HTTP SERVER]: Doing HTTP Grunt work with response"); |
||
1679 | int responsecode = (int)responsedata["int_response_code"]; |
||
1680 | string responseString = (string)responsedata["str_response_string"]; |
||
1681 | string contentType = (string)responsedata["content_type"]; |
||
1682 | |||
1683 | if (responsedata.ContainsKey("error_status_text")) |
||
1684 | { |
||
1685 | response.StatusDescription = (string)responsedata["error_status_text"]; |
||
1686 | } |
||
1687 | if (responsedata.ContainsKey("http_protocol_version")) |
||
1688 | { |
||
1689 | response.ProtocolVersion = (string)responsedata["http_protocol_version"]; |
||
1690 | } |
||
1691 | |||
1692 | if (responsedata.ContainsKey("keepalive")) |
||
1693 | { |
||
1694 | bool keepalive = (bool)responsedata["keepalive"]; |
||
1695 | response.KeepAlive = keepalive; |
||
1696 | |||
1697 | } |
||
1698 | |||
1699 | if (responsedata.ContainsKey("reusecontext")) |
||
1700 | response.ReuseContext = (bool) responsedata["reusecontext"]; |
||
1701 | |||
1702 | // Cross-Origin Resource Sharing with simple requests |
||
1703 | if (responsedata.ContainsKey("access_control_allow_origin")) |
||
1704 | response.AddHeader("Access-Control-Allow-Origin", (string)responsedata["access_control_allow_origin"]); |
||
1705 | |||
1706 | //Even though only one other part of the entire code uses HTTPHandlers, we shouldn't expect this |
||
1707 | //and should check for NullReferenceExceptions |
||
1708 | |||
1709 | if (string.IsNullOrEmpty(contentType)) |
||
1710 | { |
||
1711 | contentType = "text/html"; |
||
1712 | } |
||
1713 | |||
1714 | // The client ignores anything but 200 here for web login, so ensure that this is 200 for that |
||
1715 | |||
1716 | response.StatusCode = responsecode; |
||
1717 | |||
1718 | if (responsecode == (int)OSHttpStatusCode.RedirectMovedPermanently) |
||
1719 | { |
||
1720 | response.RedirectLocation = (string)responsedata["str_redirect_location"]; |
||
1721 | response.StatusCode = responsecode; |
||
1722 | } |
||
1723 | |||
1724 | response.AddHeader("Content-Type", contentType); |
||
1725 | |||
1726 | byte[] buffer; |
||
1727 | |||
1728 | if (!(contentType.Contains("image") |
||
1729 | || contentType.Contains("x-shockwave-flash") |
||
1730 | || contentType.Contains("application/x-oar") |
||
1731 | || contentType.Contains("application/vnd.ll.mesh"))) |
||
1732 | { |
||
1733 | // Text |
||
1734 | buffer = Encoding.UTF8.GetBytes(responseString); |
||
1735 | } |
||
1736 | else |
||
1737 | { |
||
1738 | // Binary! |
||
1739 | buffer = Convert.FromBase64String(responseString); |
||
1740 | } |
||
1741 | |||
1742 | response.SendChunked = false; |
||
1743 | response.ContentLength64 = buffer.Length; |
||
1744 | response.ContentEncoding = Encoding.UTF8; |
||
1745 | |||
1746 | return buffer; |
||
1747 | } |
||
1748 | |||
1749 | public byte[] SendHTML404(OSHttpResponse response, string host) |
||
1750 | { |
||
1751 | // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s |
||
1752 | response.StatusCode = 404; |
||
1753 | response.AddHeader("Content-type", "text/html"); |
||
1754 | |||
1755 | string responseString = GetHTTP404(host); |
||
1756 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); |
||
1757 | |||
1758 | response.SendChunked = false; |
||
1759 | response.ContentLength64 = buffer.Length; |
||
1760 | response.ContentEncoding = Encoding.UTF8; |
||
1761 | |||
1762 | return buffer; |
||
1763 | } |
||
1764 | |||
1765 | public byte[] SendHTML500(OSHttpResponse response) |
||
1766 | { |
||
1767 | // I know this statuscode is dumb, but the client doesn't respond to 404s and 500s |
||
1768 | response.StatusCode = (int)OSHttpStatusCode.SuccessOk; |
||
1769 | response.AddHeader("Content-type", "text/html"); |
||
1770 | |||
1771 | string responseString = GetHTTP500(); |
||
1772 | byte[] buffer = Encoding.UTF8.GetBytes(responseString); |
||
1773 | |||
1774 | response.SendChunked = false; |
||
1775 | response.ContentLength64 = buffer.Length; |
||
1776 | response.ContentEncoding = Encoding.UTF8; |
||
1777 | |||
1778 | |||
1779 | return buffer; |
||
1780 | } |
||
1781 | |||
1782 | public void Start() |
||
1783 | { |
||
1784 | StartHTTP(); |
||
1785 | } |
||
1786 | |||
1787 | private void StartHTTP() |
||
1788 | { |
||
1789 | m_log.InfoFormat( |
||
1790 | "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); |
||
1791 | |||
1792 | try |
||
1793 | { |
||
1794 | //m_httpListener = new HttpListener(); |
||
1795 | |||
1796 | NotSocketErrors = 0; |
||
1797 | if (!m_ssl) |
||
1798 | { |
||
1799 | //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); |
||
1800 | //m_httpListener.Prefixes.Add("http://10.1.1.5:" + m_port + "/"); |
||
1801 | m_httpListener2 = CoolHTTPListener.Create(m_listenIPAddress, (int)m_port); |
||
1802 | m_httpListener2.ExceptionThrown += httpServerException; |
||
1803 | m_httpListener2.LogWriter = httpserverlog; |
||
1804 | |||
1805 | // Uncomment this line in addition to those in HttpServerLogWriter |
||
1806 | // if you want more detailed trace information from the HttpServer |
||
1807 | //m_httpListener2.UseTraceLogs = true; |
||
1808 | |||
1809 | //m_httpListener2.DisconnectHandler = httpServerDisconnectMonitor; |
||
1810 | } |
||
1811 | else |
||
1812 | { |
||
1813 | //m_httpListener.Prefixes.Add("https://+:" + (m_sslport) + "/"); |
||
1814 | //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); |
||
1815 | m_httpListener2 = CoolHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert); |
||
1816 | m_httpListener2.ExceptionThrown += httpServerException; |
||
1817 | m_httpListener2.LogWriter = httpserverlog; |
||
1818 | } |
||
1819 | |||
1820 | m_httpListener2.RequestReceived += OnRequest; |
||
1821 | //m_httpListener.Start(); |
||
1822 | m_httpListener2.Start(64); |
||
1823 | |||
1824 | // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events |
||
1825 | m_PollServiceManager = new PollServiceRequestManager(this, 3, 25000); |
||
1826 | m_PollServiceManager.Start(); |
||
1827 | HTTPDRunning = true; |
||
1828 | |||
1829 | //HttpListenerContext context; |
||
1830 | //while (true) |
||
1831 | //{ |
||
1832 | // context = m_httpListener.GetContext(); |
||
1833 | // ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(HandleRequest), context); |
||
1834 | // } |
||
1835 | } |
||
1836 | catch (Exception e) |
||
1837 | { |
||
1838 | m_log.Error("[BASE HTTP SERVER]: Error - " + e.Message); |
||
1839 | m_log.Error("[BASE HTTP SERVER]: Tip: Do you have permission to listen on port " + m_port + ", " + m_sslport + "?"); |
||
1840 | |||
1841 | // We want this exception to halt the entire server since in current configurations we aren't too |
||
1842 | // useful without inbound HTTP. |
||
1843 | throw e; |
||
1844 | } |
||
1845 | |||
1846 | m_requestsProcessedStat |
||
1847 | = new Stat( |
||
1848 | "HTTPRequestsServed", |
||
1849 | "Number of inbound HTTP requests processed", |
||
1850 | "", |
||
1851 | "requests", |
||
1852 | "httpserver", |
||
1853 | Port.ToString(), |
||
1854 | StatType.Pull, |
||
1855 | MeasuresOfInterest.AverageChangeOverTime, |
||
1856 | stat => stat.Value = RequestNumber, |
||
1857 | StatVerbosity.Debug); |
||
1858 | |||
1859 | StatsManager.RegisterStat(m_requestsProcessedStat); |
||
1860 | } |
||
1861 | |||
1862 | public void httpServerDisconnectMonitor(IHttpClientContext source, SocketError err) |
||
1863 | { |
||
1864 | switch (err) |
||
1865 | { |
||
1866 | case SocketError.NotSocket: |
||
1867 | NotSocketErrors++; |
||
1868 | |||
1869 | break; |
||
1870 | } |
||
1871 | } |
||
1872 | |||
1873 | public void httpServerException(object source, Exception exception) |
||
1874 | { |
||
1875 | m_log.Error(String.Format("[BASE HTTP SERVER]: {0} had an exception: {1} ", source.ToString(), exception.Message), exception); |
||
1876 | /* |
||
1877 | if (HTTPDRunning)// && NotSocketErrors > 5) |
||
1878 | { |
||
1879 | Stop(); |
||
1880 | Thread.Sleep(200); |
||
1881 | StartHTTP(); |
||
1882 | m_log.Warn("[HTTPSERVER]: Died. Trying to kick....."); |
||
1883 | } |
||
1884 | */ |
||
1885 | } |
||
1886 | |||
1887 | public void Stop() |
||
1888 | { |
||
1889 | HTTPDRunning = false; |
||
1890 | |||
1891 | StatsManager.DeregisterStat(m_requestsProcessedStat); |
||
1892 | |||
1893 | try |
||
1894 | { |
||
1895 | m_PollServiceManager.Stop(); |
||
1896 | |||
1897 | m_httpListener2.ExceptionThrown -= httpServerException; |
||
1898 | //m_httpListener2.DisconnectHandler = null; |
||
1899 | |||
1900 | m_httpListener2.LogWriter = null; |
||
1901 | m_httpListener2.RequestReceived -= OnRequest; |
||
1902 | m_httpListener2.Stop(); |
||
1903 | } |
||
1904 | catch (NullReferenceException) |
||
1905 | { |
||
1906 | m_log.Warn("[BASE HTTP SERVER]: Null Reference when stopping HttpServer."); |
||
1907 | } |
||
1908 | } |
||
1909 | |||
1910 | public void RemoveStreamHandler(string httpMethod, string path) |
||
1911 | { |
||
1912 | string handlerKey = GetHandlerKey(httpMethod, path); |
||
1913 | |||
1914 | //m_log.DebugFormat("[BASE HTTP SERVER]: Removing handler key {0}", handlerKey); |
||
1915 | |||
1916 | lock (m_streamHandlers) |
||
1917 | m_streamHandlers.Remove(handlerKey); |
||
1918 | } |
||
1919 | |||
1920 | public void RemoveHTTPHandler(string httpMethod, string path) |
||
1921 | { |
||
1922 | lock (m_HTTPHandlers) |
||
1923 | { |
||
1924 | if (httpMethod != null && httpMethod.Length == 0) |
||
1925 | { |
||
1926 | m_HTTPHandlers.Remove(path); |
||
1927 | return; |
||
1928 | } |
||
1929 | |||
1930 | m_HTTPHandlers.Remove(GetHandlerKey(httpMethod, path)); |
||
1931 | } |
||
1932 | } |
||
1933 | |||
1934 | public void RemovePollServiceHTTPHandler(string httpMethod, string path) |
||
1935 | { |
||
1936 | lock (m_pollHandlers) |
||
1937 | m_pollHandlers.Remove(path); |
||
1938 | } |
||
1939 | |||
1940 | // public bool RemoveAgentHandler(string agent, IHttpAgentHandler handler) |
||
1941 | // { |
||
1942 | // lock (m_agentHandlers) |
||
1943 | // { |
||
1944 | // IHttpAgentHandler foundHandler; |
||
1945 | // |
||
1946 | // if (m_agentHandlers.TryGetValue(agent, out foundHandler) && foundHandler == handler) |
||
1947 | // { |
||
1948 | // m_agentHandlers.Remove(agent); |
||
1949 | // return true; |
||
1950 | // } |
||
1951 | // } |
||
1952 | // |
||
1953 | // return false; |
||
1954 | // } |
||
1955 | |||
1956 | public void RemoveXmlRPCHandler(string method) |
||
1957 | { |
||
1958 | lock (m_rpcHandlers) |
||
1959 | m_rpcHandlers.Remove(method); |
||
1960 | } |
||
1961 | |||
1962 | public void RemoveJsonRPCHandler(string method) |
||
1963 | { |
||
1964 | lock(jsonRpcHandlers) |
||
1965 | jsonRpcHandlers.Remove(method); |
||
1966 | } |
||
1967 | |||
1968 | public bool RemoveLLSDHandler(string path, LLSDMethod handler) |
||
1969 | { |
||
1970 | lock (m_llsdHandlers) |
||
1971 | { |
||
1972 | LLSDMethod foundHandler; |
||
1973 | |||
1974 | if (m_llsdHandlers.TryGetValue(path, out foundHandler) && foundHandler == handler) |
||
1975 | { |
||
1976 | m_llsdHandlers.Remove(path); |
||
1977 | return true; |
||
1978 | } |
||
1979 | } |
||
1980 | |||
1981 | return false; |
||
1982 | } |
||
1983 | |||
1984 | public string GetHTTP404(string host) |
||
1985 | { |
||
1986 | string file = Path.Combine(".", "http_404.html"); |
||
1987 | if (!File.Exists(file)) |
||
1988 | return getDefaultHTTP404(host); |
||
1989 | |||
1990 | StreamReader sr = File.OpenText(file); |
||
1991 | string result = sr.ReadToEnd(); |
||
1992 | sr.Close(); |
||
1993 | return result; |
||
1994 | } |
||
1995 | |||
1996 | public string GetHTTP500() |
||
1997 | { |
||
1998 | string file = Path.Combine(".", "http_500.html"); |
||
1999 | if (!File.Exists(file)) |
||
2000 | return getDefaultHTTP500(); |
||
2001 | |||
2002 | StreamReader sr = File.OpenText(file); |
||
2003 | string result = sr.ReadToEnd(); |
||
2004 | sr.Close(); |
||
2005 | return result; |
||
2006 | } |
||
2007 | |||
2008 | // Fallback HTTP responses in case the HTTP error response files don't exist |
||
2009 | private static string getDefaultHTTP404(string host) |
||
2010 | { |
||
2011 | 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>"; |
||
2012 | } |
||
2013 | |||
2014 | private static string getDefaultHTTP500() |
||
2015 | { |
||
2016 | 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>"; |
||
2017 | } |
||
2018 | } |
||
2019 | |||
2020 | public class HttpServerContextObj |
||
2021 | { |
||
2022 | public IHttpClientContext context = null; |
||
2023 | public IHttpRequest req = null; |
||
2024 | public OSHttpRequest oreq = null; |
||
2025 | public OSHttpResponse oresp = null; |
||
2026 | |||
2027 | public HttpServerContextObj(IHttpClientContext contxt, IHttpRequest reqs) |
||
2028 | { |
||
2029 | context = contxt; |
||
2030 | req = reqs; |
||
2031 | } |
||
2032 | |||
2033 | public HttpServerContextObj(OSHttpRequest osreq, OSHttpResponse osresp) |
||
2034 | { |
||
2035 | oreq = osreq; |
||
2036 | oresp = osresp; |
||
2037 | } |
||
2038 | } |
||
2039 | |||
2040 | /// <summary> |
||
2041 | /// Relays HttpServer log messages to our own logging mechanism. |
||
2042 | /// </summary> |
||
2043 | /// To use this you must uncomment the switch section |
||
2044 | /// |
||
2045 | /// You may also be able to get additional trace information from HttpServer if you uncomment the UseTraceLogs |
||
2046 | /// property in StartHttp() for the HttpListener |
||
2047 | public class HttpServerLogWriter : ILogWriter |
||
2048 | { |
||
2049 | // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
2050 | |||
2051 | public void Write(object source, LogPrio priority, string message) |
||
2052 | { |
||
2053 | /* |
||
2054 | switch (priority) |
||
2055 | { |
||
2056 | case LogPrio.Trace: |
||
2057 | m_log.DebugFormat("[{0}]: {1}", source, message); |
||
2058 | break; |
||
2059 | case LogPrio.Debug: |
||
2060 | m_log.DebugFormat("[{0}]: {1}", source, message); |
||
2061 | break; |
||
2062 | case LogPrio.Error: |
||
2063 | m_log.ErrorFormat("[{0}]: {1}", source, message); |
||
2064 | break; |
||
2065 | case LogPrio.Info: |
||
2066 | m_log.InfoFormat("[{0}]: {1}", source, message); |
||
2067 | break; |
||
2068 | case LogPrio.Warning: |
||
2069 | m_log.WarnFormat("[{0}]: {1}", source, message); |
||
2070 | break; |
||
2071 | case LogPrio.Fatal: |
||
2072 | m_log.ErrorFormat("[{0}]: FATAL! - {1}", source, message); |
||
2073 | break; |
||
2074 | default: |
||
2075 | break; |
||
2076 | } |
||
2077 | */ |
||
2078 | |||
2079 | return; |
||
2080 | } |
||
2081 | } |
||
2082 | } |