using System; using System.Configuration; using System.Net; using System.Reflection; using System.Text.RegularExpressions; using System.Xml; using BlogServer.WebServer; namespace BlogServer.Config { public class ConfigReader { private readonly DiffManager _diffMgr; private XmlElement _configRoot; public ConfigReader(string configFilePath) { _diffMgr = new DiffManager(configFilePath); _configRoot = _diffMgr.ConfigRoot; PrintConfig(); } private void PrintConfig() { /* XmlTextWriter xw = new XmlTextWriter(Console.Out); xw.Formatting = Formatting.Indented; xw.Indentation = 2; xw.IndentChar = ' '; xw.WriteNode(new XmlNodeReader(_configRoot), false); Console.WriteLine(); */ } internal TrivialWebServer Create() { lock (this) { ReloadConfig(); TrivialWebServer server = new TrivialWebServer(new IPEndPoint(IPAddress.Any, Port), LocalOnly); server.HandleConnectionBegin += new EventHandler(server_HandleConnectionBegin); ConfigureProperties(); ConfigurePaths(server); return server; } } internal void Reconfigure(TrivialWebServer tws) { lock (this) { tws.ClearHandlers(); ConfigureProperties(); ConfigurePaths(tws); } } private void server_HandleConnectionBegin(object sender, EventArgs e) { lock (this) { if (ReloadConfig()) { Console.WriteLine("Configuration file change detected, reloading"); PrintConfig(); Reconfigure((TrivialWebServer) sender); } } } private bool ReloadConfig() { if (_diffMgr.MaybeReload()) { _configRoot = _diffMgr.ConfigRoot; return true; } return false; } private void ConfigureProperties() { foreach (XmlElement prop in _configRoot.SelectNodes("property")) { string name = XmlUtil.ReadString(prop, "@name", null); if (name == null) throw new ConfigurationException("Property element requires a name attribute"); object value = XmlUtil.ReadString(prop, "@value", null); if (value == null) value = XmlUtil.ReadPath(prop, "@path", null); if (value == null) { string clazz = XmlUtil.ReadString(prop, "@className", null); if (clazz == null) throw new ConfigurationException("Property element requires either a 'value' or 'className' attribute"); ConstructorInfo type = Type.GetType(clazz, true).GetConstructor(new Type[] {typeof (XmlElement)}); value = type.Invoke(new object[] {prop}); } ConfigProperties.Instance[name] = value; } } private void ConfigurePaths(TrivialWebServer server) { foreach (XmlElement pathEl in _configRoot.SelectNodes("path")) { string pattern = XmlUtil.ReadString(pathEl, "@value", null); if (pattern != null) pattern = "^" + Regex.Escape(pattern) + "$"; else pattern = XmlUtil.ReadString(pathEl, "@pattern", null); if (pattern == null) throw new ConfigurationException("Path element requires either 'value' or 'pattern'"); HttpRequestPath path = new HttpRequestPath(); path.Register(new Regex(pattern), server); bool hasSeenHandler = false; XmlNode childNode = pathEl.FirstChild; while (childNode != null) { XmlElement childEl = childNode as XmlElement; if (childEl == null) { childNode = childNode.NextSibling; continue; } bool isFilter; switch (childEl.Name) { case "filter": isFilter = true; if (hasSeenHandler) throw new ConfigurationException("All filters for a given path must come before all the handlers in that path"); break; case "handler": isFilter = false; hasSeenHandler = true; break; default: throw new ConfigurationException("Element '" + childEl.Name + "' was not expected here"); } string clazz = XmlUtil.ReadString(childEl, "@className", null); Type filterType = Type.GetType(clazz, true); ConstructorInfo constructor = filterType.GetConstructor(new Type[] {typeof(XmlElement)}); if (constructor == null) throw new ConfigurationException("Class " + filterType.FullName + " does not have a constructor that takes XmlElement"); object filterOrHandler = constructor.Invoke(new object[] {childEl}); if (isFilter) { if (!(filterOrHandler is HttpRequestFilter)) throw new ConfigurationException("Class " + filterType.FullName + " does not derive from HttpRequestFilter"); path.AddFilter((HttpRequestFilter) filterOrHandler); } else { if (!(filterOrHandler is HttpRequestHandler)) throw new ConfigurationException("Class " + filterType.FullName + " does not derive from HttpRequestHandler"); path.AddHandler((HttpRequestHandler) filterOrHandler); } childNode = childNode.NextSibling; } } } private bool LocalOnly { get { return !XmlUtil.ReadBool(_configRoot, "@allowRemote", true); } } private int Port { get { return XmlUtil.ReadInt(_configRoot, "@port", 80); } } } } #if false Proposed syntax: WordPress http://wordpress.org/ http://unknown.wordpress.com ]]> #endif