ABAP

CL_HTTP_CLIENT 없이 REST 호출 3가지 실수 #shorts #SAP #ABAP

▶ YouTube에서 보기

📖 개요 — 이 글에서 다루는 내용

ABAP 시스템에서 외부 REST API를 호출해야 하는 순간은 반드시 옵니다. 환율 조회, 배송 추적, 사내 미들웨어 연동까지, 온프레미스 SAP ECC / S/4HANA(온프레미스) 환경에서는 CL_HTTP_CLIENT가 사실상 표준 진입점입니다. 이 글은 "어디서 본 것 같은데 막상 짜면 막히는" HTTP 클라이언트 코드를 GET/POST 실전 예제로 정리합니다.

  • CL_HTTP_CLIENT의 생성 방식 3가지(URL / Destination / Proxy) 구분
  • ✅ GET으로 JSON 응답 받아 파싱하는 기본 흐름
  • ✅ POST + JSON Body + 에러 처리 + 로깅이 들어간 실무 코드
  • ✅ SM59 Destination, 타임아웃, SSL, 커넥션 정리 등 프로덕션 체크포인트

📚 미리 알아두면 좋은 배경

ABAP OO 문법(클래스, 메서드 호출, 예외 처리 TRY...CATCH)과 HTTP 기본 개념(메서드, 상태 코드, 헤더, Body)을 알고 있다면 충분합니다. JSON 직렬화는 /UI2/CL_JSON을 사용하므로 별도 파서 지식은 필요 없습니다.

🔧 환경 · 버전 · 준비물

  • 시스템: SAP NetWeaver AS ABAP 7.40 SP08 이상 (ECC 6.0 EHP7+, S/4HANA 온프레미스 전 버전에서 동일 패턴 적용 가능)
  • 클래스: CL_HTTP_CLIENT(구현), IF_HTTP_CLIENT(인터페이스) — ICM(Internet Communication Manager) 기반
  • HTTPS 호출 시: 트랜잭션 STRUST에서 대상 서버의 CA 인증서를 SSL Client(Anonymous 또는 표준) PSE에 등록
  • 권장 사전 설정: SM59에서 Type G(HTTP to External Server) Destination 생성 — URL 하드코딩 대신 운영 표준으로 일반적으로 권장됩니다
  • 참고로 SAP BTP ABAP Environment(Steampunk)에서는 CL_HTTP_CLIENT가 릴리스되지 않아 CL_WEB_HTTP_CLIENT_MANAGER를 사용해야 합니다. 이 글은 온프레미스 기준입니다.

💡 핵심 개념 — ICM 위에 올라탄 우체국 창구

CL_HTTP_CLIENT를 우체국 창구에 비유하면 구조가 쉽게 잡힙니다. 개발자는 편지(Request)를 쓰고 창구(클라이언트 인스턴스)에 접수하면, 실제 배달은 ICM이라는 우체부가 수행합니다. 즉 ABAP 코드는 소켓을 직접 다루지 않고, 커널 레벨의 ICM에 위임합니다.

흐름은 항상 5단계로 고정됩니다.

  1. 생성: create_by_url(테스트·간단 호출) 또는 create_by_destination(운영 권장) — Destination 방식은 URL·인증·프록시·SSL 설정을 SM59로 외부화하므로 이관 시 코드 수정이 필요 없습니다.
  2. 요청 구성: request 객체(IF_HTTP_REQUEST)에 메서드·헤더·Body 설정
  3. 전송: send( ) — 이 시점에 ICM이 실제 TCP 연결을 열고 요청을 내보냄
  4. 수신: receive( ) — 응답이 올 때까지 대기(타임아웃 적용 지점)
  5. 정리: response에서 상태 코드·Body를 읽고 close( )로 커넥션 반납

자주 혼동하는 포인트 두 가지. 첫째, sendreceive는 반드시 쌍으로 호출해야 하며 각각 별도의 예외를 던집니다. 둘째, 예외는 클래스 기반이 아니라 클래식 예외(EXCEPTIONS 절 + sy-subrc)라는 점입니다. 그래서 TRY...CATCH가 아니라 호출 직후 sy-subrc 분기가 필요합니다. 이 두 가지만 지켜도 "왜 응답이 비어 있지?" 류의 문제 대부분이 사라집니다.

