SOAP로 Data 주고받기

Simple Object Access Protocol에 대해 간략히 알아봅시다.

회사에서 근무하던 중 갑자기 고객사 측에서 SOAP을 쓰겠다는 요구사항이 들어왔습니다. SOAP는 그저 옛날 기술이라 생각해서 등한시했던 지라 XML로 어쩌구 저쩌구 하는 프로토콜이라고 대충 알고만 있었습니다만, 이번 기회에 급하게 공부한 내용을 정리해 봅니다.

💡SOAP이 무엇인가요?

SOAP(Simple Object Access Protocol)는 XML 기반의 메시지를 네트워크 통신에 사용하는 W3C 공식 통신 프로토콜을 말합니다. HTTP, SMTP, TCP 등의 다양한 애플리케이션 계층 프로토콜을 사용할 수 있지만 HTTP를 사용하는 것이 일반적입니다.

다른 언어다른 플랫폼에서 빌드된 애플리케이션이 통신할 수 있도록 하는 목적으로 상호 이해 가능한 포맷의 메세지를 송수신함으로써 원격지에 있는 서비스객체나 API를 자유롭게 사용하고자 하는 기업의 요구에서부터 탄생했습니다.

💡SOAP 메시지 구조

SOAP 메시지는 다음과 같은 기본 구성 요소로 구성된 XML 문서입니다.

  • SOAP 엔벨로프(Envelope): 메시지의 모든 데이터를 캡슐화하고 XML 문서를 SOAP 메시지로 식별합니다. 모든 SOAP 메시지의 루트 요소이며, 두 개의 하위 요소를 포함합니다.

  • SOAP 헤더(Header): SOAP 엔벨로프의 선택적 하위 요소이며, SOAP 메시지에 대한 응용프로그램별 정보(보안, 트랜잭션 관리, 라우팅 등)가 포함되어 있습니다. SOAP 헤더에 정의된 속성은 수신인이 SOAP 메시지를 처리하는 방법을 정의합니다.

  • SOAP 본문(Body): 메시지의 최종 수신자를 위한 정보를 포함하는 SOAP 엔벨로프의 필수 하위 요소입니다. 웹 서비스에서 응용 프로그램으로 전송해야 하는 실제 메시지의 세부 정보가 포함됩니다.

  • SOAP 결함(Fault): 오류에 사용되는 SOAP 본문의 선택적 하위 요소입니다. SOAP 오류가 생성되면 HTTP 500 오류로 반환됩니다. 오류 메시지에는 오류 코드, 문자열, 액터 및 세부 정보가 포함됩니다.

실제 XML로는 다음 예제와 같이 표현될 수 있습니다.

<?xml version="1.0"?>

<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope/"
soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">

<soap:Header>
  <m:Trans xmlns:m="https://www.w3schools.com/transaction/"
  soap:mustUnderstand="1">234
  </m:Trans>
</soap:Header>

<soap:Body>
  <m:GetPrice xmlns:m="https://www.w3schools.com/prices">
    <m:Item>Apples</m:Item>
  </m:GetPrice>
</soap:Body>

</soap:Envelope>
  • Envelopexmlns:soap 네임스페이스 값은 항상 "http://www.w3.org/2003/05/soap-envelope/"이어야 합니다. 또한 SOAP 메시지에는 기본 인코딩이 없습니다. soap:encodingStyle로 xml 문서에 적용될 인코딩 규칙을 정의합니다.

  • Header의 Trans 엘리먼트에 있는 soap:mustUnderstand 속성은 SOAP 헤더 요소의 속성 중 하나로, 속성 값이 "1"로 설정되어 있으면 필수 처리 항목이라는 뜻으로(선택 처리라면 0), SOAP 수신자는 이 Trans 엘리먼트를 이해하고 처리해야 합니다. 만약 수신자가 해당 요소를 처리할 수 없는 경우, Fault를 반환할 수 있습니다.

  • Body에는 실제 메시지가 포함되어 있습니다. 위의 예시는 사과의 가격을 요청하는 메시지입니다. m:GetPricem:Item 요소는 응용 프로그램별 요소입니다. SOAP 네임스페이스의 일부가 아닙니다.

💡SOAP 동작 방식

SOAP는 WSDL을 통해 서비스가 현재 어떤 내용들을 제공해주는지 알려줍니다. ProviderBroker에 사용 가능한 WSDL을 등록합니다. 웹 서비스는 Consumer가 WSDL에서 검색한 Service에 요청 XML을 보내면 Provider가 이 문서를 객체로 변환해서 처리한 후, 다시 Consumer에게 응답 XML을 보내는 과정을 가집니다. SOAP 요청과 응답은 모두 HTTP(Hypertext Transfer Protocol Secure) 또는 HTTP와 같은 유사한 프로토콜을 사용하여 전송됩니다.

💡WSDL(Web Service Description Language)이란?

WSDL은 웹 서비스를 제공하는 Server와 Client를 연결해 주는 문서입니다. 시스템 요구사항대로 구현된 서비스들의 명세를 WSDL에 담아 명시해두면, 서비스를 요청하는 쪽에서 이 문서를 보고 해당 서버의 서비스는 어떤 것들이 있고, 각 서비스들의 파라미터와 리턴 객체는 어떤 타입으로 주고받는지 확인합니다. 즉, 서비스를 어떤 형태로 요청하고 응답받아서 처리할 것인지를 WSDL을 통해 결정할 수 있습니다.

