# SOAP로 Data 주고받기

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

### 💡**SOAP이 무엇인가요?**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689729569176/c4fea085-0d6c-4d73-a552-96cc941eb480.png align="center")

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

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

### 💡**SOAP 메시지 구조**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689730448720/ffd6fab1-fd87-41ad-9f22-a513a19955f9.png align="center")

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

* **SOAP 엔벨로프(Envelope)**: 메시지의 모든 데이터를 캡슐화하고 XML 문서를 SOAP 메시지로 식별합니다. 모든 SOAP 메시지의 루트 요소이며, 두 개의 하위 요소를 포함합니다.
    
* **SOAP 헤더(Header)**: SOAP 엔벨로프의 `선택적 하위 요소`이며, SOAP 메시지에 대한 응용프로그램별 정보(보안, 트랜잭션 관리, 라우팅 등)가 포함되어 있습니다. SOAP 헤더에 정의된 속성은 수신인이 SOAP 메시지를 처리하는 방법을 정의합니다.
    
* **SOAP 본문(Body)**: 메시지의 최종 수신자를 위한 정보를 포함하는 SOAP 엔벨로프의 `필수 하위 요소`입니다. 웹 서비스에서 응용 프로그램으로 전송해야 하는 실제 메시지의 세부 정보가 포함됩니다.
    
* **SOAP 결함(Fault)**: 오류에 사용되는 SOAP 본문의 `선택적 하위 요소`입니다. SOAP 오류가 생성되면 HTTP 500 오류로 반환됩니다. 오류 메시지에는 오류 코드, 문자열, 액터 및 세부 정보가 포함됩니다.
    

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

```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>
```

* **Envelope**의 `xmlns:soap` 네임스페이스 값은 항상 [`"http://www.w3.org/2003/05/soap-envelope/"`](http://www.w3.org/2003/05/soap-envelope/)이어야 합니다. 또한 SOAP 메시지에는 기본 인코딩이 없습니다. `soap:encodingStyle`로 xml 문서에 적용될 인코딩 규칙을 정의합니다.
    
* **Header**의 Trans 엘리먼트에 있는 `soap:mustUnderstand` 속성은 SOAP 헤더 요소의 속성 중 하나로, 속성 값이 "1"로 설정되어 있으면 필수 처리 항목이라는 뜻으로(선택 처리라면 0), SOAP 수신자는 이 Trans 엘리먼트를 이해하고 처리해야 합니다. 만약 수신자가 해당 요소를 처리할 수 없는 경우, Fault를 반환할 수 있습니다.
    
* **Body**에는 실제 메시지가 포함되어 있습니다. 위의 예시는 사과의 가격을 요청하는 메시지입니다. `m:GetPrice` 및 `m:Item` 요소는 응용 프로그램별 요소입니다. SOAP 네임스페이스의 일부가 아닙니다.
    

### 💡**SOAP 동작 방식**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689740649848/1a06e451-37e4-4dd5-8b2b-214ac743af16.png align="center")

SOAP는 **WSDL**을 통해 서비스가 현재 어떤 내용들을 제공해주는지 알려줍니다. `Provider`는 `Broker`에 사용 가능한 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](https://www.w3.org/TR/wsdl.html) 참고 바랍니다.

### 💡**Spring Boot로 SOAP 써보기**

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

```xml
<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`이 언마샬링되어 자바 클래스로 변환됩니다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1689750692394/ba7a89cd-7784-48ff-b575-5a2867d67cd1.png align="center")

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

```xml
<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 메시지를 작성하여 정보를 요청해봅시다.

```xml
<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](http://localhost:8080/ws)로 요청하면 Provider는 Spain에 대한 정보를 응답해줍니다. Content-Type은 text/xml이고 POST 메서드로 요청해야 합니다.

```xml
<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에게 반환
        

---

### 🔖**참고**

* [https://aws.amazon.com/ko/compare/the-difference-between-soap-rest/](https://aws.amazon.com/ko/compare/the-difference-between-soap-rest/)
    
* [https://www.ibm.com/docs/ko/integration-bus/10.0?topic=ssmkhh-10-0-0-com-ibm-etools-mft-doc-ac55770--htm](https://www.ibm.com/docs/ko/integration-bus/10.0?topic=ssmkhh-10-0-0-com-ibm-etools-mft-doc-ac55770--htm)
    
* [https://www.techtarget.com/searchapparchitecture/definition/SOAP-Simple-Object-Access-Protocol](https://www.techtarget.com/searchapparchitecture/definition/SOAP-Simple-Object-Access-Protocol)
    
* [https://gruuuuu.github.io/programming/soap/](https://gruuuuu.github.io/programming/soap/)
    
* [https://www.w3schools.com/xml/xml\_soap.asp](https://www.w3schools.com/xml/xml_soap.asp)
    
* [https://www.nextree.co.kr/p11410/](https://www.nextree.co.kr/p11410/)
    
* [https://brewagebear.github.io/soap-and-wsdl/](https://brewagebear.github.io/soap-and-wsdl/)
    
* [https://spring.io/guides/gs/producing-web-service/](https://spring.io/guides/gs/producing-web-service/)
