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