LazarusCompleteGuide:11.2

From 흡혈양파의 번역工房
Revision as of 09:16, 16 March 2013 by Onionmixer (talk | contribs) (오타수정)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

웹서비스

웹서비스는 RPC(원격 절차 호출)에 지나지 않는다-오랜 독자들은 아마도 이 점을 알 것이다. 하지만 웹서비스는 이 단순한 RPC 함수와 여러 면에서 구별된다. 웹사이트 기술을 통해 두 개의 소프트웨어 컴포넌트는 서로 원격으로 통신할 수 있다. 두 컴포넌트는 바로 제공자와 (서비스 제공자 또는 서버) 소비자를 (서비스 소비자 또는 클라이언트) 일컫는다. 그들은 컴포넌트의 위치, 하드웨어, 운영체제, 또는 개발에 사용되는 언어와 상관없이 서로 통신할 수 있다.

그림 11.2: 블랙박스 원리


간단히 말해, 웹서비스는 인터넷 상의 고정된 주소에서 호출되는 프로그램 함수이다. 프로그램들 간 통신은 XML 포맷으로 표준화된 메시지의 교환으로 발생한다. 웹서비스는 프로그램으로 통합할 수 있고, 나중에 프로그램의 일부인 것처럼 호출된다. 개발자는 함수가 어떻게 구현되었는지 알 필요가 없으며, 서비스의 URL만 필요로 한다.


