npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nova-http

v0.1.1

Published

零依赖、高性能、全链路可控的 Node.js HTTP 框架

Downloads

230

Readme

Nova

基于 net 模块的高性能轻量 Node.js Web 框架。
全链路可控 · TypeScript 原生 · Express 风格 API


目录


特性

| 特性 | 说明 | | ------ | ------ | | 零依赖 | 生产环境零第三方依赖,仅使用 Node.js 内置模块 | | 内置 HTTP 解析 | 基于 net 模块 + 9 状态机,全链路可控 | | TypeScript 原生 | 源码即 TypeScript,完整类型导出,无需 @types/nova-http | | Radix Tree 路由 | O(k) 路由查找(k=路径分段数),支持 :param* 通配符 | | Keep-Alive 多路复用 | 单 TCP 连接处理多请求,支持流水线,内置 Slowloris 防御 | | 全链路钩子 | 10 个生命周期钩子,支持异步,覆盖 连接→解析→路由→响应→断开 全链路 | | 内置中间件 | bodyParser(JSON/urlencoded)、staticFiles(ETag/Range/流式) | | 流式响应 | sendFile() 支持 HTTP Range 206、ETag 缓存、背压(drain)感知 | | Express 兼容风格 | app.get/post/use/route(),中间件签名 (req, res, next) |


快速开始

import { createApp, bodyParser } from 'nova-http';

const app = createApp();

app.use(bodyParser());

app.get('/', (_req, res) => {
  res.json({ hello: 'Nova!' });
});

app.get('/hello/:name', (req, res) => {
  res.json({ greeting: `你好,${req.params.name}!` });
});

app.listen(3000, '0.0.0.0', () => {
  console.log('服务已在 http://localhost:3000 启动');
});

安装

依赖要求:

  • Node.js >= 18.0.0
  • TypeScript >= 5.0(dev)
# npm
npm install nova-http

# 本地开发
npm install
npm run build

核心概念

请求处理流程

TCP 连接到达
    │
    ▼
ConnectionHandler          ← 超时管理 / Keep-Alive / 背压
    │
    ▼
BufferReader               ← 滑动窗口 Buffer,零拷贝追加
    │
    ▼
HttpParser (9状态机)       ← 请求行 / 头部 / 定长/分块 Body
    │
    ▼
Nova._dispatch()           ← 全局中间件链
    │
    ▼
Router.find()              ← Radix Tree,O(k) 匹配
    │
    ▼
路由处理器 + 局部中间件
    │
    ▼
NovaResponse._flush()      ← socket.cork() 聚合写入
    │
    ▼
TCP 响应 / Keep-Alive 复用

HTTP 解析器状态机

IDLE
  └=> REQUEST_LINE    解析 "GET /path HTTP/1.1\r\n"
        └=> HEADERS   逐行解析请求头,检测 Content-Length / Transfer-Encoding
              └=> BODY_DETECT
                    ├=> BODY_FIXED     定长 Body(Content-Length)
                    ├=> CHUNK_SIZE     分块传输第一个 chunk-size 行
                    │     └=> CHUNK_DATA → CHUNK_SIZE(循环)
                    └=> DONE          请求解析完成,回调 Nova._dispatch()

API 参考

createApp

function createApp(config?: NovaConfig): Nova

NovaConfig 选项:

| 字段 | 类型 | 默认值 | 说明 | | ------ | ------ | -------- | ------ | | maxBodySize | number | 1048576 (1MB) | 请求体最大字节数,超出则返回 413 | | keepAliveTimeout | number | 65000 | Keep-Alive 空闲超时(毫秒) | | headersTimeout | number | 60000 | 接收完整请求头的超时(毫秒),防 Slowloris | | requestTimeout | number | 600000 | 单请求最大处理时间(毫秒) | | trustProxy | boolean | false | 信任 X-Forwarded-For 头,影响 req.ip |


路由注册

app.get(path, ...handlers)
app.post(path, ...handlers)
app.put(path, ...handlers)
app.patch(path, ...handlers)
app.delete(path, ...handlers)
app.head(path, ...handlers)
app.options(path, ...handlers)
app.all(path, ...handlers)   // 匹配所有 HTTP 方法

路径语法:

| 模式 | 示例 | 说明 | | ------ | ------ | ------ | | 静态路径 | /users/profile | 精确匹配 | | 参数路径 | /users/:id | 匹配单段,值存入 req.params.id | | 通配符 | /static/* | 匹配剩余所有路径段,值存入 req.params['*'] |

优先级: 静态 > 参数 > 通配符


链式路由

app.route('/users/:id')
  .get((req, res) => { /* 查询 */ })
  .put((req, res) => { /* 更新 */ })
  .delete((req, res) => { /* 删除 */ });

中间件

// 全局中间件
app.use(middleware)

// 路径前缀中间件
app.use('/api', middleware)

// 多个中间件
app.use('/api', authMiddleware(), logMiddleware())

中间件签名:

// 普通中间件
type Middleware = (req: NovaRequest, res: NovaResponse, next: NextFunction) => void | Promise<void>

