diff --git a/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj b/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj index d60dbba..c2e0417 100644 --- a/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj +++ b/CityWebServer.Extensibility/CityWebServer.Extensibility.csproj @@ -41,6 +41,7 @@ + @@ -52,6 +53,7 @@ + diff --git a/CityWebServer.Extensibility/ICityWebMod.cs b/CityWebServer.Extensibility/ICityWebMod.cs new file mode 100644 index 0000000..84ba47f --- /dev/null +++ b/CityWebServer.Extensibility/ICityWebMod.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CityWebServer.Extensibility +{ + public interface ICityWebMod + { + /// + /// The name of this mod. Shown in the index page/nav menu as a link. + /// + String ModName { get; } + + /// + /// The author of this mod. + /// + String ModAuthor { get; } + + /// + /// The ID/slug for this mod. + /// + /// + /// This must be unique across all CityWebMods, and is used as the root for all request handlers provided by this mod. + /// + String ModID { get; } + + /// + /// Whether this mod should show up in the top menu. + /// + Boolean TopMenu { get; } + + /// + /// Called when the server first starts and is registering handlers. Returns an enumerable collection of IRequestHandler objects. + /// + List GetHandlers(IWebServer server); + } +} diff --git a/CityWebServer.Extensibility/IRequestHandler.cs b/CityWebServer.Extensibility/IRequestHandler.cs index b2ff843..1a919e2 100644 --- a/CityWebServer.Extensibility/IRequestHandler.cs +++ b/CityWebServer.Extensibility/IRequestHandler.cs @@ -10,11 +10,6 @@ public interface IRequestHandler { IWebServer Server { get; } - /// - /// Gets a unique identifier for this handler. Only one handler can be loaded with a given identifier. - /// - Guid HandlerID { get; } - /// /// Gets the priority of this request handler. A request will be handled by the request handler with the lowest priority. /// @@ -41,11 +36,11 @@ public interface IRequestHandler /// /// Returns a value that indicates whether this handler is capable of servicing the given request. /// - Boolean ShouldHandle(HttpListenerRequest request); + Boolean ShouldHandle(HttpListenerRequest request, String slug); /// /// Handles the specified request. The method should not close the stream. /// - IResponseFormatter Handle(HttpListenerRequest request); + IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot); } } \ No newline at end of file diff --git a/CityWebServer.Extensibility/IWebServer.cs b/CityWebServer.Extensibility/IWebServer.cs index 286ce12..2bf56e6 100644 --- a/CityWebServer.Extensibility/IWebServer.cs +++ b/CityWebServer.Extensibility/IWebServer.cs @@ -1,10 +1,28 @@ -namespace CityWebServer.Extensibility +using System; +using System.Collections.Generic; + +namespace CityWebServer.Extensibility { public interface IWebServer { /// - /// Gets an array containing all currently registered request handlers. + /// Gets an array containing all currently registered CityWebMods. /// - IRequestHandler[] RequestHandlers { get; } + ICityWebMod[] Mods { get; } + + /// + /// Gets the name of the current city. + /// + String CityName { get; } + + /// + /// Gets all of the content in the log buffer. + /// + List LogLines { get; } + + /// + /// Gets the base static file path for the server. + /// + String WebRoot { get; } } } \ No newline at end of file diff --git a/CityWebServer.Extensibility/RequestHandlerBase.cs b/CityWebServer.Extensibility/RequestHandlerBase.cs index a09f7ac..1ea3c83 100644 --- a/CityWebServer.Extensibility/RequestHandlerBase.cs +++ b/CityWebServer.Extensibility/RequestHandlerBase.cs @@ -22,36 +22,41 @@ protected void OnLogMessage(String message) #endregion ILogAppender Implementation protected readonly IWebServer _server; - protected Guid _handlerID; - protected int _priority; - protected String _name; - protected String _author; + protected int _priority = 100; + protected String _name = null; + protected String _author = null; protected String _mainPath; private RequestHandlerBase() { } - protected RequestHandlerBase(IWebServer server, Guid handlerID, String name, String author, int priority, String mainPath) + protected RequestHandlerBase(IWebServer server, String mainPath) + { + _server = server; + _mainPath = mainPath; + } + + protected RequestHandlerBase(IWebServer server, String name, String author, int priority, String mainPath) { _server = server; - _handlerID = handlerID; _name = name; _author = author; _priority = priority; _mainPath = mainPath; } + public RequestHandlerBase(IWebServer server) + { + _server = server; + _mainPath = null; + } + /// /// Gets the server that is currently servicing this instance. /// public virtual IWebServer Server { get { return _server; } } - /// - /// Gets a unique identifier for this handler. Only one handler can be loaded with a given identifier. - /// - public virtual Guid HandlerID { get { return _handlerID; } } - /// /// Gets the priority of this request handler. A request will be handled by the request handler with the lowest priority. /// @@ -70,23 +75,27 @@ protected RequestHandlerBase(IWebServer server, Guid handlerID, String name, Str /// /// Gets the absolute path to the main page for this request handler. Your class is responsible for handling requests at this path. /// - /// - /// When set to a value other than null, the Web Server will show this url as a link on the home page. - /// public virtual String MainPath { get { return _mainPath; } } /// /// Returns a value that indicates whether this handler is capable of servicing the given request. /// - public virtual Boolean ShouldHandle(HttpListenerRequest request) + public virtual Boolean ShouldHandle(HttpListenerRequest request, String slug) { - return (request.Url.AbsolutePath.Equals(_mainPath, StringComparison.OrdinalIgnoreCase)); + if (slug == null) + { + return request.Url.AbsolutePath.Equals(_mainPath, StringComparison.OrdinalIgnoreCase); + } + else + { + return request.Url.AbsolutePath.Equals(String.Format("/{0}{1}", slug, _mainPath), StringComparison.OrdinalIgnoreCase); + } } /// /// Handles the specified request. The method should not close the stream. /// - public abstract IResponseFormatter Handle(HttpListenerRequest request); + public abstract IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot); /// /// Returns a response in JSON format. diff --git a/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs b/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs index 02f370f..4ec0f73 100644 --- a/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs +++ b/CityWebServer.Extensibility/RestfulRequestHandlerBase.cs @@ -5,27 +5,19 @@ namespace CityWebServer.Extensibility { public abstract class RestfulRequestHandlerBase : RequestHandlerBase { - public RestfulRequestHandlerBase(IWebServer server, Guid handlerID, String name, String author, int priority, String mainPath) - : base(server, handlerID, name, author, priority, mainPath) + public RestfulRequestHandlerBase(IWebServer server, String mainPath) + : base(server, mainPath) { } - public override Guid HandlerID { get { return _handlerID; } } - - public override int Priority { get { return _priority; } } - - public override string Name { get { return _name; } } - - public override string Author { get { return _author; } } - public override string MainPath { get { return _mainPath; } } - public override bool ShouldHandle(HttpListenerRequest request) + public override bool ShouldHandle(HttpListenerRequest request, String slug) { - return (request.Url.AbsolutePath.StartsWith(_mainPath, StringComparison.OrdinalIgnoreCase)); + return (request.Url.AbsolutePath.StartsWith(String.Format("/{0}{1}", slug, _mainPath), StringComparison.OrdinalIgnoreCase)); } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, string slug, string wwwroot) { switch (request.HttpMethod) { diff --git a/CityWebServer/Helpers/TemplateHelper.cs b/CityWebServer.Extensibility/TemplateHelper.cs similarity index 61% rename from CityWebServer/Helpers/TemplateHelper.cs rename to CityWebServer.Extensibility/TemplateHelper.cs index 1c887f8..02b81cc 100644 --- a/CityWebServer/Helpers/TemplateHelper.cs +++ b/CityWebServer.Extensibility/TemplateHelper.cs @@ -2,41 +2,19 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using CityWebServer.Extensibility; -using ColossalFramework.Plugins; -namespace CityWebServer.Helpers +namespace CityWebServer.Extensibility { public static class TemplateHelper { - /// - /// Gets the full path of the directory that contains this assembly. - /// - public static String GetModPath() - { - var modPaths = PluginManager.instance.GetPluginsInfo().Select(obj => obj.modPath); - - foreach (var path in modPaths) - { - var indexPath = Path.Combine(path, "index.html"); - if (File.Exists(indexPath)) - { - return indexPath; - } - } - return null; - } - /// /// Gets the full content of a template. /// - public static String GetTemplate(String template) + public static String GetTemplate(String template, String tmplPath) { // Templates seem like something we shouldn't handle internally. // Perhaps we should force request handlers to implement their own templating if they so desire, and maintain a more "API" approach within the core. - String modPath = GetModPath(); - String templatePath = Path.Combine(modPath, "wwwroot"); - String specifiedTemplatePath = String.Format("{0}{1}{2}.html", templatePath, Path.DirectorySeparatorChar, template); + String specifiedTemplatePath = String.Format("{0}{1}{2}.html", tmplPath, Path.DirectorySeparatorChar, template); if (File.Exists(specifiedTemplatePath)) { @@ -57,11 +35,11 @@ public static String GetTemplate(String template) /// /// The value of should not include the file extension. /// - public static String PopulateTemplate(String template, Dictionary tokenReplacements) + public static String PopulateTemplate(String template, String tmplPath, Dictionary tokenReplacements) { try { - String templateContents = GetTemplate(template); + String templateContents = GetTemplate(template, tmplPath); foreach (var tokenReplacement in tokenReplacements) { templateContents = templateContents.Replace(tokenReplacement.Key, tokenReplacement.Value); @@ -70,7 +48,7 @@ public static String PopulateTemplate(String template, Dictionary /// Gets a dictionary that contains standard replacement tokens using the specified values. /// - public static Dictionary GetTokenReplacements(String cityName, String title, List handlers, String body) + public static Dictionary GetTokenReplacements(String cityName, String title, ICityWebMod[] mods, String body) { - var orderedHandlers = handlers.OrderBy(obj => obj.Priority).ThenBy(obj => obj.Name); - var handlerLinks = orderedHandlers.Select(obj => String.Format("
  • {1}
  • ", obj.MainPath, obj.Name)).ToArray(); + var handlerLinks = mods.Select(obj => obj.TopMenu ? String.Format("
  • {1}
  • ", obj.ModID, obj.ModName) : "").ToArray(); String nav = String.Join(Environment.NewLine, handlerLinks); return new Dictionary { { "#PAGETITLE#", title }, { "#NAV#", nav}, - { "#CSS#", ""}, // Moved directly into the template. { "#PAGEBODY#", body}, { "#CITYNAME#", cityName}, - { "#JS#", ""}, // Moved directly into the template. }; } } diff --git a/CityWebServer.sln b/CityWebServer.sln index a1e90ff..9898c04 100644 --- a/CityWebServer.sln +++ b/CityWebServer.sln @@ -7,7 +7,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CityWebServer", "CityWebSer EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CityWebServer.Extensibility", "CityWebServer.Extensibility\CityWebServer.Extensibility.csproj", "{DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebServerExtension", "SampleWebServerExtension\SampleWebServerExtension.csproj", "{7DF15DF6-C475-4866-9111-F5150C1336E1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleNakedHandlerMod", "SampleNakedHandlerMod\SampleNakedHandlerMod.csproj", "{671CB9E5-3EFD-4466-935E-1BF58470296F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleCityWebMod", "SampleCityWebMod\SampleCityWebMod.csproj", "{7DF15DF6-C475-4866-9111-F5150C1336E1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -23,6 +25,10 @@ Global {DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB96EFB4-FA45-4ACC-8D51-7ED37065CC79}.Release|Any CPU.Build.0 = Release|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {671CB9E5-3EFD-4466-935E-1BF58470296F}.Release|Any CPU.Build.0 = Release|Any CPU {7DF15DF6-C475-4866-9111-F5150C1336E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7DF15DF6-C475-4866-9111-F5150C1336E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {7DF15DF6-C475-4866-9111-F5150C1336E1}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/CityWebServer/CityWebServer.csproj b/CityWebServer/CityWebServer.csproj index 8abdf1f..208e2a1 100644 --- a/CityWebServer/CityWebServer.csproj +++ b/CityWebServer/CityWebServer.csproj @@ -63,11 +63,16 @@ + + + + + + - diff --git a/CityWebServer/Helpers/ApacheMimeTypes.cs b/CityWebServer/Helpers/ApacheMimeTypes.cs index 4d807a2..6c5389d 100644 --- a/CityWebServer/Helpers/ApacheMimeTypes.cs +++ b/CityWebServer/Helpers/ApacheMimeTypes.cs @@ -3,7 +3,7 @@ using System.Linq; // Source: https://raw.githubusercontent.com/cymen/ApacheMimeTypesToDotNet/master/ApacheMimeTypes.cs -namespace ApacheMimeTypes +namespace CityWebServer.Helpers { internal class Apache { diff --git a/CityWebServer/Helpers/CityWebMod.cs b/CityWebServer/Helpers/CityWebMod.cs new file mode 100644 index 0000000..daeee37 --- /dev/null +++ b/CityWebServer/Helpers/CityWebMod.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using ColossalFramework.Plugins; +using CityWebServer.Extensibility; + +namespace CityWebServer.Helpers +{ + public class CityWebMod : ICityWebMod + { + protected String _name = null; + protected String _author = null; + protected List _handlers = null; + protected String _wwwroot = null; + protected String _ID = null; + protected Boolean _isEnabled = false; + protected Boolean _topMenu = true; + + public String ModName { get { return _name; } } + public String ModAuthor { get { return _author; } } + public String ModID { get { return _ID; } } + public Boolean HasStatic { get { return _wwwroot != null; } } + public Boolean IsEnabled { get { return _isEnabled; } } + public Boolean TopMenu { get { return _topMenu; } } + public String WebRoot { get { return _wwwroot; } } + public List GetHandlers(IWebServer server) + { + return _handlers; + } + + protected CityWebMod() + { + } + + public Boolean HandleRequest(HttpListenerRequest request, HttpListenerResponse response, String slug, String wwwroot) + { + var handler = _handlers.FirstOrDefault(obj => obj.ShouldHandle(request, slug)); + if (handler == null) { return false; } + + IResponseFormatter responseFormatterWriter = handler.Handle(request, slug, wwwroot); + responseFormatterWriter.WriteContent(response); + return true; + } + + public static CityWebMod CreateByReflection(UserMod um, IWebServer server) + { + // just casting um.Mod to ICityWebMod is not reliable; somtimes works, sometimes not + // instead we need to reflect on it to identify mods that have implemented ICityWebMod + Type ut = um.Mod.GetType(); + MethodInfo getHandlers = null; + PropertyInfo pName = null; + PropertyInfo pAuthor = null; + PropertyInfo pID = null; + PropertyInfo pTop = null; + try + { + getHandlers = ut.GetMethod("GetHandlers"); + pName = ut.GetProperty("ModName"); + pAuthor = ut.GetProperty("ModAuthor"); + pID = ut.GetProperty("ModID"); + pTop = ut.GetProperty("TopMenu"); + } + catch + { + } + + if (getHandlers != null && getHandlers.ReturnType != typeof(List)) { return null; } + if (getHandlers == null || pName == null || pAuthor == null || pID == null || pTop == null) { return null; } + + CityWebMod cwm = new CityWebMod(); + + // grab values from the PluginManager.PluginInfo + cwm._isEnabled = um.PluginInfo.isEnabled; + String testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); + if (Directory.Exists(testPath)) + { + cwm._wwwroot = testPath; + } + + // grab reflected property values + cwm._name = (String)pName.GetValue(um.Mod, null); + cwm._author = (String)pAuthor.GetValue(um.Mod, null); + cwm._topMenu = (Boolean)pTop.GetValue(um.Mod, null); + cwm._ID = (String)pID.GetValue(um.Mod, null); + if (cwm._ID != null) + { + cwm._ID = cwm._ID.ToLower().Replace(" ", "_"); + } + + // invoke the method to get the list of request handlers managed by this CityWebMod + List h = null; + try + { + h = (List)getHandlers.Invoke(um.Mod, new Object[1] { server }); + } + catch (Exception ex) + { + IntegratedWebServer.LogMessage(String.Format("failed to invoke GetHandlers: {0}", ex)); + return null; + } + if (h != null) + { + cwm._handlers = h; + } + else + { + cwm._handlers = new List(); + } + + return cwm; + } + + } +} diff --git a/CityWebServer/Helpers/UserMod.cs b/CityWebServer/Helpers/UserMod.cs new file mode 100644 index 0000000..dedc02f --- /dev/null +++ b/CityWebServer/Helpers/UserMod.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICities; +using ColossalFramework; +using ColossalFramework.Plugins; + +namespace CityWebServer.Helpers +{ + // this is a wrapper class to encapsulate both the PluginManager.PluginInfo and its + // associated IUserMod instance, to simplify spinning through these in tandem + public class UserMod + { + private IUserMod _umod = null; + private PluginManager.PluginInfo _pi = null; + + public IUserMod Mod { get { return _umod; } } + public PluginManager.PluginInfo PluginInfo { get { return _pi; } } + public Boolean isMe { get; set; } + + UserMod(PluginManager.PluginInfo pi) + { + _pi = pi; + IUserMod[] insts = null; + try + { + insts = pi.GetInstances(); + } + catch + { + } + if (insts != null && insts.Length > 0) + { + _umod = insts[0]; + } + isMe = false; + } + + /// + /// Static factory to collect plugins from the PluginManager into a list of UserMods + /// + public static List CollectPlugins() + { + List miList = new List(); + PluginManager pm = Singleton.instance; + if (null == pm) { return miList; } + + List piList = new List(pm.GetPluginsInfo()); + foreach (PluginManager.PluginInfo pi in piList) + { + miList.Add(new UserMod(pi)); + } + + return miList; + } + + } +} diff --git a/CityWebServer/IntegratedWebServer.cs b/CityWebServer/IntegratedWebServer.cs index 711e6cf..6a45931 100644 --- a/CityWebServer/IntegratedWebServer.cs +++ b/CityWebServer/IntegratedWebServer.cs @@ -4,10 +4,10 @@ using System.Linq; using System.Net; using System.Reflection; -using ApacheMimeTypes; using CityWebServer.Extensibility; using CityWebServer.Extensibility.Responses; using CityWebServer.Helpers; +using CityWebServer.RequestHandlers; using ColossalFramework; using ColossalFramework.Plugins; using ICities; @@ -22,66 +22,28 @@ public class IntegratedWebServer : ThreadingExtensionBase, IWebServer private const String WebServerHostKey = "webServerHost{0}"; private static List _logLines; - private static string _endpoint; + private static String _endpoint; private WebServer _server; - private List _requestHandlers; private String _cityName = "CityName"; + private String _wwwroot = null; + private Boolean _secondPass = false; + private Boolean _cwmPass = false; - // Not required, but prevents a number of spurious entries from making it to the log file. - private static readonly List IgnoredAssemblies = new List - { - "Anonymously Hosted DynamicMethods Assembly", - "Assembly-CSharp", - "Assembly-CSharp-firstpass", - "Assembly-UnityScript-firstpass", - "Boo.Lang", - "ColossalManaged", - "ICSharpCode.SharpZipLib", - "ICities", - "Mono.Security", - "mscorlib", - "System", - "System.Configuration", - "System.Core", - "System.Xml", - "UnityEngine", - "UnityEngine.UI", - }; + private HandlerCWM _hWrap; // the CWM to use for wrapping naked handlers + private Dictionary _cwMods; // the list of CityWebMods we've identified and registered + private List _usrMods = null; // utility list of all UserMods found in ColossalFramework PluginManager /// /// Gets the root endpoint for which the server is configured to service HTTP requests. /// - public static String Endpoint - { - get { return _endpoint; } - } + public static String Endpoint { get { return _endpoint; } } + + public List LogLines { get { return _logLines; } } - /// - /// Gets the full path to the directory where static pages are served from. - /// - public static String GetWebRoot() - { - var modPaths = PluginManager.instance.GetPluginsInfo().Select(obj => obj.modPath); - foreach (var path in modPaths) - { - var testPath = Path.Combine(path, "wwwroot"); + public String CityName { get { return _cityName; } } - if (Directory.Exists(testPath)) - { - return testPath; - } - } - return null; - } - - /// - /// Gets an array containing all currently registered request handlers. - /// - public IRequestHandler[] RequestHandlers - { - get { return _requestHandlers.ToArray(); } - } + public String WebRoot { get { return _wwwroot; } } /// /// Initializes a new instance of the class. @@ -91,9 +53,6 @@ public IntegratedWebServer() // For the entire lifetime of this instance, we'll preseve log messages. // After a certain point, it might be worth truncating them, but we'll cross that bridge when we get to it. _logLines = new List(); - - // We need a place to store all the request handlers that have been registered. - _requestHandlers = new List(); } #region Create @@ -149,18 +108,8 @@ private void InitializeServer() WebServer ws = new WebServer(HandleRequest, bindings.ToArray()); _server = ws; _server.Run(); + LogMessage("Server Initialized."); - - _requestHandlers = new List(); - - try - { - RegisterHandlers(); - } - catch (Exception ex) - { - UnityEngine.Debug.LogException(ex); - } } #endregion Create @@ -175,7 +124,11 @@ public override void OnReleased() ReleaseServer(); // TODO: Unregister from events (i.e. ILogAppender.LogMessage) - _requestHandlers.Clear(); + if (_cwMods != null) + { + _cwMods.Clear(); + _cwMods = null; + } Configuration.SaveSettings(); @@ -196,6 +149,52 @@ private void ReleaseServer() #endregion Release + /// ; + /// Performs init tasks on first request. + /// + /// + /// Certain plugin init processes must take place after all plugins have loaded. This defers these tasks + /// until the first request arrives at the server, which should be after the plugin stack is done. + /// + private void SecondPassInit() + { + LogMessage("Second pass initialization..."); + try + { + SimulationManager sm = Singleton.instance; + if (sm != null) + { + _cityName = sm.m_metaData.m_CityName; + } + else + { + LogMessage(String.Format("failed to get city name: null SimulationManager")); + _cityName = "foo"; + } + } + catch (Exception ex) + { + LogMessage(String.Format("failed to get city name: {0}", ex)); + return; + } + LogMessage(String.Format("set city name: {0}", _cityName)); + + try + { + _usrMods = UserMod.CollectPlugins(); + RegisterDefaultCWM(); + } + catch (Exception ex) + { + UnityEngine.Debug.LogException(ex); + LogMessage(String.Format("failed to register plugins: {0}", ex)); + return; + } + + _secondPass = true; + LogMessage("Second pass complete."); + } + /// ; /// Handles the specified request. /// @@ -206,38 +205,56 @@ private void ReleaseServer() private void HandleRequest(HttpListenerRequest request, HttpListenerResponse response) { LogMessage(String.Format("{0} {1}", request.HttpMethod, request.RawUrl)); + if (!_secondPass) { SecondPassInit(); } + if (!_cwmPass) { RegisterCWM(); } - var simulationManager = Singleton.instance; - _cityName = simulationManager.m_metaData.m_CityName; - - // There are two reserved endpoints: "/" and "/Log". - // These take precedence over all other request handlers. - if (ServiceRoot(request, response)) + // Get the request handler associated with the current request. + String url = request.Url.AbsolutePath; + String slug = null; + String wwwroot = _wwwroot; + Boolean handled = false; + if (url.IsNullOrWhiteSpace() || url.Equals("/")) { - return; + slug = "root"; } - - if (ServiceLog(request, response)) + else if (url.StartsWith("/")) { - return; + String[] urlparts = url.Split('/'); + if (urlparts.Length < 3) + { + slug = "root"; + } + else + { + slug = urlparts[1]; + } } - // Get the request handler associated with the current request. - var handler = _requestHandlers.FirstOrDefault(obj => obj.ShouldHandle(request)); - if (handler != null) + if (slug != null) { + CityWebMod cwm = null; try { - IResponseFormatter responseFormatterWriter = handler.Handle(request); - responseFormatterWriter.WriteContent(response); - - return; + if (_cwMods.ContainsKey(slug)) + { + _cwMods.TryGetValue(slug, out cwm); + wwwroot = cwm.WebRoot; + handled = cwm.HandleRequest(request, response, slug, wwwroot); + } + if (!handled) + { + handled = _hWrap.HandleRequest(request, response, null, this.WebRoot); + if (handled) + { + wwwroot = this.WebRoot; + } + } } catch (Exception ex) { String errorBody = String.Format("

    An error has occurred!

    {0}
    ", ex); - var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Error", _requestHandlers, errorBody); - var template = TemplateHelper.PopulateTemplate("index", tokens); + var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Error", this.Mods, errorBody); + var template = TemplateHelper.PopulateTemplate("content", _wwwroot, tokens); IResponseFormatter errorResponseFormatter = new HtmlResponseFormatter(template); errorResponseFormatter.WriteContent(response); @@ -246,222 +263,148 @@ private void HandleRequest(HttpListenerRequest request, HttpListenerResponse res } } - var wwwroot = GetWebRoot(); + if (handled) { return; } // something handled it, great // At this point, we can guarantee that we don't need any game data, so we can safely start a new thread to perform the remaining tasks. - ServiceFileRequest(wwwroot, request, response); + if (ServiceFileRequest(wwwroot, request, response, slug)) { return; } // check for static files + + String body = String.Format("No resource is available at the specified filepath: {0}", url); + IResponseFormatter notFoundResponseFormatter = new PlainTextResponseFormatter(body, HttpStatusCode.NotFound); + notFoundResponseFormatter.WriteContent(response); + return; } + + private static Boolean ServiceFileRequest(String wwwroot, HttpListenerRequest request, HttpListenerResponse response, String slug) { + if (wwwroot == null || wwwroot.IsNullOrWhiteSpace()) { return false; } - private static void ServiceFileRequest(String wwwroot, HttpListenerRequest request, HttpListenerResponse response) - { - var relativePath = request.Url.AbsolutePath.Substring(1); + var relativePath = request.Url.AbsolutePath; + if (!slug.Equals("root")) + { + relativePath = relativePath.Replace(String.Format("/{0}/", slug), ""); + } + else + { + relativePath = relativePath.Substring(1); + } relativePath = relativePath.Replace("/", Path.DirectorySeparatorChar.ToString()); var absolutePath = Path.Combine(wwwroot, relativePath); - - if (File.Exists(absolutePath)) + if (!File.Exists(absolutePath)) { return false; } + + var extension = Path.GetExtension(absolutePath); + response.ContentType = Apache.GetMime(extension); + response.StatusCode = 200; // HTTP 200 - SUCCESS + + // Open file, read bytes into buffer and write them to the output stream. + using (FileStream fileReader = File.OpenRead(absolutePath)) { - var extension = Path.GetExtension(absolutePath); - response.ContentType = Apache.GetMime(extension); - response.StatusCode = 200; // HTTP 200 - SUCCESS - - // Open file, read bytes into buffer and write them to the output stream. - using (FileStream fileReader = File.OpenRead(absolutePath)) + byte[] buffer = new byte[4096]; + int read; + while ((read = fileReader.Read(buffer, 0, buffer.Length)) > 0) { - byte[] buffer = new byte[4096]; - int read; - while ((read = fileReader.Read(buffer, 0, buffer.Length)) > 0) - { - response.OutputStream.Write(buffer, 0, read); - } + response.OutputStream.Write(buffer, 0, read); } } - else - { - String body = String.Format("No resource is available at the specified filepath: {0}", absolutePath); - IResponseFormatter notFoundResponseFormatter = new PlainTextResponseFormatter(body, HttpStatusCode.NotFound); - notFoundResponseFormatter.WriteContent(response); - } + return true; } /// - /// Searches all the assemblies in the current AppDomain for class definitions that implement the interface. Those classes are instantiated and registered as request handlers. + /// Searches all the plugins in the PluginManager for ones that implement . /// - private void RegisterHandlers() + private void RegisterCWM() { - IEnumerable handlers = FindHandlersInLoadedAssemblies(); - RegisterHandlers(handlers); - } - - private void RegisterHandlers(IEnumerable handlers) - { - if (handlers == null) { return; } - - if (_requestHandlers == null) - { - _requestHandlers = new List(); - } - - foreach (var handler in handlers) - { - // Only register handlers that we don't already have an instance of. - if (_requestHandlers.Any(h => h.GetType() == handler)) + LogMessage("Looking for CityWebMods and naked handlers..."); + foreach (UserMod um in _usrMods) { + if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin || um.isMe) { continue; } + if (um.Mod == null) { continue; } + + LogMessage(String.Format("scanning {0} <{1}>...", um.PluginInfo.name, um.Mod.GetType())); + CityWebMod cwm = CityWebMod.CreateByReflection(um, this); + if (cwm == null) { - continue; + _hWrap.AddHandlers(um, this); } - - IRequestHandler handlerInstance = null; - Boolean exists = false; - - try + else { - if (typeof(RequestHandlerBase).IsAssignableFrom(handler)) + String slug = cwm.ModID; + if (slug == null || slug.IsNullOrWhiteSpace()) { - handlerInstance = (RequestHandlerBase)Activator.CreateInstance(handler, this); + LogMessage(String.Format("Invalid CityWebMod \"{0}\" by \"{1}\"", cwm.ModName, cwm.ModAuthor)); } - else + else if (_cwMods.ContainsKey(slug)) { - handlerInstance = (IRequestHandler)Activator.CreateInstance(handler); + LogMessage(String.Format("Conflicting CityWebMod; ID already exists! {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); } - - if (handlerInstance == null) + else { - LogMessage(String.Format("Request Handler ({0}) could not be instantiated!", handler.Name)); - continue; + LogMessage(String.Format("Loaded CityWebMod {0}:\"{1}\" by \"{2}\"", slug, cwm.ModName, cwm.ModAuthor)); + _cwMods.Add(slug, cwm); + List hList = cwm.GetHandlers(this); + if (hList != null) + { + foreach (IRequestHandler h in hList) + { + if (h is ILogAppender) + { + ILogAppender la = (h as ILogAppender); + la.LogMessage += RequestHandlerLogAppender_OnLogMessage; + } + } + } } - - // Duplicates handlers seem to pass the check above, so now we filter them based on their identifier values, which should work. - exists = _requestHandlers.Any(obj => obj.HandlerID == handlerInstance.HandlerID); - } - catch (Exception ex) - { - LogMessage(ex.ToString()); } + } - if (exists) - { - // TODO: Allow duplicate registrations to occur; previous registration is removed and replaced with a new one? - LogMessage(String.Format("Supressing duplicate handler registration for '{0}'", handler.Name)); - } - else - { - _requestHandlers.Add(handlerInstance); - if (handlerInstance is ILogAppender) - { - var logAppender = (handlerInstance as ILogAppender); - logAppender.LogMessage += RequestHandlerLogAppender_OnLogMessage; - } + // we are done with _usrMods now; clear it up so we aren't holding references into the engine + _usrMods.Clear(); + _usrMods = null; - LogMessage(String.Format("Added Request Handler: {0}", handler.FullName)); - } - } + _cwmPass = true; } + public ICityWebMod[] Mods { get { return _cwMods.Values.ToArray(); } } + private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEventArgs logAppenderEventArgs) { var senderTypeName = sender.GetType().Name; LogMessage(logAppenderEventArgs.LogLine, senderTypeName, false); } - - /// - /// Searches all the assemblies in the current AppDomain, and returns a collection of those that implement the interface. - /// - private static IEnumerable FindHandlersInLoadedAssemblies() - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - - foreach (var assembly in assemblies) - { - var handlers = FetchHandlers(assembly); - foreach (var handler in handlers) - { - yield return handler; - } - } - } - - private static IEnumerable FetchHandlers(Assembly assembly) - { - var assemblyName = assembly.GetName().Name; - - // Skip any assemblies that we don't anticipate finding anything in. - if (IgnoredAssemblies.Contains(assemblyName)) { yield break; } - - Type[] types = new Type[0]; - try - { - types = assembly.GetTypes(); - } - catch { } - - foreach (var type in types) - { - Boolean isValid = false; - try - { - isValid = typeof(IRequestHandler).IsAssignableFrom(type) && type.IsClass && !type.IsAbstract; - } - catch { } - - if (isValid) - { - yield return type; - } - } - } - - #region Reserved Endpoint Handlers - + /// - /// Services requests to ~/ + /// Does some initial setup dependent on scanning mods and also default/core CWM registration. /// - private Boolean ServiceRoot(HttpListenerRequest request, HttpListenerResponse response) + private void RegisterDefaultCWM() { - if (request.Url.AbsolutePath.ToLower() == "/") + if (_cwMods != null) { - List links = new List(); - foreach (var requestHandler in this._requestHandlers.OrderBy(obj => obj.Priority)) - { - links.Add(String.Format("
  • {0} by {2} (Priority: {3})
  • ", requestHandler.Name, requestHandler.MainPath, requestHandler.Author, requestHandler.Priority)); - } - - String body = String.Format("

    Cities: Skylines - Integrated Web Server

      {0}
    ", String.Join("", links.ToArray())); - var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Home", _requestHandlers, body); - var template = TemplateHelper.PopulateTemplate("index", tokens); - - IResponseFormatter htmlResponseFormatter = new HtmlResponseFormatter(template); - htmlResponseFormatter.WriteContent(response); - - return true; + _cwMods.Clear(); + _cwMods = null; } - - return false; - } - - /// - /// Services requests to ~/Log - /// - private Boolean ServiceLog(HttpListenerRequest request, HttpListenerResponse response) - { - if (request.Url.AbsolutePath.ToLower() == "/log") - { + foreach (UserMod um in _usrMods) { + if (!um.PluginInfo.isEnabled || um.PluginInfo.isBuiltin) continue; + if (null == um.Mod) continue; + if (um.Mod.Name.Equals("Integrated Web Server")) { - String body = String.Format("

    Server Log

    {0}
    ", String.Join("", _logLines.ToArray())); - var tokens = TemplateHelper.GetTokenReplacements(_cityName, "Log", _requestHandlers, body); - var template = TemplateHelper.PopulateTemplate("index", tokens); - - IResponseFormatter htmlResponseFormatter = new HtmlResponseFormatter(template); - htmlResponseFormatter.WriteContent(response); - - return true; + // hey it's me! get our web root + um.isMe = true; + String testPath = Path.Combine(um.PluginInfo.modPath, "wwwroot"); + if (Directory.Exists(testPath)) + { + LogMessage(String.Format("Setting server wwwroot location: {0}", testPath)); + _wwwroot = testPath; + } + break; } } - return false; + LogMessage("adding default CityWebMods"); + _cwMods = new Dictionary(); + _cwMods.Add("root", new RootCWM(this)); + _cwMods.Add("log", new LogCWM(this)); + _cwMods.Add("api", new APICWM(this)); + _cwMods.Add("handlers", _hWrap = new HandlerCWM(this)); } - #endregion Reserved Endpoint Handlers - #region Logging /// diff --git a/CityWebServer/Models/PublicTransportLine.cs b/CityWebServer/Models/PublicTransportLine.cs index 462f158..1d81dee 100644 --- a/CityWebServer/Models/PublicTransportLine.cs +++ b/CityWebServer/Models/PublicTransportLine.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace CityWebServer.Models { diff --git a/CityWebServer/RequestHandlers/APICWM.cs b/CityWebServer/RequestHandlers/APICWM.cs new file mode 100644 index 0000000..3195baa --- /dev/null +++ b/CityWebServer/RequestHandlers/APICWM.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + public class APICWM : CityWebMod + { + public APICWM(IWebServer server) + { + _ID = "api"; + _name = "JSON API"; + _author = "Rychard"; + _isEnabled = true; + _wwwroot = server.WebRoot; + _topMenu = true; + + _handlers = new List(); + _handlers.Add(new APIRequestHandler(server, this)); + _handlers.Add(new BudgetRequestHandler(server)); + _handlers.Add(new BuildingRequestHandler(server)); + _handlers.Add(new CityInfoRequestHandler(server)); + _handlers.Add(new MessageRequestHandler(server)); + _handlers.Add(new TransportRequestHandler(server)); + _handlers.Add(new VehicleRequestHandler(server)); + } + + private class APIRequestHandler : RequestHandlerBase + { + private APICWM _container; + public APIRequestHandler(IWebServer server, APICWM container) + : base(server, "/") + { + _container = container; + } + + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) + { + List links = new List(); + foreach (var h in _container.GetHandlers(_server).OrderBy(obj => obj.MainPath)) + { + if (h.MainPath.Equals("/")) { continue; } + links.Add(String.Format("
  • {2}
  • ", slug, h.MainPath, String.IsNullOrEmpty(h.Name) ? h.MainPath : h.Name)); + } + + String body = String.Format("
      {0}
    ", String.Join("", links.ToArray())); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "API Endpoints", _server.Mods, body); + var template = TemplateHelper.PopulateTemplate("content", wwwroot, tokens); + + return new HtmlResponseFormatter(template); + } + } + + } +} diff --git a/CityWebServer/RequestHandlers/BudgetRequestHandler.cs b/CityWebServer/RequestHandlers/BudgetRequestHandler.cs index 80425b0..5b51d27 100644 --- a/CityWebServer/RequestHandlers/BudgetRequestHandler.cs +++ b/CityWebServer/RequestHandlers/BudgetRequestHandler.cs @@ -9,11 +9,11 @@ namespace CityWebServer.RequestHandlers public class BudgetRequestHandler : RequestHandlerBase { public BudgetRequestHandler(IWebServer server) - : base(server, new Guid("87205a0d-1b53-47bd-91fa-9cddf0a3bd9e"), "Budget", "Rychard", 100, "/Budget") + : base(server, "/Budget") { } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { // TODO: Expand upon this to expose substantially more information. var economyManager = Singleton.instance; diff --git a/CityWebServer/RequestHandlers/BuildingRequestHandler.cs b/CityWebServer/RequestHandlers/BuildingRequestHandler.cs index c203aec..7d9d6d8 100644 --- a/CityWebServer/RequestHandlers/BuildingRequestHandler.cs +++ b/CityWebServer/RequestHandlers/BuildingRequestHandler.cs @@ -9,11 +9,11 @@ namespace CityWebServer.RequestHandlers public class BuildingRequestHandler : RequestHandlerBase { public BuildingRequestHandler(IWebServer server) - : base(server, new Guid("03897cb0-d53f-4189-a613-e7d22705dc2f"), "Building", "Rychard", 100, "/Building") + : base(server, "/Building") { } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { var buildingManager = Singleton.instance; diff --git a/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs b/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs index 93228d8..547c477 100644 --- a/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs +++ b/CityWebServer/RequestHandlers/CityInfoRequestHandler.cs @@ -14,11 +14,11 @@ namespace CityWebServer.RequestHandlers public class CityInfoRequestHandler : RequestHandlerBase { public CityInfoRequestHandler(IWebServer server) - : base(server, new Guid("eeada0d0-f1d2-43b0-9595-2a6a4d917631"), "City Info", "Rychard", 100, "/CityInfo") + : base(server, "/CityInfo") { } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { if (request.QueryString.HasKey("showList")) { diff --git a/CityWebServer/RequestHandlers/HandlerCWM.cs b/CityWebServer/RequestHandlers/HandlerCWM.cs new file mode 100644 index 0000000..24b7351 --- /dev/null +++ b/CityWebServer/RequestHandlers/HandlerCWM.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Linq; +using System.Reflection; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + class HandlerCWM : CityWebMod + { + public HandlerCWM(IWebServer server) + { + _ID = "handlers"; + _name = "Naked Handlers"; + _author = "nezroy"; + _isEnabled = true; + _wwwroot = server.WebRoot; + _topMenu = true; + + _handlers = new List(); + _handlers.Add(new HandlersRequestHandler(server, this)); + } + + private class HandlersRequestHandler : RequestHandlerBase + { + private HandlerCWM _container; + public HandlersRequestHandler(IWebServer server, HandlerCWM container) + : base(server, "/") + { + _container = container; + } + + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) + { + List links = new List(); + foreach (var h in _container.GetHandlers(_server).OrderBy(obj => obj.Priority)) + { + if (h.MainPath.Equals("/")) { continue; } + links.Add(String.Format("
  • [{3}] {1} by {2}
  • ", h.MainPath, h.Name, h.Author, h.Priority)); + } + + String body = String.Format("
      {0}
    ", String.Join("", links.ToArray())); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Naked Handlers", _server.Mods, body); + var template = TemplateHelper.PopulateTemplate("content", wwwroot, tokens); + + return new HtmlResponseFormatter(template); + } + } + + public void AddHandlers(UserMod umod, IWebServer server) + { + Assembly modAssm = umod.Mod.GetType().Assembly; + var handlers = FetchHandlers(modAssm); + foreach (var handler in handlers) + { + IRequestHandler handlerInstance = null; + try + { + if (typeof(RequestHandlerBase).IsAssignableFrom(handler)) + { + handlerInstance = (RequestHandlerBase)Activator.CreateInstance(handler, server); + } + else + { + handlerInstance = (IRequestHandler)Activator.CreateInstance(handler); + } + + if (handlerInstance == null) + { + IntegratedWebServer.LogMessage(String.Format("Request Handler ({0}) could not be instantiated!", handler.Name)); + continue; + } + } + catch (Exception ex) + { + IntegratedWebServer.LogMessage(ex.ToString()); + } + + AddHandler(handlerInstance); + if (handlerInstance is ILogAppender) + { + var logAppender = (handlerInstance as ILogAppender); + logAppender.LogMessage += RequestHandlerLogAppender_OnLogMessage; + } + + IntegratedWebServer.LogMessage(String.Format("added request handler: {0}", handler.FullName)); + } + } + + private static IEnumerable FetchHandlers(Assembly assembly) + { + var assemblyName = assembly.GetName().Name; + + Type[] types = new Type[0]; + try + { + types = assembly.GetTypes(); + } + catch { } + + foreach (var type in types) + { + Boolean isValid = false; + try + { + isValid = typeof(IRequestHandler).IsAssignableFrom(type) && type.IsClass && !type.IsAbstract; + } + catch { } + + if (isValid) + { + yield return type; + } + } + } + + public void AddHandler(IRequestHandler handler) + { + // TODO: use prio to put it in correct order + // int prio = handler.Priority; + _handlers.Add(handler); + } + + // TODO: this is copypasta from IWS; find a cleaner way to do this + private void RequestHandlerLogAppender_OnLogMessage(object sender, LogAppenderEventArgs logAppenderEventArgs) + { + var senderTypeName = sender.GetType().Name; + IntegratedWebServer.LogMessage(logAppenderEventArgs.LogLine, senderTypeName, false); + } + } +} diff --git a/CityWebServer/RequestHandlers/LogCWM.cs b/CityWebServer/RequestHandlers/LogCWM.cs new file mode 100644 index 0000000..deb635f --- /dev/null +++ b/CityWebServer/RequestHandlers/LogCWM.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + public class LogCWM : CityWebMod + { + public LogCWM(IWebServer server) + { + _ID = "log"; + _name = "Server Log"; + _author = "Rychard"; + _isEnabled = true; + _wwwroot = server.WebRoot; + _topMenu = true; + + _handlers = new List(); + _handlers.Add(new LogRequestHandler(server)); + } + + private class LogRequestHandler : RequestHandlerBase + { + public LogRequestHandler(IWebServer server) + : base(server, "/") + { + } + + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) + { + String body = String.Format("
    {0}
    ", String.Join("", _server.LogLines.ToArray())); + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Log", _server.Mods, body); + var template = TemplateHelper.PopulateTemplate("content", wwwroot, tokens); + + return new HtmlResponseFormatter(template); + } + } + + } +} diff --git a/CityWebServer/RequestHandlers/MessageRequestHandler.cs b/CityWebServer/RequestHandlers/MessageRequestHandler.cs index 1fa060b..4802c7f 100644 --- a/CityWebServer/RequestHandlers/MessageRequestHandler.cs +++ b/CityWebServer/RequestHandlers/MessageRequestHandler.cs @@ -12,13 +12,13 @@ public class MessageRequestHandler : RequestHandlerBase private readonly ChirpRetriever _chirpRetriever; public MessageRequestHandler(IWebServer server) - : base(server, new Guid("b4efeced-1dbb-435a-8999-9f8adaa5036e"), "Chirper Messages", "Rychard", 100, "/Messages") + : base(server, "/Messages") { _chirpRetriever = new ChirpRetriever(); _chirpRetriever.LogMessage += (sender, args) => { OnLogMessage(args.LogLine); }; } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { // TODO: Customize request handling. var messages = _chirpRetriever.Messages; diff --git a/CityWebServer/RequestHandlers/RootCWM.cs b/CityWebServer/RequestHandlers/RootCWM.cs new file mode 100644 index 0000000..621f9c5 --- /dev/null +++ b/CityWebServer/RequestHandlers/RootCWM.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; +using CityWebServer.Helpers; +using CityWebServer.Extensibility; +using CityWebServer.Extensibility.Responses; + +namespace CityWebServer.RequestHandlers +{ + public class RootCWM : CityWebMod + { + public RootCWM(IWebServer server) + { + _ID = "root"; + _name = "Index Page"; + _author = "Rychard"; + _isEnabled = true; + _wwwroot = server.WebRoot; + _topMenu = false; + + _handlers = new List(); + _handlers.Add(new RootRequestHandler(server)); + } + + private class RootRequestHandler : RequestHandlerBase + { + public RootRequestHandler(IWebServer server) + : base(server, "/") + { + } + + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) + { + var tokens = TemplateHelper.GetTokenReplacements(_server.CityName, "Home", _server.Mods, ""); + var template = TemplateHelper.PopulateTemplate("index", wwwroot, tokens); + + return new HtmlResponseFormatter(template); + } + + public override Boolean ShouldHandle(HttpListenerRequest request, String slug) + { + String url = request.Url.AbsolutePath; + return (null != url && (url.Equals("") || url.Equals("/"))); + } + } + } +} diff --git a/CityWebServer/RequestHandlers/TransportRequestHandler.cs b/CityWebServer/RequestHandlers/TransportRequestHandler.cs index 42bc11b..43288b7 100644 --- a/CityWebServer/RequestHandlers/TransportRequestHandler.cs +++ b/CityWebServer/RequestHandlers/TransportRequestHandler.cs @@ -13,11 +13,11 @@ namespace CityWebServer.RequestHandlers public class TransportRequestHandler : RequestHandlerBase { public TransportRequestHandler(IWebServer server) - : base(server, new Guid("89c8ef27-fc8c-4fe8-9793-1f6432feb179"), "Transport", "Rychard", 100, "/Transport") + : base(server, "/Transport") { } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { var transportManager = Singleton.instance; diff --git a/CityWebServer/RequestHandlers/VehicleRequestHandler.cs b/CityWebServer/RequestHandlers/VehicleRequestHandler.cs index 8dc5278..e0de0cc 100644 --- a/CityWebServer/RequestHandlers/VehicleRequestHandler.cs +++ b/CityWebServer/RequestHandlers/VehicleRequestHandler.cs @@ -12,11 +12,11 @@ namespace CityWebServer.RequestHandlers public class VehicleRequestHandler : RequestHandlerBase { public VehicleRequestHandler(IWebServer server) - : base(server, new Guid("2be6546a-d416-4939-8e08-1d0b739be835"), "Vehicle", "Rychard", 100, "/Vehicle") + : base(server, "/Vehicle") { } - public override IResponseFormatter Handle(HttpListenerRequest request) + public override IResponseFormatter Handle(HttpListenerRequest request, String slug, String wwwroot) { var vehicleManager = Singleton.instance; diff --git a/CityWebServer/wwwroot/content.html b/CityWebServer/wwwroot/content.html new file mode 100644 index 0000000..3c708c4 --- /dev/null +++ b/CityWebServer/wwwroot/content.html @@ -0,0 +1,35 @@ + + + + + + + + #PAGETITLE# + + + + + + +
    +

    #PAGETITLE#

    + #PAGEBODY# +
    + + + + \ No newline at end of file diff --git a/CityWebServer/wwwroot/index.html b/CityWebServer/wwwroot/index.html index ebe0dd3..0b056fd 100644 --- a/CityWebServer/wwwroot/index.html +++ b/CityWebServer/wwwroot/index.html @@ -5,7 +5,7 @@ - Cities: Skylines - Test + #PAGETITLE#