写点什么

ABAP 调用第三方 API,遇到乱码该怎么办?

作者:Jerry Wang
  • 2022 年 1 月 15 日
  • 本文字数:1873 字

    阅读完需:约 6 分钟

ABAP 调用第三方 API,遇到乱码该怎么办?

之前有一个朋友在知乎上向我咨询过这个问题,我觉得很有代表性,所以专门用一篇文章来讲述一些相关知识点。


先看这位朋友遇到的具体问题。


用 Postman 调用第三方接口,里面的中文字符能够正常显示。



然而当用 ABAP 的 HTTP 工具类 CL_HTTP_CLIENT 的 response->get_data( ) 读取响应之后,发现里面的中文字符,例如 "访问成功" 是乱码:



首先明确一点,既然 Postman 能正确显示响应数据中的中文内容,说明 API provider 是不存在问题的,这个乱码问题出现在接收方,即 ABAP 代码的编程实现需要调整。


我们只要弄清楚出现乱码的原因,就能有的放矢进行修复了。


上个世纪 60 年代,美国制定了一套字符编码,定义了英文字符与二进制位之间的一一映射关系,称为 ASCII 码。将一个符号的图形显示,关联到其二进制存储位的这种行为,就称之为字符编码。ASCII 就是一种最简单的字符集和字符编码方式。


一个字节有 8 位,2 的 8 次方为 256,因此 1 个字节只能表示 256 种符号,而汉字的总数超过了 10 万个,显然无法用 1 个字节来存储。


除了大家熟悉的英文字符和汉字外,还有很多历史更悠久的文字,比如埃及象形文字:



以及周杰伦《爱在西元前》里提到的楔形文字:



有没有这样一种计算机编码方式,能够将这些稀奇古怪的符号都纳入其中呢?有,这就是 Unicode,正如其命名暗示的,Unicode 将世界各种语言的每个字符都分配了一个唯一的编码,以满足跨语言、跨平台的文本信息转换。


我们根据 Unicode 编码表,就能查到一个字符对应的 Unicode 编码,比如汉字 "汪"对应的 Unicode 编码为 00006C6A.



6C6A 的二进制表示为 0110 1100 0110 1010,需要两个字节进行存储。表示其他的符号,可能需要三个甚至四个字节存储。



另一方面,对于原本就存在于 ASCII 编码表中的英文字符,仅需 1 个字节就能存储。如果 Unicode 强制要求每个字符按照最大需要的存储空间,即 4 字节进行存储,显然对于英文字符来说,意味着极大的空间浪费。


因此,Unicode 仅仅定义字符到其编码的映射关系。而这些编码到底采取多少个字节进行存储,由 Unicode 具体的实现方式,比如 UTF-8,UTF16 等来决定。


UTF-8 是一种变长的编码方式,使用 1 到 4 个字节表示一个字符,符号不同,用于存储的字节长度也不同。比如 "汪" 的 UTF-8 码值为 E6B1AA,需要三个字节存储。


根据 SAP 帮助文档,ABAP 采用 UCS-2 编码方式,可以看成 UTF-16 的子集,因为 UCS-2 不支持 UTF-16 的 surrogates 区间内定义的一些特殊符号。



所谓 UTF-16,就是所有字符固定都用两个字节表示。


从下面这张表格能够看出,UTF-16 又分 UTF-16BE 和 UTF-16LE 两种实现方式。以汉字 "汪" 的 Unicode 编码值 6C6A 为例,如果 6C 存储在内存低位地址,6A 存储在内存高位地址,这就是 Big Endian 即大尾序(有时也译作大头,大端)存储方式,反之则为 Little Endian 即小尾序存储方式。



这两个名称来自英国讽刺寓言作家斯威夫特的《格列佛游记》。书中的小人国爆发了内战,战争起因竟然是人们争论吃鸡蛋时究竟应该从大头(Big Endian)一端敲开,还是从小头(Little Endian)敲开。


那么 ABAP 的 UCS-2(UTF-16 的子集), 到底是 BE 存储还是 LE 存储?一试便知。


在我的系统里,答案是 UTF-16LE.




另一种方式,直接检查系统类 CL_ABAP_CHAR_UTILITIES 的属性 ENDIAN. 在 Jerry 的系统里,该属性的值为 L,代表 Little Endian:



我们了解了这些知识,再来修复文章开头描述的乱码问题。


仔细观察 Postman 调用 API 的返回结果,发现还有一条重要信息:charset=GB18030,意思是 API 响应数据采取 GB18030 字符集编码。



汉字 "访" 的 GB18030 编码值为 B7C3,完全不等同于 UTF-16LE 中的编码值 BF8B.



如果我们在 ABAP 代码里,按照默认的 UTF-16LE 的方式去读取一个根据 GB18030 编码的符号,当然不会得到期望的结果。这种张冠李戴的解码方式见下图第 55 行的 get_cdata 方法,最后就会出现乱码。



正确的方式,采取第 57 行 get_data,返回一个 16 进制数据流,类型为 xstring:



在这个 16 进制数据流里,我们已经看到了汉字 "访" 和 "问" 对应的 GB18030 编码值。


剩下的事情就容易了,使用字符集 GB18030 对这段数据流进行解码。


我们首先打开数据库表 TCP00, 根据关键词 18030 查询表字段 CPCOMMENT:



得到 GB18030 对应的 SAP Code Page 为 8401:



在下面这段代码中,传入 8401,变量 lv_binary 存储的是 16 进制数据流,变量 lv_text 存放的就是基于 GB18030 的 API 响应内容:



可以看到乱码已经消失了,在 ABAP 程序里显示的内容已经和 Postman 里观察到的完全一致了。



希望本文介绍的这个例子,能对大家在 ABAP 里处理中文乱码问题有所启发,感谢阅读。

发布于: 刚刚阅读数: 2
用户头像

Jerry Wang

关注

个人微信公众号:汪子熙 2017.12.03 加入

SAP成都研究院开发专家,SAP社区导师,SAP中国技术大使。

评论

发布
暂无评论
ABAP 调用第三方 API,遇到乱码该怎么办?