// 错误处理中间件(4 个参数,必须放在所有普通中间件之后)
type ErrorMiddleware = (err: Error, req: NovaRequest, res: NovaResponse, next: NextFunction) => void | Promise<void>

示例:

// 请求日志
app.use((req, _res, next) => {
  console.log(`${req.method} ${req.pathname}`);
  next();
});

// 错误处理
app.use((err: Error, _req, res, _next) => {
  res.status(500).json({ error: err.message });
});

NovaRequest

| 属性 | 类型 | 说明 | | ------ | ------ | ------ | | method | HttpMethod | HTTP 方法,如 'GET' | | path | string | 原始路径字符串(含查询字符串) | | pathname | string | 不含查询字符串的路径 | | httpVersion | string | '1.0''1.1' | | headers | Map<string, string> | 请求头(键已小写化) | | body | Buffer | 原始请求体 Buffer | | bodyParsed | unknown | bodyParser() 解析后的结构化数据 | | params | Record<string, string> | 路径参数,如 { id: '42' } | | query | URLSearchParams | 查询字符串(惰性解析) | | cookies | Record<string, string> | Cookie 键值对(惰性解析) | | ip | string | 客户端 IP(trustProxy 时读 X-Forwarded-For) | | context | Record<string, unknown> | 中间件间共享的请求上下文 | | keepAlive | boolean | 是否为 Keep-Alive 连接 | | socket | net.Socket | 底层 TCP socket |


NovaResponse

// 状态码
res.status(404)

// 响应头
res.setHeader('X-Custom', 'value')
res.getHeader('content-type')
res.removeHeader('x-powered-by')

// 发送响应
res.send(data: string | Buffer)       // 自动推断 Content-Type
res.json(data: unknown)               // application/json
res.html(html: string)                // text/html
res.end()                             // 无 Body

// 重定向
res.redirect(location: string, status?: 301 | 302 | 307 | 308)

// 文件发送(支持 Range 206、ETag 缓存、流式传输)
await res.sendFile(absolutePath: string)

钩子系统

app.addHook(hookName, handler)
app.removeHook(hookName, handler)

可用钩子:

| 钩子名 | 触发时机 | Handler 签名 | | -------- | --------- | ------------- | | onConnect | TCP 连接建立 | (socket: net.Socket) => void | | onDisconnect | TCP 连接断开 | (socket: net.Socket) => void | | onRequest | HTTP 请求解析完成,进入中间件前 | (req: NovaRequest) => void | | onRoute | 路由匹配成功后 | (req: NovaRequest, match: RouteMatch) => void | | onBodyParsed | bodyParser() 完成解析后 | (req: NovaRequest) => void | | onResponse | 响应发送完成 | (req: NovaRequest, res: NovaResponse) => void | | onError | 中间件链抛出未捕获异常 | (err: Error, req: NovaRequest) => void | | onNotFound | 路由未命中 | (req: NovaRequest) => void | | onListen | 服务开始监听 | (info: { host: string; port: number }) => void | | onClose | 服务关闭 | () => void |

示例:

// 全链路耗时统计
app.addHook('onRequest', (req) => {
  req.context['_start'] = process.hrtime.bigint();
});

app.addHook('onResponse', (req) => {
  const ns = process.hrtime.bigint() - (req.context['_start'] as bigint);
  console.log(`${req.pathname} 耗时 ${Number(ns) / 1e6}ms`);
});

// 或直接使用内置插件
const timer = createRequestTimer();
app.addHook('onRequest', timer.onRequest);
app.addHook('onResponse', timer.onResponse);

内置中间件

bodyParser(options?)

解析 application/jsonapplication/x-www-form-urlencoded 请求体。

app.use(bodyParser({
  maxBodySize: 1 * 1024 * 1024,  // 1MB,默认与 app 配置相同
  strict: true,                   // JSON 根值必须是 object/array
}));

解析结果写入 req.bodyParsed

staticFiles(root, options?)

静态文件服务,支持 ETag、Range 206、Gzip(由 Content-Negotiation 决定)。

app.use('/static', staticFiles('./public', {
  dotfiles: 'ignore',   // 'ignore' | 'allow' | 'deny'
  maxAge: 3600,         // Cache-Control: max-age=3600(秒)
  index: 'index.html',  // 目录默认索引文件
}));

CLI 工具

# 创建最小化项目
npx nova-http create my-app

# 创建完整 API 项目
npx nova-http create my-api --template api

# 创建 JavaScript 项目
npx nova-http create my-app --lang js

# 强制覆盖已存在目录
npx nova-http create my-app --force

# initializer 入口
npx create-nova-http my-app

可用模板:

| 模板 | 描述 | | ------ | ------ | | minimal | 最小化 Hello World,适合快速体验 | | api | 完整 CRUD API + 路由/中间件/身份验证示例 |

本地前验:

npm run verify:create

这个脚本会验证:

  • 打包后的 CLI 是否仍可执行
  • nova-http create ...create-nova-http ... 两种入口是否都能生成项目
  • minimal / api 模板是否都能正确替换变量并通过本地 TypeScript 构建
  • 重复创建时不带 --force 会失败,带 --force 可覆盖

