source: TFP-WebServer/WebServer/src/UrlHandlers/ApiHandler.cs@ 462

Last change on this file since 462 was 460, checked in by alloc, 15 months ago

More OpenAPI specs added
OpenAPI specs cleanup to have everything validate fine
Added CORS support to API endpoints

File size: 4.1 KB
RevLine 
[391]1using System;
2using System.Collections.Generic;
3using System.Net;
4using System.Reflection;
5using Webserver.WebAPI;
6
7namespace Webserver.UrlHandlers {
8 public class ApiHandler : AbsHandler {
9 private readonly Dictionary<string, AbsWebAPI> apis = new CaseInsensitiveStringDictionary<AbsWebAPI> ();
10
11 public ApiHandler () : base (null) {
12 }
[404]13
14 private static readonly Type[] apiWithParentCtorTypes = { typeof (Web) };
15 private static readonly object[] apiWithParentCtorArgs = new object[1];
16 private static readonly Type[] apiEmptyCtorTypes = { };
17 private static readonly object[] apiEmptyCtorArgs = { };
[391]18
19 public override void SetBasePathAndParent (Web _parent, string _relativePath) {
20 base.SetBasePathAndParent (_parent, _relativePath);
21
[404]22 apiWithParentCtorArgs[0] = _parent;
[391]23
[404]24 ReflectionHelpers.FindTypesImplementingBase (typeof (AbsWebAPI), apiFoundCallback);
[391]25
26 // Permissions that don't map to a real API
27 addApi (new Null ("viewallclaims"));
28 addApi (new Null ("viewallplayers"));
29 }
30
[404]31 private void apiFoundCallback (Type _type) {
[453]32 ConstructorInfo ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
33 apiWithParentCtorTypes, (ParameterModifier[])null);
[404]34 if (ctor != null) {
35 AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiWithParentCtorArgs);
36 addApi (apiInstance);
37 return;
38 }
39
[453]40 ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
41 apiEmptyCtorTypes, (ParameterModifier[])null);
[404]42 if (ctor != null) {
43 AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiEmptyCtorArgs);
44 addApi (apiInstance);
45 }
46 }
47
[391]48 private void addApi (AbsWebAPI _api) {
49 apis.Add (_api.Name, _api);
[459]50 parent.OpenApiHelpers.LoadOpenApiSpec (_api);
[391]51 }
52
53 private static readonly UnityEngine.Profiling.CustomSampler apiHandlerSampler = UnityEngine.Profiling.CustomSampler.Create ("API_Handler");
54
[460]55 private bool HandleCors (RequestContext _context) {
56 _context.Request.Headers.TryGetValue ("Origin", out string origin);
57 _context.Response.AddHeader ("Access-Control-Allow-Origin", origin ?? "*");
58
59 if (_context.Method != ERequestMethod.OPTIONS) {
60 return false;
61 }
62
63 if (!_context.Request.Headers.TryGetValue ("Access-Control-Request-Method", out _)) {
64 return false;
65 }
66
67 _context.Response.AddHeader ("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS, HEAD");
68 _context.Response.AddHeader ("Access-Control-Allow-Headers", "X-SDTD-API-TOKENNAME, X-SDTD-API-SECRET");
69 _context.Response.AddHeader ("Access-Control-Allow-Credentials", "true");
70 return true;
71 }
72
[391]73 public override void HandleRequest (RequestContext _context) {
74
75 string apiName;
76 string subPath = null;
77
78 int pathSeparatorIndex = _context.RequestPath.IndexOf ('/', urlBasePath.Length);
79 if (pathSeparatorIndex >= 0) {
80 apiName = _context.RequestPath.Substring (urlBasePath.Length, pathSeparatorIndex - urlBasePath.Length);
81 subPath = _context.RequestPath.Substring (pathSeparatorIndex + 1);
82 } else {
83 apiName = _context.RequestPath.Substring (urlBasePath.Length);
84 }
85
86 if (!apis.TryGetValue (apiName, out AbsWebAPI api)) {
[399]87 Log.Warning ($"[Web] In {nameof(ApiHandler)}.HandleRequest(): No handler found for API \"{apiName}\"");
[391]88 _context.Response.StatusCode = (int) HttpStatusCode.NotFound;
89 return;
90 }
91
[460]92 // CORS specific stuff
93 if (HandleCors (_context)) {
94 return;
95 }
96 // CORS end
97
[418]98 _context.RequestPath = subPath;
99
100 if (!api.Authorized (_context)) {
[391]101 _context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
102 if (_context.Connection != null) {
103 //Log.Out ($"{nameof(ApiHandler)}: user '{user.SteamID}' not allowed to execute '{apiName}'");
104 }
105
106 return;
107 }
108
109 try {
110 apiHandlerSampler.Begin ();
111 api.HandleRequest (_context);
112 apiHandlerSampler.End ();
113 } catch (Exception e) {
[399]114 Log.Error ($"[Web] In {nameof(ApiHandler)}.HandleRequest(): Handler {api.Name} threw an exception:");
[391]115 Log.Exception (e);
116 _context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
117 }
118 }
119 }
120}
Note: See TracBrowser for help on using the repository browser.