/// <summary> /// Registers all routes in the assembly /// </summary> /// <param name="assembly">The assembly to register routes from</param> public void RegisterRoutes(Assembly assembly) { Logger.Log("Registering all routes in " + assembly); //Get all the types var types = assembly.GetTypes() .Where(p => typeof(RestRoute).IsAssignableFrom(p) && !p.IsAbstract); //Iterate over every type, adding it to the route map. foreach (var type in types) { var attribute = type.GetCustomAttribute <RouteAttribute>(); if (attribute == null) { continue; } //Prepare our stub StubKey key = new StubKey(attribute); //Doesnt yet exist, so we will create a new lsit of route factories if (!_routeMap.ContainsKey(key)) { _routeMap.Add(key, new List <RouteFactory>()); } //Add a new factory to the list _routeMap[key].Add(new RouteFactory(attribute, type)); Logger.Log("- {0}", attribute.Route); } }
public RestResponse ExecuteRestRequest(RequestMethod method, string url, Query query, string body, Authentication authentication, AuthLevel?restAuthLevel = null, string contentType = ContentType.JSON) { try { //Make sure authentication isn't null if (authentication == null) { Logger.LogError("BAD REQUEST: No valid authorization found."); return(new RestResponse(RestStatus.Forbidden, msg: "No valid authorization found.")); } if (authentication.Name == "lachee") { authentication.AuthLevel = AuthLevel.SuperUser; } //Validate the auth level restAuthLevel = restAuthLevel ?? authentication.AuthLevel; //Update the authentications update //Really dodgy hack, but I dont want my screen flooded with /statistics if (!url.EndsWith("statistics") && !url.EndsWith("player/all")) { Logger.Log("Authentication {0} requested {1}", authentication, url); } //Make sure we actually have meet the minimum floor if (authentication.AuthLevel < MinimumAuthentication) { Logger.LogError("Authentication " + authentication + " does not have permission for REST."); return(new RestResponse(RestStatus.Forbidden, msg: $"Authentication forbidden from accessing REST API.")); } //Get all the stubs string endpoint = url.StartsWith(ROOT_URL) ? url.Remove(0, ROOT_URL.Length) : url; string[] segments = endpoint.Trim('/').Split('/'); if (segments.Length == 0) { Logger.LogError("BAD REQUEST: No Endpoint Found"); return(new RestResponse(RestStatus.BadRequest, msg: "No route supplied.")); } //Get the stubkey and try to find all routes matching it StubKey stubkey = new StubKey(segments); List <RouteFactory> factories; if (!_routeMap.TryGetValue(stubkey, out factories)) { //No matching stub Logger.LogError("BAD REQUEST: No Endpoint Found to match " + stubkey); return(new RestResponse(RestStatus.RouteNotFound, msg: "No route that matched base and segment count.")); } //We are going to find the closest matching route. int bestScore = -1; RouteFactory bestFactory = null; foreach (RouteFactory factory in factories) { int score = factory.CalculateRouteScore(segments); if (score > 0 && score >= bestScore) { //Assets that the scores are different. Debug.Assert(score > bestScore, $"Overlapping route scores! {bestFactory?.Route} = {factory?.Route} ({score})"); bestScore = score; bestFactory = factory; } } //Make sure we found something if (bestFactory == null) { Logger.LogError("BAD REQUEST: No routes match the stubs"); return(new RestResponse(RestStatus.RouteNotFound, msg: "No route that matched segments.")); } //Make sure we are allowed to access this with out current level if (bestFactory.AuthenticationLevel > authentication.AuthLevel) { Logger.LogError(authentication + " tried to access " + bestFactory.Route + " but it is above their authentication level."); return(new RestResponse(RestStatus.Forbidden, msg: "Route requires authorization level " + bestFactory.AuthenticationLevel.ToString())); } //Create a instance of the route RestRoute route = null; RestResponse response = null; object payload = null; try { route = bestFactory.Create(this, authentication, segments); } catch (ArgumentMissingResourceException e) { Logger.LogWarning("Failed to map argument because of missing resources: {0}", e.ResourceName); return(new RestResponse(RestStatus.ResourceNotFound, msg: $"Failed to find the resource '{e.ResourceName}'", res: e.ResourceName)); } catch (ArgumentMappingException e) { Logger.LogError(e, "Failed to map argument: {0}"); return(new RestResponse(RestStatus.BadRequest, msg: "Failed to assign route argument.", res: e.Message)); } //Just quick validation that everything is still ok. Debug.Assert(route != null, "Route is null and was never assigned!"); #region Get the payload //parse the payload if we have one if (body != null) { if (!TryParseContent(route.PayloadType, body, contentType, out payload)) { Logger.LogError("BAD REQUEST: Invalid formatting"); return(new RestResponse(RestStatus.BadRequest, $"Invalid payload format for {contentType}.")); } } #endregion //Execute the correct method Stopwatch watch = Stopwatch.StartNew(); switch (method) { default: response = RestResponse.BadMethod; break; case RequestMethod.Get: response = route.OnGet(query); break; case RequestMethod.Delete: response = route.OnDelete(query); break; case RequestMethod.Post: response = route.OnPost(query, payload); break; //Put is obsolete, so keeping it just like a PATCH case RequestMethod.Patch: case RequestMethod.Put: response = route.OnPatch(query, payload); break; } //Make sure response isn't null at this point Debug.Assert(response != null); //Update the authentications update authentication.RecordAction("rest:" + bestFactory.Route); //Write the resulting json response.Route = bestFactory.Route; response.Time = watch.ElapsedMilliseconds; return(response); } catch (Exception e) { Logger.LogError(e, "Exception occured while processing rest: {0}"); if (ReportExceptions) { return(new RestResponse(RestStatus.InternalError, e.Message, e.StackTrace)); } return(new RestResponse(RestStatus.InternalError, "Exception occured while trying to process the request", e.Message)); } }