더 자세한 내용은 https://www.w3.org/TR/wsdl.html 참고 바랍니다.

💡Spring Boot로 SOAP 써보기

지겨운 개념은 여기까지 공부하고, 일단 코드를 짜면서 이해해 봅시다. 해당 내용은 공식 문서를 참고하여 진행합니다. 문서에 나와있는 코드 중 저희가 중요하게 봐야 할 파일은 countries.xml입니다. 이 파일의 내용은 Spring-WS에 의해 WSDL에 자동으로 들어갈 스키마가 됩니다.

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://spring.io/guides/gs-producing-web-service"
           targetNamespace="http://spring.io/guides/gs-producing-web-service" elementFormDefault="qualified">

    <xs:element name="getCountryRequest">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="getCountryResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="country" type="tns:country"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="country">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="population" type="xs:int"/>
            <xs:element name="capital" type="xs:string"/>
            <xs:element name="currency" type="tns:currency"/>
        </xs:sequence>
    </xs:complexType>

    <xs:simpleType name="currency">
        <xs:restriction base="xs:string">
            <xs:enumeration value="GBP"/>
            <xs:enumeration value="EUR"/>
            <xs:enumeration value="PLN"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

countries.xml은 WSDL에 들어갈 스키마 파일입니다. country, currency 타입을 주고받으며 getCountryRequest으로 요청을 하고 getCountryResponse로 응답을 하는 형태입니다.

우리가 작성한 커스텀 태스크인 genJaxb을 실행하면 JAXB를 이용하여 countries.xml이 언마샬링되어 자바 클래스로 변환됩니다.

이제 서버를 띄운 후, http://localhost:8080/ws/countries.wsdl에 접속하면 Brocker에서 지원하는(저희가 등록한) WSDL을 볼 수 있습니다.

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://spring.io/guides/gs-producing-web-service" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://spring.io/guides/gs-producing-web-service" targetNamespace="http://spring.io/guides/gs-producing-web-service">
    <wsdl:types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://spring.io/guides/gs-producing-web-service">
            <xs:element name="getCountryRequest">
            ...
            </xs:element>    
            <xs:element name="getCountryResponse">
            ...
            </xs:element>
            <xs:complexType name="country">
            ...
            </xs:complexType>
            <xs:simpleType name="currency">
            ...
            </xs:simpleType>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="getCountryResponse">
        <wsdl:part element="tns:getCountryResponse" name="getCountryResponse"> </wsdl:part>
    </wsdl:message>
    <wsdl:message name="getCountryRequest">
        <wsdl:part element="tns:getCountryRequest" name="getCountryRequest"> </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="CountriesPort">
    ...
    </wsdl:portType>
    <wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort">
    ...
    </wsdl:binding>
    <wsdl:service name="CountriesPortService">
    ...
    </wsdl:service>
</wsdl:definitions>

해당 WSDL의 구조를 간략히 살펴보면 다음과 같습니다.

  • types에는 교환될 메시지의 설명 및 해당 메시지에 사용될 타입을 정의되어 있는데, 저희가 등록했던 스키마를 확인할 수 있습니다.

  • message에는 어떠한 메시지가 교환되는지에 대한 내용이 담겨있습니다. 위의 WSDL에서는 getCountryRequest 메시지가 요청될 경우, part 블록이 수행됩니다. part 블록은 타입에 대한 바인딩 정보를 담고 있습니다.

  • portType에는 어떠한 요청이 들어왔을 때, 응답이 어떠한 값으로 나오는지에 대한 명세가 나옵니다.

  • binding에는 메시지 포맷이나 프로토콜, operation에 대한 명세가 나옵니다.

  • service에는 서비스에 관련된 포트들이 나옵니다.

WSDL을 알아냈으니, 이제 SOAP 메시지를 작성하여 정보를 요청해봅시다.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:gs="http://spring.io/guides/gs-producing-web-service">
   <soapenv:Header/>
   <soapenv:Body>
      <gs:getCountryRequest>
         <gs:name>Spain</gs:name>
      </gs:getCountryRequest>
   </soapenv:Body>
</soapenv:Envelope>

해당 메시지를 HTTP Body에 담아 http://localhost:8080/ws로 요청하면 Provider는 Spain에 대한 정보를 응답해줍니다. Content-Type은 text/xml이고 POST 메서드로 요청해야 합니다.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
        <ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service">
            <ns2:country>
                <ns2:name>Spain</ns2:name>
                <ns2:population>46704314</ns2:population>
                <ns2:capital>Madrid</ns2:capital>
                <ns2:currency>EUR</ns2:currency>
            </ns2:country>
        </ns2:getCountryResponse>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

CountryRepository에서 하드 코딩했던 데이터들이 잘 응답되네요.


🎯정리

  • SOAP는 HTTP(일반적으로)에 XML로 통신하는 프로토콜입니다.

  • 전체적인 동작 방식은 다음과 같습니다.

    1. Provider가 Broker에 WSDL 등록

    2. Consumer가 Broker에서 WSDL 검색 및 다운로드

    3. Consumer가 WSDL에 맞게 SOAP 메시지 작성 및 Provider에게 HTTP 요청

    4. Provider가 메시지를 받은 후 서비스 로직 수행

    5. 결과값을 SOAP 메시지로 작성 후 Consumer에게 반환


🔖참고