授人以鱼不如授人以渔:记录一次使用 ABAP 调用 OData
最近我的知识星球里,一位朋友向我求助。
他用 ABAP 的 cl_http_client 调用 OData 服务,通过 authenticate 方法,维护了正确的用户名和 password,如下图所示。

但是发送 HTTP 请求之后,仍然从服务器端收到了 HTTP 401 Unauthorized 的回应,让他觉得百思不得其解。
因为他通过同样的用户名和 password,在浏览器和 Postman 里都是可以正常访问 OData 服务的。
这位朋友按照我的文章保姆级教程:ABAP 通过 HTTP POST 调用 OData 服务创建业务数据的具体例子,已经在 Postman 里跑通了业务流程。
在完成 ABAP 代码编写后测试的时候,遇到了这个 401 错误。

我给他建议的分析思路是,HTTP 401 Unauthorized 只是一个表象,想知道更多的细节,可以查看 ICM 的通信细节。
SAP ABAP 服务器上的 ICM(Internet Communication Manager)是一个关键组件,负责处理 SAP 系统与外部网络的通信。ICM 允许 SAP 系统通过多种互联网协议(如 HTTP、HTTPS 和 SMTP)与外部世界进行通信,起到了网关的作用,管理着 SAP 系统内部和外部之间的数据流。
通过 ICM,SAP 系统可以处理不同类型的互联网请求,如网页浏览器的 HTTP 请求、Web 服务调用,或者通过 SMTP 发送电子邮件。ICM 通过支持各种协议,实现了 SAP 系统的跨平台交互功能。
下图可以看出 ICM 内部维护了一个线程池(Thread Pool), 和一个专有的 Thread Control 线程。后者负责接收传入的 TCP/IP 请求,并从线程池中创建(或唤醒)工作线程,来实现请求的并发处理。
线程池里的工作线程,负责完成网络 I/O 处理,以及对各种支持协议(HTTPS、SMTP,WebSocket 等等)的请求执行。

图片出处:https://help.sap.com/doc/saphelp_em900/9.0/en-US/48/039d48c0070c84e10000000a42189c/frameset.htm
ABAP 应用服务器上可以通过事务码 SMICM 进行管理。选择菜单 Goto -> Trace Level -> Set, 可以设置 Trace 级别。

因为需要分析问题,可以将 Trace Level 调整到最高级,获得 Trace 文件后别忘记把级别再降下来。

设置 Trace Level 之后,重新运行一次会引发 401 Unauthorized 错误的 ABAP 程序,然后选择 Display All,显示 Trace 文件并下载到本地。

打开 Trace 文件,通过 401 作为关键字进行搜索,定位到出错的位置后,向上查看。这种思路有点像 ST22 事务码里看到了抛出异常的那一行代码之后,查看调用栈来获得更多的上下文信息。

在抛出 401 错误上面不远处,找到了 SSSLRC_CONN_CLOSED 这个异常,还有 peer has closed connection 的文字描述。
使用关键字 SSSLRC_CONN_CLOSED 在 Google 搜索,前两条记录都和 SSL handshake 即客户端和服务器端建立连接时的错误相关,也和我们正在分析的这个例子强相关。
首先需要简要了解什么是 SSL Handshake?
SSL Handshake 是 SSL/TLS(Transport Layer Security)协议中的一个关键过程,它在客户端和服务器之间建立安全通信通道时发生。这个过程确保双方能够在不被第三方窃听的情况下交换加密密钥,从而保护数据的机密性和完整性。
既然叫 Handshake,必然涉及到双方互相问候,Say Hello.
客户端问候 (ClientHello)
客户端向服务器发送一个 ClientHello 消息。这条消息包含客户端支持的 SSL/TLS 版本、支持的加密算法(cipher suites)、客户端生成的随机数,以及客户端可以接受的压缩方法等信息。
这一阶段的目的是让服务器了解客户端的能力,并为后续的加密协商提供初始信息。
Trace 文件里观察到的 ClientHello,ABAP 客户端告诉服务器,自己使用的 TLS 版本号为 1.2

服务器回应 (ServerHello)
服务器收到 ClientHello 消息后,回复一个 ServerHello 消息。这条消息包含服务器选择的 SSL/TLS 版本、加密算法、服务器生成的随机数,以及其他必要的协商参数。
服务器还会在这一步发送其数字证书(通常是 X.509 格式),该证书包含服务器的公钥以及用于验证服务器身份的信息。
下图是 Trace 文件里观察到的服务器回应的 ServerHello,里面包含了服务器确认的 TLS 版本号 1.2,以及服务器端的数字证书:

回到我们通过关键字 SSSLRC_CONN_CLOSED 搜索到的 Google 结果网页,里面包含了详细介绍 ABAP 服务器 SSL 连接相关配置的 SAP Note.

这些配置算是传统 ABAP Basis 的工作,我也从中学到了很多。
The intended purpose of ICM "SSL client (Standard)" PSE is for communication with other (organization-internal) SAP Systems and other servers of the same SAP system (including itself), using an SSL/TLS client certificate for authentication. Unsolicited SSL/TLS client certificates are prohibited in SSL/TLS. An SSL/TLS client certificate can only be used on a connection when the server explicitly requests it during the SSL/TLS handshake (which for icm is configured through icm/HTTPS/verify_client=1/2 or the service-specific VCLIENT=1/2 setting).
大意是,当 ABAP 服务器的 ICM 参数,icm/HTTPS/verify_client 设置为 1 或者 2 时,客户端和 ABAP 服务器进行 SSL HandShake 时,服务器还会要求客户端出示其 Client Certificate.

事务码 SMICM 里,通过 Parameters -> Display 查看 ICM 的配置参数。

icm/https/verify_client = 1, 意思是 SSL Handshake 时,客户端需要向服务器端提供自己的 Certificate:

至此错误定位的思路就清晰了。
两个检查方向:
- 检查 ABAP 客户端事务码 STRUST 里的 Certificate 是否正确安装。

- 确保 ABAP 代码中,CL_HTTP_CLIENT 根据 STRUST 事务码里配置的 Certificate profile,通过下图输入参数 ssl_id 传递了正确的 Profile 名称:ANONYM 或者 DEFAULT.

关于 ABAP 事务码 STRUST 的更多介绍,也可以参阅我的学习笔记:
我的学习笔记 (3) - ABAP STRUST 事务码和背后的基础知识
按照这两点进行排查后,HTTP 401 Unauthorized 错误消失,收到了期望的 HTTP 200 OK 状态码。
欢迎大家加入我的知识星球,一起交流 SAP 技术和业务。通过这些交流,我也能从中学到很多新东西。