@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.
Files changed (3) hide show
  1. package/README.md +414 -0
  2. package/httpm.js +2188 -0
  3. 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