-
Notifications
You must be signed in to change notification settings - Fork 0
/
Http304.cs
114 lines (96 loc) · 5.02 KB
/
Http304.cs
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
namespace LC6464.ASPNET.Http304;
/// <summary>
/// 实现快速设置 HTTP 304 状态码的类。
/// </summary>
public class Http304 : IHttp304 {
private readonly HttpRequest Request;
private readonly HttpResponse Response;
private readonly ILogger<Http304> _logger;
private readonly IHttpConnectionInfo _info;
private readonly string _lastModified;
/// <summary>
/// 使用 <paramref name="accessor"/>, <paramref name="info"/>, <paramref name="logger"/> 和 <paramref name="lastModified"/> (可选) 初始化所有属性的构造函数。
/// </summary>
/// <param name="accessor">用于初始化的 <see cref="IHttpContextAccessor"/></param>
/// <param name="info">当前的 HTTP 连接信息</param>
/// <param name="logger">用于记录日志的 <see cref="ILogger"/></param>
/// <param name="lastModified">上次修改时间,默认为当前执行的程序集的执行文件的上次修改时间</param>
public Http304(IHttpContextAccessor accessor, IHttpConnectionInfo info, ILogger<Http304> logger, DateTime? lastModified = null) {
Request = accessor.HttpContext!.Request;
Response = accessor.HttpContext.Response;
_info = info;
_logger = logger;
_lastModified = (lastModified ?? File.GetLastWriteTime(System.Reflection.Assembly.GetExecutingAssembly().Location)).ToUniversalTime().ToString("R");
}
/// <summary>
/// 强制指定是否设置 HTTP 304 状态码。
/// </summary>
/// <param name="isSet">若为 <see langword="true"/> 则设置</param>
/// <returns>返回 <paramref name="isSet"/>.</returns>
public bool Set(bool isSet = true) {
if (isSet) {
Response.Clear();
// Set the 304 status code.
Response.StatusCode = (int)HttpStatusCode.NotModified;
}
_logger.LogInformation("{}设置 HTTP 304.", isSet ? "已" : "未");
return isSet;
}
/// <summary>
/// HTTP 协商缓存验证客户端缓存有效性。
/// </summary>
/// <param name="withIP">是否带上 IP 地址</param>
/// <param name="value">附加字符</param>
/// <returns>如果有效,则为 <see langword="true"/>;否则为 <see langword="false"/>.</returns>
public bool IsValid(bool withIP = false, string value = "") {
ReadOnlySpan<char> ip = withIP ? _info.RemoteAddress?.ToString() ?? "" : "";
StringValues clientLastModifiedHeaders = Request.Headers.IfModifiedSince,
clientETagHeaders = Request.Headers.IfNoneMatch;
_logger.LogDebug("验证 HTTP 协商缓存是否有效,客户端 If-Modified-Since: {}, 客户端 If-None-Match: {}.", clientLastModifiedHeaders, clientETagHeaders);
if (clientETagHeaders.Count == 1 && clientLastModifiedHeaders.Count == 1 && clientETagHeaders[0]!.Length == 50 && clientLastModifiedHeaders[0] == _lastModified) {
var clientETag = clientETagHeaders[0].AsSpan(1, 48);
ReadOnlySpan<char> clientSHA256 = string.Concat(clientETag[..22], clientETag[27..]),
clientSalt = clientETag[22..27];
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(string.Concat(ip, clientSalt, value)));
var computedHash = Convert.ToBase64String(hash)[..43];
_logger.LogDebug("HTTP 协商缓存初步检查有效,计算得到的 SHA256: {}.", computedHash);
return clientSHA256.ToString() == computedHash;
}
return false;
}
/// <summary>
/// 验证客户端缓存有效性,若有效,则设置 HTTP 304 状态码。
/// </summary>
/// <param name="withIP">是否带上 IP 地址</param>
/// <param name="value">附加字符</param>
/// <returns>若已设置,则返回 <see langword="true"/>;否则返回 <see langword="false"/> 并向客户端输出相关响应头。</returns>
public bool TrySet(bool withIP = false, string value = "") {
var isValid = IsValid(withIP, value);
if (!isValid) { // 若无效
ReadOnlySpan<char> ip = withIP ? _info.RemoteAddress?.ToString() ?? "" : "";
var charList = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()_+{}|:<>?-=[];',./"; // Salt 中可包含的字符列表
StringBuilder sb = new();
for (var i = 0; i < 5; i++) {
_ = sb.Append(charList[Random.Shared.Next(charList.Length)]);
}
ReadOnlySpan<char> salt = sb.ToString();
_ = sb.Clear();
ReadOnlySpan<char> hash = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(string.Concat(ip, salt, value))));
Response.Headers.Add("Last-Modified", _lastModified);
Response.Headers.Add("ETag", $"\"{string.Concat(hash[..22], salt, hash[22..43])}\"");
}
return Set(isValid);
}
/// <summary>
/// HTTP 协商缓存验证客户端缓存有效性。
/// </summary>
/// <param name="value">附加字符</param>
/// <returns>如果有效,则为 <see langword="true"/>;否则为 <see langword="false"/>.</returns>
public bool IsValid(string value = "") => IsValid(false, value);
/// <summary>
/// 验证客户端缓存有效性,若有效,则设置 HTTP 304 状态码。
/// </summary>
/// <param name="value">附加字符</param>
/// <returns>若已设置,则返回 <see langword="true"/>;否则返回 <see langword="false"/> 并向客户端输出相关响应头。</returns>
public bool TrySet(string value = "") => TrySet(false, value);
}