Index: binary-improvements2/MapRendering/Web/API/AbsWebAPI.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/AbsWebAPI.cs	(revision 387)
+++ binary-improvements2/MapRendering/Web/API/AbsWebAPI.cs	(revision 387)
@@ -0,0 +1,15 @@
+namespace AllocsFixes.NetConnections.Servers.Web.API {
+	public abstract class AbsWebAPI {
+		public readonly string Name;
+
+		protected AbsWebAPI (string _name = null) {
+			Name = _name ?? GetType ().Name;
+		}
+
+		public abstract void HandleRequest (RequestContext _context);
+
+		public virtual int DefaultPermissionLevel () {
+			return 0;
+		}
+	}
+}
Index: binary-improvements2/MapRendering/Web/API/ExecuteConsoleCommand.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/ExecuteConsoleCommand.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/ExecuteConsoleCommand.cs	(revision 387)
@@ -1,25 +1,21 @@
 using System;
 using System.Net;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class ExecuteConsoleCommand : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
-			if (string.IsNullOrEmpty (_req.QueryString ["command"])) {
-				_resp.StatusCode = (int) HttpStatusCode.BadRequest;
-				Web.SetResponseTextContent (_resp, "No command given");
+	public class ExecuteConsoleCommand : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
+			if (string.IsNullOrEmpty (_context.Request.QueryString ["command"])) {
+				WebUtils.WriteText (_context.Response, "No command given", HttpStatusCode.BadRequest);
 				return;
 			}
 
 			WebCommandResult.ResultType responseType =
-				_req.QueryString ["raw"] != null
+				_context.Request.QueryString ["raw"] != null
 					? WebCommandResult.ResultType.Raw
-					: (_req.QueryString ["simple"] != null
+					: (_context.Request.QueryString ["simple"] != null
 						? WebCommandResult.ResultType.ResultOnly
 						: WebCommandResult.ResultType.Full);
 
-			string commandline = _req.QueryString ["command"];
+			string commandline = _context.Request.QueryString ["command"];
 			string commandPart = commandline.Split (' ') [0];
 			string argumentsPart = commandline.Substring (Math.Min (commandline.Length, commandPart.Length + 1));
@@ -28,6 +24,5 @@
 
 			if (command == null) {
-				_resp.StatusCode = (int) HttpStatusCode.NotFound;
-				Web.SetResponseTextContent (_resp, "Unknown command");
+				WebUtils.WriteText (_context.Response, "Unknown command", HttpStatusCode.NotFound);
 				return;
 			}
@@ -35,12 +30,11 @@
 			int commandPermissionLevel = GameManager.Instance.adminTools.GetCommandPermissionLevel (command.GetCommands ());
 
-			if (_permissionLevel > commandPermissionLevel) {
-				_resp.StatusCode = (int) HttpStatusCode.Forbidden;
-				Web.SetResponseTextContent (_resp, "You are not allowed to execute this command");
+			if (_context.PermissionLevel > commandPermissionLevel) {
+				WebUtils.WriteText (_context.Response, "You are not allowed to execute this command", HttpStatusCode.Forbidden);
 				return;
 			}
 
-			_resp.SendChunked = true;
-			WebCommandResult wcr = new WebCommandResult (commandPart, argumentsPart, responseType, _resp);
+			_context.Response.SendChunked = true;
+			WebCommandResult wcr = new WebCommandResult (commandPart, argumentsPart, responseType, _context.Response);
 			SdtdConsole.Instance.ExecuteAsync (commandline, wcr);
 		}
Index: binary-improvements2/MapRendering/Web/API/GetAllowedCommands.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetAllowedCommands.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetAllowedCommands.cs	(revision 387)
@@ -1,15 +1,12 @@
 using AllocsFixes.JSON;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetAllowedCommands : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+	public class GetAllowedCommands : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
 			JSONObject result = new JSONObject ();
 			JSONArray entries = new JSONArray ();
 			foreach (IConsoleCommand cc in SdtdConsole.Instance.GetCommands ()) {
 				int commandPermissionLevel = GameManager.Instance.adminTools.GetCommandPermissionLevel (cc.GetCommands ());
-				if (_permissionLevel <= commandPermissionLevel) {
+				if (_context.PermissionLevel <= commandPermissionLevel) {
 					string cmd = string.Empty;
 					foreach (string s in cc.GetCommands ()) {
@@ -29,5 +26,5 @@
 			result.Add ("commands", entries);
 
-			WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 
Index: binary-improvements2/MapRendering/Web/API/GetAnimalsLocation.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetAnimalsLocation.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetAnimalsLocation.cs	(revision 387)
@@ -2,13 +2,10 @@
 using AllocsFixes.JSON;
 using AllocsFixes.LiveData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	internal class GetAnimalsLocation : WebAPI {
+	internal class GetAnimalsLocation : AbsWebAPI {
 		private readonly List<EntityAnimal> animals = new List<EntityAnimal> ();
 
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+		public override void HandleRequest (RequestContext _context) {
 			JSONArray animalsJsResult = new JSONArray ();
 
@@ -37,5 +34,5 @@
 			}
 
-			WriteJSON (_resp, animalsJsResult);
+			WebUtils.WriteJson (_context.Response, animalsJsResult);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetHostileLocation.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetHostileLocation.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetHostileLocation.cs	(revision 387)
@@ -2,13 +2,10 @@
 using AllocsFixes.JSON;
 using AllocsFixes.LiveData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	internal class GetHostileLocation : WebAPI {
+	internal class GetHostileLocation : AbsWebAPI {
 		private readonly List<EntityEnemy> enemies = new List<EntityEnemy> ();
 
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+		public override void HandleRequest (RequestContext _context) {
 			JSONArray hostilesJsResult = new JSONArray ();
 
@@ -37,5 +34,5 @@
 			}
 
-			WriteJSON (_resp, hostilesJsResult);
+			WebUtils.WriteJson (_context.Response, hostilesJsResult);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetLandClaims.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetLandClaims.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetLandClaims.cs	(revision 387)
@@ -3,16 +3,12 @@
 using AllocsFixes.JSON;
 using AllocsFixes.PersistentData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetLandClaims : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+	public class GetLandClaims : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
 			PlatformUserIdentifierAbs requestedUserId = null;
-			if (_req.QueryString ["userid"] != null) {
-				if (!PlatformUserIdentifierAbs.TryFromCombinedString (_req.QueryString ["userid"], out requestedUserId)) {
-					_resp.StatusCode = (int) HttpStatusCode.BadRequest;
-					Web.SetResponseTextContent (_resp, "Invalid user id given");
+			if (_context.Request.QueryString ["userid"] != null) {
+				if (!PlatformUserIdentifierAbs.TryFromCombinedString (_context.Request.QueryString ["userid"], out requestedUserId)) {
+					WebUtils.WriteText (_context.Response, "Invalid user id given", HttpStatusCode.BadRequest);
 					return;
 				}
@@ -20,7 +16,7 @@
 
 			// default user, cheap way to avoid 'null reference exception'
-			PlatformUserIdentifierAbs userId = _user?.UserId;
+			PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
 
-			bool bViewAll = WebConnection.CanViewAllClaims (_permissionLevel);
+			bool bViewAll = WebConnection.CanViewAllClaims (_context.PermissionLevel);
 
 			JSONObject result = new JSONObject ();
@@ -74,5 +70,5 @@
 			}
 
-			WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetLog.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetLog.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetLog.cs	(revision 387)
@@ -1,14 +1,11 @@
 using System.Collections.Generic;
 using AllocsFixes.JSON;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetLog : WebAPI {
+	public class GetLog : AbsWebAPI {
 		private const int MAX_COUNT = 1000;
 		
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
-			if (_req.QueryString ["count"] == null || !int.TryParse (_req.QueryString ["count"], out int count)) {
+		public override void HandleRequest (RequestContext _context) {
+			if (_context.Request.QueryString ["count"] == null || !int.TryParse (_context.Request.QueryString ["count"], out int count)) {
 				count = 50;
 			}
@@ -26,5 +23,5 @@
 			}
 
-			if (_req.QueryString ["firstLine"] == null || !int.TryParse (_req.QueryString ["firstLine"], out int firstLine)) {
+			if (_context.Request.QueryString ["firstLine"] == null || !int.TryParse (_context.Request.QueryString ["firstLine"], out int firstLine)) {
 				firstLine = count > 0 ? LogBuffer.Instance.OldestLine : LogBuffer.Instance.LatestLine;
 			}
@@ -37,6 +34,4 @@
 			foreach (LogBuffer.LogEntry logEntry in logEntries) {
 				JSONObject entry = new JSONObject ();
-				entry.Add ("date", new JSONString (logEntry.date));
-				entry.Add ("time", new JSONString (logEntry.time));
 				entry.Add ("isotime", new JSONString (logEntry.isoTime));
 				entry.Add ("uptime", new JSONString (logEntry.uptime.ToString ()));
@@ -51,5 +46,5 @@
 			result.Add ("entries", entries);
 
-			WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetPlayerInventories.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetPlayerInventories.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetPlayerInventories.cs	(revision 387)
@@ -2,12 +2,9 @@
 using AllocsFixes.JSON;
 using AllocsFixes.PersistentData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetPlayerInventories : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
-			GetPlayerInventory.GetInventoryArguments (_req, out bool showIconColor, out bool showIconName);
+	public class GetPlayerInventories : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
+			GetPlayerInventory.GetInventoryArguments (_context.Request, out bool showIconColor, out bool showIconName);
 
 			JSONArray AllInventoriesResult = new JSONArray ();
@@ -25,5 +22,5 @@
 			}
 
-			WriteJSON (_resp, AllInventoriesResult);
+			WebUtils.WriteJson (_context.Response, AllInventoriesResult);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetPlayerInventory.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetPlayerInventory.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetPlayerInventory.cs	(revision 387)
@@ -4,20 +4,16 @@
 using AllocsFixes.PersistentData;
 using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetPlayerInventory : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp,
-			WebConnection _user, int _permissionLevel) {
-			if (_req.QueryString ["userid"] == null) {
-				_resp.StatusCode = (int) HttpStatusCode.BadRequest;
-				Web.SetResponseTextContent (_resp, "No user id given");
+	public class GetPlayerInventory : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
+			if (_context.Request.QueryString ["userid"] == null) {
+				WebUtils.WriteText (_context.Response, "No user id given", HttpStatusCode.BadRequest);
 				return;
 			}
 
-			string userIdString = _req.QueryString ["userid"];
+			string userIdString = _context.Request.QueryString ["userid"];
 			if (!PlatformUserIdentifierAbs.TryFromCombinedString (userIdString, out PlatformUserIdentifierAbs userId)) {
-				_resp.StatusCode = (int) HttpStatusCode.BadRequest;
-				Web.SetResponseTextContent (_resp, "Invalid user id given");
+				WebUtils.WriteText (_context.Response, "Invalid user id given", HttpStatusCode.BadRequest);
 				return;
 			}
@@ -25,14 +21,13 @@
 			Player p = PersistentContainer.Instance.Players [userId, false];
 			if (p == null) {
-				_resp.StatusCode = (int) HttpStatusCode.NotFound;
-				Web.SetResponseTextContent (_resp, "Unknown user id given");
+				WebUtils.WriteText (_context.Response, "Unknown user id given", HttpStatusCode.NotFound);
 				return;
 			}
 
-			GetInventoryArguments (_req, out bool showIconColor, out bool showIconName);
+			GetInventoryArguments (_context.Request, out bool showIconColor, out bool showIconName);
 
 			JSONObject result = DoPlayer (userIdString, p, showIconColor, showIconName);
 
-			WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 
Index: binary-improvements2/MapRendering/Web/API/GetPlayerList.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetPlayerList.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetPlayerList.cs	(revision 387)
@@ -5,9 +5,7 @@
 using AllocsFixes.JSON;
 using AllocsFixes.PersistentData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetPlayerList : WebAPI {
+	public class GetPlayerList : AbsWebAPI {
 		private static readonly Regex numberFilterMatcher =
 			new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
@@ -17,21 +15,20 @@
 #endif
 
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+		public override void HandleRequest (RequestContext _context) {
 			AdminTools admTools = GameManager.Instance.adminTools;
-			PlatformUserIdentifierAbs userId = _user?.UserId;
-
-			bool bViewAll = WebConnection.CanViewAllPlayers (_permissionLevel);
+			PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
+
+			bool bViewAll = WebConnection.CanViewAllPlayers (_context.PermissionLevel);
 
 			// TODO: Sort (and filter?) prior to converting to JSON ... hard as how to get the correct column's data? (i.e. column name matches JSON object field names, not source data)
 
 			int rowsPerPage = 25;
-			if (_req.QueryString ["rowsperpage"] != null) {
-				int.TryParse (_req.QueryString ["rowsperpage"], out rowsPerPage);
+			if (_context.Request.QueryString ["rowsperpage"] != null) {
+				int.TryParse (_context.Request.QueryString ["rowsperpage"], out rowsPerPage);
 			}
 
 			int page = 0;
-			if (_req.QueryString ["page"] != null) {
-				int.TryParse (_req.QueryString ["page"], out page);
+			if (_context.Request.QueryString ["page"] != null) {
+				int.TryParse (_context.Request.QueryString ["page"], out page);
 			}
 
@@ -83,9 +80,9 @@
 			IEnumerable<JSONObject> list = playerList;
 
-			foreach (string key in _req.QueryString.AllKeys) {
+			foreach (string key in _context.Request.QueryString.AllKeys) {
 				if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) {
 					string filterCol = key.Substring (key.IndexOf ('[') + 1);
 					filterCol = filterCol.Substring (0, filterCol.Length - 1);
-					string filterVal = _req.QueryString.Get (key).Trim ();
+					string filterVal = _context.Request.QueryString.Get (key).Trim ();
 
 					list = ExecuteFilter (list, filterCol, filterVal);
@@ -95,9 +92,9 @@
 			int totalAfterFilter = list.Count ();
 
-			foreach (string key in _req.QueryString.AllKeys) {
+			foreach (string key in _context.Request.QueryString.AllKeys) {
 				if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) {
 					string sortCol = key.Substring (key.IndexOf ('[') + 1);
 					sortCol = sortCol.Substring (0, sortCol.Length - 1);
-					string sortVal = _req.QueryString.Get (key);
+					string sortVal = _context.Request.QueryString.Get (key);
 
 					list = ExecuteSort (list, sortCol, sortVal == "0");
@@ -120,5 +117,5 @@
 			result.Add ("players", playersJsResult);
 
-			WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 
Index: binary-improvements2/MapRendering/Web/API/GetPlayersLocation.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetPlayersLocation.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetPlayersLocation.cs	(revision 387)
@@ -2,20 +2,17 @@
 using AllocsFixes.JSON;
 using AllocsFixes.PersistentData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetPlayersLocation : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+	public class GetPlayersLocation : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
 			AdminTools admTools = GameManager.Instance.adminTools;
-			PlatformUserIdentifierAbs userId = _user?.UserId;
+			PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
 
 			bool listOffline = false;
-			if (_req.QueryString ["offline"] != null) {
-				bool.TryParse (_req.QueryString ["offline"], out listOffline);
+			if (_context.Request.QueryString ["offline"] != null) {
+				bool.TryParse (_context.Request.QueryString ["offline"], out listOffline);
 			}
 
-			bool bViewAll = WebConnection.CanViewAllPlayers (_permissionLevel);
+			bool bViewAll = WebConnection.CanViewAllPlayers (_context.PermissionLevel);
 
 			JSONArray playersJsResult = new JSONArray ();
@@ -57,5 +54,5 @@
 			}
 
-			WriteJSON (_resp, playersJsResult);
+			WebUtils.WriteJson (_context.Response, playersJsResult);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetPlayersOnline.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetPlayersOnline.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetPlayersOnline.cs	(revision 387)
@@ -2,11 +2,8 @@
 using AllocsFixes.JSON;
 using AllocsFixes.PersistentData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetPlayersOnline : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+	public class GetPlayersOnline : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
 			JSONArray players = new JSONArray ();
 
@@ -44,5 +41,5 @@
 			}
 
-			WriteJSON (_resp, players);
+			WebUtils.WriteJson (_context.Response, players);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetServerInfo.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetServerInfo.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetServerInfo.cs	(revision 387)
@@ -1,11 +1,8 @@
 using System;
 using AllocsFixes.JSON;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetServerInfo : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+	public class GetServerInfo : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
 			JSONObject serverInfo = new JSONObject ();
 
@@ -43,5 +40,5 @@
 
 
-			WriteJSON (_resp, serverInfo);
+			WebUtils.WriteJson (_context.Response, serverInfo);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/GetStats.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetStats.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetStats.cs	(revision 387)
@@ -1,11 +1,8 @@
 using AllocsFixes.JSON;
 using AllocsFixes.LiveData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetStats : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+	public class GetStats : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
 			JSONObject result = new JSONObject ();
 
@@ -20,5 +17,5 @@
 			result.Add ("animals", new JSONNumber (Animals.Instance.GetCount ()));
 
-			WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 
Index: binary-improvements2/MapRendering/Web/API/GetWebMods.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetWebMods.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetWebMods.cs	(revision 387)
@@ -1,8 +1,6 @@
 using AllocsFixes.JSON;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetWebMods : WebAPI {
+	public class GetWebMods : AbsWebAPI {
 		private readonly JSONArray loadedWebMods = new JSONArray ();
 
@@ -27,8 +25,6 @@
 		}
 
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
-
-			WriteJSON (_resp, loadedWebMods);
+		public override void HandleRequest (RequestContext _context) {
+			WebUtils.WriteJson (_context.Response, loadedWebMods);
 		}
 
Index: binary-improvements2/MapRendering/Web/API/GetWebUIUpdates.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/GetWebUIUpdates.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/GetWebUIUpdates.cs	(revision 387)
@@ -1,14 +1,11 @@
 using AllocsFixes.JSON;
 using AllocsFixes.LiveData;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class GetWebUIUpdates : WebAPI {
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
+	public class GetWebUIUpdates : AbsWebAPI {
+		public override void HandleRequest (RequestContext _context) {
 			int latestLine;
-			if (_req.QueryString ["latestLine"] == null ||
-			    !int.TryParse (_req.QueryString ["latestLine"], out latestLine)) {
+			if (_context.Request.QueryString ["latestLine"] == null ||
+			    !int.TryParse (_context.Request.QueryString ["latestLine"], out latestLine)) {
 				latestLine = 0;
 			}
@@ -28,5 +25,5 @@
 			result.Add ("newlogs", new JSONNumber (LogBuffer.Instance.LatestLine - latestLine));
 
-			WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 
Index: binary-improvements2/MapRendering/Web/API/Null.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/Null.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/API/Null.cs	(revision 387)
@@ -1,17 +1,14 @@
 ﻿using System.Text;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public class Null : WebAPI {
+	public class Null : AbsWebAPI {
 		public Null (string _name) : base(_name) {
 		}
 		
-		public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
-			int _permissionLevel) {
-			_resp.ContentLength64 = 0;
-			_resp.ContentType = "text/plain";
-			_resp.ContentEncoding = Encoding.ASCII;
-			_resp.OutputStream.Write (new byte[] { }, 0, 0);
+		public override void HandleRequest (RequestContext _context) {
+			_context.Response.ContentLength64 = 0;
+			_context.Response.ContentType = "text/plain";
+			_context.Response.ContentEncoding = Encoding.ASCII;
+			_context.Response.OutputStream.Write (new byte[] { }, 0, 0);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/API/WebAPI.cs
===================================================================
--- binary-improvements2/MapRendering/Web/API/WebAPI.cs	(revision 386)
+++ 	(revision )
@@ -1,54 +1,0 @@
-using System.Text;
-using AllocsFixes.JSON;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
-
-namespace AllocsFixes.NetConnections.Servers.Web.API {
-	public abstract class WebAPI {
-		public readonly string Name;
-
-		protected WebAPI (string _name = null) {
-			Name = _name ?? GetType ().Name;
-		}
-
-#if ENABLE_PROFILER
-		private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Serialize");
-		private static readonly UnityEngine.Profiling.CustomSampler netWriteSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Write");
-#endif
-
-		public static void WriteJSON (HttpListenerResponse _resp, JSONNode _root) {
-#if ENABLE_PROFILER
-			jsonSerializeSampler.Begin ();
-#endif
-			StringBuilder sb = new StringBuilder ();
-			_root.ToString (sb);
-#if ENABLE_PROFILER
-			jsonSerializeSampler.End ();
-			netWriteSampler.Begin ();
-#endif
-			byte[] buf = Encoding.UTF8.GetBytes (sb.ToString ());
-			_resp.ContentLength64 = buf.Length;
-			_resp.ContentType = "application/json";
-			_resp.ContentEncoding = Encoding.UTF8;
-			_resp.OutputStream.Write (buf, 0, buf.Length);
-#if ENABLE_PROFILER
-			netWriteSampler.End ();
-#endif
-		}
-
-		public static void WriteText (HttpListenerResponse _resp, string _text) {
-			byte[] buf = Encoding.UTF8.GetBytes (_text);
-			_resp.ContentLength64 = buf.Length;
-			_resp.ContentType = "text/plain";
-			_resp.ContentEncoding = Encoding.UTF8;
-			_resp.OutputStream.Write (buf, 0, buf.Length);
-		}
-
-		public abstract void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp,
-			WebConnection _user, int _permissionLevel);
-
-		public virtual int DefaultPermissionLevel () {
-			return 0;
-		}
-	}
-}
Index: binary-improvements2/MapRendering/Web/Handlers/AbsHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/AbsHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/AbsHandler.cs	(revision 387)
@@ -1,5 +1,2 @@
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
-
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
 	public abstract class AbsHandler {
@@ -16,6 +13,5 @@
 		}
 
-		public abstract void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel);
+		public abstract void HandleRequest (RequestContext _context);
 
 		public virtual bool IsAuthorizedForHandler (WebConnection _user, int _permissionLevel) {
Index: binary-improvements2/MapRendering/Web/Handlers/ApiHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/ApiHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/ApiHandler.cs	(revision 387)
@@ -4,10 +4,8 @@
 using System.Reflection;
 using AllocsFixes.NetConnections.Servers.Web.API;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
 	public class ApiHandler : AbsHandler {
-		private readonly Dictionary<string, WebAPI> apis = new CaseInsensitiveStringDictionary<WebAPI> ();
+		private readonly Dictionary<string, AbsWebAPI> apis = new CaseInsensitiveStringDictionary<AbsWebAPI> ();
 
 		public ApiHandler () : base (null) {
@@ -25,8 +23,8 @@
 			
 			foreach (Type t in Assembly.GetExecutingAssembly ().GetTypes ()) {
-				if (!t.IsAbstract && t.IsSubclassOf (typeof (WebAPI))) {
+				if (!t.IsAbstract && t.IsSubclassOf (typeof (AbsWebAPI))) {
 					ConstructorInfo ctor = t.GetConstructor (apiWithParentCtorTypes);
 					if (ctor != null) {
-						WebAPI apiInstance = (WebAPI) ctor.Invoke (apiWithParentCtorArgs);
+						AbsWebAPI apiInstance = (AbsWebAPI) ctor.Invoke (apiWithParentCtorArgs);
 						addApi (apiInstance);
 						continue;
@@ -35,5 +33,5 @@
 					ctor = t.GetConstructor (apiEmptyCtorTypes);
 					if (ctor != null) {
-						WebAPI apiInstance = (WebAPI) ctor.Invoke (apiEmptyCtorArgs);
+						AbsWebAPI apiInstance = (AbsWebAPI) ctor.Invoke (apiEmptyCtorArgs);
 						addApi (apiInstance);
 					}
@@ -46,5 +44,5 @@
 		}
 
-		private void addApi (WebAPI _api) {
+		private void addApi (AbsWebAPI _api) {
 			apis.Add (_api.Name, _api);
 			WebPermissions.Instance.AddKnownModule ("webapi." + _api.Name, _api.DefaultPermissionLevel ());
@@ -55,17 +53,26 @@
 #endif
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
-			string apiName = _requestPath.Remove (0, urlBasePath.Length);
+		public override void HandleRequest (RequestContext _context) {
 
-			if (!apis.TryGetValue (apiName, out WebAPI api)) {
+			string apiName;
+			string subPath = null;
+
+			int pathSeparatorIndex = _context.RequestPath.IndexOf ('/', urlBasePath.Length);
+			if (pathSeparatorIndex >= 0) {
+				apiName = _context.RequestPath.Substring (urlBasePath.Length, pathSeparatorIndex - urlBasePath.Length);
+				subPath = _context.RequestPath.Substring (pathSeparatorIndex + 1);
+			} else {
+				apiName = _context.RequestPath.Substring (urlBasePath.Length);
+			}
+			
+			if (!apis.TryGetValue (apiName, out AbsWebAPI api)) {
 				Log.Out ($"Error in {nameof(ApiHandler)}.HandleRequest(): No handler found for API \"{apiName}\"");
-				_resp.StatusCode = (int) HttpStatusCode.NotFound;
+				_context.Response.StatusCode = (int) HttpStatusCode.NotFound;
 				return;
 			}
 
-			if (!AuthorizeForApi (apiName, _permissionLevel)) {
-				_resp.StatusCode = (int) HttpStatusCode.Forbidden;
-				if (_con != null) {
+			if (!AuthorizeForApi (apiName, _context.PermissionLevel)) {
+				_context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
+				if (_context.Connection != null) {
 					//Log.Out ($"{nameof(ApiHandler)}: user '{user.SteamID}' not allowed to execute '{apiName}'");
 				}
@@ -74,9 +81,11 @@
 			}
 
+			_context.RequestPath = subPath;
+
 			try {
 #if ENABLE_PROFILER
 				apiHandlerSampler.Begin ();
 #endif
-				api.HandleRequest (_req, _resp, _con, _permissionLevel);
+				api.HandleRequest (_context);
 #if ENABLE_PROFILER
 				apiHandlerSampler.End ();
@@ -85,5 +94,5 @@
 				Log.Error ($"Error in {nameof(ApiHandler)}.HandleRequest(): Handler {api.Name} threw an exception:");
 				Log.Exception (e);
-				_resp.StatusCode = (int) HttpStatusCode.InternalServerError;
+				_context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
 			}
 		}
Index: binary-improvements2/MapRendering/Web/Handlers/ItemIconHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/ItemIconHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/ItemIconHandler.cs	(revision 387)
@@ -4,6 +4,4 @@
 using System.Net;
 using UnityEngine;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 using Object = UnityEngine.Object;
 
@@ -26,26 +24,25 @@
 		public static ItemIconHandler Instance { get; private set; }
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
+		public override void HandleRequest (RequestContext _context) {
 			if (!loaded) {
-				_resp.StatusCode = (int) HttpStatusCode.InternalServerError;
+				_context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
 				Log.Out ("Web:IconHandler: Icons not loaded");
 				return;
 			}
 
-			string requestFileName = _requestPath.Remove (0, urlBasePath.Length);
+			string requestFileName = _context.RequestPath.Remove (0, urlBasePath.Length);
 			requestFileName = requestFileName.Remove (requestFileName.LastIndexOf ('.'));
 
-			if (icons.ContainsKey (requestFileName) && _requestPath.EndsWith (".png", StringComparison.OrdinalIgnoreCase)) {
-				_resp.ContentType = MimeType.GetMimeType (".png");
+			if (icons.ContainsKey (requestFileName) && _context.RequestPath.EndsWith (".png", StringComparison.OrdinalIgnoreCase)) {
+				_context.Response.ContentType = MimeType.GetMimeType (".png");
 
 				byte[] itemIconData = icons [requestFileName];
 
-				_resp.ContentLength64 = itemIconData.Length;
-				_resp.OutputStream.Write (itemIconData, 0, itemIconData.Length);
+				_context.Response.ContentLength64 = itemIconData.Length;
+				_context.Response.OutputStream.Write (itemIconData, 0, itemIconData.Length);
 			} else {
-				_resp.StatusCode = (int) HttpStatusCode.NotFound;
+				_context.Response.StatusCode = (int) HttpStatusCode.NotFound;
 				if (logMissingFiles) {
-					Log.Out ("Web:IconHandler:FileNotFound: \"" + _requestPath + "\" ");
+					Log.Out ("Web:IconHandler:FileNotFound: \"" + _context.RequestPath + "\" ");
 				}
 			}
Index: binary-improvements2/MapRendering/Web/Handlers/RewriteHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/RewriteHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/RewriteHandler.cs	(revision 387)
@@ -1,5 +1,2 @@
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
-
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
 	public class RewriteHandler : AbsHandler {
@@ -12,8 +9,7 @@
 		}
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
-			string newRequestPath = fixedTarget ? target : target + _requestPath.Remove (0, urlBasePath.Length);
-			parent.ApplyPathHandler (newRequestPath, _req, _resp, _con, _permissionLevel);
+		public override void HandleRequest (RequestContext _context) {
+			_context.RequestPath = fixedTarget ? target : target + _context.RequestPath.Remove (0, urlBasePath.Length);
+			parent.ApplyPathHandler (_context);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/Handlers/SessionHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/SessionHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/SessionHandler.cs	(revision 387)
@@ -3,6 +3,4 @@
 using System.Net;
 using System.Text;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
@@ -29,14 +27,13 @@
 		}
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
+		public override void HandleRequest (RequestContext _context) {
 			
-			IPEndPoint reqRemoteEndPoint = _req.RemoteEndPoint;
+			IPEndPoint reqRemoteEndPoint = _context.Request.RemoteEndPoint;
 			if (reqRemoteEndPoint == null) {
-				_resp.Redirect (pageBasePath);
+				_context.Response.Redirect (pageBasePath);
 				return;
 			}
 
-			string subpath = _requestPath.Remove (0, urlBasePath.Length);
+			string subpath = _context.RequestPath.Remove (0, urlBasePath.Length);
 
 			StringBuilder result = new StringBuilder ();
@@ -47,5 +44,5 @@
 
 				try {
-					ulong id = OpenID.Validate (_req);
+					ulong id = OpenID.Validate (_context.Request);
 					if (id > 0) {
 						WebConnection con = connectionHandler.LogIn (id, reqRemoteEndPoint.Address);
@@ -60,6 +57,6 @@
 							Secure = false
 						};
-						_resp.AppendCookie (cookie);
-						_resp.Redirect (pageBasePath);
+						_context.Response.AppendCookie (cookie);
+						_context.Response.Redirect (pageBasePath);
 
 						return;
@@ -73,11 +70,11 @@
 				result.Append ($"<h1>Login failed, <a href=\"{pageBasePath}\">click to return to main page</a>.</h1>");
 			} else if (subpath.StartsWith ("logout")) {
-				if (_con != null) {
-					connectionHandler.LogOut (_con.SessionID);
+				if (_context.Connection != null) {
+					connectionHandler.LogOut (_context.Connection.SessionID);
 					Cookie cookie = new Cookie ("sid", "", "/") {
 						Expired = true
 					};
-					_resp.AppendCookie (cookie);
-					_resp.Redirect (pageBasePath);
+					_context.Response.AppendCookie (cookie);
+					_context.Response.Redirect (pageBasePath);
 					return;
 				}
@@ -85,7 +82,7 @@
 				result.Append ($"<h1>Not logged in, <a href=\"{pageBasePath}\">click to return to main page</a>.</h1>");
 			} else if (subpath.StartsWith (steamLoginUrl)) {
-				string host = (Web.IsSslRedirected (_req) ? "https://" : "http://") + _req.UserHostName;
+				string host = (Web.IsSslRedirected (_context.Request) ? "https://" : "http://") + _context.Request.UserHostName;
 				string url = OpenID.GetOpenIdLoginUrl (host, host + urlBasePath + steamOpenIdVerifyUrl);
-				_resp.Redirect (url);
+				_context.Response.Redirect (url);
 				return;
 			} else {
@@ -95,9 +92,5 @@
 			result.Append (footer);
 
-			_resp.ContentType = MimeType.GetMimeType (".html");
-			_resp.ContentEncoding = Encoding.UTF8;
-			byte[] buf = Encoding.UTF8.GetBytes (result.ToString ());
-			_resp.ContentLength64 = buf.Length;
-			_resp.OutputStream.Write (buf, 0, buf.Length);
+			WebUtils.WriteText (_context.Response, result.ToString (), _mimeType: WebUtils.MimeHtml);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/Handlers/SimpleRedirectHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/SimpleRedirectHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/SimpleRedirectHandler.cs	(revision 387)
@@ -1,5 +1,2 @@
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
-
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
 	public class SimpleRedirectHandler : AbsHandler {
@@ -10,7 +7,6 @@
 		}
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
-			_resp.Redirect (target);
+		public override void HandleRequest (RequestContext _context) {
+			_context.Response.Redirect (target);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/Handlers/StaticHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/StaticHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/StaticHandler.cs	(revision 387)
@@ -2,6 +2,4 @@
 using System.Net;
 using AllocsFixes.FileCache;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
@@ -18,18 +16,17 @@
 		}
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
-			string fn = _requestPath.Remove (0, urlBasePath.Length);
+		public override void HandleRequest (RequestContext _context) {
+			string fn = _context.RequestPath.Remove (0, urlBasePath.Length);
 
 			byte[] content = cache.GetFileContent (datapath + fn);
 
 			if (content != null) {
-				_resp.ContentType = MimeType.GetMimeType (Path.GetExtension (fn));
-				_resp.ContentLength64 = content.Length;
-				_resp.OutputStream.Write (content, 0, content.Length);
+				_context.Response.ContentType = MimeType.GetMimeType (Path.GetExtension (fn));
+				_context.Response.ContentLength64 = content.Length;
+				_context.Response.OutputStream.Write (content, 0, content.Length);
 			} else {
-				_resp.StatusCode = (int) HttpStatusCode.NotFound;
+				_context.Response.StatusCode = (int) HttpStatusCode.NotFound;
 				if (logMissingFiles) {
-					Log.Out ("Web:Static:FileNotFound: \"" + _requestPath + "\" @ \"" + datapath + fn + "\"");
+					Log.Out ("Web:Static:FileNotFound: \"" + _context.RequestPath + "\" @ \"" + datapath + fn + "\"");
 				}
 			}
Index: binary-improvements2/MapRendering/Web/Handlers/UserStatusHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/UserStatusHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Handlers/UserStatusHandler.cs	(revision 387)
@@ -1,6 +1,3 @@
 using AllocsFixes.JSON;
-using AllocsFixes.NetConnections.Servers.Web.API;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
@@ -9,10 +6,9 @@
 		}
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
+		public override void HandleRequest (RequestContext _context) {
 			JSONObject result = new JSONObject ();
 
-			result.Add ("loggedin", new JSONBoolean (_con != null));
-			result.Add ("username", new JSONString (_con != null ? _con.UserId.ToString () : string.Empty));
+			result.Add ("loggedin", new JSONBoolean (_context.Connection != null));
+			result.Add ("username", new JSONString (_context.Connection != null ? _context.Connection.UserId.ToString () : string.Empty));
 
 			JSONArray perms = new JSONArray ();
@@ -20,5 +16,5 @@
 				JSONObject permObj = new JSONObject ();
 				permObj.Add ("module", new JSONString (perm.module));
-				permObj.Add ("allowed", new JSONBoolean (perm.permissionLevel >= _permissionLevel));
+				permObj.Add ("allowed", new JSONBoolean (perm.permissionLevel >= _context.PermissionLevel));
 				perms.Add (permObj);
 			}
@@ -26,5 +22,5 @@
 			result.Add ("permissions", perms);
 
-			WebAPI.WriteJSON (_resp, result);
+			WebUtils.WriteJson (_context.Response, result);
 		}
 	}
Index: binary-improvements2/MapRendering/Web/LogBuffer.cs
===================================================================
--- binary-improvements2/MapRendering/Web/LogBuffer.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/LogBuffer.cs	(revision 387)
@@ -127,6 +127,4 @@
 		public class LogEntry {
 			public readonly DateTime timestamp;
-			public readonly string date;
-			public readonly string time;
 			public readonly string isoTime;
 			public readonly string message;
@@ -137,6 +135,4 @@
 			public LogEntry (DateTime _timestamp, string _message, string _trace, LogType _type, long _uptime) {
 				timestamp = _timestamp;
-				date = $"{_timestamp.Year:0000}-{_timestamp.Month:00}-{_timestamp.Day:00}";
-				time = $"{_timestamp.Hour:00}:{_timestamp.Minute:00}:{_timestamp.Second:00}";
 				isoTime = _timestamp.ToString ("o");
 
Index: binary-improvements2/MapRendering/Web/RequestContext.cs
===================================================================
--- binary-improvements2/MapRendering/Web/RequestContext.cs	(revision 387)
+++ binary-improvements2/MapRendering/Web/RequestContext.cs	(revision 387)
@@ -0,0 +1,19 @@
+using SpaceWizards.HttpListener;
+
+namespace AllocsFixes.NetConnections.Servers.Web {
+	public class RequestContext {
+		public string RequestPath;
+		public readonly HttpListenerRequest Request;
+		public readonly HttpListenerResponse Response;
+		public readonly WebConnection Connection;
+		public readonly int PermissionLevel;
+
+		public RequestContext (string _requestPath, HttpListenerRequest _request, HttpListenerResponse _response, WebConnection _connection, int _permissionLevel) {
+			RequestPath = _requestPath;
+			Request = _request;
+			Response = _response;
+			Connection = _connection;
+			PermissionLevel = _permissionLevel;
+		}
+	}
+}
Index: binary-improvements2/MapRendering/Web/SSE/EventLog.cs
===================================================================
--- binary-improvements2/MapRendering/Web/SSE/EventLog.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/SSE/EventLog.cs	(revision 387)
@@ -10,6 +10,4 @@
 
 		private void LogCallback (string _formattedMsg, string _plainMsg, string _trace, LogType _type, DateTime _timestamp, long _uptime) {
-			string date = $"{_timestamp.Year:0000}-{_timestamp.Month:00}-{_timestamp.Day:00}";
-			string time = $"{_timestamp.Hour:00}:{_timestamp.Minute:00}:{_timestamp.Second:00}";
 			string isotime = _timestamp.ToString ("o");
 			string uptime = _uptime.ToString ();
@@ -20,6 +18,4 @@
 			data.Add ("type", new JSONString (_type.ToStringCached ()));
 			data.Add ("trace", new JSONString (_trace));
-			data.Add ("date", new JSONString (date));
-			data.Add ("time", new JSONString (time));
 			data.Add ("isotime", new JSONString (isotime));
 			data.Add ("uptime", new JSONString (uptime));
Index: binary-improvements2/MapRendering/Web/SSE/SseHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/SSE/SseHandler.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/SSE/SseHandler.cs	(revision 387)
@@ -5,6 +5,4 @@
 using System.Threading;
 using AllocsFixes.NetConnections.Servers.Web.Handlers;
-using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 // Implemented following HTML spec
@@ -52,17 +50,16 @@
 		}
 
-		public override void HandleRequest (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
-			string eventName = _requestPath.Remove (0, urlBasePath.Length);
+		public override void HandleRequest (RequestContext _context) {
+			string eventName = _context.RequestPath.Remove (0, urlBasePath.Length);
 
 			if (!events.TryGetValue (eventName, out EventBase eventInstance)) {
 				Log.Out ($"Error in {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
-				_resp.StatusCode = (int)HttpStatusCode.NotFound;
+				_context.Response.StatusCode = (int)HttpStatusCode.NotFound;
 				return;
 			}
 
-			if (!IsAuthorizedForEvent (eventName, _permissionLevel)) {
-				_resp.StatusCode = (int)HttpStatusCode.Forbidden;
-				if (_con != null) {
+			if (!IsAuthorizedForEvent (eventName, _context.PermissionLevel)) {
+				_context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+				if (_context.Connection != null) {
 					//Log.Out ($"{nameof(SseHandler)}: user '{user.SteamID}' not allowed to access '{eventName}'");
 				}
@@ -72,15 +69,15 @@
 
 			try {
-				eventInstance.AddListener (_resp);
+				eventInstance.AddListener (_context.Response);
 
 				// Keep the request open
-				_resp.SendChunked = true;
+				_context.Response.SendChunked = true;
 
-				_resp.AddHeader ("Content-Type", "text/event-stream");
-				_resp.OutputStream.Flush ();
+				_context.Response.AddHeader ("Content-Type", "text/event-stream");
+				_context.Response.OutputStream.Flush ();
 			} catch (Exception e) {
 				Log.Error ($"Error in {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
 				Log.Exception (e);
-				_resp.StatusCode = (int)HttpStatusCode.InternalServerError;
+				_context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
 			}
 		}
Index: binary-improvements2/MapRendering/Web/Web.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Web.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/Web.cs	(revision 387)
@@ -6,5 +6,4 @@
 using HttpStatusCode = System.Net.HttpStatusCode;
 using IPEndPoint = System.Net.IPEndPoint;
-using System.Text;
 using System.Threading;
 using AllocsFixes.FileCache;
@@ -226,6 +225,8 @@
 					return;
 				}
-				
-				ApplyPathHandler (requestPath, request, response, conn, permissionLevel);
+
+				RequestContext context = new RequestContext (requestPath, request, response, conn, permissionLevel);
+				
+				ApplyPathHandler (context);
 
 			} catch (IOException e) {
@@ -252,13 +253,12 @@
 		}
 
-		public void ApplyPathHandler (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
-			int _permissionLevel) {
+		public void ApplyPathHandler (RequestContext _context) {
 			for (int i = handlers.Count - 1; i >= 0; i--) {
 				AbsHandler handler = handlers [i];
 				
-				if (_requestPath.StartsWith (handler.UrlBasePath)) {
-					if (!handler.IsAuthorizedForHandler (_con, _permissionLevel)) {
-						_resp.StatusCode = (int)HttpStatusCode.Forbidden;
-						if (_con != null) {
+				if (_context.RequestPath.StartsWith (handler.UrlBasePath)) {
+					if (!handler.IsAuthorizedForHandler (_context.Connection, _context.PermissionLevel)) {
+						_context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+						if (_context.Connection != null) {
 							//Log.Out ("Web.HandleRequest: user '{0}' not allowed to access '{1}'", _con.SteamID, handler.ModuleName);
 						}
@@ -267,5 +267,5 @@
 						handlerSampler.Begin ();
 #endif
-						handler.HandleRequest (_requestPath, _req, _resp, _con, _permissionLevel);
+						handler.HandleRequest (_context);
 #if ENABLE_PROFILER
 						handlerSampler.End ();
@@ -279,5 +279,5 @@
 			// Not really relevant for non-debugging purposes:
 			//Log.Out ("Error in Web.HandleRequest(): No handler found for path \"" + _requestPath + "\"");
-			_resp.StatusCode = (int) HttpStatusCode.NotFound;
+			_context.Response.StatusCode = (int) HttpStatusCode.NotFound;
 		}
 
@@ -314,12 +314,4 @@
 			return guestPermissionLevel;
 		}
-
-		public static void SetResponseTextContent (HttpListenerResponse _resp, string _text) {
-			byte[] buf = Encoding.UTF8.GetBytes (_text);
-			_resp.ContentLength64 = buf.Length;
-			_resp.ContentType = "text/html";
-			_resp.ContentEncoding = Encoding.UTF8;
-			_resp.OutputStream.Write (buf, 0, buf.Length);
-		}
 	}
 }
Index: binary-improvements2/MapRendering/Web/WebCommandResult.cs
===================================================================
--- binary-improvements2/MapRendering/Web/WebCommandResult.cs	(revision 386)
+++ binary-improvements2/MapRendering/Web/WebCommandResult.cs	(revision 387)
@@ -49,5 +49,5 @@
 
 				if (responseType == ResultType.Raw) {
-					WebAPI.WriteText (response, sb.ToString ());
+					WebUtils.WriteText (response, sb.ToString ());
 				} else {
 					JSONNode result;
@@ -64,5 +64,5 @@
 					}
 
-					WebAPI.WriteJSON (response, result);
+					WebUtils.WriteJson (response, result);
 				}
 			} catch (IOException e) {
Index: binary-improvements2/MapRendering/Web/WebUtils.cs
===================================================================
--- binary-improvements2/MapRendering/Web/WebUtils.cs	(revision 387)
+++ binary-improvements2/MapRendering/Web/WebUtils.cs	(revision 387)
@@ -0,0 +1,47 @@
+using System.Net;
+using System.Text;
+using AllocsFixes.JSON;
+using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
+
+namespace AllocsFixes.NetConnections.Servers.Web {
+	public static class WebUtils {
+		public const string MimePlain = "text/plain";
+		public const string MimeHtml = "text/html";
+		public const string MimeJson = "application/json";
+		
+#if ENABLE_PROFILER
+		private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Serialize");
+		private static readonly UnityEngine.Profiling.CustomSampler netWriteSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Write");
+#endif
+
+		public static void WriteJson (HttpListenerResponse _resp, JSONNode _root, HttpStatusCode _statusCode = HttpStatusCode.OK) {
+#if ENABLE_PROFILER
+			jsonSerializeSampler.Begin ();
+#endif
+			StringBuilder sb = new StringBuilder ();
+			_root.ToString (sb);
+#if ENABLE_PROFILER
+			jsonSerializeSampler.End ();
+			netWriteSampler.Begin ();
+#endif
+			WriteText (_resp, sb.ToString(), _mimeType: MimeJson);
+#if ENABLE_PROFILER
+			netWriteSampler.End ();
+#endif
+		}
+
+		public static void WriteText (HttpListenerResponse _resp, string _text, HttpStatusCode _statusCode = HttpStatusCode.OK, string _mimeType = null) {
+			byte[] buf = Encoding.UTF8.GetBytes (_text);
+			_resp.ContentLength64 = buf.Length;
+			_resp.OutputStream.Write (buf, 0, buf.Length);
+			
+			_resp.ContentType = _mimeType ?? MimePlain;
+			_resp.ContentEncoding = Encoding.UTF8;
+			_resp.StatusCode = (int)_statusCode;
+		}
+
+		public static string GenerateGuid () {
+			return System.Guid.NewGuid ().ToString ();
+		}
+	}
+}
Index: binary-improvements2/MapRendering/WebAndMapRendering.csproj
===================================================================
--- binary-improvements2/MapRendering/WebAndMapRendering.csproj	(revision 386)
+++ binary-improvements2/MapRendering/WebAndMapRendering.csproj	(revision 387)
@@ -11,4 +11,5 @@
     <AssemblyName>MapRendering</AssemblyName>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <LangVersion>8</LangVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@@ -86,9 +87,12 @@
     <Compile Include="Commands\EnableRendering.cs" />
     <Compile Include="API.cs" />
+    <Compile Include="Web\API\AbsRestApi.cs" />
     <Compile Include="Web\API\GetAnimalsLocation.cs" />
     <Compile Include="Web\API\GetHostileLocation.cs" />
     <Compile Include="Web\API\GetWebMods.cs" />
+    <Compile Include="Web\API\Markers.cs" />
     <Compile Include="Web\API\Null.cs" />
     <Compile Include="Web\Handlers\RewriteHandler.cs" />
+    <Compile Include="Web\RequestContext.cs" />
     <Compile Include="Web\SSE\EventLog.cs" />
     <Compile Include="Web\SSE\SseHandler.cs" />
@@ -97,5 +101,5 @@
     <Compile Include="Web\MimeType.cs" />
     <Compile Include="Web\API\GetPlayersOnline.cs" />
-    <Compile Include="Web\API\WebAPI.cs" />
+    <Compile Include="Web\API\AbsWebAPI.cs" />
     <Compile Include="Web\API\GetPlayersLocation.cs" />
     <Compile Include="Web\API\GetPlayerInventory.cs" />
@@ -128,4 +132,5 @@
     <Compile Include="Commands\EnableOpenIDDebug.cs" />
     <Compile Include="Web\API\GetPlayerInventories.cs" />
+    <Compile Include="Web\WebUtils.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
