@lzpong/httpm 1.0.0
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/README.md +414 -0
- package/httpm.js +2188 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# httpm
|
|
2
|
+
|
|
3
|
+
基于 Node.js 原生模块的**单文件、零依赖** HTTP 服务库,兼容 Express API。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **单文件架构** — 所有代码整合至 `httpm.js`,拷贝即用
|
|
8
|
+
- **零第三方依赖** — 仅使用 Node.js 内置模块(`http`、`https`、`fs`、`path`、`crypto`、`zlib`)
|
|
9
|
+
- **Express 兼容** — 路由、中间件、请求/响应 API 对齐 Express 语法
|
|
10
|
+
- **静态文件服务** — Range 断点续传、ETag/Last-Modified 缓存、Gzip 压缩
|
|
11
|
+
- **WebSocket** — 路径分组、心跳保活、广播、文本/二进制子事件
|
|
12
|
+
- **SSE** — 服务端推送事件,支持 event/data/retry/comment
|
|
13
|
+
- **流式文件上传** — multipart/form-data 解析,内存零占用,临时文件自动清理
|
|
14
|
+
- **日志系统** — 彩色控制台输出 + 文件持久化,按级别过滤
|
|
15
|
+
- **Cookie 签名** — HMAC-SHA256 签名与验证
|
|
16
|
+
- **配置管理** — 默认配置 → app.json → 代码参数 → 运行时 app.set()
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install httpm
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
或直接拷贝 `httpm.js` 到项目中:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
const httpm = require('./httpm');
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 快速开始
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
const httpm = require('httpm');
|
|
34
|
+
const app = httpm({ rootPath: './public', svrPort: 3000 });
|
|
35
|
+
|
|
36
|
+
// 全局中间件
|
|
37
|
+
app.use((req, res, next) => {
|
|
38
|
+
console.log(req.method, req.path);
|
|
39
|
+
next();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 路由
|
|
43
|
+
app.get('/api/hello', (req, res) => {
|
|
44
|
+
res.json({ message: 'Hello, httpm!' });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 动态路由
|
|
48
|
+
app.get('/api/users/:id', (req, res) => {
|
|
49
|
+
res.json({ id: req.params.id });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
app.listen(3000, () => console.log('Server running on port 3000'));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 路由
|
|
56
|
+
|
|
57
|
+
支持 GET、POST、PUT、DELETE、PATCH、ALL 方法:
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
app.get('/users', (req, res) => res.json([]));
|
|
61
|
+
app.post('/users', (req, res) => res.json(req.body));
|
|
62
|
+
app.put('/users/:id', (req, res) => res.json({ id: req.params.id }));
|
|
63
|
+
app.delete('/users/:id', (req, res) => res.json({ deleted: true }));
|
|
64
|
+
app.all('/any', (req, res) => res.json({ method: req.method }));
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
路由匹配优先级:精准静态路由 > 动态参数路由 > ALL 通用路由 > 静态文件服务。
|
|
68
|
+
|
|
69
|
+
路由处理器返回 `false` 时,请求进入静态文件兜底:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
app.get('/api/users/:id', (req, res) => {
|
|
73
|
+
const user = findUser(req.params.id);
|
|
74
|
+
if (!user) return false; // 跳转至静态文件服务
|
|
75
|
+
res.json(user);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 中间件
|
|
80
|
+
|
|
81
|
+
### 应用级中间件
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
app.use((req, res, next) => {
|
|
85
|
+
console.log(`${req.method} ${req.path}`);
|
|
86
|
+
next();
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 路径级中间件
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
app.use('/api', (req, res, next) => {
|
|
94
|
+
// 仅匹配 /api 及其子路径
|
|
95
|
+
next();
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 错误处理中间件
|
|
100
|
+
|
|
101
|
+
4 个参数的函数自动识别为错误处理中间件:
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
app.use((err, req, res, next) => {
|
|
105
|
+
console.error(err);
|
|
106
|
+
res.status(500).json({ error: err.message });
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 请求对象 (req)
|
|
111
|
+
|
|
112
|
+
| 属性/方法 | 说明 |
|
|
113
|
+
|-----------|------|
|
|
114
|
+
| `req.method` | 请求方法 |
|
|
115
|
+
| `req.path` | 请求路径 |
|
|
116
|
+
| `req.url` | 完整请求 URL |
|
|
117
|
+
| `req.query` | Query 参数对象 |
|
|
118
|
+
| `req.params` | 路由参数对象 |
|
|
119
|
+
| `req.body` | 请求体(JSON/URL-encoded 自动解析) |
|
|
120
|
+
| `req.formData` | 表单数据(multipart 解析后) |
|
|
121
|
+
| `req.cookies` | Cookie 对象 |
|
|
122
|
+
| `req.headers` | 请求头对象 |
|
|
123
|
+
| `req.ip` | 客户端 IP |
|
|
124
|
+
| `req.protocol` | 协议(http/https) |
|
|
125
|
+
| `req.get(name)` | 获取请求头 |
|
|
126
|
+
|
|
127
|
+
## 响应对象 (res)
|
|
128
|
+
|
|
129
|
+
| 方法 | 说明 |
|
|
130
|
+
|------|------|
|
|
131
|
+
| `res.status(code)` | 设置状态码,链式调用 |
|
|
132
|
+
| `res.json(obj)` | 发送 JSON 响应 |
|
|
133
|
+
| `res.send(data)` | 发送响应(自动识别类型) |
|
|
134
|
+
| `res.sendFile(path, opts)` | 发送文件 |
|
|
135
|
+
| `res.download(path, name)` | 下载文件 |
|
|
136
|
+
| `res.redirect(url)` | 重定向(默认 302) |
|
|
137
|
+
| `res.redirect(code, url)` | 重定向(指定状态码) |
|
|
138
|
+
| `res.cookie(name, value, opts)` | 设置 Cookie |
|
|
139
|
+
| `res.clearCookie(name, opts)` | 清除 Cookie |
|
|
140
|
+
| `res.set(name, value)` / `res.setHeader()` | 设置响应头 |
|
|
141
|
+
| `res.get(name)` / `res.getHeader()` | 获取响应头 |
|
|
142
|
+
| `res.type(type)` | 设置 Content-Type |
|
|
143
|
+
| `res.sse()` | 创建 SSE 实例 |
|
|
144
|
+
|
|
145
|
+
### Cookie 签名
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
const app = httpm({ cookieParserSecret: 'your-secret' });
|
|
149
|
+
|
|
150
|
+
// 设置签名 Cookie
|
|
151
|
+
res.cookie('token', 'abc123', { signed: true });
|
|
152
|
+
|
|
153
|
+
// 读取时自动验证签名
|
|
154
|
+
req.cookies.token; // 'abc123'(签名验证通过)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 静态文件服务
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
const app = httpm({ rootPath: './public', showDir: true, enableGzip: true, enableCache: true });
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
或使用内置中间件:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
app.use(httpm.static('./public'));
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| 配置项 | 默认值 | 说明 |
|
|
170
|
+
|--------|--------|------|
|
|
171
|
+
| `rootPath` | `process.cwd()` | 静态文件根目录 |
|
|
172
|
+
| `showDir` | `false` | 是否显示目录列表 |
|
|
173
|
+
| `enableGzip` | `false` | 启用 Gzip 压缩 |
|
|
174
|
+
| `enableCache` | `false` | 启用 ETag/Last-Modified 缓存 |
|
|
175
|
+
| `enableRange` | `true` | 启用 Range 断点续传 |
|
|
176
|
+
| `cacheControl` | `'public, max-age=3600'` | Cache-Control 头值 |
|
|
177
|
+
|
|
178
|
+
## 文件上传
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
app.post('/upload', (req, res) => {
|
|
182
|
+
const { fields, files } = req.formData;
|
|
183
|
+
console.log(fields); // { field1: 'value1' }
|
|
184
|
+
console.log(files); // [{ originalname, path, size, mimetype }]
|
|
185
|
+
res.json({ success: true });
|
|
186
|
+
// 临时文件在响应结束后自动清理
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
| 配置项 | 默认值 | 说明 |
|
|
191
|
+
|--------|--------|------|
|
|
192
|
+
| `tempDir` | `'tempupdir'` | 临时文件目录 |
|
|
193
|
+
| `maxFileSize` | `128MB` | 单文件大小限制 |
|
|
194
|
+
| `maxFieldSize` | `1MB` | 表单字段大小限制 |
|
|
195
|
+
|
|
196
|
+
## WebSocket
|
|
197
|
+
|
|
198
|
+
### 简化注册
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
app.ws('/chat', (ws, req) => {
|
|
202
|
+
ws.send('Welcome!');
|
|
203
|
+
|
|
204
|
+
ws.on('text', msg => {
|
|
205
|
+
app.wsServer.broadcast('/chat', msg, ws.id);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
ws.on('binary', data => {
|
|
209
|
+
// 处理二进制数据
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
ws.on('close', () => {
|
|
213
|
+
console.log('Client disconnected');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### WebSocketServer API
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const wss = app.wsServer;
|
|
222
|
+
|
|
223
|
+
wss.broadcast('/chat', 'Hello everyone'); // 按路径分组广播
|
|
224
|
+
wss.broadcast('/chat', 'Hello', excludeWsId); // 排除指定连接
|
|
225
|
+
wss.getConnections(); // 获取所有连接
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### WebSocket 事件
|
|
229
|
+
|
|
230
|
+
| 事件 | 说明 |
|
|
231
|
+
|------|------|
|
|
232
|
+
| `message` | 接收消息(文本或二进制) |
|
|
233
|
+
| `text` | 接收文本消息 |
|
|
234
|
+
| `binary` | 接收二进制消息 |
|
|
235
|
+
| `close` | 连接关闭 |
|
|
236
|
+
| `error` | 连接错误 |
|
|
237
|
+
|
|
238
|
+
## SSE (Server-Sent Events)
|
|
239
|
+
|
|
240
|
+
### 简化注册
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
app.sse('/events', (sse, req) => {
|
|
244
|
+
const timer = setInterval(() => {
|
|
245
|
+
sse.event('time', new Date().toISOString());
|
|
246
|
+
}, 1000);
|
|
247
|
+
|
|
248
|
+
// 返回清理函数,连接关闭时自动执行
|
|
249
|
+
return () => clearInterval(timer);
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### SSE API
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
const sse = res.sse();
|
|
257
|
+
|
|
258
|
+
sse.send(data); // 发送 data 事件
|
|
259
|
+
sse.event(name, data); // 发送命名事件
|
|
260
|
+
sse.retry(milliseconds); // 设置重连间隔
|
|
261
|
+
sse.comment(text); // 发送注释(心跳保活)
|
|
262
|
+
sse.close(); // 关闭连接
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## 日志系统
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
const app = httpm({ logLevel: 'debug', logDir: './logs' });
|
|
269
|
+
|
|
270
|
+
// 使用 Application 内置 Logger
|
|
271
|
+
app._logger.info('Server started');
|
|
272
|
+
app._logger.error('Something went wrong', err);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 独立使用
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
const { Logger } = require('httpm');
|
|
279
|
+
|
|
280
|
+
const logger = new Logger({ level: 'debug', logDir: './logs', name: 'myapp' });
|
|
281
|
+
logger.debug('Debug message');
|
|
282
|
+
logger.info('Info message');
|
|
283
|
+
logger.notice('Notice message');
|
|
284
|
+
logger.warn('Warning message');
|
|
285
|
+
logger.error('Error message');
|
|
286
|
+
logger.fatal('Fatal message');
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
| 日志级别 | 颜色 | 说明 |
|
|
290
|
+
|----------|------|------|
|
|
291
|
+
| `debug` | 灰色 | 调试信息 |
|
|
292
|
+
| `info` | 白色 | 常规信息 |
|
|
293
|
+
| `notice` | 品红 | 通知信息 |
|
|
294
|
+
| `warn` | 黄色 | 警告信息 |
|
|
295
|
+
| `error` | 红色 | 错误信息 |
|
|
296
|
+
| `fatal` | 红色加粗 | 致命错误 |
|
|
297
|
+
|
|
298
|
+
日志文件路径格式:`./logDir/YYYY/MM/name_DD.log`,时间格式:`HH:MM:SS`。
|
|
299
|
+
|
|
300
|
+
## 配置管理
|
|
301
|
+
|
|
302
|
+
配置加载优先级(后者覆盖前者):
|
|
303
|
+
|
|
304
|
+
1. **默认配置** — 内置默认值
|
|
305
|
+
2. **app.json** — 项目根目录或模块目录下的配置文件
|
|
306
|
+
3. **代码初始化参数** — `httpm({ ... })` 传入的选项
|
|
307
|
+
4. **运行时 app.set()** — 动态设置
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
// app.json
|
|
311
|
+
{
|
|
312
|
+
"svrPort": 8080,
|
|
313
|
+
"enableGzip": true
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 代码参数覆盖 app.json
|
|
317
|
+
const app = httpm({ svrPort: 3000 }); // svrPort=3000, enableGzip=true(来自app.json)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 完整配置项
|
|
321
|
+
|
|
322
|
+
| 配置项 | 默认值 | 说明 |
|
|
323
|
+
|--------|--------|------|
|
|
324
|
+
| `rootPath` | `process.cwd()` | 静态文件根目录 |
|
|
325
|
+
| `tempDir` | `'tempupdir'` | 上传临时文件目录 |
|
|
326
|
+
| `maxFileSize` | `134217728` | 单文件大小限制(128MB) |
|
|
327
|
+
| `maxFieldSize` | `1048576` | 表单字段大小限制(1MB) |
|
|
328
|
+
| `svrPort` | `80` | 服务端口 |
|
|
329
|
+
| `svrIP` | `null` | 绑定 IP(null=所有接口) |
|
|
330
|
+
| `showDir` | `false` | 显示目录列表 |
|
|
331
|
+
| `enableCache` | `false` | 启用缓存 |
|
|
332
|
+
| `enableGzip` | `false` | 启用 Gzip 压缩 |
|
|
333
|
+
| `enableRange` | `true` | 启用断点续传 |
|
|
334
|
+
| `cacheControl` | `'public, max-age=3600'` | Cache-Control 头 |
|
|
335
|
+
| `timeout` | `120000` | 请求超时(ms) |
|
|
336
|
+
| `keepAliveTimeout` | `65000` | Keep-Alive 超时(ms) |
|
|
337
|
+
| `https` | `null` | HTTPS 配置(cert/key) |
|
|
338
|
+
| `http2` | `false` | 启用 HTTP/2 |
|
|
339
|
+
| `logLevel` | `'info'` | 日志级别 |
|
|
340
|
+
| `logDir` | `'./log'` | 日志文件目录 |
|
|
341
|
+
| `cors` | `{ origin: '*', ... }` | CORS 配置 |
|
|
342
|
+
| `useBodyParser` | `true` | 自动解析请求体 |
|
|
343
|
+
| `useCookieParser` | `true` | 自动解析 Cookie |
|
|
344
|
+
| `bodyParserOptions` | `{}` | bodyParser 选项 |
|
|
345
|
+
| `cookieParserSecret` | `null` | Cookie 签名密钥 |
|
|
346
|
+
|
|
347
|
+
## HTTPS / HTTP2
|
|
348
|
+
|
|
349
|
+
```javascript
|
|
350
|
+
// HTTPS
|
|
351
|
+
const app = httpm({
|
|
352
|
+
https: {
|
|
353
|
+
key: fs.readFileSync('server.key'),
|
|
354
|
+
cert: fs.readFileSync('server.crt')
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// HTTP2
|
|
359
|
+
const app = httpm({
|
|
360
|
+
http2: true,
|
|
361
|
+
https: {
|
|
362
|
+
key: fs.readFileSync('server.key'),
|
|
363
|
+
cert: fs.readFileSync('server.crt')
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## 导出接口
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
const httpm = require('httpm');
|
|
372
|
+
|
|
373
|
+
// 核心类
|
|
374
|
+
httpm.Application
|
|
375
|
+
httpm.Router
|
|
376
|
+
httpm.Request
|
|
377
|
+
httpm.Response
|
|
378
|
+
httpm.SSE
|
|
379
|
+
httpm.WebSocket
|
|
380
|
+
httpm.WebSocketServer
|
|
381
|
+
httpm.Logger
|
|
382
|
+
|
|
383
|
+
// 内置中间件
|
|
384
|
+
httpm.bodyParser
|
|
385
|
+
httpm.cookieParser
|
|
386
|
+
httpm.static
|
|
387
|
+
|
|
388
|
+
// 工具函数
|
|
389
|
+
httpm.parseUrl
|
|
390
|
+
httpm.parseQuery
|
|
391
|
+
httpm.parseCookies
|
|
392
|
+
httpm.getMimeType
|
|
393
|
+
httpm.fmtSize
|
|
394
|
+
httpm.fmtTime
|
|
395
|
+
httpm.isPathSafe
|
|
396
|
+
httpm.generateETag
|
|
397
|
+
httpm.parseRange
|
|
398
|
+
httpm.WebSocketHandShark
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## 运行要求
|
|
402
|
+
|
|
403
|
+
- Node.js >= 14.0.0
|
|
404
|
+
- 零第三方依赖
|
|
405
|
+
|
|
406
|
+
## 测试
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
npm test
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## License
|
|
413
|
+
|
|
414
|
+
MIT
|