-
Notifications
You must be signed in to change notification settings - Fork 85
Expand file tree
/
Copy pathCustomHttpStatusCodeResult.cs
More file actions
131 lines (111 loc) · 4.16 KB
/
Copy pathCustomHttpStatusCodeResult.cs
File metadata and controls
131 lines (111 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
namespace Teapot.Web;
public class CustomHttpStatusCodeResult(ResponseOptions options, int maxSleepMilliseconds = 30 * 1000) : IResult
{
private const int SLEEP_MIN = 0;
private const int SLEEP_MAX = 5 * 60 * 1000; // 5 mins in milliseconds
internal static readonly string[] onlySingleHeader = ["Location", "Retry-After"];
private static readonly MediaTypeHeaderValue jsonMimeType = new("application/json");
public ResponseOptions Options => options;
public async Task ExecuteAsync(HttpContext context)
{
await DoSleep(Options.Sleep);
if (Options.AbortBeforeHeaders == true)
{
context.Abort();
return;
}
context.Response.StatusCode = Options.StatusCode;
if (!string.IsNullOrEmpty(Options.Metadata.Description))
{
IHttpResponseFeature? httpResponseFeature = context.Features.Get<IHttpResponseFeature>();
if (httpResponseFeature is not null)
{
httpResponseFeature.ReasonPhrase = Options.Metadata.Description;
}
}
if (Options.Metadata.IncludeHeaders is not null)
{
foreach ((string header, string values) in Options.Metadata.IncludeHeaders)
{
context.Response.Headers.Append(header, values);
}
}
foreach ((string header, StringValues values) in Options.CustomHeaders)
{
if (onlySingleHeader.Contains(header))
{
context.Response.Headers[header] = values;
}
else
{
context.Response.Headers.Append(header, values);
}
}
if (Options.Metadata.ExcludeBody || Options.SuppressBody == true)
{
//remove Content-Length and Content-Type when there isn't any body
context.Response.Headers.Remove("Content-Length");
context.Response.Headers.Remove("Content-Type");
}
else
{
IList<MediaTypeHeaderValue> acceptTypes = context.Request.GetTypedHeaders().Accept;
(string body, string contentType) = acceptTypes.Contains(jsonMimeType) switch
{
true => (JsonSerializer.Serialize(new { code = Options.StatusCode, description = Options.Metadata.Body ?? Options.Metadata.Description }), "application/json"),
false => (Options.Metadata.Body ?? $"{Options.StatusCode} {Options.Metadata.Description}", "text/plain")
};
context.Response.ContentType = contentType;
context.Response.ContentLength = body.Length;
await context.Response.StartAsync();
await DoSleep(Options.SleepAfterHeaders);
if (Options.AbortAfterHeaders == true)
{
await DoSleep(10);
context.Abort();
return;
}
if (Options.AbortDuringBody == true)
{
await context.Response.WriteAsync(body.Substring(0, 1));
await context.Response.Body.FlushAsync();
await DoSleep(10);
context.Abort();
return;
}
if (Options.DribbleBody == true)
{
for (int i = 0; i < body.Length; i++)
{
await context.Response.WriteAsync(body[i..(i + 1)]);
if (i < 20)
{
await context.Response.Body.FlushAsync();
await DoSleep(10);
}
}
}
else
{
await context.Response.WriteAsync(body);
}
}
}
private async Task DoSleep(int? sleep)
{
int sleepData = Math.Clamp(sleep ?? 0, SLEEP_MIN, SLEEP_MAX);
if (sleepData > 0)
{
await Task.Delay(sleepData);
}
}
}