说明:运行时包名为 nova-http,对应 initializer 包名为 create-nova-http,因此用户侧命令应为 npm create nova-http


架构说明

nova/
├== src/
│   ├== core/
│   │   ├== BufferReader.ts       滑动窗口 TCP Buffer 读取器
│   │   ├== HttpParser.ts         9 状态机 HTTP/1.1 解析器
│   │   ├== NovaRequest.ts        请求对象(惰性属性)
│   │   ├== NovaResponse.ts       响应对象(直写 net.Socket)
│   │   ├== ConnectionHandler.ts  TCP 连接生命周期管理器
│   │   ├== Router.ts             Radix Tree 路由器
│   │   ├== MiddlewareChain.ts    异步中间件组合器
│   │   ├== Hooks.ts              EventEmitter 生命周期钩子
│   │   └== Nova.ts               主应用类
│   ├== middlewares/
│   │   ├== bodyParser.ts         请求体解析中间件
│   │   ├== staticFiles.ts        静态文件中间件
│   │   └== index.ts              中间件桶导出
│   └== index.ts                  包主入口(公共 API)
├== cli/
│   ├== nova.ts                   CLI 工具入口(nova-http create)
│   ├== create-nova.ts            initializer 入口(create-nova-http)
│   ├== shared.ts                 CLI 共享实现
│   └== templates/
│       ├== minimal/              最小化项目模板
│       └== api/                  完整 API 项目模板
├== package.json
├== tsconfig.json
└== tsconfig.build.json

关键设计决策

为什么使用 net 模块而非 http 模块?

Node.js http 模块基于 llhttp(C++ 解析器),无法从 JavaScript 层控制解析细节。net 模块提供原始 TCP 流,让 Nova 对以下层面拥有完整控制权:

  • 超时粒度:可区分 "接收头部超时"(防 Slowloris)和 "请求处理超时"
  • Keep-Alive 策略:自定义空闲超时、连接复用策略
  • 请求走私防御:主动检测 CL+TE 冲突,立即返回 400
  • 背压感知:直接监听 socket.drain 事件,流式传输时无内存堆积
  • 性能调优socket.cork()/uncork() 减少系统调用,TCP_NODELAY 消除 Nagle 延迟

性能调优指南

1. 调整连接超时

const app = createApp({
  headersTimeout: 30_000,    // 降低以更快丢弃慢连接
  keepAliveTimeout: 30_000,  // 根据客户端行为调整
  requestTimeout: 120_000,   // 接口最长处理时间
});

2. Body 大小限制

// 上传接口设较大限制
app.post('/upload', bodyParser({ maxBodySize: 50 * 1024 * 1024 }), handler);

// 默认限制 1MB 防止 OOM
const app = createApp({ maxBodySize: 1 * 1024 * 1024 });

3. 使用钩子而非中间件做观测

钩子为 fire-and-forget(非阻塞),中间件会阻塞请求链路。对于日志、指标采集,优先使用钩子:

// 推荐:用钩子做指标采集
app.addHook('onResponse', (req, res) => {
  metrics.record(req.method, req.pathname, res._statusCode);
});

// 谨慎:用中间件做阻塞式日志(会增加 P99 延迟)
app.use(async (req, res, next) => {
  await writeToLogFile(...);  // I/O 操作
  next();
});

4. 静态文件缓存策略

app.use(staticFiles('./public', {
  maxAge: 86400,   // 强缓存 1 天(生产环境)
  dotfiles: 'ignore',
}));

5. 流式大文件

app.get('/download/:file', async (req, res) => {
  // sendFile 自动处理 Range、ETag、drain 背压
  await res.sendFile(path.join(STORAGE_DIR, req.params['file']!));
});

扩展点文档

自定义中间件

import type { Middleware } from 'nova-http';

export function rateLimiter(maxRpm: number): Middleware {
  const counts = new Map<string, number>();

  setInterval(() => counts.clear(), 60_000);

  return (req, res, next) => {
    const ip = req.ip;
    const count = (counts.get(ip) ?? 0) + 1;
    counts.set(ip, count);

    if (count > maxRpm) {
      res.status(429).json({ error: '请求过于频繁,请稍后重试' });
      return;
    }

    next();
  };
}

插件模式

import type { Nova } from 'nova-http';

export function metricsPlugin(app: Nova): void {
  const counters = { total: 0, errors: 0 };

  app.addHook('onRequest', () => { counters.total++; });
  app.addHook('onError', () => { counters.errors++; });

  // 暴露指标端点
  app.get('/metrics', (_req, res) => {
    res.json(counters);
  });
}

// 使用
metricsPlugin(app);

子应用路由挂载

const usersApp = createApp();
usersApp.get('/', listUsers);
usersApp.post('/', createUser);
usersApp.get('/:id', getUser);

// 挂载到 /api/users
app.use('/api/users', usersApp);

许可证

MIT © Owl23007