ADT 协议技术笔记

August 28, 2026 · View on GitHub

本文档是 @nefevcore/abap-adt-protocol 客户端实现的事实依据,交叉验证自:

ADT 协议没有官方公开规范(见 SAP Community 讨论),社区以"抓包 + 客户端实现"为事实标准。原始调研材料保存在仓库 .research/ 目录。

1. 基础

  • 所有资源挂 /sap/bc/adt/ 前缀;对象 URI 命名一律小写
  • 子资源模式:<uri>/source/main(源码)、<uri>/source/main/versions(版本)、<uri>/includes/<段>(类段)、<uri>/transport
  • URI 有两种形态:对象 URI…/oo/classes/zcl_demo)与源码形态…/oo/classes/zcl_demo/source/main,读/搜索输出常见)。对象级端点(versions、lock、where-used、transport、删除等)只认对象形态,直接对源码形态拼接会得到 …/source/main/source/main/versions → 404;客户端所有对象级方法先经 objectBaseUri() 剥掉 /source/main 后缀再拼接
  • 旧系统(BASIS ≤ 7.40)仅 /sap/bc/adt/discovery,现代系统 /sap/bc/adt/core/discovery

认证

方式适用要点
Basicon-premAuthorization: Basic b64(user:pass) + X-SAP-Client 头或 ?sap-client= 参数;Set-Cookie 建立会话
JWT/OAuth2BTP ABAP CloudAuthorization: Bearer <jwt>,token 过期需刷新