💻 실전 코드 — 3단계로 완성하기

1단계: GET 기본 예제 — 환율 조회

공개 환율 API에서 USD 기준 환율 JSON을 받아오는 최소 코드입니다.

DATA: lo_client   TYPE REF TO if_http_client,
      lv_json     TYPE string,
      lv_status   TYPE i.

cl_http_client=>create_by_url(
  EXPORTING url                = 'https://api.frankfurter.app/latest?from=USD&to=KRW'
  IMPORTING client             = lo_client
  EXCEPTIONS argument_not_found = 1
             plugin_not_active  = 2
             internal_error     = 3
             OTHERS             = 4 ).
IF sy-subrc <> 0.
  MESSAGE '클라이언트 생성 실패' TYPE 'E'.
ENDIF.

lo_client->request->set_method( if_http_request=>co_request_method_get ).
lo_client->request->set_header_field( name = 'Accept' value = 'application/json' ).

lo_client->send( EXCEPTIONS OTHERS = 1 ).
lo_client->receive( EXCEPTIONS OTHERS = 1 ).

lo_client->response->get_status( IMPORTING code = lv_status ).
lv_json = lo_client->response->get_cdata( ).
lo_client->close( ).

cl_demo_output=>display( |HTTP { lv_status }: { lv_json }| ).

get_cdata는 문자 데이터, 바이너리(파일 다운로드 등)는 get_data를 사용합니다.

2단계: 실무 시나리오 — POST + 에러 처리 + 로깅

판매오더 확정 결과를 외부 물류 시스템에 POST로 전송하는 시나리오입니다. JSON 직렬화, 상태 코드 분기, 실패 로그를 포함합니다.

TYPES: BEGIN OF ty_shipment_req,
         sales_order  TYPE vbeln_va,
         ship_to      TYPE string,
         total_weight TYPE p LENGTH 13 DECIMALS 3,
       END OF ty_shipment_req.

DATA(ls_req) = VALUE ty_shipment_req(
                 sales_order  = '0000012345'
                 ship_to      = 'Busan DC-02'
                 total_weight = '148.500' ).

" ABAP 구조 → JSON (camelCase 변환)
DATA(lv_body) = /ui2/cl_json=>serialize(
                  data        = ls_req
                  pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).

DATA lo_client TYPE REF TO if_http_client.
cl_http_client=>create_by_destination(
  EXPORTING destination = 'ZLOGISTICS_API'   " SM59 Type G
  IMPORTING client      = lo_client
  EXCEPTIONS OTHERS     = 1 ).
IF sy-subrc <> 0.
  RAISE EXCEPTION TYPE zcx_api_error MESSAGE e001(zapi).
ENDIF.

lo_client->request->set_method( if_http_request=>co_request_method_post ).
lo_client->request->set_header_field( name  = 'Content-Type'
                                      value = 'application/json; charset=utf-8' ).
lo_client->request->set_cdata( lv_body ).

lo_client->send( EXCEPTIONS http_communication_failure = 1
                            http_invalid_state         = 2
                            http_processing_failed     = 3
                            OTHERS                     = 4 ).
IF sy-subrc = 0.
  lo_client->receive( EXCEPTIONS http_communication_failure = 1
                                 OTHERS                     = 2 ).
ENDIF.

IF sy-subrc <> 0.
  lo_client->get_last_error( IMPORTING message = DATA(lv_errtext) ).
  zcl_app_log=>get( object = 'ZAPI' )->add_error( lv_errtext )->save( ).
  lo_client->close( ).
  RETURN.
ENDIF.

lo_client->response->get_status( IMPORTING code = DATA(lv_code) ).
DATA(lv_resp) = lo_client->response->get_cdata( ).
lo_client->close( ).