W3C는 이 목적을 달성하기 위한 기준을 설정하도록 만들어진 정의를 발표하였다:

  • SOAP (단순 객체 접근 프로토콜, http://www.w3.org/TR/soap): 소비자와 제공자 간 교환된 메시지의 포맷.
  • WSDL (웹 서비스 기술 언어, http://www.w3.org/TR/wsdl): 서비스 인터페이스의 형식적 정의: 각 서비스가 제공하는 것과 서비스를 어떻게 호출하고 어디로 호출하는지.


그 외 사양은 W3C와 상관없는 그룹에서 정의하였다:

  • XmlRPC (http://www.xmlrpc.com): 원격 절차 호출 모델로, 교환된 메시지가 XML을 기반으로 하고 HTTP 상에서 전송된다. XmlRPC는 SOAP보다 훨씬 간단하다.* JsonRPC over HTTP (http://json-rpc.org): XmlRPC와 비슷하지만 메시지가 JSON 포맷된다.


마지막으로, 조금 덜 형식적인 REST 스타일의 웹 서비스가 있다 (표현 상태 전송방식). http://www.ics.uci.edu/~fieling/pubs/dissertation/top.htm 을 참고한다. FPC/라자루스 개발자는 웹서비스로 작업 시 웹 서비스 툴킷(WST)을 사용해야 한다. 이 소프트웨어 패키지는 웹서비스를 소비하고 (소비자 측) 생성하는 것을 (제공자 측) 훨씬 수월하게 해준다. 이는 개발자들이 교환된 메시지의 포맷과 (SOAP, XMLRPC, 또는 JSON) 전송 프로토콜을 (HTTP, TCP, 그리고 후에 간략히 살펴볼 WST에 특정적인 프로토콜) 코딩하는 데 수반되는 세부내용에 휘말리는 수고를 덜어준다.


WST에서는 서비스 소비자와 (클라이언트) 서비스 제공자 (서버) 간 교환되는 메시지의 포맷이 전송 프로토콜에 의존하지 않으며, 그 반대도 마찬가지다. 오히려 그 반대로 WST로 생성된 프로그램이ㅡ클라이언트와 서버 모두ㅡ다른 메시지 포맷과 전송 프로토콜의 결합과 관련해 매우 유연하다. WST는 다음과 같은 메시지 포맷을 지원한다: SOAP 1.1, XmlRPC, JSON, 속도와 메모리 성능에 최적화된 커스텀 바이너리 포맷.


서버 측에서는 특별히 WST가 아래의 경우 호스팅된 서비스의 생성을 허용한다:

  • Indy 기반의 HTTP 서버로서 단일 실행 파일 내
  • Indy 기반의 TCP 서버로서 단일 실행 파일 내
  • Synapse 기반의 TCP 서버로서 단일 실행 파일 내
  • Apache HTTP 서버 모듈
  • 동적 라이브러리 (WST에 특정적)


WST의 서버 측은 최소한의 코드를 작성하여 커스텀 애플리케이션에서 쉽게 관리될(hosted) 수 있다.


클라이언트 측에서 WST는 여러 전송 구현을 제공한다:

  • Indy 기반의 HTTP 전송
  • Indy 기반의 TCP 전송
  • Synapse 기반의 HTTP 전송
  • Synapse 기반의 TCP 전송
  • ICS 기반의 HTTP 전송
  • ICS 기반의 TCP 전송
  • WST에 한정된 동적 라이브러리 전송 프로토콜
  • 서버와 클라이언트가 동일한 실행 모듈에 있는 in-process 전송 프로토콜


네트워크 접근을 통한 원격 기능을 위해 Indy, ICS, Synapse 프레임워크 중 하나를 설치해야 한다. http://www.indyproject.org/Sockets/fpc/ 에서 이용 가능한 Indy 컴포넌트는 FPC와 함께 이용할 수 있으며, Indy 패키지는 라자루스에 설치 가능하다. 인터넷 컴포넌트 스위트(suite) ICS는 http://www.overbyte.be/ 에서 관리된다. Synapse는 http://www.ararat.cz/synapse/ 에서 다운로드할 수 있다. 이 세 가지 스위트(suite)는 테스트를 거친(well-tested) TCP/IP 컴포넌트를 제공하며, WST는 세 가지를 모두 HTTP 상의 전송 계층으로서 이용할 수 있다. 서버 서비스는 Indy로부터 HTTP 서버를 이용하거나 ICS로부터 TCP-서버를 이용해 개발할 수 있다.


WST 자체는 아래 주소에서 무료로 이용 가능하다.

http://inoussa12.googlepages.com/webservicetoolkitforfpc%26lazarus 또는 http://wiki.freepascal.org/Web_Service_Toolkit


본 장에서는 라자루스와 WST를 이용해 웹서비스를 정의하고 사용하는 것이 얼마나 쉬운지를 보여주고자 한다.


서버 프로그래밍하기

웹 서비스는 다음 세 가지 기본 모듈로 구성된다.

  • 서비스가 무엇을 제공하는지를 정의하는 인터페이스(interface)
  • 서비스를 어떻게 제공할 것인지 명시하는 바인딩(binding)
  • 서비스를 어디에 제공할 것인지 명시하는 종단점(endpoint)


인터페이스는 소비자들이 서비스의 기능을 사용하면서 요청할 수 있는 동작(operations)과 관련해 서비스가 제공하는 것을 정의한다. 이는 서비스 제공자와 소비자 간 교환되는 메시지 및 데이터 구조뿐만 아니라 동작도 정의한다.


바인딩은 이러한 메시지의 내부 세부사항과 전송 프로토콜을 명시하는데, 특히 동작의 스타일과 요구조건 (SOAP 헤더와 바인딩 스타일), 메시지 포맷 프로토콜 (SOAP 직렬화 또는 JSON 또는 XmlRPC 직렬화), 전송 프로토콜을 (HTTP 또는 TCP) 명시한다.


종단점은 서비스를 어디 위치시킬 것인지, 이 주소에서 어떤 바인딩을 이용할 수 있는지를 명시한다. 예를 들어 종단점은 주소에 위치한 웹 서비스로 SOAP 인터페이스를 나타낼 수 있다: http://wst-services/services/CalcService?format=soap


WST를 이용한 웹서비스 저작(authoring)은 주로 네 가지 기본 단계로 이루어진다: 서비스 설명하기; 서비스에 대한 오브젝트 파스칼 지원 파일 생성하기; 서비스의 구현 클래스 완성하기; 서비스를 애플리케이션 서버에 호스팅하기.


서버프로그래밍의 단계

단계 1: 서비스 설명하기

WST는 표준 웹 서비스 기술 언어(WSDL)를 이용해 서비스를 설명한다. 이를 위해 WSDL 파일을 그 네이티브 저장 포맷으로 생성하는 타입 라이브러리 에디터(Type Library Editor)를 제공한다. 이는 그래픽 사용자 인터페이스를 갖고 있으므로 WSDL 파일을 수동으로 코딩할 필요가 없다.


타입 라이브러리 에디터는 두 가지 포맷으로 제공된다: 어떤 IDE든 그 외부에서 작동하는 자립형 라자루스 애플리케이션 typ_lib_edtr.exe가 첫 번째이다. 애플리케이션 소스는 WST를 이용해 제공되며 type_lib_edtr 폴더에 위치한다. 두 번째는 라자루스 IDE에 여러 메뉴 엔트리를 추가하는 라자루스 확장 패키지이다. 설치 시 라자루스 IDE 내부에서 타입 라이브러리 에디터를 호출하는 프로젝트 메뉴에 타입 라이브러리 에디터 엔트리를 추가한다. 패키지 소스는 WST를 이용해 제공되며 ide\lazarus 폴더에 위치한다.


단계 2: 서비스를 위한 오브젝트 파스칼 지원 파일 생성하기

단계 1에서 WSDL 파일이 생산되었다면 이 파일은 프리 파스칼 컴파일러가 사용 가능한 오브젝트 파스칼로 해석되어야 한다. WST는 이 과정을 촉진시키기 위해 두 개의 툴을 제공한다:

  • 이전 단계에서 살펴본 GUI 타입 라이브러리 에디터는 'File ▷ Save generated files...' 메뉴 항목을 통해 호출하거나, 에디터 페이지에 보이는 소스 파일을 오른쪽 마우스로 클릭해 'Save generated files...' 팝업 메뉴 항목을 통해 호출되는 내보내기 대화창을 이용하는 방법이 있다. 이 대화창에서 사용자는 내보내야 하는 파일 타입을 선택하고, 파스칼로 해석이 시작되기 전에 여러 옵션을 설정한다.
  • 명령 행 프로그램 ws_helper.exe. 이 유틸리티는 WSDL 파일과 XSD(XML 스키마 정의) 파일을 오브젝트 파스칼 언어 파일로 해석하기 위한 여러 옵션들을 제공한다. 그 성능에 대한 개요를 확인하기 위해서는 파라미터 없이 실행 시 콘솔에 사용 세부내용이 출력될 것이다.


서비스 제공자 구현에는 아래 파일들이 필요하다:

  • 서비스 인터페이스에 적절한 인터페이스 파일. 이 파일을 데이터 구조와 (열거형, 복합 구조, ...) WSDL 파일 내 동작을 정규 오브젝트 파스칼 인터페이스로 표현을 포함한다. 서비스 개발자는 이 파일을 수정해선 안 된다.
  • 서비스 바인더 클래스를 포함하는 바인더(binder) 파일. 이 클래스는 메시지의 직렬화와 역직렬화, 호출 스택의 준비, 서비스 구현 클래스로 호출하기를 책임진다. 서비스 개발자는 이 파일 역시 수정해선 안 된다.
  • 실제 서비스 구현 클래스를 포함하는 구현 파일. 이 파일은 인터페이스 파일에 정의된 서비스 인터페이스를 구현하는 클래스에 대한 골격(skeleton)을 포함할 것이다.


이러한 파일들은 Type Library Editor 또는 ws_helper.exe 에 의해 생성된다.


단계 3: 서비스의 구현 클래스 완성하기

웹 서비스는 IInvocable 로부터 상속되는 일반 인터페이스에 의해 오브젝트 파스칼에 표현된다. 서비스 구현 클래스-실제로 서비스 기능을 제공하는 클래스-는 TBaseServiceImplementation 또는 TactivableServiceImplementation 으로부터 구현되는 오브젝트 파스칼 클래스이다. 이 클래스는 서비스 인터페이스를 구현한다. WST 툴이 생성한 구현 파일은 서비스 개발자가 완성해야 하는 skeleton 구현 클래스를 포함한다.



단계 4: 서비스를 애플리케이션 서버로 호스팅하기

앞의 세 단계가 완료되면 서비스는 이제 호스트라 불리는 애플리케이션 서버로 포함될(embed) 준비가 된 것이다. 호스팅 환경은 서비스를 위한 전송 기반시설을 제공한다. WST 아키텍처는 WST 기반의 서비스를 다양한 서비스로 호스팅하기에 충분히 유연하다. 특히 WST는 Indy 기반의 HTTP 및 TCP 서버, Synapse 기반의 TCP 서버, 동적 연결/공유된 라이브러리 (dll/dso) 기반의 서버, Apache HTTPD 서버 기반의 모듈에 대해 지원한다.


서비스 인터페이스의 정의

본 장에서 첫 번째 예제는 간단한 정수 계산 서비스를 다룬다. 이 서비스는 두 가지 정수 연산을 제공한다: Add와 Subtract. 서비스 인터페이스를 정의하기 위해 우리는 WST 타입 라이브러리 에디터를 사용할 것이다. 그 인터페이스에 대해 두 가지 연산을 정의하는 인터페이스가 생성된다. 우리는 인터페이스 생성 대화창을 이용해 인터페이스를 생성할 것인데, 이는 아래 스크린숏에서 보이는 바와 같이 'Edit ▷ Create Interface' 메뉴 항목을 통해 이용할 수 있다. 인터페이스 생성 대화창은 에디터의 소스 보기 페이지 중 아무 곳이나 오른쪽 마우스를 클릭하면 팝업되는 'Create Interface' 메뉴 항목을 통해 이용할 수 있다.

그림 11.3: 'Create Interface' 메뉴 항목


그림 11.4는 인터페이스 프로퍼티를 표시하고, 그림 11.5는 인터페이스의 동작을 생성하는 대화창의 모습이다.

그림 11.4: 서비스 프로퍼티 대화창


Operation properties 대화창은 동작 인수(operation argument)의 추가, 편집, 삭제 기능을 제공한다. Argument 대화창은 (아래 그림) 동작에 필요한 인수를 추가하기 위해 Add 라는 이름으로 사용되어 왔다. 모든 동작이 인터페이스에 추가되면 그림 11.7과 같이 'Edit binding' 팝업 메뉴 항목을 통해 바인딩 에디터 대화창이 호출된다.

그림 11.5: 동작을 생성하는 대화창


그림 11.6: 인수 프로퍼티를 표시하는 대화창


그림 11.7: Edit 바인딩 메뉴 항목


그림 11.8: 바인딩의 프로퍼티


WSDL이 완성되면 서비스에 필요한 오브젝트 파스칼 파일이 생성되어야 한다. 이를 위해선 'Files ▷ Save generated files...' 메뉴 항목을 통해 'Export file options...' 대화창을 사용해야 한다. 이를 그림 11.9에 표시하였다.

그림 11.9: 파일 대화창 생성하기


힌트: Generate easy access interface for wrapped parameters(래핑된 파라미터에 대해 쉬운 접근 인터페이스 생성하기 체크상자는 생성자에게 래핑된 리터럴(literal) 문서 웹 서비스에 대해 더 간단한 인터페이스를 생성하라고 지시한다. 이는 주로 Microsoft .Net 웹 서비스의 구현에서 사용된다.


인터페이스 파일 추출에 뒤따라 생성된 오브젝트 파스칼 인터페이스 타입을 제공한다. 서비스 기본 주소, 스타일 (문서 또는 RPC), 인코딩 등과 같이 연산의 커스텀 옵션과 서비스를 기록하는 Register_calcservice_ServiceMetadata() 프로시저(calcservice.pas 내)를 주목한다. 이 루틴은 호출 스택의 직렬화를 적절히 처리하기 위해 바인더와 프록시에 의해 사용된다.

ICalcService = interface(IInvokable)
  ['{090EADB7-6B25-4F35-9419-2AF113D44BF8}']
  function Add(const A: integer; const B: integer): integer;
  function Subtract(const A: integer; const B: integer): integer;
end;

procedure Register_calcservice_ServiceMetadata();
var
  mm : IModuleMetadataMngr;
begin
  mm := GetModuleMetadataMngr();
  mm.SetRepositoryNameSpace(sUNIT_NAME, sNAME_SPACE);
  mm.SetServiceCustomData(sUNIT_NAME, 'ICalcService',
     'TRANSPORT_Address',
     'http://127.0.0.1:8000/services/ICalcService');
  mm.SetServiceCustomData(sUNIT_NAME, 'ICalcService',
     'FORMAT_Style','rpc');
  mm.SetOperationCustomData(sUNIT_NAME, 'ICalcService',
     'Add','_E_N_','Add');
  mm.SetOperationCustomData(sUNIT_NAME,'ICalcService','Add',
     'FORMAT_Input_EncodingStyle','literal');
  mm.SetOperationCustomData(sUNIT_NAME,'ICalcService','Add',
     FORMAT_OutputEncodingStyle','literal');
............{same for subtract}
end;


서비스 구현 클래스

구현 클래스는 서비스가 제공한 기능을 실제로 구현하는 클래스이다 (우리 예제에서는 Add와 Subtract 연산). WST는 ws_helper를 통해 구현 파일을 생성하는데, 이 파일은 서비스 인터페이스를 구현하는 TCalcService_ServiceImp라 불리는 skeleton 클래스를 포함한다. 이제 이 클래스를 완성하기만 하면 된다.


아래 코드 조각은 클래스 정의와 Add() 메소드 구현을 보여준다. 우리는 Add() 구현을 수동으로 완료하였으므로 더 이상 골격(skeleton)이 아니라는 점을 명심한다. 서비스 구현이 제공되고 나면 서비스는 서비스 구현 레지스트리에 등록되어야 하는데, 그래야만 WST 런타임이 그것에 대해 알기 때문이다. 생성된 구현 유닛은 이를 실행하는 RegisterCalcServiceImplementationFctory 프로시저를 포함한다.

TcalcService_ServiceImp=
     class(TBaseServiceImplementation, ICalcService)
Protected
   function Add(const A: integer; const B: integer): integer;
   function Subtract(const A: integer; const B: integer): integer;
end;
...
function TCalcService_ServiceImp.Add(
   const A: integer; const B: integer): integer;
begin
   Result := A + B;
end;


애플리케이션 서버

서비스가 정의 및 구현되면 애플리케이션 서버에 의해 호스팅되어야 한다. 애플리케이션 서버는 서비스에 대한 전송 기반 시설을 제공한다. 우리가 빌드하고자 하는 HTTP 애플리케이션 서버의 경우, 아래를 의미한다:

  • 클라이언트 연결을 듣기 위한 HTTP 서버가 될 것이다.
  • WST 런타임으로 클라이언트 요청 페이로드(payload)를 운반한다.
  • 결과가 되는 응답 스트림을 클라이언트에게 리턴한다.


WST 런타임은 실제 전송 계층을 구현하는 server listener object model로서 설계된다. 이는 그것이 클라이언트 요청을 듣고, 클라이언트 연결을 수락하며, 연결 수명에 걸쳐 통신 및 연결을 처리하는 객체를 제공함을 의미한다. 각 지원되는 프로토콜마다 특수화된 listener 객체가 구현된다. 예제에서는 Indy 기반의 HTTP 서버 listener가 사용된다. 프로그램의 메인 유닛에 대한 완전한 소스 코드를 아래 표시하였다. 서버의 완전한 소스 코드는 sources\web-services\server 폴더에서 찾을 수 있다.


프로그램은 SOAP 직렬화 포맷을 Server_service_RegisterSoapFormat() 에 등록하여 WST 런타임이 이 포맷을 수용하도록 한다. 해당 루틴은 server_service.soap.pas 유닛에 위치한다. 이후 이는 CalcService에 대한 구현과 바인더를 WST 런타임 라이브러리에 등록한다. 구현과 바인더는 calcservice_imp와 calcservice_binder 유닛에서 각각 정의된다.


이후 그것은 HTTP 요청을 듣게 될 (HTTP listener) Indy 기반의 객체를 생성한다. 객체 인스턴스는 TwstListener의 인스턴스로서 선언되는 AppObject 변수에 저장된다. 이 클래스는 모든 listener가 상속되는 기반 클래스이다. TwstListener는 추상 클래스로서, WST listener 모델을 정의한다. 이 클래스는 server_listener 유닛에 정의된다. TwstIndyHttpListener는 기반 listener 클래스로부터 상속되는 클래스이며, 애플리케이션 서버를 위해 INDY HTTP 라이브러리를 이용해 HTTP 서버 전송 기반 시설을 구현한다. INDY HTTP listener는 indy_http_server 유닛에 정의된다. 이후 프로그램은 listener를 호출하고, 들어오는 요청을 기다린다.


아직 소개하지 않은 유닛으로 metadata_service가 있다. 이는 WST 런타임 레지스트리에서 등록된 서비스를 노출하는 메타데이터 서비스 구현을 제공한다. HTTP listener는 브라우저를 열고 http://127.0.0.1:8000/ 주소를 입력해 간단히 테스트할 수 있다. 등록된 서비스 리스트가 포함된 HTTP 페이지가 표시될 것이다. 이는 WSDL 포맷으로 된 서비스 기술(description)의 링크를 포함할 것이다. 이러한 기술은 런타임 시 생성된다.

program srv_calc;
  {$mode objfpc}{$H+}
uses
  {$IFDEF UNIX}cthreads,{$ENDIF}
  Classes, SysUtils,
  server_listener, indy_http_server,
  server_service_intf, server_service_soap, metadata_service,
  calcservice, calcservice_binder, calcservice_imp;
var
  AppObject: TwstListener;
begin
  Server_service_RegisterSoapFormat();
  RegisterCalcServiceImplementationFactory();
  Server_service_RegisterCalcServiceService();
  AppObject := TwstIndyHttpListener.Create('127.0.0.1',8000);
  try
     WriteLn('"Web Service Toolkit" HTTP Server listening at:');
     WriteLn('');
     WriteLn('http://127.0.0.1:8000/');
     WriteLn('');
     WriteLn('Press enter to quit.');
     AppObject.Start();
     ReadLn();
  finally
     FreeAndNil(AppObject);
  end;
end.


라이브러리 기반의 애플리케이션 서버

WST 웹 서비스는 동적 배열을 이용해 (윈도우 동적 연결된 라이브러리 DLL, 또는 유닉스 공유 객체 SO) 호스팅될 수 있다. 런타임 시에 라이브러리는 클라이언트 프로세스로 로딩되어 클라이언트와 서버 코드가 동일한 프로세스에서 실행되도록 한다. 라이브러리 기반의 애플리케이션 서버는 고성능 시나리오에 사용할 수 있는데, 서버와 클라이언트가 동일한 주소 공간을 공유하고 어떤 원격 프로토콜 스택도 사용되지 않기 때문이다. 라이브러리 전송 프로토콜은 클라이언트 요청과 서버 응답 버퍼를 공유하기 위해 로컬 프로세스 주소 공간을 사용한다. 모든 라이브러리 기반의 애플리케이션 서버는 library_server_intf 유닛에 위치한 wstHandleRequest 함수를 내보내는데, 이는 클라이언트 라이브러리 프로토콜 구현에서 서버 코드를 호출 시 사용한다. 아래 코드는 함수 서명(function signature)을 보여준다. ARequestBuffer 파라미터는 요청 및 응답 버퍼를 공유하는 데 사용되는 파라미터이다.

그림 11.10: 클라이언트 프로세스 내 클라이언트 모듈과 서버 모듈


라이브러리 기반의 애플리케이션 서버는 플러그인 개발에 적합한데, 플러그인 인터페이스가 고수준 언어로 정의되므로 개발자는 저수준 라이브러리 코드에 대해 염려할 필요가 없기 때문이다. 예제 서버의 완전한 소스 코드는 sources\web-services\protocols\library_server 폴더에 위치한다. 이 서버는 sources\web-services\protocols\client 폴더에 위치한 클라이언트와 함께 사용할 수 있으며, 실행 시 라이브러리 프로토콜을 선택한다.

unit = library_server_intf.pas>
function wstHandleRequest (
  ARequestBuffer: IwstStream;
  AErrorBuffer: Pointer;
  var AErrorBufferLen: LongInt
  ):LongInt;


SOAP 헤더

SOAP 헤더는 SOAP 메시지에서 선택적 부분으로, 메시지에 관한 메타 정보를 포함한다. 예를 들어, 암호화(encryption) 정보에 사용되거나 라우팅 명령어를 제공할 때 사용할 수 있다. WST는 서버 측과 클라이언트 측 모두에서 SOAP 헤더를 지원한다. WST에서 THeaderBlock 자손들은 (아래 표시) 헤더 엔트리를 표현하는 데에 사용된다. 이 클래스는 세 가지 중요한 프로퍼티를 가진다. 첫째, Direction은 헤더가 들어오는지(incoming) 아니면 나가는(outgoing) 헤더인지를 명시한다. 둘째, mustUnderstand는 이 헤더의 이해가 의무임을 서버로 지시하는 동일한 이름으로 된 SOAP 헤더 프로퍼티로 매핑(map to)한다. 셋째, Understood는 헤더가 이해되었는지 아닌지를 나타내기 위해 서비스 개발자에 의해 사용된다. 헤더가 의무일 경우 (mustUnderstand =1), WST 런타임은 이 프로퍼티가 True인지 아닌지를 검사하고, 아닐 경우 호출자에게 예외를 리턴한다. 따라서 서버 측에서는 이해된 헤더는 모두 표시되어 있어야 한다.


ICallContext 인터페이스는 (아래 참고) 호출하거나 호출을 실행하는 과정에서 헤더로 접근, 헤더를 추가, 제거하기 위한 메소드를 제공한다. 예제의 서버 측 프로젝트와 클라이언트 측 프로젝트의 완전한 코드는 sources\web-services\headers 폴더에서 찾을 수 있다.

type
  THeaderDirection = ( hdOut, hdIn );
  THeaderBlock = class(TBaseComplexRemotable)
...
public
  property Direction: THeaderDirection read FDirection write
     FDirection;
  property Understood: Boolean read FUnderstood write FUnderstood;
published
  property mustUnerstand: Integer read FmustUnderstand
  write SetmustUnderstand stored HasmustUnderstand
end;

ICallContext = Interface
  ['{855EB8E2-0700-45B1-B852-2101023200E0}']
  procedure AddObjectToFree(const AObject: TObject);
  procedure Clear();
  function AddHeader(
     const AHeader: THeaderBlock;
     const AKeepOwnership: Boolean
  ):Integer;
  function GetHeaderCount(const Adirections:
     THeaderDirections):Integer;
  function GetHeader(const AIndex: Integer: THeaderBlock;
  procedure ClearHeaders(const ADirection: THeaderDirection);
end;


서버 측 SOAP 헤더

WST Type Library Editor를 이용해 헤더 엔트리 클래스를 정의하는 것은 클래스를 생성하여 클래스를 직접 (또는 부모 클래스를 통해) THeaderBlock에서 상속하는 것만큼이나 단순하다. 그림 11.11은 이번 장의 예제에 사용된 TLoginHeader를 정의 시 사용하는 대화창을 표시한다.

그림 11.11: SOAP-헤더 클래스 정의


구현 클래스 내 서버 측에서 실행되는 호출의 컨텍스트는 모든 서비스 구현 클래스가 파생되는 TBaseServiceImplementation의 GetCallContext 메소드를 통해 얻을 수 있다. 서비스 확장 시 컨텍스트는 ProcessMessage 메소드의 파라미터이다. 서버로 전송되어야 하는 헤더가 추가된 경우 그 방향은 hdOut으로 설정되어야 함을 명심한다.


클라이언트 측 SOAP 헤더

클라이언트 측 헤더를 처리하기 위해서는 헤더 클래스가 THeaderBlock에서 직접 상속되거나 부모 클래스를 통해 상속되도록 해야 한다. WST 웹 서비스의 경우WST 타입 라이브러리 에디터가 생성한 WSDL 파일은 그러한 지시를 포함한다. 호출의 컨텍스트는 아래 코드 조각에서 볼 수 있듯이 연산자를 이용해 서비스 프록시를 캐스트(cast)함으로써 얻을 수 있다. 서버로 전송되어야 하는 헤더가 추가된 경우 그 방향은 hdOut으로 설정되어야 함을 명심한다.

callContext := theProxy as ICallContext;


서버 측 다중 직렬화 포맷 지원

WST 웹 서비스를 관리하는 애플리케이션 서버는 다양한 직렬화 포맷을 사용하는 클라이언트를 수용하도록 개발할 수 있다. WST 서버 측은 아래의 직렬화 포맷을 지원한다:

  • SOAP: 지원되는 버전은 널리 사용되는 SOAP 1.1 버전이다. 구현은 server_service_soap 유닛에서 제공한다.
  • XmlRPC: 구현은 server_service_xmlrpc 유닛에서 제공한다.
  • JSON: JsonRPC의 WST 구현은 JsonRPC 1.0에 표시된 P2P(peer-to-peer) 방식이 아닌 클라이언트-서버 지향적으로 이루어진다. WST 구현은 JsonRPC 1.0 포맷과 JsonRPC 1.1 포맷을 모두 지원한다. 구현은 server_service_json 유닛에서 찾을 수 있다.
  • Binary: 이것은 WST 커스텀 바이너리 포맷이다. 구현은 server_binary_formatter 유닛에서 찾을 수 있다.

직렬화 프로토콜을 사용하기 위해서는 WST 런타임으로 그 구현을 등록해야 한다. 모든 직렬화 포맷 구현 유닛은 등록 절차를 제공하는데, 이 절차는 listener 객체를(들을) 시작하기 전에 애플리케이션 서버를 관리함으로써 호출되어야 한다. 등록 절차 이름은 아래 발췌한 소스 코드에서 볼 수 있듯이 이름만으로 기능을 알 수 있다. (예제의 전체 소스 코드는 sources\web-services\protocols 폴더에 위치한다.) 서버 프로젝트는 교환된 메시지를 파일로 로그(log)하기 위해 로깅 확장자(logging extension)를 사용한다. 클라이언트 프로젝트는 사용자가 직렬화 포맷을 선택하도록 해준다.

Server_service_RegisterSoapFormat();
Server_service_RegisterXmlRpcFormat();
Server_service_RegisterJsonFormat();
Server_service_RegisterBinaryFormat();


서버 측 다중 전송 프로토콜 지원

WST listener 모델은 다중 전송 프로토콜의 처리를 간소화한다. 여러 프로토콜을 처리하기 위해서는 애플리케이션 서버가 필요한 listener를 생성하고 시작하기만 하면 된다. WST 서버 측은 아래와 같은 전송 프로토콜을 지원한다:

  • HTTP: 구현은 (INDY 소켓 라이브러리 기반) indy_http_server 유닛에서 제공한다.
  • TCP: WST는 다른 소켓 라이브러리를 이용해 두 개의 전송 구현을 제공한다. INDY 소켓 라이브러리와 (indy_tcp_server 유닛에서 구현) SYNAPSE 소켓 라이브러리 (synapse_tcp_server 유닛에서 구현).


각 활성화된 listener는 유일한 포트를 가져야 한다. 아래 소스 코드 부분은 HTTP와 TCP listener의 생성 및 실행을 보여준다. 예제의 전체 소스 코드는 sources\web-services\protocols\server 폴더에 위치한다.

...
listener := TwstIndyHttpListener.Create('127.0.0.1',8000);
listenerList.Add(listener);
listener.Start();
listener := TwstIndyTcpListener.Create('127.0.0.1',1234);
listenerList.Add(listener);
listener.Start();
...


클라이언트 프로그래밍하기

개요

웹 서비스 툴킷(WST)은 오브젝트 파스칼 개발자들이 서비스 제공자가 실행 중인 운영체제나 하드웨어와 상관없이, 그리고 서비스 제공자를 프로그래밍하는 데 사용된 개발언어와 상관없이 웹 서비스를 사용하는 소프트웨어를 개발하도록 해준다. 이를 가능하도록 만들기 위해 WST는 WSDL 기술 파일을 파싱한 후 그에 설명된 서비스를 나타내는 오브젝트 파스칼 언어 파일을 생성한다. 후자는 일반 오브젝트 파스칼 인터페이스 정의를 포함하는데, 이러한 정의의 프로시저와 함수는 서비스 동작의 기술(description)에 일치할 뿐 아니라 필요한 타입 정의를 모두 포함시킨다. 파일은 또한 서비스 제공자와 소비자 간 교환되는 메시지를 구성 시 필요한 전송 프로토콜과 직렬화와 관련된 옵션을 포함한다.


WST는 이전 단계의 결과를 바탕으로 프록시 클래스를 생성한다. 이것은 위에서 서비스를 나타내도록 생성된 인터페이스를 구현하는 오브젝트 파스칼 클래스이다. 프록시 클래스는 로컬 클라이언트와 원격 서비스 제공자 사이에 호출을 모으는데, 이는 오브젝트 파스칼 유닛 파일에 따로 위치되어 있다.


서비스는 프록시 클래스를 통해 오브젝트 파스칼 개발자에겐 일반적인 파스칼 인터페이스로 나타난다. 개발자는 이를 이용해 서비스 구현을 나타내는 인스턴스를 생성하고 서비스가 제공하는 기능을 소비한다.

그림 11.12: WST 클라이언트 프로세스


WST는 WSDL 파일을 파싱하는 두 가지 방법을 제공한다: 콘솔 프로그램 ws_helper.exe는 웹 서비스 기술 언어(WSDL) 파일을 XML 스키마 정의(XSD) 파일로 파싱하고, WSDL 가져오기(importer) 마법사는 wst_design.lpk 패키지로서 라자루스에 통합된다. 이 파일은 wsdl-<versionnumber>.zip 패키지의 일부인데, 이는 저자의 구글 페이지에서 다운로드할 수 있다: http://inoussa12.googlepages.com/webservicetoolkitforfpc%26lazarus


다운로드가 끝나면 아카이브를 라자루스 설치 컴포넌트 디렉터리로 압축 해제하고, 디렉터리명에서 버전을 제거해야 한다. 라자루스에서는 wst\ide\lazarus 디렉터리에서 wst_design.lpk 파일을 선택하기 위해 Package→Open package 파일 메뉴를 사용해야 한다 (그림 11.13 참고).

그림 11.13: Package→Open package 파일 메뉴 항목을 이용해 .lpk 파일을 선택하면 Package 대화창에서 컴파일된 후 설치된다


이후 나타나는 패키지 대화창에서 wst_design을 먼저 컴파일한 후 설치해야 한다. 이는 라자루스의 재컴파일을 유도할 것이다 (그림 11.14).

그림 11.14: 패키지를 설치하기 위해 라자루스를 재컴파일해야 한다


여기까지 완료되면 IDE Project 메뉴는 Web services Toolkit 라는 새로운 하위메뉴를 포함하는데, 여기에는 Import WSDL Files…와 Type library editor… 라는 두 개의 엔트리가 있다.


참고: 라자루스 가져오기 마법사와 ws_helper.exe는 동일한 파서와 코드 생성기에 대한 두 개의 전단부(front end)에 해당한다. 두 개의 툴 모두 같은 파일에서 시작하여 동일한 결과를 생산한다.


따라서 간단한 클라이언트 애플리케이션이 작성되고 이는 일찍이 정의된 CalcService를 사용할 것이다. WSDL는 ws_helper부터 시작해 두 개의 전단부(front end)로 파싱된다. 전송 프로토콜의 구현은 Synapse HTTP 전송이 제공한다.



WSDL 파일을 오브젝트 파스칼로 해석하기

파일을 파싱하기 위해서는 명령 행에 ws_helper 를 호출한다:

ws_helper\ws_helper -p -uA .\samples\book\ws\calcservice.wsdl -a.\samples\book\ws\client\
그림 11.15: WSDL 가져오기(importer) 대화창


ws_helper, Web Service Toolkit 0.6 Copyright (c) 2006, 2007, 2008 by
  Inoussa OUEDRAOGO
Parsing the file: .\samples\book\ws\calcservice.wsdl
Interface file generation...
Proxy file generation...
Metadata file generation...
File ".\samples\book\ws\calcservice.wsdl" parsed successfully.


파라미터는 아래와 같은 의미를 지닌다:

파라미터 설명
-p 프록시 파일을 생성한다.
-uA 입력 파일 내 모든 타입 정의에 대한 설명을 파싱 및 생성한다. 이 옵션은 서비스 동작이 직접 사용하지 않는 타입이 있을 때 유용하다.
-a<path> 이 파라미터는 서버가 듣는 포트를 명시한다.
표 11.1: ws_helper에 유용한 세 가지 명령 행 옵션


ws_helper 는 실행을 커스터마이즈하는 여러 옵션을 제공한다. 어떤 파라미터도 없이 시작할 경우 해당하는 모든 옵션에 대한 설명을 출력한다.


ws_helper 는 두 개의 오브젝트 파스칼 파일, interface description 파일과 proxy 파일을 생성한다. 또한 프록시 파일에 포함된 리소스 파일을 작성한다.


라자루스 IDE 내부로부터 WSDL 파일을 해석하는 것도 가능하다. WST 패키지가 (wst_design.lpk) 라자루스에 설치된 경우 새 하위메뉴 Web Services Toolkit 이 Project 메뉴 하에 나타나는데, 이는 두 개의 메뉴 항목을 제공한다 (그림 11.16 참고). 가져오기 마법사는 가져올 파일에 대한 프롬프트(prompt)가 있는 대화상자, 사용자 설정(customization) 옵션, 출력 경로로 구성된다.

그림 11.16: WST 패키지 설치 후 메뉴 항목


클라이언트 프로그램에서 프록시 사용하기

생성된 인터페이스와 프록시를 사용하기란 매우 쉽다. soap_formatter 유닛은 WST 런타임 레지스트리에 SOAP 직렬화 핸들러를 등록한다. SYNAPSE_RegisterHTTP_Transport 는 시냅스 HTTP 전송 프로토콜의 구현을 등록한다. 이러한 프로시저는 synapse_http_protocol.pas 유닛에서 선언된다. wst_CreateInstance_ICalcService 는 프록시 인스턴스를 생성한다. 이 함수는 생성된 프록시 유닛인 calcservice_proxy.pas 에 위치한다. 나머지 코드는 단순히 사용자에게 계산에 필요한 피연산자와 연산자로 진입하길(enter) 요청할 뿐이다.

program client_calc;
  {$mode objfpc}{$H+}
uses
  Classes, SysUtils,
  soap_formatter, synapse_http_protocol, calcservice,
  calcservice_proxy
var
  theProxy: ICalcService;
  a, b: Integer;
  op, c: Char;
begin
  SYNAPSE_RegisterHTTP_Transport();
  theProxy := wst_CreateInstance_ICalcService();
  WriteLn('Calculator Client demo');
  repeat
     Write('Enter first operand : '); ReadLn(a);
     Write('Enter second operand : '); ReadLn(b);
     Write('Enter operator ( + or - ) : '); ReadLn(op);
     case op of
        '+' : WriteLn(a,' + ',b,' = ',theProxy.Add(a,b));
        '-' : WriteLn(a,' - ',b,' = ',theProxy.Subtract(a,b));
        else WriteLn('Unknown operator : ',op);
     end;
     WriteLn(''); Write(' Continue (y/n) ? : '); ReadLn(c);
     WriteLn('')
  until ( c <> 'y' );
end.


클라이언트 연결 파라미터

service_intf.pas에서 정의되고 모든 서비스 프록시 클래스가 이로부터 파생되는 TBaseProxy 클래스는 직렬화와 연결 프로퍼티를 설정(customize)하는 데 사용할 수 있는 가상 생성자(virtual constructor)를 제공한다.

tBaseProxy = Class(TInterfaceObject,IInterface,ICallContext)
...
public
  constructor Create(
  Const ATarget : String;
  Const AProtocolData : string;
  Const ATransportData : string
  );overload; virtual;
...
End;


위에 발췌한 코드는 생성자와 그 파라미터를 보여준다. ATarget 은 대상 웹 서비스 이름이며, AProtocolData 는 직렬화 프로토콜과 (SOAP, XmlRPC, JSON 또는 바이너리) 그 파라미터를 (예: SOAP rpc/문서 스타일, 또는 SOAP Encoded/Literal 인코딩 스타일) 명시한다. ATransportData 파라미터는 전송 프로토콜과 (HTTP, TCP, WST) 그 파라미터를 (예: TCP 전송을 위한 주소와 포트, HTTP 전송을 위한 주소와 프록시 파라미터) 명시한다.


전송 연결 문자열과 직렬화에 대한 일반 포맷은 아래와 같다:

protocol:paramName=paramValue(;paramName=paramValue)*


문자열에 어떤 파라미터도 명시되지 않은 경우 아래가 된다:

protocol:


protocol 은 전송 프로토콜의 이름을 의미하며 http 를 예로 들 수 있겠다. 아래는 파라미터가 두개인경우에 대한 예제이다.

http:address = http://ws.sample/service;ProxyServer=192.168.10.1


파라미터가 세개인 경우는 다음과 같다:

http:address = http://ws.sample/service;ProxyServer=192.168.10.1;ProxyPort=4567

파라미터는 세미콜론으로 구분된다.


현재 WST가 지원하는 직렬화 프로토콜을 표 11.2에 정리하였다.

클래스 설명
SOAP 지원되는 버전은 널리 사용되는 SOAP 1.1 버전이다. 구현은 soap_formatter 유닛이 제공한다.
이용 가능한 파라미터는:
Style 이 파라미터는 SOAP 문서 스타일을 명시한다. 가능한 값으로는 rpc 와 Document 가 있다.
EncodingStyle 이 파라미터는 SOAP 인코딩 스타일을 명시한다. 가능한 값으로는 Literal 과 Encoded 가 있다.
XmlRPC 구현은 xmlrpc_formatter 유닛이 제공한다. 이 프로토콜에는 파라미터가 없다.
JSON JsonRPC의 WST 구현은 JsonRPC 1.0에 표시된 P2P(peer-to-peer) 방식이 아닌 클라이언트-서버 지향적으로 이루어진다.
WST 구현은 JsonRPC 1.0 포맷과 JsonRPC 1.1 포맷을 모두 지원한다. 구현은 json_formatter 유닛에서 제공한다. 이용 가능한 파라미터는:
Version 이 파라미터는 프로토콜의 버전을 명시한다. 가능한 값으로는 1.0과 1.1이 있다.
base_json_formatter 유닛은 사용 가능한 1.0과 1.1로 map to된 심볼릭 상수 s_json_rpc_version_10 과 s_json_rpc_version_11 을 포함한다.
Binary 이것은 WST 커스텀 바이너리 포맷이다. 구현은 binary_formatter 유닛이 제공한다. 이 포맷은 파라미터가 없다.
표 11.2: WST가 지원하는 직렬화 프로토콜


직렬화 프로토콜을 사용하려면 WST 런타임으로 그 구현을 등록시켜야 한다. 구체적인 프로토콜을 구현하는 유닛을 프로젝트 유닛들 중 최소 하나에 위치한 프로젝트 파일의 uses 절로 추가해야만 한다. 클라이언트 측 직렬화 프로토콜 구현 유닛은 자동으로 initialization 섹션에 등록된다. 직렬화를 선택하는 문자열을 예로 들자면 아래와 같다:

'SOAP:'
'SOAP:Style=rpc;EncodingStyle=Literal'
'XMLRPC:'
'JSON:Version=1.0'


WST는 다음과 같은 HTTP 전송 구현을 제공한다:

  • INDY 소켓 라이브러리는 indy_http_protocol 유닛에 위치한다.
  • SYNAPSE 소켓 라이브러리는 synapse_http_protocol 유닛에 위치한다.
  • ICS 소켓 라이브러리는 ics_http_protocol 유닛에 위치한다.


이용 가능한 파라미터는 다음과 같다:

파라미터 설명
Address: 해당 파라미터는 웹 서비스 HTTP 주소를 명시한다.
ProxyServer: 파라미터는 프록시를 통해 연결 시 사용된다. 프록시 서버의 주소를 명시한다.
ProxyPort: 해당 파라미터는 프록시를 통해 연결 시 사용된다. 프록시 서버의 포트를 명시한다.
ProxyUsername: 해당 파라미터는 프록시를 통해 연결 시 사용된다. 프록시 서버 인증(authentication)을 위한 사용자 이름을 명시한다.
ProxyPassword: 해당 파라미터는 프록시를 통해 연결 시 사용된다. 프록시 서버 인증을 위한 사용자 비밀번호를 명시한다.
표 11.3: HTTP 전송 파라미터


WST는 아래의 TCP 전송 구현을 제공한다:

  • INDY 소켓 라이브러리는 indy_tcp_protocol 유닛에 위치한다.
  • SYNAPSE 소켓 라이브러리는 synapse_tcp_protocol 유닛에 위치한다.
  • ICS 소켓 라이브러리는 ics_tcp_protocol 유닛에 위치한다.


이용 가능한 파라미터는 아래와 같다:

파라미터 설명
Target: 해당 파라미터는 서버에 의해 등록되는 웹 서비스 이름을 명시한다.
Address: 해당 파라미터는 서버 IP 주소를 명시한다.
Port: 해당 파라미터는 서버가 듣는 포트를 명시한다.
표 11.4: TCP 전송 파라미터


표 11.5는 Windows DLL (동적 연결된 라이브러리) 또는 Unix의 동적 공유 객체(DLL/DSO)를 이용해 관리되는 서비스를 사용하기 위한 WST 특정적인 LIB 전송 프로토콜과 관련된 파라미터를 열거하고 있다. 구현은 library_protocol 유닛에 위치한다.


이용 가능한 파라미터는 다음과 같다:

파라미터 설명
Target: 해당 파라미터는 서버에 의해 등록되는 웹 서비스 이름을 명시한다.
FileName: 해당 파라미터는 서버 동적 라이브러리 파일명을 명시한다.
표 11.5: LIB 전송 파라미터


서버 교환 메시지 로깅하기

웹 서비스를 프로그래밍할 때는 서버가 클라이언트로 전송하거나 클라이언로부터 전송된 메시지를 찾아보는 것이 도움이 된다. 이것이 용이하도록 WST는 클라이언트-서버 사이에 이루어지는 모든 메시지 교환을 파일로 로그(log)할 때 사용할 수 있는 서비스 확장을 제공한다. 이를 사용하려면 file_logger_extension 유닛을 프로젝트에 포함시켜야 하고, LogFileCompleteName 변수의 (이 변수는 file_logger_extension 유닛에서 정의된다) 값을 설정함으로써 로깅 파일 위치를 명시해야 한다; 마지막으로 확장은 로깅의 대상이 될 서비스 구현을 이용해 등록되어야 한다.


대상 서비스 구현은 이미 구현 레지스트리에 등록되어 있도록 확보하는 것이 중요하다. 아래 발췌한 코드는 ICalcService 의 구현을 이용해 파일 로거 확장(file logger extension)을 등록시키는 방법을 보여준다. 해당 예제의 완전한 소스는 sources\web-services\logging 폴더에 위치한다. 테스트하기 전에 서버 프로젝트와 클라이언트 프로젝트 모두 컴파일해야 한다. 컴파일이 끝난 후에야 셸 터미널(shell terminal)을 열고 서버 폴더를 검색한다. 가장 먼저, 서버 프로세스를 시작한 후 클라이언트를 시작할 수 있다. 서버를 호출하려면 최소 한 번의 계산을 실행해야 한다. 클라이언트는 구분된 셸 터미널에서 시작되어야 함을 주목한다. 서버를 중단한 후 로깅 파일은 시간 스탬프(time stamp)로 표시된 메시지를 일부 포함한다.

...
LogFileCompleteName := Format('.%slog.txt',[PathDelim]);
theFactory :=
  GetServiceImplementationRegistry().FindFactory('ICalcService');
if ( theFactory <> nil ) then
  theFactory.RegisterExtension(['TFileLoggerServiceExtension'])
else
  WriteLn('Unable to find the service implementation factory.');
...


웹 서비스 객체 풀링 구현하기

객체 풀링(object pooling)은 객체 생성을 가속화하는 데 사용되는 팩토리 패턴(factory pattern)이다. 이 기법은 객체의 풀링이 요청되기 전에 객체를 생성하고, 객체를 파괴되는 대신 재활용(recycle)한다. 객체 풀링은 애플리케이션이 수많은 파라미터로 객체를 반복 생성해야 할 때 사용하면 애플리케이션의 성능을 크게 향상시킬 수 있다. 다수의 객체를 생성하는 비용이 높다는 것을 깨닫게 되면 성능이 향상됨을 분명히 눈치챌 것이다. 예를 들어, 원격 데이터베이스 엔진으로 연결을 구축하는 객체를 생성하는 데에는 비용이 많이 드는 것이 보통이다. 이러한 상황에서 객체를 추후 재사용하기 위해 재활용한다면 성능을 유의하게 향상시킬 수 있다. 객체 풀링은 소프트웨어를 실행해야 하는 하드웨어에 따라 풀링 용량(pool capacity)을 조정함으로써 소프트웨어 크기조정(scaling)을 향상시키는 요인이 될 수 있다.


WST는 객체 풀링을 구현하는 데에 필요한 툴을 모두 제공한다. WST가 제공하는 웹 서비스 구현객체를 위한 기본 객체 팩토리, TImplementationFactory (server_service_intf.pas에 정의)는 풀링을 지원하는 구현 객체를 위해 단순히 활성화시키기만 하면 되는 풀링 기능을 가진다. 구현 객체는 server_service_intf.pas에 정의된 IObjectControl 인터페이스를 구현해야 한다.


WST에 구현되는 풀(pool)은 세 가지 주요 프로퍼티를 가진다. PoolMax 는 풀 용량을 명시한다. 풀은 이 값을 제외한 객체를 포함할 수 없다. PoolMin 는 풀 자체가 생성될 때 생성될 최소 객체 수를 명시한다. 주로 생성되는 객체의 수는 최소이며, 동시 요청 수가 증가할수록 풀은 용량에 가득 차게 된다. 이 프로퍼티에 가능한 값은 0과 풀의 전체 용량(capacity) 사이의 값이다. TimeOut 은 객체 요청자(object requester)가 풀이 이용할 수 있는 객체를 얻을 때까지 기다릴 수 있는 최대 시간을 명시한다. 이 시간이 지나면 풀은 예외를 발생시켜 실패를 표시한다. 타임아웃 값은 밀리 초로 제공된다. 현재 타임아웃 기능은 윈도우 플랫폼에서만 이용 가능한 상태이다.


풀링 가능 객체 개발하기

풀링 가능(poolable) 객체는 2가지 기본 단계로 개발할 수 있다: IObjectControl 인터페이스를 구현해야 하고, 풀 프로퍼티를 명시함으로써 객체를 풀링 가능한 객체로 등록해야 한다.


단계 1: IObjectControl 인터페이스 구현하기

WST 런타임은 IObjectControl 인터페이스-server_service_intf.pas에 정의-를 이용해 객체의 수명을 관리한다. 이는 사용 전에 객체를 활성화시키고, 객체가 해제되고 나면 풀로 객체를 리턴 가능한지 테스트 시 사용할 수 있다.

IObjectControl = interface
['{C422C7CA-4C95-48A4-9A82-2616E619F851}']
  procedure Activate();
  procedure Deactivate();
  function CanBePooled(): Boolean;
end.


이러한 메소드들은 다음을 의미한다:

Activate는 풀로부터 객체를 검색하여 사용되기 직전에 런타임에 의해 호출된다. 이는 객체가 스스로 요청을 처리할 준비하기에 적절한 순간이다. Deactivate는 객체가 사용되어 더 이상 필요하지 않을 때 런타임에 의해 호출된다. 이는 객체가 다시 풀링 또는 폐기될 준비를 하기에 적절한 순간이다. Deactivate는 CanBePooled 메소드를 통해 풀링된 객체의 용량을 문의(query)하기 전에 호출된다. CanBePooled 는 풀이 객체를 추후 재사용하기 위해 풀링 가능한지 결정 시 호출한다. True를 리턴할 경우 객체는 풀의 비활성 객체 리스트로 추가된다. False를 리턴할 경우, 객체는 해제(freed)된다.


server_service_intf.pas 유닛에서 WST는 IObjectControl을 구현하는 클래스이자 풀링 가능한 객체에 대한 기반 클래스로 사용되는 클래스인 TactivableServiceImplementation을 정의한다. IObjectControl의 구현은 Activate와 Deactivate에 대해 빈 메소드를 제공하는 반면 CanBePooled는 False를 리턴한다. 이러한 메소드들은 가상이며, 자손 클래스들이 이를 오버라이드해야 한다. 풀링 가능 객체는 Deactivate 메소드가 리턴할 때 풀링될 수 있는 기능으로 접근해야 한다. 내부 상태가 일관되지 않을 경우 풀링 가능 객체가 CanBePooled에 대해 False를 리턴하여 스스로 폐기하는 결과를 야기할지도 모른다.


단계 2: 풀링 가능한 객체 등록하기

풀링 가능하든 가능하지 않든 웹서비스를 구현하는 객체들은 WST 런타임으로 등록되어야 한다. 사실 등록되는 것은 구현 팩토리이므로 WST 런타임은 그 팩토리의 CreateInstance 메소드를 호출함으로써 구현 객체를 생성할 수 있다. CreateInstance 는 ItemFactory의 메소드이며 base_service_intf.pas 에서 선언된다. WST 타입 라이브러리 에디터 또는 ws_helper.exe가 생성한 skeleton 구현 파일은 구현 레지스트리를 이용해 구현 객체를 등록하는 프로시저를 포함한다:

procedure RegisterCalcServiceImplementationFactory();
begin
  GetServiceImplementationRegistry().Register(
     'ICalcService',
     TImplementationFactory.Create(
     TCalcService_ServiceImp,wst_GetServiceConfigText('ICalcService')
     ) as IServiceImplementationFactory
  );
end;


factory Create 메소드는 중복정의(overloaded) 생성자를 사용하는데, 이는 두 가지 파라미터를 기대한다: 구현 클래스, 그리고 프로퍼티를 설명하는 문자열. 위의 코드에서 기본 프로퍼티 문자열은 config_objects.pas 파일에 정의된 wst_GetServiceConfigText 함수로 검색할 수 있다. 프로퍼티 문자열은 구현 팩토리를 구성하는 가장 직접적인 방법이다. 일반 포맷은 아래 포맷과 같이 세미콜론으로 구분된 이름/값 쌍의 리스트이다.

name=value(;name=value)*


파라미터 Pooled 가 문자열에 나타날 경우 마지막에 위치해야 한다. 문자열은 빈 채로 놔둘 수도 있다. 이름은 개발자가 값을 설정하길 원하는 풀 파라미터를 명시한다. 인스턴스를 최소 3개 생성하고 용량이 10인 풀을 설치하려면 아래와 같은 프로퍼티 문자열을 사용할 수 있을 것이다:

'PoolMax=10;PoolMin=3;Pooled=True'


구성 파일을 통해 풀을 구성하는 수도 가능하다. 이러한 파일로, 개발자가 서비스 구성 프로퍼티를 명시할 때 사용할 수 있는 XML 파일을 들 수 있겠다. 파일 위치는 config_objects 유닛에 위치한 wst_GetConfigFileName 함수를 호출하여 얻을 수 있다. WST 런타임이 이 파일을 자동으로 생성하지 않음을 명심한다. 유닛은 wst_CreateDefaultFile 함수를 포함하는데, 이 함수는 WST 런타임에 기본 값으로 등록된 서비스를 모두 포함하는 기본 구성 파일을 생성 시 사용할 수 있다.


아래 예제의 내용은 앞서 제공한 프로퍼티 문자열이 생성한 구성과 중복된다.

<?xml version="1.0"?>
<Application xmlns:ns1="urn:wst_base">
   <WST_Configuration>
   <Services>
      <service Name="IWSTMetadataService">
         <PoolMax>0</PoolMax>
         <PoolMin>0</PoolMin>
         <Pooled>false</Pooled>
         <TimeOut>0</TimeOut>
      </service>
      <service Name="ICalcService">
         <PoolMax>10</PoolMax>
         <PoolMin>3</PoolMin>
         <Pooled>true</Pooled>
         <TimeOut>0</TimeOut>
      </service>
   </Services>
   </WST_Configuration>
</Application>


sources\web-services\protocols 폴더는 완전한 소스 코드 예제를 포함한다. 구현 객체의 등록 프로시저는 객체를 싱글톤(singleton)으로 등록한다. 객체는 그 생성, 실행 순간, 선언 시 터미널(terminal)에 그것의 포인터 주소를 표시한다. 객체가 싱글톤이므로 서버로 여러 번 호출하면 동일한 객체 포인트 주소를 표시할 것이다. 구체적인 인스턴스는 다섯 번(5) 요청까지 처리할 수 있고, 이후 CanBePooled 에 False를 리턴한다. 따라서 객체 포인터 주소는 새 주소로 새 객체를 생성하기 전에 5회 표시되고 오래된 객체는 파괴된다.


서비스 확장

서비스 확장은 서비스 개발자가 요청 처리 단계로 연결하도록 해준다. 클라이언트 요청 처리는 그림 11.17에 표시된 네 가지 단계로 나뉠 수 있다.

그림 11.17: 클라이언트 요청 처리 중 단계


WST 런타임이 제공하는 서비스 확장 메커니즘은 IServiceExtension 인터페이스를 통해 아래 네 가지 단계로 접근을 제공한다:

프로퍼티 효과
msBeforeDeserialize: 런타임이 방금 요청 버퍼를 수신하였다.
그 단계에서 AMsgData는 IRequestBuffer 참조이다.
msAfterDeserialize: 요청 버퍼가 AMsgData에 상응하는 IFormatterResponse로 역직렬화되었다.
msBeforeSerialize: 서비스 구현 객체가 이미 호출을 처리하였다. 호출 스택은 응답 버퍼로 직렬화되려고 한다.
AMsgData 는 여전히 IFormatterResponse로의 참조를 포함한다.
msAfterSerialize: 호출 스택이 직렬화되고 응답 버퍼가 클라이언트로 전송 준비가 되어 있다. AMsgData는 IRequestBuffer로의 참조를 포함한다.
표 11.6: 가능한 TMessageStage 값


서비스 확장은 이 인터페이스를 구현하고 서비스 확장 레지스트리를 이용해 등록되어야 하는데, 이는 server_service_intf_pas에 선언된 GetServiceExtensionRegistry 함수를 통해 이용할 수 있다. 서비스 구현의 처리 단계에서 서비스 확장을 호출하기 위해서는 구현이 서비스 확장을 등록해야만 한다. IServiceImplementationFactory 는 이를 위해 RegisterExtension 메소드를 제공한다.

TMessageStage = (
  msAfterDeserialize, msAfterSerialize, msBeforeDeserialize,
  msBeforeSerialize
  );
IServiceException = interface
['{E192E6B3-7932-4D44-A8AC-135D7A0B8C93}']
procedure ProcessMessage(
  const
     AMessageStage: TMessageStage;
     ACallContext: ICallContext;
     AMsgData: IInterface
     );
end;


서비스 확장은 다음 단계로 개발된다:

  • IServiceExtension 인터페이스를 구현하는 클래스를 생성해야 한다. TSimpleFactoryItem 을 기반 클래스로 사용 시 개발자는 TSimpleFactory를 사용할 수 있으므로 팩토리 구현을 생성하는 수고를 덜어준다.
  • 서비스 확장은 서비스 확장 레지스트리로 등록되어야 한다. 포함하는 유닛의 initialization 섹션에서 등록되는 것이 이상적이다. 서비스 확장 레지스트리는 GetServiceExtensionRegistry 함수를 통해 이용 가능하다.
  • 확장이 필요한 서비스의 모든 구현 클래스는 RegisterExtension 메소드를 이용해 팩토리를 통하여 등록할 수 있다. 서비스 이름을 고려해볼 때 구현 팩토리는 서비스 구현 레지스트리의 FindFactory 메소드를 통해 찾을 수 있다.


참고내용

사양:


WST 사이트: