Asynchronous JavaScript and XML (Ajax) 디자인 패턴에 기반하여 크로스 플랫폼, JavaScript 기반 SOAP 웹 서비스 클라이언트를 구현하는 방법을 다룬 시리즈의 첫 번째 기술자료입니다.
GMail, Google Maps, Flickr, Odeo.com 같은 유명한 웹 애플리케이션 서비스에서 사용된다는 이유로 유명해진 Ajax는 웹 개발자에게 비동기식 XML 메시징을 사용함으로써 그들의 웹 애플리케이션의 가치와 기능을 늘릴 수 있는 방식을 제공하고 있다. 이 글에서 소개하는 Web Services JavaScript Library는 SOAP 기반 웹 서비스를 호출하는데 지원을 추가함으로써 Ajax 패턴에 힘을 실어주는 기본 메커니즘을 확장한 것이다.
브라우저의 웹 서비스
웹 브라우저 내에서 SOAP 웹 서비스를 호출하는 것은 복잡한 일이 될 수 있다. 대부분의 웹 브라우저가 각각 XML의 생성과 프로세싱을 약간 다른 방식으로 처리할 경우 특히 그렇다. 모든 브라우저들이 일관성 있게 구현하는 표준 API나 XML 프로세싱 기능들은 매우 적다.
브라우저 구현에서 일치하는 메커니즘 중 하나가 XMLHttpRequest API인데, 이것은 Ajax 디자인 패턴의 심장이다. Philip McCarthy가 developerWorks에 기고한 최신 글에서도, XMLHttpRequest는 비동기식 HTTP 요청들을 수행하는데 사용할 수 있는 JavaScript 객체이다. 이 글에서는 XMLHttpRequest 객체가 Ajax 디자인(참고자료)을 실행하는 방법을 이해하는데 도움이 되는 시퀀스 다이어그램(그림 1) 도 소개한다.
그림 1. Philip McCarthy의 Ajax Roundtrip 시퀀스 다이어그램
이 다이어그램에서, XMLHttpRequest 객체가 어떻게 작동하는지 정확히 볼 수 있다. 웹 브라우저 내에서 실행되는 일부 JavaScript 조각들은 XMLHttpRequest 인스턴스와 비동기식 호출로서 작동하는 함수를 만들어 낸다. 스크립트는 XMLHttpRequest 객체를 사용하여 서버에 대해 HTTP 연산을 수행한다. 응답을 받으면, 콜백 함수가 호출된다. 콜백 함수 내에서, 리턴된 데이터가 처리될 수 있다. 데이터가 XML이라면, XMLHttpRequest 객체는 브라우저의 XML 프로세싱 방식을 사용하여 그 데이터를 자동으로 파싱한다.
안타깝게도, XMLHttpRequest 객체는 Ajax 방식이 실행되는데 어려움을 겪는 곳에서 XML을 자동으로 파싱한다. 예를 들어, 필자가 요청하는 데이터가 다른 많은 XML Namespaces에서 온 엘리먼트를 포함하고 있는 SOAP Envelope이고, 필자는 yetAnotherElement
에서 attr
애트리뷰트의 값을 얻어야 하는 경우를 생각해 보자. (Listing 1.)
Listing 1. 다중 네임스페이스를 가진 SOAP Envelope
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<s:Header/>
<s:Body>
<m:someElement xmlns:m="http://example">
<n:someOtherElement
xmlns:n="http://example"
xmlns:m="urn:example">
<m:yetAnotherElement
n:attr="abc"
xmlns:n="urn:foo"/>
</n:someOtherElement>
</m:someElement>
</s:Body>
</s:Envelope>
|
Mozilla와 Firefox 브라우저에서, attr
애트리뷰트의 값을 추출하는 것은 간단하다. (Listing 2)
Listing 2. Mozilla와 Firefox에서 attr 애트리뷰트를 가져오는 메소드는 Internet Explorer에서는 작동하지 않는다.
var m = el.getElementsByTagNameNS(
'urn:example',
'yetAnotherElement')[0].
getAttributeNS(
'urn:foo',
'attr');
alert(m); // displays 'abc'
|
안타깝게도, 이 코드는 Internet Explorer Version 6에서는 작동하지 않는다. 이 브라우저는 getElementsByTagNameNS 함수를 구현하지 않고, 사실 다소 도움이 안되는 방식인 XML 네임스페이스 접두사가 엘리먼트와 애트리뷰트 이름의 일부인 것처럼 취급하는 방식을 취하기 때문이다.
Internet Explorer의 XML 네임스페이스 지원 부족으로 SOAP 같이 네임스페이스 중심의 XML 포맷을 브라우저와 독립된 방식으로 처리하기가 더욱 어렵다. 결과에서 애트리뷰트의 값을 얻는 것 같은 간단한 일을 수행하기 위해서, 여러분은 기대한 작동이 여러 브라우저에서도 일관되도록 특별한 케이스 코드를 작성해야 한다. 다행히도, 이러한 특수 케이스 코드는 캡슐화 및 재사용 될 수 있다.
웹 브라우저 내에서 웹 서비스를 호출하고 SOAP 메시지들을 신뢰성 있게 처리하려면, 보안 문제를 이해해야 한다. ("보안" 참조) 또한, 기반 브라우저 XML 구현들의 불일치를 제거할 수 있는 JavaScript 스크립트 라이브러리를 작성하고(그림 2), 웹 서비스 데이터와 직접 작동할 수 있어야 한다.
그림 2. Web Services JavaScript Library를 사용하여 웹 브라우저 내에서 JavaScript로부터 웹 서비스 호출하기
그림 2의 Web Services JavaScript Library (ws.js)는 JavaScript 객체들과 유틸리티 함수들의 컬렉션으로서, SOAP 1.1 기반 웹 서비스를 지원한다. ws.js는 다음 객체들을 정의한다.
- WS.Call: XMLHttpRequest를 래핑하는 웹 서비스 클라이언트
- WS.QName: XML 수식명 구현
- WS.Binder: 커스텀 XML 직렬화/비직렬화 베이스
- WS.Handler: 요청/응답 핸들러 베이스
- SOAP.Element: XML DOM을 래핑하는 기본 SOAP 엘리먼트
- SOAP.Envelope: SOAP.Element를 확장하는 SOAP Envelope 객체
- SOAP.Header: SOAP.Element를 확장하는 SOAP 헤더 객체
- SOAP.Body: SOAP.Element를 확장하는 SOAP 바디 객체
- XML: XML을 핸들링 하는 크로스 플랫폼 유틸리티 메소드
ws.js의 핵심에는 웹 서비스를 호출하는 메소드를 제공하는 WS.Call 객체가 있다. WS.Call은 기본적으로 XMLHttpRequest 객체와의 인터랙션과 SOAP 응답 프로세싱을 담당하고 있다.
WS.Call 객체는 다음과 같이 세 개의 메소드를 노출한다.
- add_handler. Request/Response 핸들러를 프로세싱 체인에 추가한다. Handler 객체들은 웹 서비스 호출 전후에 호출되어 확장성 있는 사전 사후 호출 프로세싱을 발생시킨다.
- invoke. 지정된 SOAP.Envelope 객체를 웹 서비스로 보내고 응답을 받으면 콜백을 호출한다. 리터럴 XML 인코딩을 사용하는 문서 스타일의 웹 서비스를 호출할 때 이 메소드를 사용한다.
- invoke_rpc. RPC-Style 요청을 캡슐화 하여 SOAP.Envelope을 만들고 이것을 웹 서비스로 보내면서, 응답을 받을 때 콜백을 호출한다.
WS.Call 객체는 일반적으로 XMLHttpRequest 객체의 맨 위에 있는 씬(thin) 래퍼일 뿐이지만, 여러분의 삶을 편하게 해주는 많은 액션을 수행한다. 이 액션에는 SOAP 1.1 스팩에서 필요로 하는 SOAPAction HTTP 헤더를 설정하는 것이 포함된다.
위로
ws.js 사용하기
Web services JavaScript Library에서 제공된 API는 다소 단순하다.
SOAP.* 객체들 (SOAP.Element
, SOAP.Envelope
, SOAP.Header
, SOAP.Body
)은 SOAP Envelopes를 구현하고 읽는 방식을 제공하여(Listing 3), XML 문서 객체 모델을 실행하는 상세를 없앤다.
Listing 3. SOAP Envelope 구현하기
var envelope = new SOAP.Envelope();
var body = envelope.create_body();
var el = body.create_child(new WS.QName('method','urn:foo'));
el.create_child(new WS.QName('param','urn:foo')).set_value('bar');
|
Listing 4는 Listing 3의 코드에서 만들어진 SOAP Envelope이다.
Listing 4. SOAP Envelope 구현하기
<Envelope xmlns="http://schemas.xmlsoap.org">
<Body>
<method xmlns="urn:foo">
<param>bar</param>
</method>
</Body>
</Envelope>
|
여러분이 만들고 있는 SOAP Envelope가 RPC-Style 요청을 대표하는 것이라면, SOAP.Body 엘리먼트는 set_rpc
메소드(Listing 5)를 제공한다. 이것은 연산 이름, 인풋 매개변수의 어레이, SOAP 인코딩 스타일 URI에 주어진 요청의 전체 바디를 구현한다.
Listing 5. RPC-Request Envelope 구현하기
var envelope = new SOAP.Envelope();
var body = envelope.create_body();
body.set_rpc(
new WS.QName('param','urn:foo'),
new Array(
{name:'param',value:'bar'}
), SOAP.NOENCODING
);
|
각각의 매개변수는 다음과 같은 프로퍼티를 가진 JavaScript 객체 구조로서 전달된다.
- name. 매개변수의 이름을 지정하는 스트링 또는 WS.QName 객체. 필수.
- value. 매개변수의 값. 값이 단순한 데이터 유형(스트링 또는 정수)이 아니라면, WS.Binder가 지정된다. 이것은 해당 XML 구조로 값을 직렬화 할 수 있다.필수.
- xsitype: 매개변수의 XML Schema Instance Type을 구분하는 WS.QName (예를 들어,
xsi:type="int"
라면, xsitype:new WS.QName('int','http://www.w3.org/2000/10/XMLSchema')
이다.) 선택.
- encodingstyle.: 이 매개변수에 의해 사용되는 SOAP Encoding Style을 구분하는 URI. 선택.
- binder: 매개변수를 XML로 직렬화 할 수 있는 WS.Binder 구현. 선택
예를 들어, "abc"라는 이름을 가진 매개변수를 XML Namespace "urn:foo", "int"의 xsi:type, "3" 으로 지정하려면, 다음 코드를 사용한다: new Array({name:new WS.QName('abc','urn:foo'), value:3, xsitype:new WS.QName('int','http://www.w3.org/2000/10/XMLSchema')})
.
서비스 요청을 위해 SOAP.Envelope을 구현하면, 그 SOAP.Envelope을 WS.Call 객체 invoke
메소드로 보내서 Envelope 내부에 인코딩 된 메소드를 호출한다: (new WS.Call(service_uri)).invoke(envelope, callback)
SOAP.Envelope을 직접 구현하는 것에 대한 대안으로서, WS.QName, 매개변수 어레이, 인코딩 스타일을 WS.Call 객체의 invoke_rpc
메소드로 전달할 수 있다. (Listing 6)
Listing 6. WS.Call 객체를 사용하여 웹 서비스 호출하기
var call = new WS.Call(serviceURI);
var nsuri = 'urn:foo';
var qn_op = new WS.QName('method',nsuri);
var qn_op_resp = new WS.QName('methodResponse',nsuri);
call.invoke_rpc(
qn_op,
new Array(
{name:'param',value:'bar'}
),SOAP.NOENCODING,
function(call,envelope) {
// envelope is the response SOAP.Envelope
// the XML Text of the response is in arguments[2]
}
);
|
invoke
메소드 또는 invoke_rpc
메소드를 호출할 때, WS.Call 객체는 기반 XMLHttpRequest 객체를 만들고, SOAP Envelope를 포함하고 있는 XML 엘리먼트로 전달하고, 응답을 받아 파싱하며, 콜백 함수를 호출한다.
SOAP 메시지의 사전 및 사후 처리를 확장하기 위해, WS.Call 객체는 WS.Handler 객체의 컬렉션을 등록할 수 있다. (Listing 7) 이들은 호출 사이클 동안 모든 요청, 응답, 에러 시 호출된다. 새로운 핸들러가 WS.Handler JavaScript 객체를 확장함으로써 구현될 수 있다.
Listing 7. 요청/응답 핸들러 생성 및 등록하기
var MyHandler = Class.create();
MyHandler.prototype = (new WS.Handler()).extend({
on_request : function(envelope) {
// pre-request processing
},
on_response : function(call,envelope) {
// post-response, pre-callback processing
},
on_error : function(call,envelope) {
}
});
var call = new WS.Call(...);
call.add_handler(new MyHandler());
|
핸들러들은 전달되는 SOAP Envelope에서 정보를 삽입 또는 추출하는 태스크에 가장 유용하다. 예를 들어, 적절한 Web Services Addressing 엘리먼트를 SOAP Envelope 헤더로 자동 삽입하는 것을 생각해 볼 수 있다. (Listing 8)
Listing 8. WS-Addressing Action 헤더를 요청에 추가하는 샘플 핸들러
var WSAddressingHandler = Class.create();
WSAddressingHandler.prototype = (new WS.Handler()).extend({
on_request : function(call,envelope) {
envelope.create_header().create_child(
new WS.QName('Action','http://ws-addressing','wsa')
).set_value('http://www.example.com');
}
});
|
WS.Binder 객체(Listing 9)는 SOAP.Element 객체의 커스텀 직렬화/비직렬화를 수행한다. WS.Binder 구현은 다음과 같은 두 개의 메소드를 제공해야 한다.
- to_soap_element. JavaScript 객체를 SOAP.Element로 직렬화 한다. 전달되는 첫 번째 매개변수는 직렬화 되는 값이다. 두 번째 매개변수 값은 SOAP.Element로 직렬화 되어야 한다. 이 메소드는 어떤 값도 리턴하지 않는다.
- to_value_object. SOAP.Element를 JavaScript 객체로 비직렬화 한다. 메소드는 비직렬화 된 값 객체를 리턴해야 한다.
Listing 9. WS.Binding 구현 샘플
var MyBinding = Class.create();
MyBinding.prototype = (new WS.Binding()).extend({
to_soap_element : function(value,element) {
...
},
to_value_object : function(element) {
...
}
});
|
위로
예제
Web Services JavaScript Library의 기본 기능을 설명하는 샘플 객체를 설명했다. 이 데모에서 사용되는 웹 서비스(Listing 10)은 WebSphere Application Server에서 구현되고 간단한 Hello World 함수를 제공한다.
Listing 10. 간단한 자바 기반 Hello World 웹 서비스
package example;
public class HelloWorld {
public String sayHello(String name) {
return "Hello " + name;
}
}
|
서비스를 WebSphere Application Server로 구현 및 전개한 후에, 서비스의 WSDL 디스크립션(Listing 11)은 Hello World 서비스를 호출하기 위해 전달해야 하는 SOAP 메시지를 정의한다.
Listing 11. HelloWorld.wsdl의 코드
<wsdl:portType name="HelloWorld">
<wsdl:operation name="sayHello">
<wsdl:input
message="impl:sayHelloRequest"
name="sayHelloRequest"/>
<wsdl:output
message="impl:sayHelloResponse"
name="sayHelloResponse"/>
</wsdl:operation>
</wsdl:portType>
|
Web Services JavaScript Library를 사용하여, Hello World 서비스를 호출하는 메소드를 구현할 수 있다. (Listing 12)
Listing 12. WS.Call을 사용하여 Hello World 서비스 호출하기
<html>
<head>
...
<script
type="text/javascript"
src="scripts/prototype.js"></script>
<script
type="text/javascript"
src="scripts/ws.js"></script>
<script type="text/javascript">
function sayHello(name, container) {
var call = new WS.Call('/AjaxWS/services/HelloWorld');
var nsuri = 'http://example';
var qn_op = new WS.QName('sayHello',nsuri);
var qn_op_resp = new WS.QName('sayHelloResponse',nsuri);
call.invoke_rpc(
qn_op,
new Array(
{name:'name',value:name}
),null,
function(call,envelope) {
var ret =
envelope.get_body().get_all_children()[0].
get_all_children()[0].get_value();
container.innerHTML = ret;
$('soap').innerHTML = arguments[2].escapeHTML();
}
);
}
</script>
</head>
...
|
웹 애플리케이션 어디에서나 sayHello
함수를 호출함으로써 Hello World 서비스를 호출할 수 있다. (Listing 13)
Listing 13. sayHello 함수 호출하기
<body>
<input name="name" id="name" />
<input value="Invoke the Web Service"
type="button"
onclick="sayHello($('name').value,$('result'))" />
<div id="container">Result:
<div id="result">
</div>
<div id="soap">
</div>
</div>
</body>
|
성공적인 호출은 Figure 3과 같은 결과를 만들어 낸다. 이 예제를 Mozilla, Firefox, Internet Explorer에서 실행하면 모두 같은 결과가 나온다.
그림 3. Firefox에서의 Hello World 예제
위로
다음 단계
Web Services JavaScript Library는 기본 SOAP 웹 서비스를 웹 애플리케이션에 통합하는데 사용될 수 있다. 다음 글에서는 라이브러리를 사용하여 WS-Resource Framework 스팩군에 기반한 고급 웹 서비스를 호출하는 방법과 웹 서비스 기능이 웹 애플리케이션으로 확장 및 통합되는 방법을 연구할 것이다.
위로
다운로드 하십시오
설명 |
이름 |
크기 |
다운로드 방식 |
프로젝트 샘플 |
ws-wsajaxcode.zip |
19 KB |
HTTP |
다운로드 방식에 대한 정보
참고자료
교육
제품 및 기술 얻기
토론
필자소개
James Snell은 IBM의 WebAhead 개발 랩의 연구원이고, 여기에서 IBM 내부에서 사용할 소프트웨어 기술과 표준에 대한 프로토타입 개발을 수행하고 있다. 그의 관심 분야는 Atom, AJAX, REST, Open Source, 퍼스널 퍼블리싱 시스템, semantic web, situational application 등 광범위한 신 기술까지 포괄하고 있다. Apache Abdera의 활동적인 커미터이며, 최근에는 Atom Syndication Format과 Atom Publishing Protocol 표준의 구현 작업을 하고 있다. http://www.ibm.com/developerworks/blogs/dw_blog.jspa?blog=351.