XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEToolBox

【WPF】XFE工具箱

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFEToolBox

引入ServerHttpResponseHelper统一下载响应设置

新增ServerHttpResponseHelper工具类,统一设置HTTP下载响应的Content-Type、Content-Length、Content-Disposition、ETag等元数据,兼容CyberCommHttpResponse和HttpListenerResponse。ToolCatalogService下载工具包时改用该工具类配置响应头,提升代码复用性和一致性。ToolAdminService、AdminManagementService、RegistrationService上传工具包或注册用户成功时,状态码设置方式由Args.Response.StatusCode调整为ReturnArgs.StatusCode,更贴合当前架构。ToolAdminService校验POST请求的方法由Args.Request.HttpMethod改为Args.RequestMethod。Program.cs新增SocketDownloadResponseIsConfigured测试用例,验证下载响应头设置的正确性。

18b58ce
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

6 个文件 +86 -9
Added XFEToolBox.Server.Core/Utilities/ServerHttpResponseHelper.cs +42 -0
@@ -0,0 +1,42 @@
1 using System.Globalization;
2 using System.Net;
3 using XFEExtension.NetCore.CyberComm;
4
5 namespace XFEToolBox.Server.Core.Utilities;
6
7 /// <summary>
8 /// Applies optional HTTP metadata without assuming which CyberComm transport is serving the request.
9 /// </summary>
10 public static class ServerHttpResponseHelper
11 {
12 public static void ConfigureDownload(
13 CyberCommHttpResponse? response,
14 HttpListenerResponse? legacyResponse,
15 string contentType,
16 long contentLength,
17 string fileName,
18 string etag)
19 {
20 ArgumentException.ThrowIfNullOrWhiteSpace(contentType);
21 ArgumentOutOfRangeException.ThrowIfNegative(contentLength);
22 ArgumentException.ThrowIfNullOrWhiteSpace(fileName);
23 ArgumentException.ThrowIfNullOrWhiteSpace(etag);
24
25 var contentDisposition = $"attachment; filename=\"{fileName}\"";
26 var quotedEtag = $"\"{etag.Trim('\"')}\"";
27
28 if (response is not null)
29 {
30 response.Headers["Content-Type"] = contentType;
31 response.Headers["Content-Length"] = contentLength.ToString(CultureInfo.InvariantCulture);
32 response.Headers["Content-Disposition"] = contentDisposition;
33 response.Headers["ETag"] = quotedEtag;
34 }
35
36 if (legacyResponse is null) return;
37 legacyResponse.ContentType = contentType;
38 legacyResponse.ContentLength64 = contentLength;
39 legacyResponse.Headers["Content-Disposition"] = contentDisposition;
40 legacyResponse.Headers["ETag"] = quotedEtag;
41 }
42 }
Modified XFEToolBox.Server.Test/Program.cs +31 -0
@@ -6,6 +6,7 @@ using XFEToolBox.Server.Core.Exceptions;
6 6 using XFEToolBox.Server.Core.Options;
7 7 using XFEToolBox.Server.Core.Services;
8 8 using XFEToolBox.Server.Core.Utilities;
9 using XFEExtension.NetCore.CyberComm;
9 10
10 11 var tests = new (string Name, Action Run)[]
11 12 {
@@ -14,6 +15,7 @@ var tests = new (string Name, Action Run)[]
14 15 ("语义化版本按预期排序", SemanticVersionsAreOrdered),
15 16 ("文件仓库可保存、查询和下架工具包", RepositoryRoundTripsPackage),
16 17 ("文件仓库拒绝覆盖已存在的工具版本", RepositoryRejectsDuplicateVersion),
18 ("Socket 传输可安全配置工具下载响应", SocketDownloadResponseIsConfigured),
17 19 ("系统 CPU 使用率可在负载下被采样", SystemCpuUsageIsMeasuredUnderLoad)
18 20 };
19 21
@@ -133,6 +135,35 @@ static void RepositoryRejectsDuplicateVersion()
133 135 }
134 136 }
135 137
138 static void SocketDownloadResponseIsConfigured()
139 {
140 var response = new CyberCommHttpResponse();
141 ServerHttpResponseHelper.ConfigureDownload(
142 response,
143 legacyResponse: null,
144 "application/vnd.xfestudio.xfetool",
145 1234,
146 "text-encryption-1.0.1.xfetool",
147 "abc123");
148
149 Assert(
150 response.Headers["Content-Type"] == "application/vnd.xfestudio.xfetool",
151 "工具包响应类型未设置。");
152 Assert(response.Headers["Content-Length"] == "1234", "工具包响应长度未设置。");
153 Assert(
154 response.Headers["Content-Disposition"] == "attachment; filename=\"text-encryption-1.0.1.xfetool\"",
155 "工具包下载文件名未设置。");
156 Assert(response.Headers["ETag"] == "\"abc123\"", "工具包 ETag 未设置。");
157
158 ServerHttpResponseHelper.ConfigureDownload(
159 response: null,
160 legacyResponse: null,
161 "application/vnd.xfestudio.xfetool",
162 0,
163 "empty.xfetool",
164 "empty");
165 }
166
136 167 static void SystemCpuUsageIsMeasuredUnderLoad()
137 168 {
138 169 using var loadStarted = new ManualResetEventSlim();
Modified XFEToolBox.Server/Services/AdminManagementService.cs +1 -1
@@ -213,7 +213,7 @@ public partial class AdminManagementService : ServerCoreUserServiceBase
213 213 stream,
214 214 GetNullableBoolean("published") ?? true,
215 215 GetNullableBoolean("overwrite") ?? false);
216 Args.Response.StatusCode = (int)HttpStatusCode.Created;
216 ReturnArgs.StatusCode = HttpStatusCode.Created;
217 217 await Close(ToolPackageContractMapper.ToUploadResult(package));
218 218 }
219 219 catch (ToolPackageValidationException exception)
Modified XFEToolBox.Server/Services/RegistrationService.cs +1 -1
@@ -64,7 +64,7 @@ public partial class RegistrationService : ServerCoreStandardServiceBase
64 64 return;
65 65 }
66 66
67 Args.Response.StatusCode = (int)HttpStatusCode.Created;
67 ReturnArgs.StatusCode = HttpStatusCode.Created;
68 68 await Close(ToolBoxUserFaceInfo.FromUser(user));
69 69 }
70 70
Modified XFEToolBox.Server/Services/ToolAdminService.cs +2 -2
@@ -77,7 +77,7 @@ public partial class ToolAdminService : ServerCoreStandardServiceBase
77 77 stream,
78 78 published,
79 79 overwrite);
80 Args.Response.StatusCode = (int)HttpStatusCode.Created;
80 ReturnArgs.StatusCode = HttpStatusCode.Created;
81 81 await Close(ToolPackageContractMapper.ToUploadResult(package));
82 82 }
83 83 catch (ToolPackageValidationException exception)
@@ -127,7 +127,7 @@ public partial class ToolAdminService : ServerCoreStandardServiceBase
127 127 return false;
128 128 }
129 129
130 if (requirePost && !string.Equals(Args.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
130 if (requirePost && !string.Equals(Args.RequestMethod, "POST", StringComparison.OrdinalIgnoreCase))
131 131 {
132 132 await CloseWithError("此接口只接受 POST 请求。", HttpStatusCode.MethodNotAllowed);
133 133 return false;
Modified XFEToolBox.Server/Services/ToolCatalogService.cs +9 -5
@@ -113,11 +113,15 @@ public partial class ToolCatalogService : ServerCoreStandardServiceBase
113 113 return;
114 114 }
115 115
116 Args.Response.ContentType = "application/vnd.xfestudio.xfetool";
117 Args.Response.ContentLength64 = package.Package.PackageSize;
118 Args.Response.Headers["Content-Disposition"] =
119 $"attachment; filename=\"{package.Package.Manifest.Id}-{package.Package.Manifest.Version}.xfetool\"";
120 Args.Response.Headers["ETag"] = $"\"{package.Package.Sha256}\"";
116 #pragma warning disable CS0618 // Keep HttpListener compatibility while preferring the current socket response.
117 ServerHttpResponseHelper.ConfigureDownload(
118 Args.Context?.Response,
119 Args.Response,
120 "application/vnd.xfestudio.xfetool",
121 package.Package.PackageSize,
122 $"{package.Package.Manifest.Id}-{package.Package.Manifest.Version}.xfetool",
123 package.Package.Sha256);
124 #pragma warning restore CS0618
121 125 await using var stream = new FileStream(
122 126 package.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 81920,
123 127 FileOptions.Asynchronous | FileOptions.SequentialScan);