@filebox/s3-server 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1372 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.mjs +1341 -0
- package/package.json +23 -17
- package/context.js +0 -62
- package/driver.js +0 -337
- package/handlers/createBucket.js +0 -129
- package/handlers/deleteBucket.js +0 -84
- package/handlers/deleteObject.js +0 -77
- package/handlers/deleteObjects.js +0 -148
- package/handlers/getObject.js +0 -195
- package/handlers/headBucket.js +0 -72
- package/handlers/headObject.js +0 -134
- package/handlers/listBuckets.js +0 -38
- package/handlers/listObjects.js +0 -230
- package/handlers/putObject.js +0 -158
- package/index.js +0 -268
- package/responses.js +0 -193
package/handlers/listObjects.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
const Responses = require("../responses");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 处理S3 ListObjects(V2)请求
|
|
6
|
-
* 列出存储桶中的对象
|
|
7
|
-
*/
|
|
8
|
-
class ListObjects {
|
|
9
|
-
constructor(server) {
|
|
10
|
-
this.server = server;
|
|
11
|
-
this.handle = this.handle.bind(this);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 解析请求查询参数
|
|
16
|
-
* @param {object} req - Express请求对象
|
|
17
|
-
* @returns {object} 解析后的参数
|
|
18
|
-
*/
|
|
19
|
-
parseQueryParams(req) {
|
|
20
|
-
if (!req || !req.query) {
|
|
21
|
-
return { prefix: "", delimiter: "" };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
prefix:
|
|
26
|
-
typeof req.query.prefix === "string" && req.query.prefix.length > 0
|
|
27
|
-
? req.query.prefix
|
|
28
|
-
: "",
|
|
29
|
-
delimiter:
|
|
30
|
-
typeof req.query.delimiter === "string" &&
|
|
31
|
-
req.query.delimiter.length > 0
|
|
32
|
-
? req.query.delimiter
|
|
33
|
-
: "",
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 规范化前缀
|
|
39
|
-
* @param {string} prefix - 原始前缀
|
|
40
|
-
* @returns {string} 规范化后的前缀
|
|
41
|
-
*/
|
|
42
|
-
normalizePrefix(prefix) {
|
|
43
|
-
let trimmed = decodeURIComponent(prefix).trim();
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
trimmed.length === 0 ||
|
|
47
|
-
trimmed === "/" ||
|
|
48
|
-
trimmed.startsWith("./") ||
|
|
49
|
-
trimmed.startsWith("../") ||
|
|
50
|
-
trimmed.includes("../")
|
|
51
|
-
) {
|
|
52
|
-
return "/";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!trimmed.startsWith("/")) {
|
|
56
|
-
trimmed = `/${trimmed}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return trimmed;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 处理ListObjects请求
|
|
64
|
-
* @param {object} req - Express请求对象
|
|
65
|
-
* @param {object} res - Express响应对象
|
|
66
|
-
*/
|
|
67
|
-
async handle(req, res) {
|
|
68
|
-
try {
|
|
69
|
-
// 检查是否为位置查询
|
|
70
|
-
if (req.url.includes("?location")) {
|
|
71
|
-
await Responses.getBucketLocation(
|
|
72
|
-
res,
|
|
73
|
-
this.server.config.region || "default",
|
|
74
|
-
);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 获取存储桶和键
|
|
79
|
-
const { bucket } = this.extractBucketAndKey(req);
|
|
80
|
-
|
|
81
|
-
if (!bucket) {
|
|
82
|
-
await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 解析查询参数
|
|
87
|
-
const params = this.parseQueryParams(req);
|
|
88
|
-
const normalizedPrefix = this.normalizePrefix(params.prefix);
|
|
89
|
-
|
|
90
|
-
// 构建目录路径
|
|
91
|
-
let dirname = this.normalizePrefix(
|
|
92
|
-
normalizedPrefix === "/"
|
|
93
|
-
? `/${bucket}`
|
|
94
|
-
: path.posix.dirname(path.posix.join(bucket, normalizedPrefix)),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const requestedPath = this.normalizePrefix(
|
|
98
|
-
path.posix.join(bucket, normalizedPrefix),
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// 检查请求的路径是否存在
|
|
102
|
-
const requestedPathStats = await this.server.driver.stat(requestedPath);
|
|
103
|
-
|
|
104
|
-
if (requestedPathStats && requestedPathStats.isDirectory) {
|
|
105
|
-
dirname = requestedPath;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 检查目录是否存在
|
|
109
|
-
const dirnameStats = await this.server.driver.stat(dirname);
|
|
110
|
-
|
|
111
|
-
if (!dirnameStats) {
|
|
112
|
-
await Responses.listObjectsV2(
|
|
113
|
-
res,
|
|
114
|
-
params.prefix,
|
|
115
|
-
[],
|
|
116
|
-
[],
|
|
117
|
-
bucket,
|
|
118
|
-
params.delimiter,
|
|
119
|
-
);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 读取目录内容
|
|
124
|
-
const depth = params.delimiter.length === 0 ? 10 : 0;
|
|
125
|
-
const dirContent = await this.readdir(dirname, depth);
|
|
126
|
-
|
|
127
|
-
// 处理目录内容
|
|
128
|
-
const objects = [];
|
|
129
|
-
const commonPrefixes = [];
|
|
130
|
-
|
|
131
|
-
for (const item of dirContent) {
|
|
132
|
-
const itemPath = path.posix.join(dirname, item);
|
|
133
|
-
const itemStats = await this.server.driver.stat(itemPath);
|
|
134
|
-
|
|
135
|
-
if (!itemStats) continue;
|
|
136
|
-
|
|
137
|
-
// 计算相对路径
|
|
138
|
-
const relativePath = itemPath.startsWith(`/${bucket}/`)
|
|
139
|
-
? itemPath.slice(`/${bucket}/`.length)
|
|
140
|
-
: itemPath;
|
|
141
|
-
|
|
142
|
-
if (itemStats.isDirectory) {
|
|
143
|
-
commonPrefixes.push(`${relativePath}/`);
|
|
144
|
-
} else {
|
|
145
|
-
objects.push({
|
|
146
|
-
path: relativePath,
|
|
147
|
-
mtimeMs: itemStats.mtime || Date.now(),
|
|
148
|
-
size: itemStats.size || 0,
|
|
149
|
-
uuid: itemStats.uuid || crypto.randomUUID(),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 返回对象列表
|
|
155
|
-
await Responses.listObjectsV2(
|
|
156
|
-
res,
|
|
157
|
-
normalizedPrefix === "/" ? "/" : normalizedPrefix.slice(1),
|
|
158
|
-
objects,
|
|
159
|
-
commonPrefixes,
|
|
160
|
-
bucket,
|
|
161
|
-
params.delimiter,
|
|
162
|
-
);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
console.error("Error in ListObjects:", error);
|
|
165
|
-
await Responses.error(res, 500, "InternalError", "服务器内部错误");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* 从请求参数中提取存储桶和键
|
|
171
|
-
* @param {object} req - Express请求对象
|
|
172
|
-
* @returns {object} 包含存储桶和键的对象
|
|
173
|
-
*/
|
|
174
|
-
extractBucketAndKey(req) {
|
|
175
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
176
|
-
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
bucket: pathParts[0],
|
|
180
|
-
key: pathParts.slice(1).join("/"),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* 读取目录内容
|
|
186
|
-
* @param {string} dirPath - 目录路径
|
|
187
|
-
* @param {number} depth - 递归深度
|
|
188
|
-
* @returns {Promise<Array>} 目录内容列表
|
|
189
|
-
*/
|
|
190
|
-
async readdir(dirPath, depth) {
|
|
191
|
-
try {
|
|
192
|
-
// 基本情况:不递归
|
|
193
|
-
if (depth <= 0) {
|
|
194
|
-
return await this.server.driver.readdir(dirPath);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const items = [];
|
|
198
|
-
|
|
199
|
-
// 递归读取目录
|
|
200
|
-
const traverse = async (currentPath, currentDepth) => {
|
|
201
|
-
if (currentDepth >= depth) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const entries = await this.server.driver.readdir(currentPath);
|
|
206
|
-
|
|
207
|
-
for (const entry of entries) {
|
|
208
|
-
const entryPath = path.posix.join(currentPath, entry);
|
|
209
|
-
const stats = await this.server.driver.stat(entryPath);
|
|
210
|
-
|
|
211
|
-
// 添加相对路径
|
|
212
|
-
items.push(entryPath.slice(dirPath.length));
|
|
213
|
-
|
|
214
|
-
// 如果是目录且未达到最大深度,则递归
|
|
215
|
-
if (stats && stats.isDirectory) {
|
|
216
|
-
await traverse(entryPath, currentDepth + 1);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
await traverse(dirPath, 0);
|
|
222
|
-
return items;
|
|
223
|
-
} catch (error) {
|
|
224
|
-
console.error("Error reading directory:", error);
|
|
225
|
-
return [];
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
module.exports = ListObjects;
|
package/handlers/putObject.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
const Responses = require("../responses");
|
|
2
|
-
const crypto = require("crypto");
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 处理S3 PutObject请求
|
|
6
|
-
* 上传对象到存储桶
|
|
7
|
-
*/
|
|
8
|
-
class PutObject {
|
|
9
|
-
constructor(server) {
|
|
10
|
-
this.server = server;
|
|
11
|
-
this.handle = this.handle.bind(this);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 处理PutObject请求
|
|
16
|
-
* @param {object} req - Express请求对象
|
|
17
|
-
* @param {object} res - Express响应对象
|
|
18
|
-
*/
|
|
19
|
-
async handle(req, res) {
|
|
20
|
-
try {
|
|
21
|
-
// 获取存储桶和键
|
|
22
|
-
const { bucket, key } = this.extractBucketAndKey(req);
|
|
23
|
-
|
|
24
|
-
if (!bucket) {
|
|
25
|
-
await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!key) {
|
|
30
|
-
await Responses.error(res, 400, "InvalidRequest", "无效的对象键");
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// 检查存储桶是否存在
|
|
35
|
-
const bucketExists = await this.server.driver.bucketExists(bucket);
|
|
36
|
-
if (!bucketExists) {
|
|
37
|
-
await Responses.error(res, 404, "NoSuchBucket", "存储桶不存在");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 构建对象路径
|
|
42
|
-
const objectPath = `/${bucket}/${key}`;
|
|
43
|
-
|
|
44
|
-
// 获取内容长度
|
|
45
|
-
const contentLength = parseInt(req.headers["content-length"] || "0", 10);
|
|
46
|
-
|
|
47
|
-
if (isNaN(contentLength) || contentLength < 0) {
|
|
48
|
-
await Responses.error(
|
|
49
|
-
res,
|
|
50
|
-
400,
|
|
51
|
-
"InvalidRequest",
|
|
52
|
-
"无效的Content-Length",
|
|
53
|
-
);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 创建写入流
|
|
58
|
-
const writeStream =
|
|
59
|
-
await this.server.driver.createWriteStream(objectPath);
|
|
60
|
-
|
|
61
|
-
// 计算MD5哈希
|
|
62
|
-
const md5Hash = crypto.createHash("md5");
|
|
63
|
-
|
|
64
|
-
// 处理请求体。body-parser.raw 会先消费请求流并写入 req.body,
|
|
65
|
-
// 如果这里只监听 data/end,真实 S3 PUT 会一直等不到 end。
|
|
66
|
-
let bytesWritten = 0;
|
|
67
|
-
|
|
68
|
-
await new Promise((resolve, reject) => {
|
|
69
|
-
let settled = false;
|
|
70
|
-
const rejectOnce = (error) => {
|
|
71
|
-
if (settled) return;
|
|
72
|
-
settled = true;
|
|
73
|
-
writeStream.destroy();
|
|
74
|
-
reject(error);
|
|
75
|
-
};
|
|
76
|
-
const complete = async () => {
|
|
77
|
-
if (settled) return;
|
|
78
|
-
settled = true;
|
|
79
|
-
try {
|
|
80
|
-
const etag = md5Hash.digest("hex");
|
|
81
|
-
|
|
82
|
-
await this.server.driver.updateObjectMetadata(objectPath, {
|
|
83
|
-
etag,
|
|
84
|
-
contentType:
|
|
85
|
-
req.headers["content-type"] || "application/octet-stream",
|
|
86
|
-
contentLength: bytesWritten,
|
|
87
|
-
lastModified: new Date(),
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
res.set("ETag", `"${etag}"`);
|
|
91
|
-
res.set("Date", new Date().toUTCString());
|
|
92
|
-
res.status(200).end();
|
|
93
|
-
resolve();
|
|
94
|
-
} catch (error) {
|
|
95
|
-
reject(error);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
writeStream.on("error", rejectOnce);
|
|
100
|
-
|
|
101
|
-
const rawBody = req.body;
|
|
102
|
-
if (Buffer.isBuffer(rawBody) || rawBody instanceof Uint8Array) {
|
|
103
|
-
const chunk = Buffer.from(rawBody);
|
|
104
|
-
if (chunk.length > 0) {
|
|
105
|
-
writeStream.write(chunk);
|
|
106
|
-
md5Hash.update(chunk);
|
|
107
|
-
bytesWritten += chunk.length;
|
|
108
|
-
}
|
|
109
|
-
writeStream.end(() => void complete());
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (typeof rawBody === "string") {
|
|
114
|
-
const chunk = Buffer.from(rawBody);
|
|
115
|
-
if (chunk.length > 0) {
|
|
116
|
-
writeStream.write(chunk);
|
|
117
|
-
md5Hash.update(chunk);
|
|
118
|
-
bytesWritten += chunk.length;
|
|
119
|
-
}
|
|
120
|
-
writeStream.end(() => void complete());
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
req.on("data", (chunk) => {
|
|
125
|
-
writeStream.write(chunk);
|
|
126
|
-
md5Hash.update(chunk);
|
|
127
|
-
bytesWritten += chunk.length;
|
|
128
|
-
});
|
|
129
|
-
req.on("end", () => {
|
|
130
|
-
writeStream.end(() => void complete());
|
|
131
|
-
});
|
|
132
|
-
req.on("error", rejectOnce);
|
|
133
|
-
});
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error("Error in PutObject:", error);
|
|
136
|
-
if (!res.headersSent) {
|
|
137
|
-
await Responses.error(res, 500, "InternalError", "服务器内部错误");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 从请求参数中提取存储桶和键
|
|
144
|
-
* @param {object} req - Express请求对象
|
|
145
|
-
* @returns {object} 包含存储桶和键的对象
|
|
146
|
-
*/
|
|
147
|
-
extractBucketAndKey(req) {
|
|
148
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
149
|
-
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
bucket: pathParts[0],
|
|
153
|
-
key: pathParts.slice(1).join("/"),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
module.exports = PutObject;
|
package/index.js
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
const express = require("express");
|
|
2
|
-
const http = require("http");
|
|
3
|
-
const https = require("https");
|
|
4
|
-
const bodyParser = require("body-parser");
|
|
5
|
-
const crypto = require("crypto");
|
|
6
|
-
const { v4: uuidv4 } = require("uuid");
|
|
7
|
-
const createContext = require("./context");
|
|
8
|
-
|
|
9
|
-
// 导入处理程序
|
|
10
|
-
const ListBuckets = require("./handlers/listBuckets");
|
|
11
|
-
const ListObjects = require("./handlers/listObjects");
|
|
12
|
-
const GetObject = require("./handlers/getObject");
|
|
13
|
-
const PutObject = require("./handlers/putObject");
|
|
14
|
-
const DeleteObject = require("./handlers/deleteObject");
|
|
15
|
-
const DeleteObjects = require("./handlers/deleteObjects");
|
|
16
|
-
const HeadObject = require("./handlers/headObject");
|
|
17
|
-
const HeadBucket = require("./handlers/headBucket");
|
|
18
|
-
const CreateBucket = require("./handlers/createBucket");
|
|
19
|
-
const DeleteBucket = require("./handlers/deleteBucket");
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* S3 服务器配置
|
|
23
|
-
* @typedef {Object} S3ServerConfig
|
|
24
|
-
* @property {string} hostname - 主机名
|
|
25
|
-
* @property {number} port - 端口
|
|
26
|
-
* @property {boolean} https - 是否启用HTTPS
|
|
27
|
-
* @property {string} region - S3区域
|
|
28
|
-
* @property {string} accessKeyId - 访问密钥ID
|
|
29
|
-
* @property {string} secretAccessKey - 访问密钥
|
|
30
|
-
* @property {string} [displayName] - 显示名称
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* S3服务器
|
|
35
|
-
*/
|
|
36
|
-
class S3Server {
|
|
37
|
-
/**
|
|
38
|
-
* 创建S3服务器实例
|
|
39
|
-
* @param {Object} config - 服务器配置
|
|
40
|
-
* @param {Object} driver - 存储驱动
|
|
41
|
-
*/
|
|
42
|
-
constructor(config, driver) {
|
|
43
|
-
this.config = {
|
|
44
|
-
hostname: config.hostname || "0.0.0.0",
|
|
45
|
-
port: config.port || 9000,
|
|
46
|
-
https: config.https || false,
|
|
47
|
-
region: config.region || "default",
|
|
48
|
-
accessKeyId: config.accessKeyId || "accessKey1",
|
|
49
|
-
secretAccessKey: config.secretAccessKey || "verySecretKey1",
|
|
50
|
-
displayName: config.displayName || "S3 User",
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
this.driver = driver;
|
|
54
|
-
this.app = express();
|
|
55
|
-
this.server = null;
|
|
56
|
-
|
|
57
|
-
// 初始化处理程序
|
|
58
|
-
this.handlers = {
|
|
59
|
-
listBuckets: new ListBuckets(this),
|
|
60
|
-
listObjects: new ListObjects(this),
|
|
61
|
-
getObject: new GetObject(this),
|
|
62
|
-
putObject: new PutObject(this),
|
|
63
|
-
deleteObject: new DeleteObject(this),
|
|
64
|
-
deleteObjects: new DeleteObjects(this),
|
|
65
|
-
headObject: new HeadObject(this),
|
|
66
|
-
headBucket: new HeadBucket(this),
|
|
67
|
-
createBucket: new CreateBucket(this),
|
|
68
|
-
deleteBucket: new DeleteBucket(this),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// 配置中间件
|
|
72
|
-
this.setupMiddleware();
|
|
73
|
-
|
|
74
|
-
// 配置路由
|
|
75
|
-
this.setupRoutes();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 配置Express中间件
|
|
80
|
-
*/
|
|
81
|
-
setupMiddleware() {
|
|
82
|
-
// 解析请求体
|
|
83
|
-
this.app.use(
|
|
84
|
-
bodyParser.raw({
|
|
85
|
-
type: "*/*",
|
|
86
|
-
limit: "10gb",
|
|
87
|
-
}),
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// 身份验证中间件
|
|
91
|
-
this.app.use((req, res, next) => {
|
|
92
|
-
// 创建请求上下文
|
|
93
|
-
const ctx = createContext(req);
|
|
94
|
-
req.s3Context = ctx;
|
|
95
|
-
|
|
96
|
-
// 检查认证信息
|
|
97
|
-
if (this.config.accessKeyId && this.config.secretAccessKey) {
|
|
98
|
-
const { accessKeyId, secretAccessKey } = ctx.auth;
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
accessKeyId !== this.config.accessKeyId ||
|
|
102
|
-
secretAccessKey !== this.config.secretAccessKey
|
|
103
|
-
) {
|
|
104
|
-
res.status(403).send({
|
|
105
|
-
Error: {
|
|
106
|
-
Code: "AccessDenied",
|
|
107
|
-
Message: "访问被拒绝",
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
next();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// 请求日志中间件
|
|
118
|
-
this.app.use((req, res, next) => {
|
|
119
|
-
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
|
|
120
|
-
next();
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* 配置API路由
|
|
126
|
-
*/
|
|
127
|
-
setupRoutes() {
|
|
128
|
-
// 根路径 - 列出所有存储桶
|
|
129
|
-
this.app.get("/", (req, res) => {
|
|
130
|
-
this.handlers.listBuckets.handle(req, res);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// 存储桶操作
|
|
134
|
-
this.app.head("/:bucket", (req, res) => {
|
|
135
|
-
this.handlers.headBucket.handle(req, res);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
this.app.get("/:bucket", (req, res) => {
|
|
139
|
-
this.handlers.listObjects.handle(req, res);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
this.app.put("/:bucket", (req, res) => {
|
|
143
|
-
this.handlers.createBucket.handle(req, res);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
this.app.delete("/:bucket", (req, res) => {
|
|
147
|
-
this.handlers.deleteBucket.handle(req, res);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// 对象操作
|
|
151
|
-
this.app.head("/:bucket/*", (req, res) => {
|
|
152
|
-
this.handlers.headObject.handle(req, res);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
this.app.get("/:bucket/*", (req, res) => {
|
|
156
|
-
this.handlers.getObject.handle(req, res);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
this.app.put("/:bucket/*", (req, res) => {
|
|
160
|
-
this.handlers.putObject.handle(req, res);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
this.app.delete("/:bucket/*", (req, res) => {
|
|
164
|
-
this.handlers.deleteObject.handle(req, res);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// 批量删除对象
|
|
168
|
-
this.app.post("/:bucket", (req, res) => {
|
|
169
|
-
if (req.query.delete !== undefined) {
|
|
170
|
-
this.handlers.deleteObjects.handle(req, res);
|
|
171
|
-
} else {
|
|
172
|
-
res.status(400).send({
|
|
173
|
-
Error: {
|
|
174
|
-
Code: "InvalidRequest",
|
|
175
|
-
Message: "无效的请求",
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// 404处理
|
|
182
|
-
this.app.use((req, res) => {
|
|
183
|
-
res.status(404).send({
|
|
184
|
-
Error: {
|
|
185
|
-
Code: "NotFound",
|
|
186
|
-
Message: "请求的资源不存在",
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// 错误处理
|
|
192
|
-
this.app.use((err, req, res, next) => {
|
|
193
|
-
console.error("Server error:", err);
|
|
194
|
-
res.status(500).send({
|
|
195
|
-
Error: {
|
|
196
|
-
Code: "InternalError",
|
|
197
|
-
Message: "服务器内部错误",
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 启动服务器
|
|
205
|
-
* @returns {Promise<void>}
|
|
206
|
-
*/
|
|
207
|
-
async start() {
|
|
208
|
-
return new Promise((resolve, reject) => {
|
|
209
|
-
try {
|
|
210
|
-
// 创建HTTP或HTTPS服务器
|
|
211
|
-
if (this.config.https) {
|
|
212
|
-
// 在实际环境中,应该提供有效的证书
|
|
213
|
-
const options = {
|
|
214
|
-
key: "...", // 私钥
|
|
215
|
-
cert: "...", // 证书
|
|
216
|
-
};
|
|
217
|
-
this.server = https.createServer(options, this.app);
|
|
218
|
-
} else {
|
|
219
|
-
this.server = http.createServer(this.app);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 启动服务器
|
|
223
|
-
this.server.listen(this.config.port, this.config.hostname, () => {
|
|
224
|
-
console.log(
|
|
225
|
-
`S3服务器已启动: ${this.config.https ? "https" : "http"}://${this.config.hostname}:${this.config.port}`,
|
|
226
|
-
);
|
|
227
|
-
resolve();
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// 错误处理
|
|
231
|
-
this.server.on("error", (err) => {
|
|
232
|
-
console.error("服务器启动失败:", err);
|
|
233
|
-
reject(err);
|
|
234
|
-
});
|
|
235
|
-
} catch (error) {
|
|
236
|
-
console.error("启动服务器时发生错误:", error);
|
|
237
|
-
reject(error);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 停止服务器
|
|
244
|
-
* @returns {Promise<void>}
|
|
245
|
-
*/
|
|
246
|
-
async stop() {
|
|
247
|
-
return new Promise((resolve, reject) => {
|
|
248
|
-
if (!this.server) {
|
|
249
|
-
resolve();
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
this.server.close((err) => {
|
|
254
|
-
if (err) {
|
|
255
|
-
console.error("停止服务器时发生错误:", err);
|
|
256
|
-
reject(err);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
console.log("S3服务器已停止");
|
|
261
|
-
this.server = null;
|
|
262
|
-
resolve();
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
module.exports = S3Server;
|