source: binary-improvements/MapRendering/Web/Web.cs @ 332

Last change on this file since 332 was 332, checked in by alloc, 5 years ago

*Latest optimizations

File size: 9.2 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net;
5using System.Net.Sockets;
6using System.Reflection;
7using System.Text;
8using System.Threading;
9using AllocsFixes.FileCache;
10using AllocsFixes.NetConnections.Servers.Web.Handlers;
11using UnityEngine;
12using UnityEngine.Profiling;
13
14namespace AllocsFixes.NetConnections.Servers.Web {
15        public class Web : IConsoleServer {
16                private const int GUEST_PERMISSION_LEVEL = 2000;
17                public static int handlingCount;
18                public static int currentHandlers;
19                public static long totalHandlingTime = 0;
20                private readonly HttpListener _listener = new HttpListener ();
21                private readonly string dataFolder;
22                private readonly Dictionary<string, PathHandler> handlers = new CaseInsensitiveStringDictionary<PathHandler> ();
23                private readonly bool useStaticCache;
24
25                public ConnectionHandler connectionHandler;
26
27                public Web () {
28                        try {
29                                int webPort = GamePrefs.GetInt (EnumGamePrefs.ControlPanelPort);
30                                if (webPort < 1 || webPort > 65533) {
31                                        Log.Out ("Webserver not started (ControlPanelPort not within 1-65533)");
32                                        return;
33                                }
34
35                                if (!Directory.Exists (Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) +
36                                                       "/webserver")) {
37                                        Log.Out ("Webserver not started (folder \"webserver\" not found in WebInterface mod folder)");
38                                        return;
39                                }
40
41                                // TODO: Read from config
42                                useStaticCache = false;
43
44                                dataFolder = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) + "/webserver";
45
46                                if (!HttpListener.IsSupported) {
47                                        Log.Out ("Webserver not started (needs Windows XP SP2, Server 2003 or later or Mono)");
48                                        return;
49                                }
50
51                                handlers.Add (
52                                        "/index.htm",
53                                        new SimpleRedirectHandler ("/static/index.html"));
54                                handlers.Add (
55                                        "/favicon.ico",
56                                        new SimpleRedirectHandler ("/static/favicon.ico"));
57                                handlers.Add (
58                                        "/session/",
59                                        new SessionHandler (
60                                                "/session/",
61                                                dataFolder,
62                                                this)
63                                );
64                                handlers.Add (
65                                        "/userstatus",
66                                        new UserStatusHandler ()
67                                );
68                                if (useStaticCache) {
69                                        handlers.Add (
70                                                "/static/",
71                                                new StaticHandler (
72                                                        "/static/",
73                                                        dataFolder,
74                                                        new SimpleCache (),
75                                                        false)
76                                        );
77                                } else {
78                                        handlers.Add (
79                                                "/static/",
80                                                new StaticHandler (
81                                                        "/static/",
82                                                        dataFolder,
83                                                        new DirectAccess (),
84                                                        false)
85                                        );
86                                }
87
88                                handlers.Add (
89                                        "/itemicons/",
90                                        new ItemIconHandler (
91                                                "/itemicons/",
92                                                true)
93                                );
94
95                                handlers.Add (
96                                        "/map/",
97                                        new StaticHandler (
98                                                "/map/",
99                                                GameUtils.GetSaveGameDir () + "/map",
100                                                MapRendering.MapRendering.GetTileCache (),
101                                                false,
102                                                "web.map")
103                                );
104
105                                handlers.Add (
106                                        "/api/",
107                                        new ApiHandler ("/api/")
108                                );
109
110                                connectionHandler = new ConnectionHandler ();
111
112                                _listener.Prefixes.Add (string.Format ("http://*:{0}/", webPort + 2));
113                                _listener.Start ();
114
115                                SdtdConsole.Instance.RegisterServer (this);
116
117                                _listener.BeginGetContext (HandleRequest, _listener);
118
119                                Log.Out ("Started Webserver on " + (webPort + 2));
120                        } catch (Exception e) {
121                                Log.Out ("Error in Web.ctor: " + e);
122                        }
123                }
124
125                public void Disconnect () {
126                        try {
127                                _listener.Stop ();
128                                _listener.Close ();
129                        } catch (Exception e) {
130                                Log.Out ("Error in Web.Disconnect: " + e);
131                        }
132                }
133
134                public void SendLine (string line) {
135                        connectionHandler.SendLine (line);
136                }
137
138                public void SendLog (string text, string trace, LogType type) {
139                        // Do nothing, handled by LogBuffer internally
140                }
141
142                public static bool isSslRedirected (HttpListenerRequest req) {
143                        string proto = req.Headers ["X-Forwarded-Proto"];
144                        if (!string.IsNullOrEmpty (proto)) {
145                                return proto.Equals ("https", StringComparison.OrdinalIgnoreCase);
146                        }
147
148                        return false;
149                }
150               
151                private readonly Version HttpProtocolVersion = new Version(1, 1);
152               
153#if ENABLE_PROFILER
154                private readonly CustomSampler authSampler = CustomSampler.Create ("Auth");
155                private readonly CustomSampler handlerSampler = CustomSampler.Create ("Handler");
156#endif
157
158                private void HandleRequest (IAsyncResult result) {
159                        if (!_listener.IsListening) {
160                                return;
161                        }
162
163                        Interlocked.Increment (ref handlingCount);
164                        Interlocked.Increment (ref currentHandlers);
165
166//                              MicroStopwatch msw = new MicroStopwatch ();
167#if ENABLE_PROFILER
168                        Profiler.BeginThreadProfiling ("AllocsMods", "WebRequest");
169                        HttpListenerContext ctx = _listener.EndGetContext (result);
170                        try {
171#else
172                        HttpListenerContext ctx = _listener.EndGetContext (result);
173                        _listener.BeginGetContext (HandleRequest, _listener);
174#endif
175                        try {
176                                HttpListenerRequest request = ctx.Request;
177                                HttpListenerResponse response = ctx.Response;
178                                response.SendChunked = false;
179
180                                response.ProtocolVersion = HttpProtocolVersion;
181
182                                WebConnection conn;
183#if ENABLE_PROFILER
184                                authSampler.Begin ();
185#endif
186                                int permissionLevel = DoAuthentication (request, out conn);
187#if ENABLE_PROFILER
188                                authSampler.End ();
189#endif
190
191
192                                //Log.Out ("Login status: conn!=null: {0}, permissionlevel: {1}", conn != null, permissionLevel);
193
194
195                                if (conn != null) {
196                                        Cookie cookie = new Cookie ("sid", conn.SessionID, "/");
197                                        cookie.Expired = false;
198                                        cookie.Expires = new DateTime (2020, 1, 1);
199                                        cookie.HttpOnly = true;
200                                        cookie.Secure = false;
201                                        response.AppendCookie (cookie);
202                                }
203
204                                // No game yet -> fail request
205                                if (GameManager.Instance.World == null) {
206                                        response.StatusCode = (int) HttpStatusCode.ServiceUnavailable;
207                                        return;
208                                }
209
210                                if (request.Url.AbsolutePath.Length < 2) {
211                                        handlers ["/index.htm"].HandleRequest (request, response, conn, permissionLevel);
212                                        return;
213                                } else {
214                                        foreach (KeyValuePair<string, PathHandler> kvp in handlers) {
215                                                if (request.Url.AbsolutePath.StartsWith (kvp.Key)) {
216                                                        if (!kvp.Value.IsAuthorizedForHandler (conn, permissionLevel)) {
217                                                                response.StatusCode = (int) HttpStatusCode.Forbidden;
218                                                                if (conn != null) {
219                                                                        //Log.Out ("Web.HandleRequest: user '{0}' not allowed to access '{1}'", conn.SteamID, kvp.Value.ModuleName);
220                                                                }
221                                                        } else {
222#if ENABLE_PROFILER
223                                                                handlerSampler.Begin ();
224#endif
225                                                                kvp.Value.HandleRequest (request, response, conn, permissionLevel);
226#if ENABLE_PROFILER
227                                                                handlerSampler.End ();
228#endif
229                                                        }
230
231                                                        return;
232                                                }
233                                        }
234                                }
235
236                                // Not really relevant for non-debugging purposes:
237                                //Log.Out ("Error in Web.HandleRequest(): No handler found for path \"" + request.Url.AbsolutePath + "\"");
238                                response.StatusCode = (int) HttpStatusCode.NotFound;
239                        } catch (IOException e) {
240                                if (e.InnerException is SocketException) {
241                                        Log.Out ("Error in Web.HandleRequest(): Remote host closed connection: " +
242                                                 e.InnerException.Message);
243                                } else {
244                                        Log.Out ("Error (IO) in Web.HandleRequest(): " + e);
245                                }
246                        } catch (Exception e) {
247                                Log.Out ("Error in Web.HandleRequest(): " + e);
248                        } finally {
249                                if (ctx != null && !ctx.Response.SendChunked) {
250                                        ctx.Response.Close ();
251                                }
252
253//                                      msw.Stop ();
254//                                      totalHandlingTime += msw.ElapsedMicroseconds;
255//                                      Log.Out ("Web.HandleRequest(): Took {0} µs", msw.ElapsedMicroseconds);
256                                Interlocked.Decrement (ref currentHandlers);
257                        }
258#if ENABLE_PROFILER
259                        } finally {
260                                _listener.BeginGetContext (HandleRequest, _listener);
261                                Profiler.EndThreadProfiling ();
262                        }
263#endif
264                }
265
266                private int DoAuthentication (HttpListenerRequest _req, out WebConnection _con) {
267                        _con = null;
268
269                        string sessionId = null;
270                        if (_req.Cookies ["sid"] != null) {
271                                sessionId = _req.Cookies ["sid"].Value;
272                        }
273
274                        if (!string.IsNullOrEmpty (sessionId)) {
275                                WebConnection con = connectionHandler.IsLoggedIn (sessionId, _req.RemoteEndPoint.Address);
276                                if (con != null) {
277                                        _con = con;
278                                        return GameManager.Instance.adminTools.GetAdminToolsClientInfo (_con.SteamID.ToString ())
279                                                .PermissionLevel;
280                                }
281                        }
282
283                        if (_req.QueryString ["adminuser"] != null && _req.QueryString ["admintoken"] != null) {
284                                WebPermissions.AdminToken admin = WebPermissions.Instance.GetWebAdmin (_req.QueryString ["adminuser"],
285                                        _req.QueryString ["admintoken"]);
286                                if (admin != null) {
287                                        return admin.permissionLevel;
288                                }
289
290                                Log.Warning ("Invalid Admintoken used from " + _req.RemoteEndPoint);
291                        }
292
293                        if (_req.Url.AbsolutePath.StartsWith ("/session/verify", StringComparison.OrdinalIgnoreCase)) {
294                                try {
295                                        ulong id = OpenID.Validate (_req);
296                                        if (id > 0) {
297                                                WebConnection con = connectionHandler.LogIn (id, _req.RemoteEndPoint.Address);
298                                                _con = con;
299                                                int level = GameManager.Instance.adminTools.GetAdminToolsClientInfo (id.ToString ())
300                                                        .PermissionLevel;
301                                                Log.Out ("Steam OpenID login from {0} with ID {1}, permission level {2}",
302                                                        _req.RemoteEndPoint.ToString (), con.SteamID, level);
303                                                return level;
304                                        }
305
306                                        Log.Out ("Steam OpenID login failed from {0}", _req.RemoteEndPoint.ToString ());
307                                } catch (Exception e) {
308                                        Log.Error ("Error validating login:");
309                                        Log.Exception (e);
310                                }
311                        }
312
313                        return GUEST_PERMISSION_LEVEL;
314                }
315
316                public static void SetResponseTextContent (HttpListenerResponse resp, string text) {
317                        byte[] buf = Encoding.UTF8.GetBytes (text);
318                        resp.ContentLength64 = buf.Length;
319                        resp.ContentType = "text/html";
320                        resp.ContentEncoding = Encoding.UTF8;
321                        resp.OutputStream.Write (buf, 0, buf.Length);
322                }
323        }
324}
Note: See TracBrowser for help on using the repository browser.