返回提交历史
Added
XFEToolBox.Server.Core/Utilities/ServerHttpResponseHelper.cs
+42
-0
Modified
XFEToolBox.Server.Test/Program.cs
+31
-0
Modified
XFEToolBox.Server/Services/AdminManagementService.cs
+1
-1
Modified
XFEToolBox.Server/Services/RegistrationService.cs
+1
-1
Modified
XFEToolBox.Server/Services/ToolAdminService.cs
+2
-2
Modified
XFEToolBox.Server/Services/ToolCatalogService.cs
+9
-5
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
代码差异
6 个文件
+86
-9
@@ -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
}
@@ -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();
@@ -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)
@@ -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
@@ -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;
@@ -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);