【ABAP】REST/HTTP技术(二):消费外部API与JSON数据解析
1. 从ABAP发起HTTP请求的基础操作
第一次用ABAP调用外部API时,我踩过不少坑。最典型的就是直接用CL_HTTP_CLIENT发请求,结果连最简单的GET请求都报错。后来发现是没设置Content-Type头信息。这里分享几个实战中总结的关键点:
创建HTTP客户端对象时,推荐用cl_http_client=>create_by_url方法。我习惯先用TRY...CATCH包裹这段代码,因为URL格式错误或者网络问题都会导致异常。下面是个典型的安全写法:
DATA: lo_client TYPE REF TO if_http_client. TRY. cl_http_client=>create_by_url( EXPORTING url = 'https://api.example.com/data' IMPORTING client = lo_client ). CATCH cx_root INTO DATA(lx_error). " 这里处理错误 ENDTRY.设置请求头时,这几个是必选项:
Content-Type: 通常用application/jsonAccept: 建议设为application/json- 如果有认证需求,还要加
Authorization头
lo_client->request->set_header_field( name = 'Content-Type' value = 'application/json' ). lo_client->request->set_header_field( name = 'Accept' value = 'application/json' ).发送GET请求相对简单,但POST请求要注意body的格式。我遇到过最坑的情况是JSON字符串里的双引号被转义了。正确姿势是:
DATA(lv_json) = '{"key":"value"}'. lo_client->request->set_method( if_http_request=>co_request_method_post ). lo_client->request->set_cdata( lv_json ).2. 处理API认证的实战技巧
去年做电商平台对接时,我花了三天时间调试OAuth2.0认证。不同API提供商的认证方案差异很大,这里分享几种常见情况的处理方式。
API Key认证是最简单的,通常放在URL参数或者Header里。比如:
" URL参数方式 lv_url = |https://api.weather.com/v1?apikey={ lv_api_key }|. " Header方式 lo_client->request->set_header_field( name = 'api-key' value = lv_api_key ).Basic认证稍微复杂点,需要做Base64编码。ABAP里有现成的工具类:
DATA: lv_auth_string TYPE string. lv_auth_string = |{ lv_username }:{ lv_password }|. CALL FUNCTION 'SCMS_STRING_TO_XSTRING' EXPORTING text = lv_auth_string IMPORTING buffer = DATA(lv_xstring). DATA(lv_base64) = cl_http_utility=>encode_base64( lv_xstring ). lo_client->request->set_header_field( name = 'Authorization' value = |Basic { lv_base64 }| ).OAuth2.0最麻烦,特别是要处理token过期的情况。我的经验是封装一个专门的认证类:
METHOD get_access_token. IF me->token_expiry_time < sy-datum AND me->token_expiry_time IS NOT INITIAL. " 调用refresh token逻辑 refresh_token( ). ELSEIF me->access_token IS INITIAL. " 首次获取token fetch_new_token( ). ENDIF. rv_token = me->access_token. ENDMETHOD.3. 解析JSON响应的正确姿势
刚开始用ABAP处理JSON时,我被各种转换问题折磨得不轻。直到发现了/UI2/CL_JSON这个神器,效率直接翻倍。
先看最简单的场景 - 解析扁平结构的JSON:
{ "name": "张三", "age": 30, "active": true }对应的ABAP处理代码:
DATA: ls_person TYPE ty_person. " 提前定义好结构 /ui2/cl_json=>deserialize( EXPORTING json = lv_json_response CHANGING data = ls_person ).遇到嵌套结构时,比如:
{ "order": { "id": "1001", "items": [ { "sku": "A001", "qty": 2 } ] } }需要先定义好对应的ABAP类型:
TYPES: BEGIN OF ty_order_item, sku TYPE string, qty TYPE i, END OF ty_order_item. TYPES: BEGIN OF ty_order, id TYPE string, items TYPE STANDARD TABLE OF ty_order_item WITH EMPTY KEY, END OF ty_order. TYPES: BEGIN OF ty_response, order TYPE ty_order, END OF ty_response.处理数组时有个坑要注意 - ABAP内表需要明确指定WITH EMPTY KEY,否则解析会失败。
对于不确定结构的动态JSON,可以用反序列化到引用变量的方式:
DATA: lo_data TYPE REF TO data. /ui2/cl_json=>deserialize( EXPORTING json = lv_json_response IMPORTING data = lo_data ).4. 异常处理与调试技巧
调试HTTP请求时,我习惯把这些信息记录下来:
- 完整的请求URL
- 请求头信息
- 请求体内容
- 响应状态码
- 响应头
- 响应体
可以封装一个日志工具方法:
METHOD log_request. DATA: lv_message TYPE string. lv_message = |URL: { iv_url }\n| && |Method: { iv_method }\n| && |Headers: { iv_headers }\n| && |Body: { iv_body }\n| && |Status: { iv_status }\n| && |Response: { iv_response }|. " 写入应用日志或数据库 ENDMETHOD.处理HTTP状态码时,常见的需要特殊处理的情况:
CASE lv_status_code. WHEN 401. " 未授权 " 刷新token或重新认证 WHEN 403. " 禁止访问 " 检查权限设置 WHEN 429. " 请求过多 " 实现重试机制 WHEN 500. " 服务器错误 " 记录错误并通知管理员 ENDCASE.对于超时问题,我推荐设置合理的超时参数:
lo_client->request->set_header_field( name = '~timeout' value = '30' ). " 30秒超时调试小技巧:在SE80里创建HTTP测试客户端,可以直观地查看请求和响应:
- 事务码SE80
- 选择"Repository Browser"
- 右键 → Create → HTTP Test Client
- 输入URL和参数进行测试
5. 性能优化实战经验
在大数据量场景下,我总结了这些优化点:
连接池管理:频繁创建销毁HTTP客户端很耗资源。可以维护一个连接池:
CLASS zcl_http_pool DEFINITION. PUBLIC SECTION. METHODS get_client IMPORTING iv_url TYPE string RETURNING VALUE(ro_client) TYPE REF TO if_http_client. METHODS release_client IMPORTING io_client TYPE REF TO if_http_client. ENDCLASS.批量请求:如果API支持,尽量使用批量接口。比如:
" 不好的做法:循环调用单条API LOOP AT lt_items INTO DATA(ls_item). CALL_API( ls_item ). ENDLOOP. " 好的做法:使用批量接口 CALL_BATCH_API( lt_items ).缓存策略:对不常变的数据实现缓存:
METHOD get_cached_data. DATA: lv_cache_key TYPE string. lv_cache_key = |API_CACHE_{ iv_key }|. " 先从缓存读取 TRY. rv_data = zcl_cache=>get( lv_cache_key ). RETURN. CATCH zcx_cache_miss. ENDTRY. " 缓存不存在则调用API rv_data = call_api( iv_key ). " 写入缓存 zcl_cache=>set( iv_key = lv_cache_key iv_data = rv_data iv_expiry = 3600 ). " 缓存1小时 ENDMETHOD.异步处理:对于耗时操作,可以考虑后台作业:
DATA: lv_jobname TYPE tbtcjob-jobname VALUE 'API_SYNC'. CALL FUNCTION 'JOB_OPEN' EXPORTING jobname = lv_jobname IMPORTING jobcount = DATA(lv_jobcount). SUBMIT zapi_sync_program WITH p_param1 = lv_param1 VIA JOB lv_jobname NUMBER lv_jobcount AND RETURN. CALL FUNCTION 'JOB_CLOSE' EXPORTING jobname = lv_jobname jobcount = lv_jobcount.6. 真实业务场景案例
去年我们对接物流跟踪系统时,需要实时获取快递状态。这个案例很有代表性:
需求分析:
- 每30分钟同步一次物流信息
- 支持多家物流公司API
- 异常状态实时预警
技术方案:
- 使用CL_HTTP_CLIENT调用各物流公司API
- 用/UI2/CL_JSON解析返回的JSON
- 数据存入自定义表ZLOGISTICS
- 后台作业定时执行
- 异常状态触发工作流通知
核心代码结构:
CLASS zcl_logistics_tracker DEFINITION. PUBLIC SECTION. METHODS: sync_all_carriers, sync_carrier IMPORTING iv_carrier TYPE string. PRIVATE SECTION. METHODS: call_carrier_api IMPORTING iv_tracking_no TYPE string RETURNING VALUE(rv_data) TYPE string, parse_response IMPORTING iv_json TYPE string EXPORTING es_status TYPE zlogistics_status, save_to_db IMPORTING is_status TYPE zlogistics_status, check_alert IMPORTING is_status TYPE zlogistics_status. ENDCLASS.处理不同API的兼容性问题时,我用了策略模式:
CLASS zcl_carrier_adapter DEFINITION ABSTRACT. PUBLIC SECTION. METHODS: get_tracking_status ABSTRACT IMPORTING iv_tracking_no TYPE string EXPORTING es_status TYPE zlogistics_status. ENDCLASS. " 针对不同物流公司实现具体适配器 CLASS zcl_sf_adapter DEFINITION INHERITING FROM zcl_carrier_adapter. METHODS: get_tracking_status REDEFINITION. ENDCLASS.7. 常见问题解决方案
SSL证书问题是最常见的拦路虎。遇到证书错误时,可以这样处理:
首先检查系统是否安装了根证书
- 事务码STRUST
- 查看SSL客户端标准PSE
如果还是有问题,可以临时关闭证书验证(仅限测试环境!)
lo_client->ssl_id = 'ANONYM'.中文乱码问题通常是因为编码不一致。确保全程使用UTF-8:
" 设置请求编码 lo_client->request->set_content_type( 'application/json; charset=utf-8' ). " 处理响应时指定编码 lv_response = cl_http_utility=>if_http_utility~decode_utf8( lo_client->response->get_cdata( ) ).大数据量分页处理方案:
METHOD get_all_data. DATA: lv_page TYPE i VALUE 1, lt_all_items TYPE ty_item_tt. DO. DATA(lv_url) = |{ iv_base_url }?page={ lv_page }&size=100|. DATA(lv_response) = call_api( lv_url ). DATA(lt_items) = parse_items( lv_response ). IF lt_items IS INITIAL. EXIT. ENDIF. APPEND LINES OF lt_items TO lt_all_items. lv_page = lv_page + 1. ENDDO. rt_items = lt_all_items. ENDMETHOD.接口限流处理建议:
METHOD call_api_with_retry. DATA: lv_retry TYPE i VALUE 0, lv_wait TYPE i VALUE 1. WHILE lv_retry < 3. TRY. RETURN call_api( iv_url ). CATCH zcx_api_limit_exceeded INTO DATA(lx_error). lv_retry = lv_retry + 1. WAIT UP TO lv_wait SECONDS. lv_wait = lv_wait * 2. " 指数退避 ENDTRY. ENDWHILE. RAISE EXCEPTION TYPE zcx_api_failed. ENDMETHOD.8. 高级技巧与最佳实践
自动化测试方案:我们可以为API调用编写单元测试
CLASS ltc_api_test DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. PRIVATE SECTION. METHODS: test_get_success FOR TESTING, test_post_success FOR TESTING, test_error_handling FOR TESTING. ENDCLASS. CLASS ltc_api_test IMPLEMENTATION. METHOD test_get_success. " 准备测试数据 " 调用被测方法 " 验证结果 ENDMETHOD. ENDCLASS.监控指标建议收集这些数据:
- 平均响应时间
- 成功率
- 失败分类统计
- 数据量大小
可以用事务码ST03N查看HTTP调用性能数据。
安全加固措施:
- 敏感信息加密存储
- 使用API网关集中管理
- 实现请求签名
- 定期轮换密钥
METHOD add_signature. DATA: lv_timestamp TYPE string, lv_signature TYPE string. GET TIME STAMP FIELD lv_timestamp. lv_signature = compute_hmac( iv_data = |{ lv_timestamp }{ iv_body }| iv_key = me->api_secret ). lo_client->request->set_header_field( name = 'X-Signature' value = lv_signature ). lo_client->request->set_header_field( name = 'X-Timestamp' value = lv_timestamp ). ENDMETHOD.文档化建议为每个API封装创建技术文档:
"! @testingClass LTC_API_TEST "! @description 物流信息查询API封装 "! @configNote 需要在表ZAPI_CONFIG配置访问密钥 CLASS zcl_logistics_api DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. "! 根据运单号查询物流信息 "! @parameter iv_tracking_no | 运单号 "! @parameter rt_status | 物流状态信息 METHODS get_tracking_status IMPORTING iv_tracking_no TYPE string RETURNING VALUE(rt_status) TYPE zlogistics_tt. ENDCLASS.