[391] | 1 | using System;
|
---|
[402] | 2 | using System.Collections.Generic;
|
---|
[391] | 3 | using System.IO;
|
---|
| 4 | using System.Net;
|
---|
[402] | 5 | using Utf8Json;
|
---|
[418] | 6 | using Webserver.Permissions;
|
---|
[391] | 7 |
|
---|
| 8 | namespace Webserver.WebAPI {
|
---|
| 9 | public abstract class AbsRestApi : AbsWebAPI {
|
---|
[394] | 10 | private static readonly UnityEngine.Profiling.CustomSampler jsonDeserializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Deserialize");
|
---|
| 11 |
|
---|
[410] | 12 | protected AbsRestApi (string _name = null) : this(null, _name) {
|
---|
| 13 | }
|
---|
| 14 |
|
---|
| 15 | protected AbsRestApi (Web _parentWeb, string _name = null) : base(_parentWeb, _name) {
|
---|
| 16 | }
|
---|
| 17 |
|
---|
[418] | 18 | protected override void RegisterPermissions () {
|
---|
[426] | 19 | AdminWebModules.Instance.AddKnownModule (new AdminWebModules.WebModule (CachedApiModuleName, DefaultPermissionLevel (),
|
---|
| 20 | DefaultMethodPermissionLevels (), true));
|
---|
[418] | 21 | }
|
---|
| 22 |
|
---|
[391] | 23 | public sealed override void HandleRequest (RequestContext _context) {
|
---|
[402] | 24 | IDictionary<string, object> inputJson = null;
|
---|
| 25 | byte[] jsonInputData = null;
|
---|
| 26 |
|
---|
[391] | 27 | if (_context.Request.HasEntityBody) {
|
---|
[402] | 28 | Stream requestInputStream = _context.Request.InputStream;
|
---|
| 29 |
|
---|
| 30 | jsonInputData = new byte[_context.Request.ContentLength64];
|
---|
| 31 | requestInputStream.Read (jsonInputData, 0, (int)_context.Request.ContentLength64);
|
---|
| 32 |
|
---|
| 33 | try {
|
---|
| 34 | jsonDeserializeSampler.Begin ();
|
---|
| 35 | inputJson = JsonSerializer.Deserialize<IDictionary<string, object>> (jsonInputData);
|
---|
| 36 |
|
---|
| 37 | // Log.Out ("JSON body:");
|
---|
| 38 | // foreach ((string key, object value) in inputJson) {
|
---|
| 39 | // Log.Out ($" - {key} = {value} ({value.GetType ()})");
|
---|
| 40 | // }
|
---|
| 41 |
|
---|
| 42 | jsonDeserializeSampler.End ();
|
---|
| 43 | } catch (Exception e) {
|
---|
| 44 | jsonDeserializeSampler.End ();
|
---|
[391] | 45 |
|
---|
[402] | 46 | SendErrorResult (_context, HttpStatusCode.BadRequest, null, "INVALID_BODY", e);
|
---|
| 47 | return;
|
---|
[391] | 48 | }
|
---|
| 49 | }
|
---|
| 50 |
|
---|
| 51 | try {
|
---|
[418] | 52 | switch (_context.Method) {
|
---|
| 53 | case ERequestMethod.GET:
|
---|
[402] | 54 | if (inputJson != null) {
|
---|
| 55 | SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "GET_WITH_BODY");
|
---|
[391] | 56 | return;
|
---|
| 57 | }
|
---|
| 58 |
|
---|
| 59 | HandleRestGet (_context);
|
---|
| 60 | return;
|
---|
[418] | 61 | case ERequestMethod.POST:
|
---|
[391] | 62 | if (!string.IsNullOrEmpty (_context.RequestPath)) {
|
---|
[402] | 63 | SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "POST_WITH_ID");
|
---|
[391] | 64 | return;
|
---|
| 65 | }
|
---|
| 66 |
|
---|
[402] | 67 | if (inputJson == null) {
|
---|
| 68 | SendErrorResult (_context, HttpStatusCode.BadRequest, null, "POST_WITHOUT_BODY");
|
---|
[391] | 69 | return;
|
---|
| 70 | }
|
---|
| 71 |
|
---|
[402] | 72 | HandleRestPost (_context, inputJson, jsonInputData);
|
---|
[391] | 73 | return;
|
---|
[418] | 74 | case ERequestMethod.PUT:
|
---|
[391] | 75 | if (string.IsNullOrEmpty (_context.RequestPath)) {
|
---|
[402] | 76 | SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "PUT_WITHOUT_ID");
|
---|
[391] | 77 | return;
|
---|
| 78 | }
|
---|
| 79 |
|
---|
[402] | 80 | if (inputJson == null) {
|
---|
| 81 | SendErrorResult (_context, HttpStatusCode.BadRequest, null, "PUT_WITHOUT_BODY");
|
---|
[391] | 82 | return;
|
---|
| 83 | }
|
---|
| 84 |
|
---|
[402] | 85 | HandleRestPut (_context, inputJson, jsonInputData);
|
---|
[391] | 86 | return;
|
---|
[418] | 87 | case ERequestMethod.DELETE:
|
---|
[391] | 88 | if (string.IsNullOrEmpty (_context.RequestPath)) {
|
---|
[402] | 89 | SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "DELETE_WITHOUT_ID");
|
---|
[391] | 90 | return;
|
---|
| 91 | }
|
---|
| 92 |
|
---|
[402] | 93 | if (inputJson != null) {
|
---|
| 94 | SendErrorResult (_context, HttpStatusCode.BadRequest, null, "DELETE_WITH_BODY");
|
---|
[391] | 95 | return;
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | HandleRestDelete (_context);
|
---|
| 99 | return;
|
---|
| 100 | default:
|
---|
[402] | 101 | SendErrorResult (_context, HttpStatusCode.BadRequest, null, "INVALID_METHOD");
|
---|
[391] | 102 | return;
|
---|
| 103 | }
|
---|
| 104 | } catch (Exception e) {
|
---|
[402] | 105 | SendErrorResult (_context, HttpStatusCode.InternalServerError, jsonInputData, "ERROR_PROCESSING", e);
|
---|
[391] | 106 | }
|
---|
| 107 | }
|
---|
| 108 |
|
---|
[404] | 109 | protected virtual void HandleRestGet (RequestContext _context) {
|
---|
| 110 | SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
|
---|
| 111 | }
|
---|
| 112 |
|
---|
| 113 | protected virtual void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
|
---|
| 114 | SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | protected virtual void HandleRestPut (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
|
---|
| 118 | SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | protected virtual void HandleRestDelete (RequestContext _context) {
|
---|
| 122 | SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
|
---|
| 123 | }
|
---|
| 124 |
|
---|
[418] | 125 | public override bool Authorized (RequestContext _context) {
|
---|
[426] | 126 | AdminWebModules.WebModule module = AdminWebModules.Instance.GetModule (CachedApiModuleName);
|
---|
[404] | 127 |
|
---|
[426] | 128 | if (module.LevelPerMethod != null) {
|
---|
| 129 | int perMethodLevel = module.LevelPerMethod [(int)_context.Method];
|
---|
| 130 | if (perMethodLevel == AdminWebModules.MethodLevelNotSupported) {
|
---|
| 131 | return false;
|
---|
| 132 | }
|
---|
[418] | 133 |
|
---|
[426] | 134 | if (perMethodLevel != AdminWebModules.MethodLevelInheritGlobal) {
|
---|
| 135 | return perMethodLevel >= _context.PermissionLevel;
|
---|
| 136 | }
|
---|
[418] | 137 | }
|
---|
| 138 |
|
---|
[426] | 139 | return module.LevelGlobal >= _context.PermissionLevel;
|
---|
| 140 | }
|
---|
[418] | 141 |
|
---|
[426] | 142 | /// <summary>
|
---|
| 143 | /// Define default permission levels per HTTP method as an array of levels. See <see cref="ERequestMethod"/> for the order of entries.
|
---|
| 144 | /// </summary>
|
---|
| 145 | /// <returns>Default permission levels for supported methods. See <see cref="AdminWebModules.MethodLevelNotSupported"/> and <see cref="AdminWebModules.MethodLevelInheritGlobal"/>.</returns>
|
---|
| 146 | public virtual int[] DefaultMethodPermissionLevels () => new[] {
|
---|
| 147 | AdminWebModules.MethodLevelNotSupported,
|
---|
| 148 | AdminWebModules.MethodLevelInheritGlobal,
|
---|
| 149 | AdminWebModules.MethodLevelInheritGlobal,
|
---|
| 150 | AdminWebModules.MethodLevelInheritGlobal,
|
---|
| 151 | AdminWebModules.MethodLevelInheritGlobal
|
---|
| 152 | };
|
---|
[418] | 153 |
|
---|
[404] | 154 | #region Helpers
|
---|
| 155 |
|
---|
[402] | 156 | protected static readonly byte[] JsonEmptyData;
|
---|
| 157 |
|
---|
[418] | 158 | static AbsRestApi () {
|
---|
| 159 | JsonWriter writer = new JsonWriter ();
|
---|
| 160 | writer.WriteBeginArray ();
|
---|
| 161 | writer.WriteEndArray ();
|
---|
| 162 | JsonEmptyData = writer.ToUtf8ByteArray ();
|
---|
| 163 | }
|
---|
| 164 |
|
---|
[404] | 165 | protected static void PrepareEnvelopedResult (out JsonWriter _writer) {
|
---|
[402] | 166 | WebUtils.PrepareEnvelopedResult (out _writer);
|
---|
| 167 | }
|
---|
[391] | 168 |
|
---|
[404] | 169 | protected static void SendEnvelopedResult (RequestContext _context, ref JsonWriter _writer, HttpStatusCode _statusCode = HttpStatusCode.OK,
|
---|
[402] | 170 | byte[] _jsonInputData = null, string _errorCode = null, Exception _exception = null) {
|
---|
| 171 |
|
---|
| 172 | WebUtils.SendEnvelopedResult (_context, ref _writer, _statusCode, _jsonInputData, _errorCode, _exception);
|
---|
[391] | 173 | }
|
---|
| 174 |
|
---|
[404] | 175 | protected static void SendErrorResult (RequestContext _context, HttpStatusCode _statusCode, byte[] _jsonInputData = null, string _errorCode = null, Exception _exception = null) {
|
---|
| 176 | PrepareEnvelopedResult (out JsonWriter writer);
|
---|
| 177 | writer.WriteRaw (JsonEmptyData);
|
---|
| 178 | SendEnvelopedResult (_context, ref writer, _statusCode, _jsonInputData, _errorCode, _exception);
|
---|
| 179 | }
|
---|
| 180 |
|
---|
| 181 | protected static bool TryGetJsonField (IDictionary<string, object> _jsonObject, string _fieldName, out int _value) {
|
---|
[391] | 182 | _value = default;
|
---|
| 183 |
|
---|
[402] | 184 | if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) {
|
---|
[391] | 185 | return false;
|
---|
| 186 | }
|
---|
| 187 |
|
---|
[402] | 188 | if (fieldNode is not double value) {
|
---|
[391] | 189 | return false;
|
---|
| 190 | }
|
---|
| 191 |
|
---|
| 192 | try {
|
---|
[402] | 193 | _value = (int)value;
|
---|
[391] | 194 | return true;
|
---|
| 195 | } catch (Exception) {
|
---|
| 196 | return false;
|
---|
| 197 | }
|
---|
| 198 | }
|
---|
| 199 |
|
---|
[404] | 200 | protected static bool TryGetJsonField (IDictionary<string, object> _jsonObject, string _fieldName, out double _value) {
|
---|
[391] | 201 | _value = default;
|
---|
| 202 |
|
---|
[402] | 203 | if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) {
|
---|
[391] | 204 | return false;
|
---|
| 205 | }
|
---|
| 206 |
|
---|
[402] | 207 | if (fieldNode is not double value) {
|
---|
[391] | 208 | return false;
|
---|
| 209 | }
|
---|
| 210 |
|
---|
| 211 | try {
|
---|
[402] | 212 | _value = value;
|
---|
[391] | 213 | return true;
|
---|
| 214 | } catch (Exception) {
|
---|
| 215 | return false;
|
---|
| 216 | }
|
---|
| 217 | }
|
---|
| 218 |
|
---|
[404] | 219 | protected static bool TryGetJsonField (IDictionary<string, object> _jsonObject, string _fieldName, out string _value) {
|
---|
[391] | 220 | _value = default;
|
---|
| 221 |
|
---|
[402] | 222 | if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) {
|
---|
[391] | 223 | return false;
|
---|
| 224 | }
|
---|
| 225 |
|
---|
[402] | 226 | if (fieldNode is not string value) {
|
---|
[391] | 227 | return false;
|
---|
| 228 | }
|
---|
| 229 |
|
---|
| 230 | try {
|
---|
[402] | 231 | _value = value;
|
---|
[391] | 232 | return true;
|
---|
| 233 | } catch (Exception) {
|
---|
| 234 | return false;
|
---|
| 235 | }
|
---|
| 236 | }
|
---|
[404] | 237 |
|
---|
[391] | 238 |
|
---|
[404] | 239 | #endregion
|
---|
[391] | 240 | }
|
---|
| 241 | }
|
---|