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

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

More cleanup, allocation improvements

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