using System; using System.Collections.Generic; using System.IO; using System.Net; using Utf8Json; using Webserver.Permissions; namespace Webserver.WebAPI { public abstract class AbsRestApi : AbsWebAPI { private static readonly UnityEngine.Profiling.CustomSampler jsonDeserializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Deserialize"); protected AbsRestApi (string _name = null) : this(null, _name) { } protected AbsRestApi (Web _parentWeb, string _name = null) : base(_parentWeb, _name) { } protected override void RegisterPermissions () { AdminWebModules.Instance.AddKnownModule (new AdminWebModules.WebModule (CachedApiModuleName, DefaultPermissionLevel (), DefaultMethodPermissionLevels (), true)); } public sealed override void HandleRequest (RequestContext _context) { IDictionary inputJson = null; byte[] jsonInputData = null; if (_context.Request.HasEntityBody) { Stream requestInputStream = _context.Request.InputStream; jsonInputData = new byte[_context.Request.ContentLength64]; requestInputStream.Read (jsonInputData, 0, (int)_context.Request.ContentLength64); try { jsonDeserializeSampler.Begin (); inputJson = JsonSerializer.Deserialize> (jsonInputData); // Log.Out ("JSON body:"); // foreach ((string key, object value) in inputJson) { // Log.Out ($" - {key} = {value} ({value.GetType ()})"); // } jsonDeserializeSampler.End (); } catch (Exception e) { jsonDeserializeSampler.End (); SendEmptyResponse (_context, HttpStatusCode.BadRequest, null, "INVALID_BODY", e); return; } } try { switch (_context.Method) { case ERequestMethod.GET: if (inputJson != null) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, jsonInputData, "GET_WITH_BODY"); return; } HandleRestGet (_context); return; case ERequestMethod.POST: if (!AllowPostWithId && !string.IsNullOrEmpty (_context.RequestPath)) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, jsonInputData, "POST_WITH_ID"); return; } if (inputJson == null) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, null, "POST_WITHOUT_BODY"); return; } HandleRestPost (_context, inputJson, jsonInputData); return; case ERequestMethod.PUT: if (string.IsNullOrEmpty (_context.RequestPath)) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, jsonInputData, "PUT_WITHOUT_ID"); return; } if (inputJson == null) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, null, "PUT_WITHOUT_BODY"); return; } HandleRestPut (_context, inputJson, jsonInputData); return; case ERequestMethod.DELETE: if (string.IsNullOrEmpty (_context.RequestPath)) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, jsonInputData, "DELETE_WITHOUT_ID"); return; } if (inputJson != null) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, null, "DELETE_WITH_BODY"); return; } HandleRestDelete (_context); return; default: SendEmptyResponse (_context, HttpStatusCode.BadRequest, null, "INVALID_METHOD"); return; } } catch (Exception e) { try { SendEmptyResponse (_context, HttpStatusCode.InternalServerError, jsonInputData, "ERROR_PROCESSING", e); } catch (Exception e2) { Log.Error ($"[Web] In {nameof(AbsRestApi)}.HandleRequest(): Handler {Name} threw an exception while trying to send a previous exception to the client:"); Log.Exception (e2); } } } protected virtual void HandleRestGet (RequestContext _context) { SendEmptyResponse (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported"); } protected virtual void HandleRestPost (RequestContext _context, IDictionary _jsonInput, byte[] _jsonInputData) { SendEmptyResponse (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported"); } protected virtual void HandleRestPut (RequestContext _context, IDictionary _jsonInput, byte[] _jsonInputData) { SendEmptyResponse (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported"); } protected virtual void HandleRestDelete (RequestContext _context) { SendEmptyResponse (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported"); } public override bool Authorized (RequestContext _context) { AdminWebModules.WebModule module = AdminWebModules.Instance.GetModule (CachedApiModuleName); if (module.LevelPerMethod == null) { return module.LevelGlobal >= _context.PermissionLevel; } int perMethodLevel = module.LevelPerMethod [(int)_context.Method]; if (perMethodLevel == AdminWebModules.MethodLevelNotSupported) { return false; } if (perMethodLevel != AdminWebModules.MethodLevelInheritGlobal) { return perMethodLevel >= _context.PermissionLevel; } return module.LevelGlobal >= _context.PermissionLevel; } protected virtual bool AllowPostWithId => false; /// /// Define default permission levels per HTTP method as an array of levels. See for the order of entries. /// /// Default permission levels for supported methods. See and . public virtual int[] DefaultMethodPermissionLevels () => new[] { AdminWebModules.MethodLevelNotSupported, AdminWebModules.MethodLevelInheritGlobal, AdminWebModules.MethodLevelInheritGlobal, AdminWebModules.MethodLevelInheritGlobal, AdminWebModules.MethodLevelInheritGlobal }; #region Helpers protected static void PrepareEnvelopedResult (out JsonWriter _writer) { WebUtils.PrepareEnvelopedResult (out _writer); } protected static void SendEnvelopedResult (RequestContext _context, ref JsonWriter _writer, HttpStatusCode _statusCode = HttpStatusCode.OK, byte[] _jsonInputData = null, string _errorCode = null, Exception _exception = null) { WebUtils.SendEnvelopedResult (_context, ref _writer, _statusCode, _jsonInputData, _errorCode, _exception); } protected static void SendEmptyResponse (RequestContext _context, HttpStatusCode _statusCode = HttpStatusCode.OK, byte[] _jsonInputData = null, string _errorCode = null, Exception _exception = null) { PrepareEnvelopedResult (out JsonWriter writer); writer.WriteRaw (WebUtils.JsonEmptyData); SendEnvelopedResult (_context, ref writer, _statusCode, _jsonInputData, _errorCode, _exception); } protected static void SendEmptyResponse (RequestContext _context, HttpStatusCode _statusCode, byte[] _jsonInputData, EApiErrorCode _errorCode, Exception _exception = null) { SendEmptyResponse (_context, _statusCode, _jsonInputData, _errorCode.ToStringCached (), _exception); } #endregion } }