CSRF 流程(所有写操作)

  1. GET /sap/bc/adt/core/discovery + 头 x-csrf-token: fetch(Accept application/atomsvc+xml
  2. 响应头取 x-csrf-token: <token>,同时 Set-Cookie
  3. 写请求带 x-csrf-token + Cookie
  4. 403(含 "CSRF")/ 401(写操作)→ 丢弃 token+cookie 重取重试一次

会话头

  • sap-adt-connection-id: <uuid> — 所有请求
  • x-sap-adt-sessiontype: stateful — 写链(锁定/激活/写入);7.40 旧系统上会导致锁进会话内存(423),需可关闭
  • sap-adt-request-id — 有状态会话中每请求唯一
  • sap-usercontext cookie 会被服务端按系统默认客户端回写,客户端应强制覆盖为请求 client

2. 关键端点

系统信息

方法路径媒体类型
GET/sap/bc/adt/core/discoveryAccept application/atomsvc+xml(CSRF fetch 端点)
GET/sap/bc/adt/discovery旧系统回退
GET/sap/bc/adt/core/http/systeminformation...core.http.systeminformation.v1+json
GET/sap/bc/adt/ato/settingsATO 设置(云环境)

对象 CRUD

方法路径媒体类型用途
POST/sap/bc/adt/oo/classes(等类型集合)CT application/vnd.sap.adt.oo.classes.v4+xml创建(?package=ZTEST?corrNr=
GET<对象URI>对应对象媒体类型读元数据(?version=active|inactive
GET<对象URI>/source/mainAccept text/plain读源码
PUT<对象URI>/source/main?lockHandle=<h>CT text/plain; charset=utf-8写源码(先锁定)
POST<对象URI>?_action=LOCK&accessMode=MODIFY.../lock.result锁定,响应 asx:abapLOCK_HANDLE/CORRNR
POST<对象URI>?_action=UNLOCK&lockHandle=<h>同上解锁
POST<对象URI>?_action=DELETE&deleteOption=deleteAndLocalVersions删除
GET/sap/bc/adt/repository/nodestructure...nodestructure.v1+xml包树/结构浏览
POST/sap/bc/adt/checkruns?reporters=abapCheckRunCT checkobjects+xml / Accept checkmessages+xml语法检查

激活

POST /sap/bc/adt/repository/activation?method=activate&preauditRequested=true
CT: application/vnd.sap.adt.activation+xml  Accept: application/xml
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
  <adtcore:objectReference adtcore:uri="..." adtcore:name="..."/>
</adtcore:objectReferences>

注意:激活错误在 HTTP 200 body 的 <chkl:messages><msg type="E"> 中。

ABAP Unit(异步)

  1. POST /sap/bc/adt/abapunit/runs(on-prem)/ /sap/bc/adt/api/abapunit/runs(BTP)— CT ...abapunit.run.v1+xml,body aunit:run + osl:objectSet
  2. GET .../runs/{runId}?withLongPolling=true — 轮询状态(status/completed
  3. GET .../results/{runId}JUnit XMLtestsuites/testsuite/testcase + failure/error/skipped

ABAP Unit(legacy 同步,BASIS < 7.5x,如 D01/NW 7.4x)

老后端没有异步 run API:POST /abapunit/runs 直接 404,只有同步的 testruns 服务 (与官方 Eclipse/VS Code ADT 客户端 com.sap.adt.abapunit bundle 的 AbapUnitRequestContentHandlerV1 走法一致,已在真实 D01 上验证):

  1. POST /sap/bc/adt/abapunit/testruns — CT/Accept 均为 application/xml, body aunit:runConfiguration(ns http://www.sap.com/adt/aunit)+ adtcore:objectReferences
  2. 响应体即结果:aunit:runResultprogramtestClasstestMethod, 方法无 aunit:alert = 通过;severity="critical|fatal" = 失败; title/元素文本为消息。无 runId、无需轮询。
  3. 对象无测试类时返回空 runResult(0 tests),不是错误。 客户端在 POST /abapunit/runs 404 时自动回退到该路径(见 client.runUnitTestsLegacy)。

ATC(异步)

  1. POST /sap/bc/adt/atc/runs(on-prem)/ /sap/bc/adt/api/atc/runs(BTP)— CT ...atc.run.parameters.v1+xml,body atc:runparameters + osl:objectSetcheckVariant 属性)
  2. GET .../runs/{runId} — 轮询(state / phases)
  3. GET .../results/{displayId}checkstyle XMLcheckstyle/file/error,severity/line/source)

传输

  • 列表:GET /sap/bc/adt/cts/transportrequests?user=...&type=... — Accept transportorganizertree.v1+xml
  • 详情:GET /sap/bc/adt/cts/transportrequests/{tr}transportorganizer.v1+xml
  • 动作:POST /sap/bc/adt/cts/transportrequests/{tr}/release(release/check/import)
  • 创建:POST /sap/bc/adt/cts/transportrequests(body tm:root tm:useraction="newrequest"
  • 旧系统(< 7.52):/sap/bc/cts/ 前缀

搜索

GET /sap/bc/adt/repository/informationsystem/search?operation=quickSearch&query=Z*&maxResults=25[&objectType=CLAS]
Accept: application/xml  →  <adtcore:objectReferences><adtcore:objectReference adtcore:name=... adtcore:uri=.../>

3. 错误格式

<exc:exception xmlns:exc="http://www.sap.com/adt/xml/exception" exc:type="...">
  <exc:message>...</exc:message>
  <exc:localizedMessage>...</exc:localizedMessage>
</exc:exception>
  • 403 还可能是锁冲突(ExceptionResourceNoAccess "currently editing")——此时不要清会话
  • 412 = ETag 过期(元数据 PUT 后源码需先 GET 刷新再 PUT)
  • 423 = 旧系统上 stateful 会话锁问题

4. 对象类型码(ADT registry)

对象类型码创建集合
CLAS/OC/sap/bc/adt/oo/classes
接口INTF/OI/sap/bc/adt/oo/interfaces
程序PROG/P/sap/bc/adt/programs/programs
includePROG/I
函数组/模块FUGR/F / FUGR/FF/sap/bc/adt/fugr
CDS 数据定义DDLS/DF/sap/bc/adt/ddls/sources
访问控制DCLS/DL/sap/bc/adt/dcls/sources
元数据扩展DDLX/EX/sap/bc/adt/ddlx/sources
行为定义BDEF/BDO/sap/bc/adt/bdef/sources
服务定义SRVD/SRV/sap/bc/adt/srvdef/sources
表/结构TABL/DT / STRU/DT`/sap/bc/adt/ddic/tables
消息类MSAG/N/sap/bc/adt/msgclass
DEVC/K/sap/bc/adt/packages

5. ABAP Cloud vs 经典差异

维度经典ABAP Cloud
认证BasicJWT(无 Basic)
discovery/discovery/core/discovery必须 /core/discovery
对象全部PROG 等经典对象不可用
单测/sap/bc/adt/abapunit/runs;老系统(< 7.5x,如 D01)仅 /sap/bc/adt/abapunit/testruns(同步,见 §3)/sap/bc/adt/api/abapunit/runs
ATC/sap/bc/adt/atc/runs/sap/bc/adt/api/atc/runs
传输传统 CTS软件组件 + release state 驱动

6. 实测补充(S/4HANA S4C 系统验证结论)

在真实 S/4HANA 系统(sap-system: S4C)上端到端验证后的经验值:

  • operation=quickSearchSourceoperation=objectSearch 可能返回 HTTP 500(搜索提供者受限时)。客户端会自动降级到 quickSearch 并通过 note 字段告知;此时全文搜索不可用、对象搜索仍可用。
  • /sap/bc/adt/repository/nodestructure 在加固系统上可能 405。包成员列表优先用搜索 + packageName 过滤(?query=*&packageName=ZPACK&maxResults=500),nodestructure 仅作回退。
  • 系统信息用 /sap/bc/adt/core/http/systeminformation(JSON:systemID/userName/client/language)比 discovery feature 可靠;discovery 的 feature 元素在真实系统上经常没有。
  • 自签名证书常见CN=...pvt 自签发):strictSSL: false 时客户端通过 undici dispatcher 关闭校验(仅该目的地生效)。
  • ATC run 启动必须显式 clientWait=false,否则 400("Only 'false' is currently supported as QueryParameter 'ClientWait'")。
  • ATC run 状态响应用 status 属性<atc:run status="Running|Completed"> + <atc:phase status=...>);完成时 <atom:link href="/sap/bc/adt/atc/results/{displayId}">displayId 与 runId 不同,必须从链接提取。
  • ATC 结果端点拒绝 checkstyle 媒体类型(406),用 application/xml 取回;解析时兼容 checkstyle 与未知格式(保留原始 XML)。
  • ATC 结果的真实格式是 atcresult 命名空间(非 checkstyle):resultList → result → objects → object → findings → finding。finding 属性:priority(1-4)、checkIdcheckTitlemessageIdmessageTitlelocation(#start=行,列)、uri。集合列表也是子元素格式(<atcresult:displayId> 等),并带 aggregatesnumPrio1..4/numFailure)。priority 映射:1→CRITICAL、2→ERROR、3→WARNING、4→INFO。
  • ATC 结果集合 GET /atc/results 需要 createdBy 参数(缺省会 400);createdBy=* 可跨用户列出全部结果;activeResult=true 语义不同(活跃结果)。结果可通过 displayId 直接获取(GET /atc/results/{displayId})。

7. 运行时转储 / 执行器 / $batch / 结构化编辑器

运行时转储(ST22 短转储分析)

方法路径说明
GET/sap/bc/adt/runtime/dumpsAtom feed。$queryand( equals( user, X ) ) 表达式过滤)、from/toYYYYMMDDHHMMSS 时间范围)、$top/$skip 分页;Accept application/atom+xml;type=feed
GET/sap/bc/adt/runtime/dump/{id}结构化 XML(application/vnd.sap.adt.runtime.dump.v1+xml)。注意集合是 dumps(复数)、单条是 dump(单数)
GET/sap/bc/adt/runtime/dump/{id}/summaryHTML 概览(Accept text/html
GET/sap/bc/adt/runtime/dump/{id}/formatted纯文本分析视图(Accept text/plain

dump id 是复合键:{YYYYMMDDHHMMSS}{hostname}_{SYSID}_{instance}{user}{client}{seq}。feed entry 的 id 从 <link href=".../runtime/dump/{id}"/> 提取(<id> 元素不总是可用)。

程序 / 类执行器

方法路径说明
POST/sap/bc/adt/programs/programrun/{NAME}运行可执行程序(F8 等价物);控制台输出 text/plain 返回;名称大写
POST/sap/bc/adt/oo/classrun/{NAME}运行实现 if_oo_adt_classrun 的类(main( ) 执行,out->write( ) 输出以 text/plain 返回);未实现该接口 → 400

两者都是状态变更 POST(需 CSRF);报文无 body。

协议级 $batch

POST /sap/bc/adt/$batch
Content-Type: multipart/mixed; boundary=batch_<uuid>
Accept: multipart/mixed

--batch_<uuid>
Content-Type: application/http
content-transfer-encoding: binary

GET /sap/bc/adt/oo/classes/zcl_demo/source/main?sap-client=100 HTTP/1.1
Accept: text/plain

--batch_<uuid>
...
--batch_<uuid>--
  • 每部分是 application/http 内嵌完整 HTTP 请求(请求行 + 头 + 空行 + body)
  • 响应是 multipart/mixed,逐部分内嵌 HTTP 响应(HTTP/1.1 200 OK + 头 + body),顺序与请求一致
  • CSRF 只需在外层 POST 校验一次;sap-client/sap-language 应附加到每个内层路径
  • 参考实现:@mcp-abap-adt/adt-clientsbatch/buildBatchPayload(boundary 随机生成、requestId 32 位);语义同 OData $batch 但挂在整个 ADT 命名空间下

结构化编辑器(MSAG / DOMA / DTEL / TTYP)

这些 DDIC 对象没有 /source/main,内容是元数据 XML;写入走 read-modify-write(lock → GET 原文 → 只补丁显式字段 → PUT ?lockHandle= → unlock),SAP 管理属性全量保留:

类型端点媒体类型结构要点
MSAG/sap/bc/adt/messageclass/{name}application/vnd.sap.adt.mc.messageclass+xmlmc:messageClass(ns http://www.sap.com/adt/MessageClass);消息是 mc:messages 元素属性(mc:msgno/mc:msgtext/mc:selfexplainatory——SAP 拼写就是没有 x);删除走 mc:deletedmessages
DOMA/sap/bc/adt/ddic/domains/{name}application/vnd.sap.adt.domains.v2+xmldoma:contentdoma:typeInformation(datatype/length/decimals)+ doma:outputInformation(conversionExit/signExists/lowercase)+ doma:fixValuesdoma:fixValuedoma:low/doma:text 子元素)
DTEL/sap/bc/adt/ddic/dataelements/{name}application/vnd.sap.adt.dataelements.v2+xmldtel:typeKind(domain/builtin)+ dtel:typeNamedtel:dataType+dtel:dataTypeLength补零到 6 位);dtel:labels → `dtel:label type="shortText
TTYP/sap/bc/adt/ddic/tabletypes/{name}application/vnd.sap.adt.tabletypes.v2+xmlttyp:typeKind/ttyp:typeName/ttyp:accessType;主键 ttyp:keyttyp:definition/ttyp:kind

TABL/STRU 不在结构化编辑器范围:现代系统上它们有 DDL 源(/ddic/tables/{name}/source/main,PUT DDL 文本),走普通源码读写路径。

8. 真实后端实战记录(impc-dev / D01 / client 110)

一次完整的真实系统实战反馈及其在工具链中的处理方式。原则:合理的后端报错保持原样透传(只加提示),行为缺陷在客户端修复。

后端不支持(合理报错,工具层给出定向提示)

现象处理
POST /sap/bc/adt/$batch → 404(未部署 $batch 服务)adt_batch 捕获 404/405,明确提示"该后端未部署 $batch 服务,请逐个调用工具"
对象元数据不含锁状态 → adt_lock_info 返回 locked=null保留 null + 强化提示:此类后端无法探测并发编辑者,应依赖写后持久性验证(见下)
自由 SQL 的 SELECT 列表带 mandt → 400(解析器拒绝跨 client 字段)adt_data_preview 描述明示禁止选 mandt 列;400 时给出定向提示

行为缺陷(客户端修复)

现象根因修复
include 用 name+type=PROG 读取 404按约定拼 URI 到 /programs/programs/,而 include 在 /programs/includes/resolveObject 对 PROG/P 与 PROG/I 先做精确名搜索,用命中对象的真实 URI/type;搜索不可用回落约定 URI
adt_list_transports(status=modifiable) 漏报未释放请求(D01K966363 状态 D 却返回 0 条)语义词 modifiable 被原样发给后端,后端按 CTO 字母码精确匹配 → 匹配 0 行客户端把 modifiableDreleasedR 再发;后端 400 拒绝 status 参数时自动去参重试,客户端侧过滤兜底
adt_list_atc_runs 带任何过滤参数 400子集实现的 ATC results 服务只接受无参数查询400 时自动回退无参数查询重试
ATC 结果 P1–P4/耗时全 0单结果体不带 <aggregates> 节点、无运行时无 aggregates 时按 finding 的 priority 属性推导 P1–P4;adt_run_atcdurationMs 改为客户端计时
ATC finding 全挂主程序名、行号却是 include 的后端把程序所有 finding 嵌在主程序 object 下,位置在 finding 自己的 location URI 里解析出 locationUri#start= 前的 URI 部分),工具输出为每条 finding 的 uri 字段,渲染时标注 (in <include>)
adt_get_transport 传任务号返回父请求真实 CTO 行为:任务号解析到父请求(版本历史记录的是任务级号)保留该行为,输出 requestedNumber + note 显式标出任务→父请求映射
adt_system_info release 为空release 藏在备用 feature 键里依次探测 JSON releasereleaseSAP_SYSTEM_RELEASESAP_BASIS_RELEASESAP_SYSTEM_RELEASE_ID(注意用 `
编辑报成功但源码被静默打回原样(本次最大事故)同一开发账号的另一个会话持有旧缓冲区,在写入解锁之后整缓冲保存覆盖;写前 OCC 哈希校验只保护写前窗口,感知不到写后覆盖;版本库还留下 99999 临时版本号加剧误判三个写工具(edit/write/push)在解锁后回读验证:与所写内容做容错比对(CRLF/行尾空白),不一致 → persisted:false + 醒目告警(重读重做、勿激活);读失败 → persisted:undefined + 提示回读确认。回读结果同时刷新本地快照(OCC 基准 = 真实服务端状态)

标准操作顺序(沉淀)

adt_permissionsadt_search(拿全对象 URI)→ adt_read_object(建快照)→ adt_edit_object立即回读确认persisted 标志 / adt_read_object)→ adt_activate(主程序 + include 一次传齐)→ adt_version_diff(saved vs active) 验证无遗留 → adt_run_atc + 无参数 adt_list_atc_runs

多人/多会话共用同一开发账号时,"编辑成功"之后的那次回读,比激活成功更值得信赖。

9. 待验证项(勿在生产依赖)

  • ?includeSupportPackageCompatibility 参数
  • x-sap-login-with
  • 通用 ?feature= 参数
  • /sap/bc/adt/repository/package_service
  • /unit/discovery 端点(不存在于任何来源)

参考