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 readonly string[] CachedPerMethodModuleNames = new string[(int)ERequestMethod.Count]; protected AbsRestApi (string _name = null) : this(null, _name) { } protected AbsRestApi (Web _parentWeb, string _name = null) : base(_parentWeb, _name) { } protected override void RegisterPermissions () { base.RegisterPermissions (); for (int i = 0; i < (int)ERequestMethod.Count; i++) { ERequestMethod method = (ERequestMethod)i; if (method is not (ERequestMethod.GET or ERequestMethod.PUT or ERequestMethod.POST or ERequestMethod.DELETE)) { continue; } CachedPerMethodModuleNames [i] = $"webapi.{Name}:{method.ToStringCached ()}"; AdminWebModules.Instance.AddKnownModule (CachedPerMethodModuleNames [i], DefaultMethodPermissionLevel (method)); } } 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 (); SendErrorResult (_context, HttpStatusCode.BadRequest, null, "INVALID_BODY", e); return; } } try { switch (_context.Method) { case ERequestMethod.GET: if (inputJson != null) { SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "GET_WITH_BODY"); return; } HandleRestGet (_context); return; case ERequestMethod.POST: if (!string.IsNullOrEmpty (_context.RequestPath)) { SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "POST_WITH_ID"); return; } if (inputJson == null) { SendErrorResult (_context, HttpStatusCode.BadRequest, null, "POST_WITHOUT_BODY"); return; } HandleRestPost (_context, inputJson, jsonInputData); return; case ERequestMethod.PUT: if (string.IsNullOrEmpty (_context.RequestPath)) { SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "PUT_WITHOUT_ID"); return; } if (inputJson == null) { SendErrorResult (_context, HttpStatusCode.BadRequest, null, "PUT_WITHOUT_BODY"); return; } HandleRestPut (_context, inputJson, jsonInputData); return; case ERequestMethod.DELETE: if (string.IsNullOrEmpty (_context.RequestPath)) { SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "DELETE_WITHOUT_ID"); return; } if (inputJson != null) { SendErrorResult (_context, HttpStatusCode.BadRequest, null, "DELETE_WITH_BODY"); return; } HandleRestDelete (_context); return; default: SendErrorResult (_context, HttpStatusCode.BadRequest, null, "INVALID_METHOD"); return; } } catch (Exception e) { SendErrorResult (_context, HttpStatusCode.InternalServerError, jsonInputData, "ERROR_PROCESSING", e); } } protected virtual void HandleRestGet (RequestContext _context) { SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported"); } protected virtual void HandleRestPost (RequestContext _context, IDictionary _jsonInput, byte[] _jsonInputData) { SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported"); } protected virtual void HandleRestPut (RequestContext _context, IDictionary _jsonInput, byte[] _jsonInputData) { SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported"); } protected virtual void HandleRestDelete (RequestContext _context) { SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported"); } public override bool Authorized (RequestContext _context) { return ActiveMethodPermissionLevel (_context.Method) >= _context.PermissionLevel; } /// /// Define default permission levels per HTTP method /// /// HTTP method to return the default value for /// Default permission level for the given HTTP method. A value of int.MinValue means no per-method default, use per-API default public virtual int DefaultMethodPermissionLevel (ERequestMethod _method) => int.MinValue; public virtual int ActiveMethodPermissionLevel (ERequestMethod _method) { string methodApiModuleName = CachedPerMethodModuleNames [(int)_method]; if (methodApiModuleName == null) { return 0; } AdminWebModules.WebModule? overrideModule = AdminWebModules.Instance.GetModule (methodApiModuleName, false); if (overrideModule.HasValue) { return overrideModule.Value.PermissionLevel; } overrideModule = AdminWebModules.Instance.GetModule (CachedApiModuleName, false); if (overrideModule.HasValue) { return overrideModule.Value.PermissionLevel; } int defaultMethodPermissionLevel = DefaultMethodPermissionLevel (_method); // ReSharper disable once ConvertIfStatementToReturnStatement if (defaultMethodPermissionLevel != int.MinValue) { return defaultMethodPermissionLevel; } return DefaultPermissionLevel (); } #region Helpers protected static readonly byte[] JsonEmptyData; static AbsRestApi () { JsonWriter writer = new JsonWriter (); writer.WriteBeginArray (); writer.WriteEndArray (); JsonEmptyData = writer.ToUtf8ByteArray (); } 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 SendErrorResult (RequestContext _context, HttpStatusCode _statusCode, byte[] _jsonInputData = null, string _errorCode = null, Exception _exception = null) { PrepareEnvelopedResult (out JsonWriter writer); writer.WriteRaw (JsonEmptyData); SendEnvelopedResult (_context, ref writer, _statusCode, _jsonInputData, _errorCode, _exception); } protected static bool TryGetJsonField (IDictionary _jsonObject, string _fieldName, out int _value) { _value = default; if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) { return false; } if (fieldNode is not double value) { return false; } try { _value = (int)value; return true; } catch (Exception) { return false; } } protected static bool TryGetJsonField (IDictionary _jsonObject, string _fieldName, out double _value) { _value = default; if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) { return false; } if (fieldNode is not double value) { return false; } try { _value = value; return true; } catch (Exception) { return false; } } protected static bool TryGetJsonField (IDictionary _jsonObject, string _fieldName, out string _value) { _value = default; if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) { return false; } if (fieldNode is not string value) { return false; } try { _value = value; return true; } catch (Exception) { return false; } } #endregion } }