http 响应报文中 ContentType 里的编码方式是浏览器对内容的解码方式,还是服务器对内容的编码方式

76 天前
 rookiemaster

在 springboot 项目里,我有如下设置:

server:
  servlet:
    encoding:
      charset: GBK
      force-response: true

然后 Controller 如下:

@RestController
public class TmpController {
    @GetMapping("/test")
    public Result test() throws UnsupportedEncodingException {
        String hello =  "你好";
        String newhello = new String( hello.getBytes("GBK") , "GBK");
        System.out.println(newhello);
        return Result.success(newhello);
    }
}

就是我想把一个字符串用 gbk 编码发给浏览器,然后让浏览器用 gbk 解码,但是最后浏览器显示的是乱码,不知该如何解决。

2080 次点击
所在节点    程序员
23 条回复
infun
76 天前
是服务器告诉浏览器可以用什么编码来处理
你看一下浏览器端拿到的信息是怎样的?
rookiemaster
76 天前
这样的 {"code":0,"message":"鎿嶄綔鎴愬姛","data":"锟斤拷锟�"}
然后 ContentType 是 application/json;charset=GBK
NessajCN
76 天前
@rookiemaster 你看到「锟斤拷」不就很明显了吗,
必然是你浏览器解析用的是 gbk, 而发上来的是 utf-8 字节。
并且这串 utf-8 同样是乱码,因为有占位符。
所以推测是你哪里多了一步 把 gbk 转成了 utf-8 发给了浏览器
Lax
76 天前
浏览器的网页部分本体是什么编码方式?
hfc
76 天前
hello="你好"这里的 hello 是 utf8 ,getBytes("GBK")这里就错了吧
chendy
76 天前
1. 谁发的 Content-Type 就是谁的内容格式
2. Content-Type 里带 charset **貌似**不是一种标准做法(但是是一种惯例做法)
3. JSON **貌似**有规定自己必须是 UTF8
4. 2024 年了,没有特殊需求忘了 GBK 吧
jinliming2
76 天前
content-type 里指定的编码,是浏览器解码的。
你的问题是 hello.getBytes 的时候得到了 GBK 编码的字符串,但是 new String 又给还原成了 UTF-16 ,最终把还原的 String 丢给 Result ,浏览器拿到的数据是 UTF-8 编码的,但是 content-type 指定用 GBK 来解,就乱码了哇。
Java 不熟,你可能需要看看那个 Result.success 最终能不能用 bytes 来返回,直接 getBytes 之后丢给浏览器
jinliming2
76 天前
@jinliming2 hello.getBytes 的时候得到了 GBK 编码的 byte[]
ShinichiYao
76 天前
锟斤拷,恩恩好熟悉的内容
rookiemaster
76 天前
@jinliming2
我把 Controller 换成这样,浏览器就正常了
@RestController
public class TmpController {
// @GetMapping(value = "/test",produces = "application/json")
@GetMapping(value = "/test",produces = "application/json;charset=GBK")
public String test() throws UnsupportedEncodingException {
String hello = "你好你好";
String newhello = new String( hello.getBytes("GBK") , "GBK");
return newhello;
}
}
rookiemaster
76 天前
@rookiemaster 然后返回 hello 而不是 newhello 也是正常的
yulgang
76 天前
烫烫烫
chinaguaiu
76 天前
先说结论:charset 字段不对 application/json 类型的媒体内容生效,无论你怎么设置 charset ,框架都只会使用 utf-8 对字符串进行编码。charset 字段只用于 text/* 类型的媒体内容生效,也就是文本内容; application/*类型的数据在规范上属于二进制数据,不应受 charset 制约(框架和浏览器会直接忽略 charset )。

如果一定要用 GBK 传输数据,不要给 Spring 框架返回 Collection 例如 Map 类型,而是直接返回 String 类型。无论是返回哪种类型都不需要你手动进行编码了,框架会自动处理的。

// @RestController 注解会自动将 map 转化为 json 并使用 utf-8 编码
// http 响应的媒体类型为 application/json
@GetMapping("/hello-json")
public Map sayHelloByJSON() {
Map map = new HashMap<String, String>();
map.put("你好", "世界");
return map;
}

// 按照指定的编码传输文本数据
// http 响应的媒体类型为 text/*,具体类型要看框架的处理
@GetMapping("/hello-gbk")
public String sayHelloByGbkString() {
return "你好,世界。";
}


-------
1. charset 字段对 text/*文本类型的影响,见: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
"例如,对于主类型为 text 的任何 MIME 类型,可以添加可选的 charset 参数,以指定数据中的字符所使用的字符集"

2. JSON 文件(文本)应该使用 applicaiton/json 媒体类型进行描述,见: https://www.iana.org/assignments/media-types/media-types.xhtml 。使用 text/*类型描述 json 文本被认为是违反规范的。

3. applicaiton/json 类型的数据不应受 charset 字段影响,见: https://datatracker.ietf.org/doc/html/rfc6838#section-4.2.1
该段有提到两点:
( 1 ). 包含“payload”的文本类型不应该使用 charset 字段,而应该由本身的规范指定(例如 xml 文件内部有编码集指定字段, 而 JSON 文件唯一指定为 utf-8 , 见 https://www.rfc-editor.org/rfc/rfc8259.html ),它们不应该受 charset 字段影响(直接忽略 charset,无论 charset 是否存在)
( 2 ). 如果一定要使用一种默认编码,使用“UTF-8”。
application/json 数据其实算是二进制数据,但是可以认为是上文所说"包含`payload`的文本类型".
------


嘛,应该挺多人对 json 数据胡乱进行处理的,乱码嘛,正常。
op 有兴趣的话可以看看这个讨论: https://github.com/libwww-perl/HTTP-Message/pull/90
讨论核心就是应该如何看待并处理 http 请求中的 json 类型"文本"(从浏览器和框架的角度)。对本问有一定的参考意义。
bestie
76 天前
chinaguaiu
76 天前
@chendy 我钻牛角尖研究过,

对于 2:是否带上 charset 字段已经成了历史遗留问题,大部分浏览器的做法是符合规范的话就适用 charset 字段指定的编码,如果不符合规范(例如说对图片类型数据也指定了 charset),就忽略(无论有没有填上 charset 字段)。见 https://datatracker.ietf.org/doc/html/rfc6657#page-3 , 该建议标准提到应尽量弱化媒体类型(尤其是 text/*类型)对 charset 字段的依赖。

对于 3:JSON 文件强制使用 UTF-8 编码。见 https://www.rfc-editor.org/rfc/rfc8259.html#section-8.1 ,"JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8 [RFC3629]."
nothingistrue
76 天前
浏览器对内容的解码方式,服务器对内容的编码方式,这不是必须配对的吗?

至于你为啥是乱码嘛,是声明的编码跟实际使用的编码不一致。默认情况下,@RestController 的结果 JSON 序列化编码是 UTF-8 。你实际使用的是 UTF-8 编码,但声明的是 GBK 编码,乱套了。你后来通过 @GetMapping 调整了编码为 GBK ,这才对上。
nothingistrue
76 天前
String newhello = new String( hello.getBytes("GBK") , "GBK");

这段代码是拖裤子放屁。存储和对外传输的时候才涉及编码,变量的值是不涉及编码的。不管你将其跟 byte[] 转换多少次,newhello 都是 String 类型对象变量,最后序列化成 JSON 的时候,它都只是一个没有编码的字符串。
rookiemaster
76 天前
@chinaguaiu 你好,谢谢你的解答,很有帮助。我还想问问当返回的是 String 类型的时候,怎么指定框架的编码方式,就是您的第二段代码,我设置 charset=utf-8 或者 gbk 的时候都能正确显示,但是 utf-32 的时候出现了乱码,是因为浏览器不支持 utf-32 吗
rookiemaster
76 天前
@nothingistrue 谢谢指出,我也发现了
nothingistrue
76 天前
Request 是强制由框架统一解码,但 Response 是默认由框架自动编码,接口代码完全可以忽略框架自行编码。故,尽量不要用 force-response 来全局配置 Response 的编码,因为它不具备强制约束性。

另,约定优先与配置,JSON 就老实用 UTF-8 。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1017487

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX