# node http `Content-Type: text/plain` 缺 charset，浏览器按 GBK 解码 → 中文乱码

> **日期**: 2026-05-14
> **类型**: HTTP / 编码陷阱
> **来源**: internal-app-platform PoC 演练，用户 macOS Chrome 打开 hello app 看到
> "🔄 PoC 演练 v10" 被显示为 "馃攧 PoC 婕旂粌 v10"

## 现象

PoC hello app 用 node http 简单返响应：
```js
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(`🔄 PoC 演练 v10 RESTORED — 访问 ${n} 次\n`);
```

`curl` 看是对的：
```
$ curl http://app/
🔄 PoC 演练 v10 RESTORED — 访问 26 次
```

**Chrome 浏览器**显示成：
```
馃攧 PoC 婕旂粌 v10 RESTORED 鈥� 璁块棶 26 娆�
```

## 根因

Node `res.writeHead` 不会自动给 `text/plain` 加 charset。响应头是裸的
`Content-Type: text/plain`。

curl 不做字符解码（按 byte 透传，终端按当前 locale `LANG=zh_CN.UTF-8` 渲染对了）。

**浏览器**遇到没声明 charset 的 text 响应会**猜**编码——基于内容字节分布、用户系统语言、
Accept-Language 等启发式。中文环境下 Chrome 倾向**先猜 GBK**，导致 UTF-8 字节
被错误地按 GBK 解码 → 经典 mojibake：
- `🔄` UTF-8 4-byte 序列 `F0 9F 94 84` → GBK 解为 `馃攧`
- `演练` UTF-8 `E6 BC 94 E7 BB 83` → GBK 解为 `婕旂粌`
- `—` U+2014 UTF-8 `E2 80 94` → GBK 解为 `鈥�`

## 解决

**Content-Type 必须显式声明 `; charset=utf-8`**：

```js
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });  // 即便 JSON 含 unicode 转义也建议加
```

Express / NestJS 大多数中间件默认会加 charset，**裸 node `http` 不会**——
PoC 员工写小工具用裸 http 是高频场景。

## 类比 / 适用面

- 任何裸 node http / Go net/http / Python http.server 等"原始"HTTP server
- text/plain / text/html / application/json 含中文 / emoji 时都要 charset
- 不只中文：日文、emoji、希腊字母、希伯来文等任何超 ASCII 内容都有此风险
- `curl` 通过不代表浏览器通过——CJK 用户的浏览器尤其爱猜 GBK / Shift-JIS

## 浏览器侧应急（用户已 hosts hack 了不想改代码时）

Chrome 没有"强制 UTF-8"的菜单选项（v55+ 移除了 encoding submenu）。三个临时变通：
1. F12 → Network → 选请求 → 右键 → Override response → 加 charset
2. 装"Charset" / "Switch encoding" 扩展
3. 直接 curl 看真实内容

**根本解还是 server 端加 charset**，浏览器侧 hack 留个人调试用。

## 状态

- ✅ PoC hello app 已修，触发 webhook 自动 deploy 后修复生效
- 经验：未来给 PoC 员工提供的"hello world template"模板里**默认带 charset**

## 模板片段（推荐给 PoC 员工的 hello）

```js
const http = require("http");
http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });  // ← 别忘 charset
  res.end("你好世界 🌏\n");
}).listen(process.env.PORT || 3000);
```
