Ignore:
Timestamp:
Aug 11, 2021, 6:22:03 PM (3 years ago)
Author:
alloc
Message:

Web:

  • Added SSE (ServerSentEvents) subsystem
  • Added log endpoint to SSE. Less heavy weight and more responsive way of watching the server log
  • Bunch of refactoring
Location:
binary-improvements/MapRendering
Files:
4 added
10 edited

Legend:

Unmodified
Added
Removed
  • binary-improvements/MapRendering/API.cs

    r351 r367  
    44namespace AllocsFixes {
    55        public class API : IModApi {
     6                private Web webInstance;
     7               
    68                public void InitMod () {
    79                        ModEvents.GameStartDone.RegisterHandler (GameStartDone);
     
    1214                private void GameStartDone () {
    1315                        // ReSharper disable once ObjectCreationAsStatement
    14                         new Web ();
     16                        webInstance = new Web ();
    1517                        LogBuffer.Init ();
    1618
     
    2123
    2224                private void GameShutdown () {
     25                        webInstance.Shutdown ();
    2326                        MapRendering.MapRendering.Shutdown ();
    2427                }
  • binary-improvements/MapRendering/Web/API/Null.cs

    r351 r367  
    44namespace AllocsFixes.NetConnections.Servers.Web.API {
    55        public class Null : WebAPI {
     6                public Null (string _name) : base(_name) {
     7                }
     8               
    69                public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
    710                        int _permissionLevel) {
  • binary-improvements/MapRendering/Web/API/WebAPI.cs

    r351 r367  
    22using System.Text;
    33using AllocsFixes.JSON;
    4 using UnityEngine.Profiling;
    54
    65namespace AllocsFixes.NetConnections.Servers.Web.API {
     
    87                public readonly string Name;
    98
    10                 protected WebAPI () {
    11                         Name = GetType ().Name;
     9                protected WebAPI (string _name = null) {
     10                        Name = _name ?? GetType ().Name;
    1211                }
    1312
    1413#if ENABLE_PROFILER
    15                 private static readonly CustomSampler jsonSerializeSampler = CustomSampler.Create ("JSON_Serialize");
    16                 private static readonly CustomSampler netWriteSampler = CustomSampler.Create ("JSON_Write");
     14                private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Serialize");
     15                private static readonly UnityEngine.Profiling.CustomSampler netWriteSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Write");
    1716#endif
    1817
  • binary-improvements/MapRendering/Web/Handlers/ApiHandler.cs

    r351 r367  
    44using System.Reflection;
    55using AllocsFixes.NetConnections.Servers.Web.API;
    6 using UnityEngine.Profiling;
    76
    87namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
    98        public class ApiHandler : PathHandler {
    109                private readonly Dictionary<string, WebAPI> apis = new CaseInsensitiveStringDictionary<WebAPI> ();
    11                 private readonly string staticPart;
    1210
    13                 public ApiHandler (string _staticPart, string _moduleName = null) : base (_moduleName) {
    14                         staticPart = _staticPart;
     11                public ApiHandler (string _moduleName = null) : base (_moduleName) {
    1512
    1613                        foreach (Type t in Assembly.GetExecutingAssembly ().GetTypes ()) {
     
    1916                                        if (ctor != null) {
    2017                                                WebAPI apiInstance = (WebAPI) ctor.Invoke (new object [0]);
    21                                                 addApi (apiInstance.Name, apiInstance);
     18                                                addApi (apiInstance);
    2219                                        }
    2320                                }
    2421                        }
    2522
    26                         // Add dummy types
    27                         Type dummy_t = typeof (Null);
    28                         ConstructorInfo dummy_ctor = dummy_t.GetConstructor (new Type [0]);
    29                         if (dummy_ctor != null) {
    30                                 WebAPI dummy_apiInstance = (WebAPI) dummy_ctor.Invoke (new object[0]);
    31 
    32                                 // Permissions that don't map to a real API
    33                                 addApi ("viewallclaims", dummy_apiInstance);
    34                                 addApi ("viewallplayers", dummy_apiInstance);
    35                         }
     23                        // Permissions that don't map to a real API
     24                        addApi (new Null ("viewallclaims"));
     25                        addApi (new Null ("viewallplayers"));
    3626                }
    3727
    38                 private void addApi (string _apiName, WebAPI _api) {
    39                         apis.Add (_apiName, _api);
    40                         WebPermissions.Instance.AddKnownModule ("webapi." + _apiName, _api.DefaultPermissionLevel ());
     28                private void addApi (WebAPI _api) {
     29                        apis.Add (_api.Name, _api);
     30                        WebPermissions.Instance.AddKnownModule ("webapi." + _api.Name, _api.DefaultPermissionLevel ());
    4131                }
    4232
    4333#if ENABLE_PROFILER
    44                 private static readonly CustomSampler apiHandlerSampler = CustomSampler.Create ("API_Handler");
     34                private static readonly UnityEngine.Profiling.CustomSampler apiHandlerSampler = UnityEngine.Profiling.CustomSampler.Create ("API_Handler");
    4535#endif
    4636
    4737                public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
    4838                        int _permissionLevel) {
    49                         string apiName = _req.Url.AbsolutePath.Remove (0, staticPart.Length);
     39                        string apiName = _req.Url.AbsolutePath.Remove (0, urlBasePath.Length);
    5040
    51                         WebAPI api;
    52                         if (!apis.TryGetValue (apiName, out api)) {
    53                                 Log.Out ("Error in ApiHandler.HandleRequest(): No handler found for API \"" + apiName + "\"");
     41                        if (!apis.TryGetValue (apiName, out WebAPI api)) {
     42                                Log.Out ($"Error in {nameof(ApiHandler)}.HandleRequest(): No handler found for API \"{apiName}\"");
    5443                                _resp.StatusCode = (int) HttpStatusCode.NotFound;
    5544                                return;
    5645                        }
    5746
    58                         if (!AuthorizeForCommand (apiName, _user, _permissionLevel)) {
     47                        if (!AuthorizeForApi (apiName, _permissionLevel)) {
    5948                                _resp.StatusCode = (int) HttpStatusCode.Forbidden;
    6049                                if (_user != null) {
    61                                         //Log.Out ("ApiHandler: user '{0}' not allowed to execute '{1}'", user.SteamID, apiName);
     50                                        //Log.Out ($"{nameof(ApiHandler)}: user '{user.SteamID}' not allowed to execute '{apiName}'");
    6251                                }
    6352
     
    7463#endif
    7564                        } catch (Exception e) {
    76                                 Log.Error ("Error in ApiHandler.HandleRequest(): Handler {0} threw an exception:", api.Name);
     65                                Log.Error ($"Error in {nameof(ApiHandler)}.HandleRequest(): Handler {api.Name} threw an exception:");
    7766                                Log.Exception (e);
    7867                                _resp.StatusCode = (int) HttpStatusCode.InternalServerError;
     
    8069                }
    8170
    82                 private bool AuthorizeForCommand (string _apiName, WebConnection _user, int _permissionLevel) {
     71                private bool AuthorizeForApi (string _apiName, int _permissionLevel) {
    8372                        return WebPermissions.Instance.ModuleAllowedWithLevel ("webapi." + _apiName, _permissionLevel);
    8473                }
  • binary-improvements/MapRendering/Web/Handlers/ItemIconHandler.cs

    r354 r367  
    1111                private readonly bool logMissingFiles;
    1212
    13                 private readonly string staticPart;
    1413                private bool loaded;
    1514
     
    1817                }
    1918
    20                 public ItemIconHandler (string _staticPart, bool _logMissingFiles, string _moduleName = null) : base (_moduleName) {
    21                         staticPart = _staticPart;
     19                public ItemIconHandler (bool _logMissingFiles, string _moduleName = null) : base (_moduleName) {
    2220                        logMissingFiles = _logMissingFiles;
    2321                        Instance = this;
     
    3432                        }
    3533
    36                         string requestFileName = _req.Url.AbsolutePath.Remove (0, staticPart.Length);
     34                        string requestFileName = _req.Url.AbsolutePath.Remove (0, urlBasePath.Length);
    3735                        requestFileName = requestFileName.Remove (requestFileName.LastIndexOf ('.'));
    3836
  • binary-improvements/MapRendering/Web/Handlers/PathHandler.cs

    r351 r367  
    33namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
    44        public abstract class PathHandler {
    5                 private readonly string moduleName;
     5                protected readonly string moduleName;
     6                protected string urlBasePath;
     7                protected Web parent;
    68
    79                protected PathHandler (string _moduleName, int _defaultPermissionLevel = 0) {
     
    2426                        return true;
    2527                }
     28
     29                public virtual void Shutdown () {
     30                }
     31
     32                public virtual void SetBasePathAndParent (Web _parent, string _relativePath) {
     33                        parent = _parent;
     34                        urlBasePath = _relativePath;
     35                }
    2636        }
    2737}
  • binary-improvements/MapRendering/Web/Handlers/SessionHandler.cs

    r351 r367  
    77                private readonly string footer = "";
    88                private readonly string header = "";
    9                 private readonly Web parent;
    10                 private readonly string staticPart;
    119
    12                 public SessionHandler (string _staticPart, string _dataFolder, Web _parent, string _moduleName = null) :
    13                         base (_moduleName) {
    14                         staticPart = _staticPart;
    15                         parent = _parent;
    16 
     10                public SessionHandler (string _dataFolder, string _moduleName = null) : base (_moduleName) {
    1711                        if (File.Exists (_dataFolder + "/sessionheader.tmpl")) {
    1812                                header = File.ReadAllText (_dataFolder + "/sessionheader.tmpl");
     
    2620                public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
    2721                        int _permissionLevel) {
    28                         string subpath = _req.Url.AbsolutePath.Remove (0, staticPart.Length);
     22                        string subpath = _req.Url.AbsolutePath.Remove (0, urlBasePath.Length);
    2923
    3024                        StringBuilder result = new StringBuilder ();
     
    5246                                        "<h1>Not logged in, <a href=\"/static/index.html\">click to return to main page</a>.</h1>");
    5347                        } else if (subpath.StartsWith ("login")) {
    54                                 string host = (Web.isSslRedirected (_req) ? "https://" : "http://") + _req.UserHostName;
     48                                string host = (Web.IsSslRedirected (_req) ? "https://" : "http://") + _req.UserHostName;
    5549                                string url = OpenID.GetOpenIdLoginUrl (host, host + "/session/verify");
    5650                                _resp.Redirect (url);
  • binary-improvements/MapRendering/Web/Handlers/StaticHandler.cs

    r351 r367  
    88                private readonly string datapath;
    99                private readonly bool logMissingFiles;
    10                 private readonly string staticPart;
    1110
    12                 public StaticHandler (string _staticPart, string _filePath, AbstractCache _cache, bool _logMissingFiles,
     11                public StaticHandler (string _filePath, AbstractCache _cache, bool _logMissingFiles,
    1312                        string _moduleName = null) : base (_moduleName) {
    14                         staticPart = _staticPart;
    1513                        datapath = _filePath + (_filePath [_filePath.Length - 1] == '/' ? "" : "/");
    1614                        cache = _cache;
     
    2018                public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
    2119                        int _permissionLevel) {
    22                         string fn = _req.Url.AbsolutePath.Remove (0, staticPart.Length);
     20                        string fn = _req.Url.AbsolutePath.Remove (0, urlBasePath.Length);
    2321
    2422                        byte[] content = cache.GetFileContent (datapath + fn);
  • binary-improvements/MapRendering/Web/Web.cs

    r360 r367  
    99using AllocsFixes.FileCache;
    1010using AllocsFixes.NetConnections.Servers.Web.Handlers;
     11using AllocsFixes.NetConnections.Servers.Web.SSE;
    1112using UnityEngine;
    12 using UnityEngine.Profiling;
    1313
    1414namespace AllocsFixes.NetConnections.Servers.Web {
     
    1818                public static int currentHandlers;
    1919                public static long totalHandlingTime = 0;
    20                 private readonly HttpListener _listener = new HttpListener ();
    21                 private readonly string dataFolder;
     20                private readonly HttpListener listener = new HttpListener ();
    2221                private readonly Dictionary<string, PathHandler> handlers = new CaseInsensitiveStringDictionary<PathHandler> ();
    23                 private readonly bool useStaticCache;
    24 
    25                 public ConnectionHandler connectionHandler;
     22
     23                public readonly ConnectionHandler connectionHandler;
    2624
    2725                public Web () {
     
    4038
    4139                                // TODO: Read from config
    42                                 useStaticCache = false;
    43 
    44                                 dataFolder = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) + "/webserver";
     40                                bool useStaticCache = false;
     41
     42                                string dataFolder = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) + "/webserver";
    4543
    4644                                if (!HttpListener.IsSupported) {
     
    4947                                }
    5048
    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/",
     49                               
     50                                RegisterPathHandler ("/index.htm", new SimpleRedirectHandler ("/static/index.html"));
     51                                RegisterPathHandler ("/favicon.ico", new SimpleRedirectHandler ("/static/favicon.ico"));
     52                                RegisterPathHandler ("/session/", new SessionHandler (dataFolder));
     53                                RegisterPathHandler ("/userstatus", new UserStatusHandler ());
     54                                RegisterPathHandler ("/static/", new StaticHandler (
    6155                                                dataFolder,
    62                                                 this)
     56                                                useStaticCache ? (AbstractCache) new SimpleCache () : new DirectAccess (),
     57                                                false)
    6358                                );
    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/",
     59                                RegisterPathHandler ("/itemicons/", new ItemIconHandler (true));
     60                                RegisterPathHandler ("/map/", new StaticHandler (
    9961                                                GameUtils.GetSaveGameDir () + "/map",
    10062                                                MapRendering.MapRendering.GetTileCache (),
     
    10264                                                "web.map")
    10365                                );
    104 
    105                                 handlers.Add (
    106                                         "/api/",
    107                                         new ApiHandler ("/api/")
    108                                 );
     66                                RegisterPathHandler ("/api/", new ApiHandler ());
     67                                RegisterPathHandler ("/sse/", new SseHandler ());
    10968
    11069                                connectionHandler = new ConnectionHandler ();
    11170
    112                                 _listener.Prefixes.Add (string.Format ("http://*:{0}/", webPort + 2));
    113                                 _listener.Start ();
     71                                listener.Prefixes.Add ($"http://*:{webPort + 2}/");
     72                                listener.Start ();
    11473
    11574                                SdtdConsole.Instance.RegisterServer (this);
    11675
    117                                 _listener.BeginGetContext (HandleRequest, _listener);
     76                                listener.BeginGetContext (HandleRequest, listener);
    11877
    11978                                Log.Out ("Started Webserver on " + (webPort + 2));
     
    12382                }
    12483
     84                public void RegisterPathHandler (string _urlBasePath, PathHandler _handler) {
     85                        if (handlers.ContainsKey (_urlBasePath)) {
     86                                Log.Error ($"Web: Handler for relative path {_urlBasePath} already registerd.");
     87                                return;
     88                        }
     89                       
     90                        handlers.Add (_urlBasePath, _handler);
     91                        _handler.SetBasePathAndParent (this, _urlBasePath);
     92                }
     93
    12594                public void Disconnect () {
    12695                        try {
    127                                 _listener.Stop ();
    128                                 _listener.Close ();
     96                                listener.Stop ();
     97                                listener.Close ();
    12998                        } catch (Exception e) {
    13099                                Log.Out ("Error in Web.Disconnect: " + e);
     
    132101                }
    133102
     103                public void Shutdown () {
     104                        foreach (KeyValuePair<string, PathHandler> kvp in handlers) {
     105                                kvp.Value.Shutdown ();
     106                        }
     107                }
     108
    134109                public void SendLine (string _line) {
    135110                        connectionHandler.SendLine (_line);
     
    140115                }
    141116
    142                 public static bool isSslRedirected (HttpListenerRequest _req) {
     117                public static bool IsSslRedirected (HttpListenerRequest _req) {
    143118                        string proto = _req.Headers ["X-Forwarded-Proto"];
    144                         if (!string.IsNullOrEmpty (proto)) {
    145                                 return proto.Equals ("https", StringComparison.OrdinalIgnoreCase);
    146                         }
    147 
    148                         return false;
     119                        return !string.IsNullOrEmpty (proto) && proto.Equals ("https", StringComparison.OrdinalIgnoreCase);
    149120                }
    150121               
     
    152123               
    153124#if ENABLE_PROFILER
    154                 private readonly CustomSampler authSampler = CustomSampler.Create ("Auth");
    155                 private readonly CustomSampler handlerSampler = CustomSampler.Create ("Handler");
     125                private readonly UnityEngine.Profiling.CustomSampler authSampler = UnityEngine.Profiling.CustomSampler.Create ("Auth");
     126                private readonly UnityEngine.Profiling.CustomSampler handlerSampler = UnityEngine.Profiling.CustomSampler.Create ("Handler");
    156127#endif
    157128
    158129                private void HandleRequest (IAsyncResult _result) {
    159                         if (!_listener.IsListening) {
     130                        if (!listener.IsListening) {
    160131                                return;
    161132                        }
     
    166137//                              MicroStopwatch msw = new MicroStopwatch ();
    167138#if ENABLE_PROFILER
    168                         Profiler.BeginThreadProfiling ("AllocsMods", "WebRequest");
     139                        UnityEngine.Profiling.Profiler.BeginThreadProfiling ("AllocsMods", "WebRequest");
    169140                        HttpListenerContext ctx = _listener.EndGetContext (_result);
    170141                        try {
    171142#else
    172                         HttpListenerContext ctx = _listener.EndGetContext (_result);
    173                         _listener.BeginGetContext (HandleRequest, _listener);
     143                        HttpListenerContext ctx = listener.EndGetContext (_result);
     144                        listener.BeginGetContext (HandleRequest, listener);
    174145#endif
    175146                        try {
     
    180151                                response.ProtocolVersion = HttpProtocolVersion;
    181152
    182                                 WebConnection conn;
    183153#if ENABLE_PROFILER
    184154                                authSampler.Begin ();
    185155#endif
    186                                 int permissionLevel = DoAuthentication (request, out conn);
     156                                int permissionLevel = DoAuthentication (request, out WebConnection conn);
    187157#if ENABLE_PROFILER
    188158                                authSampler.End ();
     
    194164
    195165                                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;
     166                                        Cookie cookie = new Cookie ("sid", conn.SessionID, "/") {
     167                                                Expired = false,
     168                                                Expires = DateTime.MinValue,
     169                                                HttpOnly = true,
     170                                                Secure = false
     171                                        };
    201172                                        response.AppendCookie (cookie);
    202173                                }
     
    260231                        } finally {
    261232                                _listener.BeginGetContext (HandleRequest, _listener);
    262                                 Profiler.EndThreadProfiling ();
     233                                UnityEngine.Profiling.Profiler.EndThreadProfiling ();
    263234                        }
    264235#endif
     
    281252                        }
    282253
     254                        string remoteEndpointString = _req.RemoteEndPoint.ToString ();
     255
    283256                        if (_req.QueryString ["adminuser"] != null && _req.QueryString ["admintoken"] != null) {
    284257                                WebPermissions.AdminToken admin = WebPermissions.Instance.GetWebAdmin (_req.QueryString ["adminuser"],
     
    288261                                }
    289262
    290                                 Log.Warning ("Invalid Admintoken used from " + _req.RemoteEndPoint);
     263                                Log.Warning ("Invalid Admintoken used from " + remoteEndpointString);
    291264                        }
    292265
     
    299272                                                int level = GameManager.Instance.adminTools.GetUserPermissionLevel (id.ToString ());
    300273                                                Log.Out ("Steam OpenID login from {0} with ID {1}, permission level {2}",
    301                                                         _req.RemoteEndPoint.ToString (), con.SteamID, level);
     274                                                        remoteEndpointString, con.SteamID, level);
    302275                                                return level;
    303276                                        }
    304277
    305                                         Log.Out ("Steam OpenID login failed from {0}", _req.RemoteEndPoint.ToString ());
     278                                        Log.Out ("Steam OpenID login failed from {0}", remoteEndpointString);
    306279                                } catch (Exception e) {
    307280                                        Log.Error ("Error validating login:");
  • binary-improvements/MapRendering/WebAndMapRendering.csproj

    r355 r367  
    3333    <Reference Include="LogLibrary">
    3434      <HintPath>..\7dtd-binaries\LogLibrary.dll</HintPath>
     35      <Private>False</Private>
     36    </Reference>
     37    <Reference Include="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     38      <HintPath>..\7dtd-binaries\mscorlib.dll</HintPath>
    3539      <Private>False</Private>
    3640    </Reference>
     
    7579    <Compile Include="Web\API\GetHostileLocation.cs" />
    7680    <Compile Include="Web\API\Null.cs" />
     81    <Compile Include="Web\SSE\EventLog.cs" />
     82    <Compile Include="Web\SSE\SseHandler.cs" />
     83    <Compile Include="Web\SSE\EventBase.cs" />
    7784    <Compile Include="Web\Web.cs" />
    7885    <Compile Include="Web\MimeType.cs" />
     
    117124    </ProjectReference>
    118125  </ItemGroup>
    119   <ItemGroup />
    120126  <ItemGroup>
    121127    <None Include="ModInfo.xml">
Note: See TracChangeset for help on using the changeset viewer.