Index: binary-improvements2/MapRendering/Web/Handlers/AbsHandler.cs
===================================================================
--- binary-improvements2/MapRendering/Web/Handlers/AbsHandler.cs	(revision 384)
+++ 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 384)
+++ 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 384)
+++ 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 384)
+++ 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 384)
+++ 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 384)
+++ 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 384)
+++ 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 384)
+++ 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);
 		}
 	}
