House rules: the build checks them, and every refusal from the server answers 400, 403 or 404
Build / Desktop, server and tools (push) Successful in 1m8s
Build / Desktop, server and tools (push) Successful in 1m8s
The server no longer answers 409, 413 or 429: a busy probe, a share or upload that is too large and a request over the limit now answer 400 with the same reason as before, and still say when to try again. The app reads the reason, so nothing changes for it; versions up to 0.15.4 report a limited speed test as unmeasured until they update. The app's commands can now be cancelled all the way down, and the house rules are written into PATTERNS.md and checked on every build, Android included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4YtEJrgxG12Pc39Fai8Bd
This commit is contained in:
1 parent
5b2210a8a7
commit
d408edea98
28 files changed
+235
-145
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
using MeWeb.Core.Knowledge;
|
||||
using MeWeb.Shared;
|
||||
@@ -46,8 +47,8 @@ public static class ServerApi
|
||||
{
|
||||
var caller = Caller(context);
|
||||
if (caller is null) return Results.BadRequest();
|
||||
if (!speed.Allow(caller)) return Refused(context, new ViewRefusal { Reason = "too-many", RetryAfterSeconds = 3600 }, StatusCodes.Status429TooManyRequests);
|
||||
if (!speed.Enter()) return Refused(context, new ViewRefusal { Reason = "busy", RetryAfterSeconds = 60 }, StatusCodes.Status429TooManyRequests);
|
||||
if (!speed.Allow(caller)) return Refused(context, new ViewRefusal { Reason = "too-many", RetryAfterSeconds = 3600 }, StatusCodes.Status400BadRequest);
|
||||
if (!speed.Enter()) return Refused(context, new ViewRefusal { Reason = "busy", RetryAfterSeconds = 60 }, StatusCodes.Status400BadRequest);
|
||||
try
|
||||
{
|
||||
var size = Math.Clamp(bytes ?? 1024 * 1024, 1024, Endpoints.Speed.MaxDownBytes);
|
||||
@@ -63,7 +64,7 @@ public static class ServerApi
|
||||
static async Task<IResult> Up(HttpContext context, CancellationToken ct)
|
||||
{
|
||||
var received = await SpeedService.Drain(context.Request.Body, ct);
|
||||
return received < 0 ? Results.StatusCode(StatusCodes.Status413PayloadTooLarge) : Results.NoContent();
|
||||
return received < 0 ? Refused(context, new ViewRefusal { Reason = "size" }, StatusCodes.Status400BadRequest) : Results.NoContent();
|
||||
}
|
||||
|
||||
public const string DeleteTokenHeader = "X-Delete-Token";
|
||||
@@ -72,16 +73,14 @@ public static class ServerApi
|
||||
{
|
||||
var caller = Caller(context);
|
||||
if (caller is null) return Results.BadRequest();
|
||||
var (ticket, refusal, status) = probe.Issue(caller);
|
||||
return ticket is not null ? Results.Json(ticket, MeWebJsonContext.Default.ViewProbeTicket) : Refused(context, refusal, status);
|
||||
return Answer(context, probe.Issue(caller), MeWebJsonContext.Default.ViewProbeTicket);
|
||||
}
|
||||
|
||||
static async Task<IResult> Run(string id, ProbeForm form, HttpContext context, ProbeService probe, CancellationToken ct)
|
||||
{
|
||||
var caller = Caller(context);
|
||||
if (caller is null) return Results.BadRequest();
|
||||
var (result, refusal, status) = await probe.Run(id, caller, form, ct);
|
||||
return result is not null ? Results.Json(result, MeWebJsonContext.Default.ViewProbe) : Refused(context, refusal, status);
|
||||
return Answer(context, await probe.Run(id, caller, form, ct), MeWebJsonContext.Default.ViewProbe);
|
||||
}
|
||||
|
||||
static async Task<IResult> Share(HttpContext context, ShareService shares, CancellationToken ct)
|
||||
@@ -90,19 +89,21 @@ public static class ServerApi
|
||||
if (caller is null) return Results.BadRequest();
|
||||
using var buffer = new MemoryStream();
|
||||
var chunk = new byte[8192];
|
||||
int read;
|
||||
var read = default(int);
|
||||
while ((read = await context.Request.Body.ReadAsync(chunk, ct)) > 0)
|
||||
{
|
||||
buffer.Write(chunk, 0, read);
|
||||
if (buffer.Length > ShareService.MaxBytes) return Refused(context, new ViewRefusal { Reason = "size" }, StatusCodes.Status413PayloadTooLarge);
|
||||
if (buffer.Length > ShareService.MaxBytes) return Refused(context, new ViewRefusal { Reason = "size" }, StatusCodes.Status400BadRequest);
|
||||
}
|
||||
var (shared, refusal, status) = shares.Create(caller, buffer.ToArray());
|
||||
return shared is not null ? Results.Json(shared, MeWebJsonContext.Default.ViewShared) : Refused(context, refusal, status);
|
||||
return Answer(context, shares.Create(caller, buffer.ToArray()), MeWebJsonContext.Default.ViewShared);
|
||||
}
|
||||
|
||||
static IResult Unshare(string id, HttpContext context, ShareService shares) =>
|
||||
Results.StatusCode(shares.Delete(id, context.Request.Headers[DeleteTokenHeader].ToString()));
|
||||
|
||||
static IResult Answer<T>(HttpContext context, WebResult<T> result, JsonTypeInfo<T> type) where T : class =>
|
||||
result.IsInvalid ? Refused(context, result.Refusal, result.StatusCode) : Results.Json(result.Data, type);
|
||||
|
||||
static IResult Refused(HttpContext context, ViewRefusal refusal, int status)
|
||||
{
|
||||
if (refusal.RetryAfterSeconds > 0) context.Response.Headers.RetryAfter = refusal.RetryAfterSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
|
||||
Reference in new issue
Block a user