outputstreamwriter 不应用于生成 http header,因 http header 必须严格使用 iso-8859-1 字节编码且仅限 ascii 字符(u+0000–u+007f),非 ascii 内容须按 rfc 5987 编码;正确做法是用 outputstream 直接写入 getbytes(standardcharsets.iso_8859_1) 转换的字节。

HTTP Header 编码规范不可绕过
HTTP 规范明确要求:header field values 只能包含 US-ASCII 字符;如需传输非 ASCII 字符(如中文、重音字母),必须使用 RFC 5987(或旧的 RFC 2231)定义的编码方式(如 filename*=UTF-8''%E6%96%87%E4%BB%B6.pdf),而不是直接用 Latin-1 编码字符。Latin-1 被选为“兜底安全编码”,是因为它的 0x00–0xFF 映射与单字节完全一致,不会出现截断或乱码,但它不是用来 encode 中文等 Unicode 字符的方案。
不要用 OutputStreamWriter 写 HTTP Header
常见错误是把 socket 输出流包装成 OutputStreamWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1")),然后用 write("Content-Type: text/html; charset=utf-8\r\n") —— 这看似指定了 Latin-1,但实际风险很大:
- 如果字符串含非 Latin-1 字符(如中文冒号、全角空格、emoji),Writer 会静默替换为 或抛出异常(取决于编码器策略)
- HTTP header 不允许 UTF-8 字节序列直接出现;必须确保所有 header 字符都在 \u0000–\u00FF 范围内
- 换行符 \r\n 必须是字节 0x0D 0x0A,而 Writer 可能因平台 line.separator 出错
正确做法:手动构造 header 字节,避免 Writer
生成 HTTP Header 应使用 OutputStream 直接写入字节,确保每个 header 行都是 ISO-8859-1 兼容的 ASCII 字符串:
- 只使用 ASCII 字符(U+0000–U+007F)构造 header 名和值(例如
Content-Type,text/plain; charset=utf-8) - 对需要国际化的内容(如带中文的
Content-Disposition),按 RFC 5987 编码:先 UTF-8 编码字节,再 percent-encode,加上filename*=utf-8''...格式 - 用
String.getBytes(StandardCharsets.ISO_8859_1)安全转换(该 Charset 对 \u0000–\u00FF 外的字符会抛出UnmappableCharacterException,便于及时发现错误) - 示例:
String header = "Content-Disposition: attachment; filename*=UTF-8''%E6%96%87%E4%BB%B6.txt\r\n"; outputStream.write(header.getBytes(StandardCharsets.ISO_8859_1));
如果必须用 Writer(极少数场景),需严格约束输入
仅当确认所有 header 字符均在 Latin-1 范围内时,才可谨慎使用:
- 创建时显式指定:
new OutputStreamWriter(out, StandardCharsets.ISO_8859_1) - 写入前校验字符串:
str.chars().allMatch(c -> c - 禁用自动 flush,手动写 \r\n(避免 Writer 换行符干扰)
- 仍推荐优先用 byte[] + OutputStream,更可控、更符合协议语义
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