CASE lv_code.
  WHEN 200 OR 201.
    /ui2/cl_json=>deserialize( EXPORTING json = lv_resp
                               CHANGING  data = DATA(ls_result) ).
  WHEN 401 OR 403.
    " 인증 문제 → SM59 인증 설정 점검 유도
  WHEN OTHERS.
    zcl_app_log=>get( object = 'ZAPI'
      )->add_error( |HTTP { lv_code }: { lv_resp }| )->save( ).
ENDCASE.

3단계: 프로덕션 체크 — 타임아웃 · 재시도 · 테스트 가능 설계

" (1) 타임아웃: receive 무한 대기 방지 — 초 단위
lo_client->set_timeout( timeout = 10 ).

" (2) 압축 응답 허용으로 대역폭 절감
lo_client->request->set_header_field( name = 'Accept-Encoding' value = 'gzip' ).

" (3) 인증 토큰은 하드코딩 금지 — SM59 or SSF/시크릿 테이블에서 조회
lo_client->request->set_header_field(
  name  = 'Authorization'
  value = |Bearer { zcl_token_store=>get( 'ZLOGISTICS' ) }| ).

" (4) 일시 오류(429/503) 백오프 재시도 골격
DO 3 TIMES.
  DATA(lv_try) = sy-index.
  " ... send / receive / get_status ...
  IF lv_code <> 429 AND lv_code <> 503. EXIT. ENDIF.
  WAIT UP TO lv_try SECONDS.
ENDDO.

테스트 관점에서는 if_http_client 타입 참조를 생성자 주입으로 받도록 래퍼 클래스(ZCL_LOGISTICS_GATEWAY 등)를 만들어 두면, ABAP Unit에서 테스트 더블로 교체해 실제 네트워크 없이 상태 코드별 분기를 검증할 수 있습니다.

⚠️ 흔한 실수와 트러블슈팅

  • close( ) 누락: 대량 배치에서 커넥션이 고갈되어 ICM 큐가 밀립니다. 성공/실패 모든 경로에서 close를 호출하세요.
  • sy-subrc 미확인: 클래식 예외라서 확인하지 않으면 오류가 조용히 지나가고 빈 응답만 남습니다.
  • Content-Type 누락 POST: 상당수 API가 415 또는 400을 반환합니다. charset=utf-8까지 명시하는 편이 안전합니다.

Q1. HTTPS 호출 시 "SSL handshake failed"가 발생합니다.
A. 대상 서버 인증서 체인(루트 CA 포함)이 STRUST의 SSL Client PSE에 없기 때문입니다.

Q2. 사내 프록시 뒤에서 외부 API가 안 나갑니다.
A. SM59 Destination의 HTTP Proxy 설정을 사용하거나 create_by_url의 proxy 파라미터를 지정하세요.

Q3. 응답 한글이 깨집니다.
A. response->get_data( )로 xstring을 받아 cl_abap_conv_in_ce(UTF-8)로 직접 변환하면 해결됩니다.

Q4. receive에서 세션이 멈춥니다.
A. set_timeout 미설정이 원인입니다. 코드에서 명시적으로 지정하세요.

🚀 이후 확장해볼 주제

  • REST 라이브러리 계층: CL_REST_HTTP_CLIENT로 리소스 중심 호출 추상화
  • OAuth 2.0: OA2C_CONFIG 기반 클라이언트 자격증명 플로우 연동
  • Clean Core 대비: BTP ABAP Environment의 CL_WEB_HTTP_CLIENT_MANAGER + Communication Arrangement 패턴으로 마이그레이션
  • 인바운드 방향: SICF 핸들러(IF_HTTP_EXTENSION)로 ABAP을 REST 서버로 노출

📚 더 깊이 볼 자료

  • SAP Help Portal — HTTP Client in ABAP (IF_HTTP_CLIENT)
  • SAP Help Portal — Internet Communication Framework(ICF) 개요
  • SAP Help Portal — Trust Manager(STRUST)와 SSL 구성
  • SAP Help Portal — RFC/HTTP Destination(SM59) 관리

댓글 0

아직 댓글이 없습니다.