Added rate limiting to RESTful API
Added token bucket and timer to degrade token bucket Modified REST API rate limiting Changed limiting to only be on token create and v2 token create Added config options to choose time limits Passed HttpContext to the execute method of endpoints Made blocking failed API logins optional Changed error codes on failed login to be ambiguous Added config to decide whether all or failed logins are limited Changed config variable names Cleaned up duplicate code in REST rate limiting Fixed my typo Changed error 431 to 403
This commit is contained in:
parent
45e762abd2
commit
09a8f95a70
6 changed files with 135 additions and 50 deletions
|
|
@ -22,6 +22,7 @@ using System.ComponentModel;
|
|||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Reflection;
|
||||
using HttpServer;
|
||||
using HttpServer.Headers;
|
||||
|
|
@ -44,21 +45,24 @@ namespace Rests
|
|||
public IParameterCollection Parameters { get; private set; }
|
||||
public IRequest Request { get; private set; }
|
||||
public SecureRest.TokenData TokenData { get; private set; }
|
||||
public IHttpContext Context { get; private set; }
|
||||
|
||||
public RestRequestArgs(RestVerbs verbs, IParameterCollection param, IRequest request)
|
||||
public RestRequestArgs(RestVerbs verbs, IParameterCollection param, IRequest request, IHttpContext context)
|
||||
{
|
||||
Verbs = verbs;
|
||||
Parameters = param;
|
||||
Request = request;
|
||||
TokenData = SecureRest.TokenData.None;
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public RestRequestArgs(RestVerbs verbs, IParameterCollection param, IRequest request, SecureRest.TokenData tokenData)
|
||||
public RestRequestArgs(RestVerbs verbs, IParameterCollection param, IRequest request, SecureRest.TokenData tokenData, IHttpContext context)
|
||||
{
|
||||
Verbs = verbs;
|
||||
Parameters = param;
|
||||
Request = request;
|
||||
TokenData = tokenData;
|
||||
Context = context;
|
||||
}
|
||||
}
|
||||
public class Rest : IDisposable
|
||||
|
|
@ -66,6 +70,8 @@ namespace Rests
|
|||
private readonly List<RestCommand> commands = new List<RestCommand>();
|
||||
private HttpListener listener;
|
||||
private StringHeader serverHeader;
|
||||
public Dictionary<string, int> tokenBucket = new Dictionary<string, int>();
|
||||
private Timer tokenBucketTimer;
|
||||
public IPAddress Ip { get; set; }
|
||||
public int Port { get; set; }
|
||||
|
||||
|
|
@ -84,6 +90,11 @@ namespace Rests
|
|||
listener = HttpListener.Create(Ip, Port);
|
||||
listener.RequestReceived += OnRequest;
|
||||
listener.Start(int.MaxValue);
|
||||
tokenBucketTimer = new Timer((e) =>
|
||||
{
|
||||
DegradeBucket();
|
||||
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(Math.Max(TShock.Config.RESTRequestBucketDecreaseIntervalMinutes, 1)));
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -122,6 +133,23 @@ namespace Rests
|
|||
commands.Add(com);
|
||||
}
|
||||
|
||||
private void DegradeBucket()
|
||||
{
|
||||
var _bucket = new List<string>(tokenBucket.Keys); // Duplicate the keys so we can modify tokenBucket whilst iterating
|
||||
foreach(string key in _bucket)
|
||||
{
|
||||
int tokens = tokenBucket[key];
|
||||
if(tokens > 0)
|
||||
{
|
||||
tokenBucket[key] -= 1;
|
||||
}
|
||||
if(tokens <= 0)
|
||||
{
|
||||
tokenBucket.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Event
|
||||
public class RestRequestEventArgs : HandledEventArgs
|
||||
{
|
||||
|
|
@ -194,7 +222,7 @@ namespace Rests
|
|||
continue;
|
||||
}
|
||||
|
||||
var obj = ExecuteCommand(com, verbs, e.Request.Parameters, e.Request);
|
||||
var obj = ExecuteCommand(com, verbs, e.Request.Parameters, e.Request, e.Context);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
}
|
||||
|
|
@ -202,21 +230,21 @@ namespace Rests
|
|||
catch (Exception exception)
|
||||
{
|
||||
return new RestObject("500")
|
||||
{
|
||||
{"error", "Internal server error."},
|
||||
{"errormsg", exception.Message},
|
||||
{"stacktrace", exception.StackTrace},
|
||||
};
|
||||
{
|
||||
{"error", "Internal server error."},
|
||||
{"errormsg", exception.Message},
|
||||
{"stacktrace", exception.StackTrace},
|
||||
};
|
||||
}
|
||||
return new RestObject("404")
|
||||
{
|
||||
{"error", "Specified API endpoint doesn't exist. Refer to the documentation for a list of valid endpoints."}
|
||||
};
|
||||
{
|
||||
{"error", "Specified API endpoint doesn't exist. Refer to the documentation for a list of valid endpoints."}
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms, IRequest request)
|
||||
protected virtual object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms, IRequest request, IHttpContext context)
|
||||
{
|
||||
object result = cmd.Execute(verbs, parms, request);
|
||||
object result = cmd.Execute(verbs, parms, request, context);
|
||||
if (cmd.DoLog && TShock.Config.LogRest)
|
||||
{
|
||||
TShock.Log.ConsoleInfo("Anonymous requested REST endpoint: " + BuildRequestUri(cmd, verbs, parms, false));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue