REST (Representational State Transfer)는 월드 와이드 웹과 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍처의 한 형식이다. 이 용어는 로이 필딩(Roy Fielding)의 2000년 박사학위 논문에서 소개되었다. 그는 하이퍼텍스트 전송 프로토콜 (HTTP)의 주요 저자들 가운데 한사람이다. 그 뒤로 이 개념은 네트워킹 문화에 널리 퍼졌다

엄격한 의미로 REST는 네트워크 아키텍처 원리의 모음이다. 여기서 네트워크 아키텍처 원리란 리소스를 정의하고 리소스에 대한 주소를 지정하는 방법에 대한 개괄을 말한다. 간단한 의미로는, 도메인 지향 데이터를 HTTP위에서 SOAP이나 쿠키를 통한 세션 트랙킹 같은 부가적인 전송 레이어 없이, 전송하기 위한 아주 간단한 인터페이스를 말한다. 이 두 가지의 의미는 당연히 겹치는 부분과 충돌되는 부분이 있다. 필딩의 REST 아키텍처 형식을 따르면 HTTP 프로토콜을 사용하지 않은채로 또 월드 와이드 웹에서 전송하지 않고도 아주 커다란 소프트웨어 시스템을 설계하는것도 가능하다. 또한 리모트 프로시져 콜을 이용하는 대신에 간단한 XMLHTTP 인터페이스(REST 원리에 부합하지는 않지만)를 이용해 설계하는것도 가능하다. 현실 세계에서의 REST 용어에 대한 이러한 두가지 의미는 기술 토론에서 종종 혼란을 야기한다.

필딩의 REST 원리를 따르는 시스템은 종종 RESTful이란 용어로 지칭된다. 열정적인 REST 옹호자들은 스스로를 RESTafrians 이라고 부른다

From Wikipedia : http://ko.wikipedia.org/wiki/REST

어렵군...ㅋ

따라 해보자....
http://www.ibm.com/developerworks/kr/library/tutorial/x-restatompp/?S_TACT=105AGX55&S_CMP=EDU

REST 서비스 작성하기

자바 기술과 Atom Publishing Protocol로 REST 서비스 만들기

본 튜토리얼은 REST(Representation State Transfer)와 Atom Publishing Protocol(이하 APP)의 개념에 대해 토론하고 서비스에 이것들을 적용하는 방법을 다룹니다. 또한 Java™ 기술을 사용하여 REST/APP 기반 서비스를 구현하는 방법도 살펴봅니다.

목표

본 튜토리얼에서는 다루는 사항은 다음과 같다.

  • REST 기초와 APP 이면의 기본 개념
  • 조직에 이 기술을 적용하여 자바 서블릿 기반 시스템을 통해 HTTP 요청 및 응답을 주고 받는다.
  • 포드캐스트, 블로그 엔트리, 이미지 저장소, 달력 엔트리 등을 위해 리소스 데이터에 접근하고 수정한다

Representational State Transfer

REST는 월드 와이드 웹처럼 자원을 정의하고 접근하는 메커니즘을 기반으로 한 네트워크가 가능한 시스템을 만드는 아키텍처 원칙과 소프트웨어 아키텍처 스타일의 집합이다. Roy Fielding의 논문(참고자료 참조)에서 정의된 용어인 REST는 부수적인 시맨틱 레이어나 세션 관리를 추가하지 않고도 HTTP 같은 프로토콜로 데이터를 전달하는 프레임워크를 설명하는 데 전반적으로 쓰인다.

REST 는 클라이언트 서버 시스템에 참여하는 구성요소(component) 간에 관심의 분리(separation of concerns)를 엄격히 정의하여 관련된 구현을 간단하게 만든다. 또한 네트워크로 연결된 시스템에서 커뮤니케이션 시맨틱을 단순화하여 확장성과 성능을 높인다. REST는 자율적인 요청에 의존해 메시지를 교환하는데, 이 때 요청은 클라이언트와 서버가 요청 컨텍스트를 이해할 수 있도록 모든 컨텍스트를 포함해야 한다. REST 기반 시스템에서는 가능한 적은 요청 세트를 사용하여 표준 미디어 타입을 교환한다.

REST 원칙은 URI(Uniform Resource Identifiers)를 사용하여 주어진 자원 표현의 위치를 알아내고 접근한다. 표현 상태(representational state)라 알려진 자원 표현은 만들어질 수도, 찾아올 수도, 수정될 수도, 삭제될 수도 있다. 예를 들어 REST를 적용하여 문서를 발행해 독자들이 이 문서를 읽을 수 있게 할 수 있다. 언제든 문서를 발행할 때는 웹 URL을 보여줘야 한다. 그래야 독자들이 그 문서의 정보(표현 상태)에 접근할 수 있다. 문서를 읽는 사람은 URL만 알면 문서 정보를 읽을 수 있고 승인이 있다면 정보를 수정할 수 있다.

Roy Fielding이 설명했듯이 REST의 기본 원칙 중 하나는 HTTP처럼 웹과 관련된 기존 기술, 표준, 프로토콜을 활용할 수 있다는 것이다. 기존 기술 및 프로토콜에 의존하므로 REST는 대다수의 기타 웹 기반 메시징 표준보다 쉽고 간단하게 사용할 수 있다. 효과적인 정보 교환을 위해 약간의 오버헤드만 있으면 되기 때문이다.

Fielding에 따르면 REST 기반 대화는 무상태 대화(stateless conversation) 내에서 동작하기 때문에 RSS, RDF, OWL, Atom 같은 등록 기반의 기술이 쉽게 보급될 수 있게 영향을 미쳤다. 여기에서 콘텐츠는 이미 등록한 클라이언트에 전달된다.

REST의 기본 개체

REST는 다음 기본 개체를 정의한다.

  • 데이터 요소: 데이터, 식별자(URI와 URL), HTML 문서, XML 문서, 이미지와 같은 데이터 표현
  • 컴포넌트: 아파치(Apache) httpd와 마이크로소프트® IIS(Internet Information Serivces) 같은 고유 서버, 스퀴드(Squid)와 CGI 같은 게이트웨이(gateways), 건틀렛(Gauntlet)과 넷스케이프(Netscape) 프록시 같은 프록시, 웹 브라우저나 모바일 기기 같은 사용자 에이전트
  • 커넥터: libwww 같은 클라이언트 커넥터, NSAPI 같은 서버 커넥터, 브라우저 캐시 같은 캐시 등

그림 1은 기본 REST 개체와 그것들이 전형적인 네트워크 시스템과 어떻게 상호작용하는지 보여준다. 이 때 커넥터에 의해 전달되거나 받게 되는 요청은 서버나 데이터베이스 같은 주어진 개체와 엮인다는 것에 주의하자. 커넥터는 주어진 프로토콜에서 전달하는 업무를 담당하므로 커넥터가 특정 프로토콜을 위해 존재하지 않는 한 개체는 어떤 프로토콜에서나 같은 방식으로 요청을 전달하거나 받는다.


그림 1. REST 개체 간 상호작용
REST 개체 간 상호작용

그림에서 볼 수 있듯이 이런 시스템의 커넥터는 컴포넌트 간에 통신할 수 있게는 프로토콜 포트로 구체화된다. REST 시스템에서 컴포넌트는 여러 개의 클라이언트 또는 서버 커넥터를 가지고 다른 컴포넌트와의 통신을 처리한다.


REST와 HTTP

Fielding의 논문에 따르면 HTTP 기반 REST 시스템에서는 GET, PUT, POST, DELETE라는 표준 HTTP 메서드를 사용하여 자원의 표현 상태에 접근해야 한다.

  • GET : 발행자가 소비자에게 자원의 현재 표현 상태를 전달할 때 이 메서드를 사용한다.
  • PUT : 소비자가 발행자에게 자원의 수정된 표현 상태를 전달할 때 이 메서드를 사용한다.
  • POST : 소비자가 발행자에게 자원의 표현 상태를 전달할 때 이 메서드를 사용한다.
  • DELETE : 자원의 표현 상태를 삭제 상태로 변경해야 하는 정보를 전달할 때 이 메서드를 사용한다.
서블릿과 JSP™(JavaServer Pages) 같은 자바 서버 기술은 표준 HTTP 메서드를 지원한다. 이제 이 기술들을 확장하여 자바 기술을 사용한 REST 기반 시스템에서 APP를 지원하는 방법에 대해 살펴보겠다

The Atom Publishing Protocol

APP 는 HTTP 기반 프로토콜로 자원을 발행, 삭제, 갱신하는 기능을 한다. APP는 발행, 갱신 등이 가능한 자원과 피드를 포함하는 인트로스펙션(introspection) 문서와 컬렉션(collection) 문서의 인스턴스를 전달하기 위해 HTTP 프로토콜이 제공하는 (GET, PUT, POST, DELETE 같은) 기본 기능을 사용하는 개념을 강조한다. 자원과 피드가 발견된 후 관련된 URI는 발행과 수정에 사용된다. 멤버 피드에는 블로그 엔트리, 포드캐스트, 위키 문서, 달력 이벤트 등이 있다. APP는 또한 데이터베이스 서버, 문서와 내용 관리 서비스, 소프트웨어 저장소 같은 폭넓은 데이터 저장 서비스를 위한 프론트 엔드 API로 쓰이고 연구되고 있다.

APP는 REST 개념과 기술을 사용하여 의미적으로 풍부한 콘텐츠 교환에 쓰이는 여러 메커니즘을 정형화기 위해 인기있는 아톰 배급 포맷(Atom Syndication Format) 위에 구축된다. 프로토콜은 컬렉션이 라 알려진 웹 자원 컨테이너에서 작동한다. 컬렉션은 인트로스펙션 문서에서 발견되는 것을 나타낸다. 모든 컬렉션에는 멤버 자원이 들어 있다. 이것들은 생성, 접근, 수정, 삭제를 위한 최종 타깃이다. 컬렉션과 이 멤버 자원 간의 상호작용은 일반적인 HTTP 동사(verb)를 기반으로 한다.

  • GET: 컬렉션이나 멤버 자원 표현을 찾아오는 데 쓰인다.
  • POST: 새 멤버 자원을 만드는 데 쓰인다.
  • PUT: 멤버 자원을 갱신하는 데 쓰인다.
  • DELETE: 멤버 자원을 삭제하는 데 쓰인다.

APP의 기본 개체

APP는 다음과 같은 기본 개체를 정의한다.

  • 인트로스펙션 문서: 인트로스펙션 문서는 application/atomserv+xml 미디어 타입을 가지고 컬렉션의 임의 서버 정의 그루핑인 workspaces를 기술한다. 컬렉션은 하나 이상의 작업공간에 나타날 수 있다. Listing 1은 전형적인 인트로스펙션 문서의 예다.


    Listing 1. 전형적인 인트로스펙션 문서
                                
    <?xml version="1.0" encoding="utf-8" ?>
    <service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
    - <workspace>
    <atom:title>Main Site</atom:title>
    - <collection href="http://localhost:8080/atompub/services/collections/main">
    <atom:title>My Main Page</atom:title>
    <accept>application/atom+xml;type=entry</accept>
    <categories fixed="yes" />
    </collection>
    <collection href="http://localhost:8080/atompub/services/collections/pics">
    <atom:title>My Pictures</atom:title>
    <accept>*/*</accept>
    <categories fixed="yes" />
    </collection>
    </workspace>
    - <workspace>
    <atom:title>Documents</atom:title>
    - <collection href="http://localhost:8080/atompub/services/collections/docs">
    <atom:title>My Documents</atom:title>
    <accept>application/atom+xml;type=entry</accept>
    <categories fixed="yes" />
    </collection>
    </workspace>
    </service>

    위에 보인 인트로스펙션 문서는 두 개의 작업공간으로 서비스를 정의한다. 첫 번째 작업공간은 "Main Site"라 부르는데 "My Main Page"와 "My Picture"라는 두 개의 컬렉션을 정의한다. "My Main Page" 컬렉션은 http://localhost:8080/atompub/services/collections/main에, "My Pictures" 컬렉션은 http://localhost:8080/atompub/services/collections/pics에 있다. 두 번째 작업공간인 "Documents"는 "My Documents"라는 하나의 컬렉션을 가지고 있고 이는 http://localhost:8080/atompub/services/collections/docs에 있다.

  • URI: 자원 식별자
  • IRI: 국제 자원 식별자 (참고자료 참조)
  • 멤버 자원: IRI나 URI가 지정하는 엔트리 자원 또는 미디어 자원으로 알려진 데이터 객체 또는 서비스. 엔트리 자원은 아톰 엔트리 문서로 표현된다. 미디어 자원은 어떤 미디어 타입에서든 표현을 가질 수 있다. 미디어 자원은 미디어 링크 엔트리를 사용하여 컬렉션 내에서 기술된다.
  • 표현: 요청이나 응답에 의해 전달되는 주어진 자원의 상태
  • 컬렉션: 특정 URI에 의해 식별된 멤버 자원의 컨테이너. 컬렉션은 아톰 피드로 표현된다. 컬렉션에서 새 엔트리를 만들려면 클라이언트는 컬렉션의 URI에 HTTP POST 요청을 보낸다. 새 엔트리에는 특정 URI가 부여되어 위치 레퍼런스로 사용된다. 컬렉션에서 엔트리를 수정하려면 클라이언트는 그 URI를 사용하여 자원을 찾아오고 수정을 한 후 PUT을 사용하여 자원의 수정된 표현을 서버로 옮긴다. 컬렉션에서 멤버 자원을 삭제하려면 멤버 자원의 URI로 HTTP DELETE 요청을 보낸다. 컬렉션의 모든 멤버는 컬렉션 문서에 따라 updated 속성을 가져야 한다.
  • 작업공간: 명명된 컬렉션 그룹
  • 서비스 문서/요소: 인트로스펙션 문서의 최상위 요소로 하나 또는 그 이상의 컬렉션 기능과 위치를 기술하고 작업공간으로 묶음
  • 카테고리 문서: 컬렉션에서 허용되는 카테고리를 기술하는 문서
다음 장에서는 자바 기술을 확장하여 REST 기반 시스템에서 APP를 지원하는 방법에 대해 알아보겠다

심화학습

HTTP 프로토콜을 거쳐 자원을 전달하는 시스템에 REST 원칙을 적용하는 데 자바 프로그래밍 언어를 사용할 수 있다. APP를 사용하여 이런 시스템을 확장하고 블로그 엔트리, 포드캐스트, 위키 페이지, 달력 엔트리 등을 사용하여 자원 접근 및 조작을 용이하게 할 수 있다. 본 튜토리얼에서는 자바 서블릿 기반 시스템을 사용하여 웹 애플리케이션 내에서 기본 REST와 APP를 적용하여 HTTP 요청 및 응답을 거쳐 자원을 전달하는 방법에 대해 보여준다.

아톰 프로토콜 명세는 클라이언트가 APP 엔드포인트를 인트로스펙트하는 데 사용할 수 있는 XML 문서를 정의한다. 인트로스펙션 문서는 주어진 엔드포인트 URI에 있는 모든 컬렉션의 목록을 가진다. 그러므로 APP가 가능한 서비스를 사용하는 첫 단계서비스 엔드포인트의 URI에서 가능한 컬렉션과 각 컬렉션이 포함할 수 있는 멤버 자원의 타입이 무엇인가를 결정하는 것이다. 인트로스펙션 문서를 찾아오려면 클라이언트는 GET 요청을 서버에 보낸다. 그리고 나면 서버는 서비스를 제공하는 컬렉션의 위치 URI를 포함하는 인트로스펙션 문서로 클라이언트에 응답한다.

인트로스펙션 문서에 있는 각각의 컬렉션 요소는 컨테이너가 컬렉션에 있는 각 멤버 자원의 정보에 저장됐다는 것을 나타낸다. 인트로스펙션 문서 안의 각 컬렉션은 특별한 URI를 가진다. 이 URI에 HTTP GET 요청을 보내면 컬렉션에 포함된 멤버 자원에 관한 정보를 포함하는 아톰 피드 문서를 반환해야 한다.

컬렉션에서 새 멤버 자원 엔트리를 만들려면 클라이언트는 컬렉션에 추가할 멤버 자원의 표현 상태와 함께 컬렉션 URI에 HTTP POST 요청을 보낸다. 새 멤버 자원 엔트리는 자신만의 URI를 받는데 이 URI로 각 멤버 자원 엔트리의 표현 상태를 찾아오도록 질의할 수 있다. 멤버 자원 엔트리를 수정하려면 클라이언트는 멤버 자원의 URI에 HTTP GET 요청을 함으로써 자원을 찾아오고 원하는 수정을 GET 요청이 반환하는 자원의 표현 상태로 만든다. 그리고 나서 HTTP PUT 요청을 사용하여 수정된 표현 상태를 컬렉션에 다시 보낸다. 컬렉션에서 멤버 자원 엔트리를 지우는 것은 클라이언트가 해당 멤버 자원 URI에 HTTP DELETE 요청을 할 때 유용하다.

REST를 사용하는 간단한 APP 애플리케이션

본 튜토리얼의 예제 애플리케이션은 컨트롤러 서블릿을 사용하여 들어오는 요청을 처리하고 결과 응답을 내보내도록 설계됐다. 들어오는 요청은 REST 기반 서비스 프레임워크로 발송되고(dispatch) 여기서 APP 인트로스펙션 문서, 컬렉션, 멤버 자원 상에서 기능을 처리할 수 있다.

REST 기반 APP 프레임워크는 그림 2처럼 클래스와 인터페이스를 포함한다.


그림 2. REST/APP 애플리케이션 프레임워크의 구성요소
REST/APP 애플리케이션 프레임워크의 구성요소

이 클래스 다이어그램에서 보는 것처럼 FrontController 서블릿은 대다수의 주요 REST 개체를 위한 중요한 레퍼런스다. FrontController 서블릿이 요청을 받으면 커넥터와 상호작용하여 각 요청의 비즈니스 로직을 처리할 BusinessService 객체를 위치시키는 데 쓰이는 ServerComponent 객체를 찾아온다. javax.activation 패키지에 있는 클래스를 사용하여 구현되는 Representation 인터페이스는 자원을 정의한다.

이제 각각의 주요 REST 엔티티와 APP 엔티티가 각 엔티티를 나타내는 특정 자바 컴포넌트와 함께 예제 프레임워크 안에서 어떻게 상호작용하는지에 관해 설명하겠다.

REST 컴포넌트

REST 컴포넌트는 주어진 시스템에서 역할에 따라 정의된다. 반복하자면 REST 시스템의 전형적인 컴포넌트는 다음과 같다.

  • 사용자 에이전트: 사용자 에이전트에 다시 응답하는 서버 컴포넌트 형태로 요청을 초기화하는 컴포넌트. 사용자 에이전트는 모바일 기기, 웹 브라우저 등이 될 수 있다.
  • 원 서버: 원(origin) 서버는 자원의 컬렉션을 위해 주어진 이름공간을 포함한다. 그리고 포함된 자원의 상태 표현을 관리한다. 사용자 에이전트에서 요청을 받고 목표 컬렉션이나 멤버 자원 상에서 필요한 동작을 수행한다. 그리고 나서 컬렉션 문서나 자원의 표현 상태로 사용자 에이전트에 응답한다.

자바 언어에서 마커 인터페이스를 사용해 REST 컴포넌트를 나타내야 할지도 모른다.

public interface Component
{
}

자바 언어의 서버 컴포넌트는 어떤 자원 컨텍스트가 참조되는지에 의해 메서드와 속성을 드러낼 것이다. Listing 2를 보자.


Listing 2. REST ServerComponent 인터페이스(자바 언어)
                    
public abstract class ServerComponent
implements Component
{
private java.util.HashMap<String, Context> contexts =
new java.util.HashMap<String, Context>();

abstract public Context addContext(String contextRootPath,
String contextPath);

public java.util.Iterator<String> getContextPaths()
{
return contexts.keySet().iterator();
}

public Context getContext(String contextPath)
{
return contexts.get(contextPath);
}

protected java.util.HashMap<String, Context> getContexts()
{
return contexts;
}
}

ServerComponent 클래스가 주어진 내용을 루트/문서 경로에 어떻게 매핑을 하는지에 주목하자. 필요에 따라 각 특정 서버 컴포넌트는 루트/문서 경로를 주어진 컨텍스트에 매핑하는 데 사용한다. 예를 들어 HTTP에 한정된 서버 컴포넌트는 Listing 3처럼 HTTP에 한정된 내용을 관리할 것이다.


Listing 3. REST ServerComponent 인터페이스(HTTP)
                    ;
public Context addContext(String contextRootPath, String contextPath)
{
Context context = getContexts().get(contextPath);
if (context == null)
{
context = new HTTPContext(contextRootPath, contextPath);
getContexts().put(contextPath, context);
}

return context;
}

HTTP에 한정된 컨텍스트는 각각의 주요 HTTP 메서드를 정의하는 인터페이스를 구현하고 목표한 컬렉션과 멤버 자원 표현 요청을 신속히 처리하는 수단을 제공한다. Listing 4는 HTTP GET 메서드 구현의 예를 보여준다. 특정 수직 도메인 서비스나 멤버 자원 피드의 자세한 비즈니스 로직은 특정 비즈니스 서비스 구현 내에서 캡슐화된다.


Listing 4. REST HTTP-컨텍스트 예
                    
public Representation handleGet(Request request)
throws ContextRequestException
{
String uri = ((HTTPRequest)request).getRequest().getRequestURI();
if (uri.startsWith(contextPath))
{
uri = reqURI.substring(contextPath.length());
}

BusinessService businessService =
ServiceLocator.locateService(contextRootPath, contextPath, uri);

Representation representation = null;

try
{
representation = businessService.read(request);
}
catch (ServiceExecutionException e)
{
e.printStackTrace();
throw new ContextRequestException(e);
}

return representation;
}

Listing 4에서 GET 요청은 요청에서 URI를 처음 추출함으로써 처리되고 URI를 사용하여 원하는 비즈니스 서비스의 위치를 찾는다. 그리고 나서 비즈니스 서비스는 요청을 처리하기 위해 호출된다.

REST 컴포넌트는 두 개씩 묶이고 커넥터 객체를 통해 각각 상호작용한다.


REST 커넥터

REST 의 커넥터는 REST 컴포넌트 사이에 통신하기 위해 속성과 메서드를 정의한 일반적인(generic) 인터페이스를 노출한다. 커넥터는 커뮤니케이션 프로토콜의 한 부분이나 엔드포인트를 나타낸다. 커넥터를 구현하여 클라이언트 커넥션이나 서버 커넥션을 나타낼 수 있다. 사용자 에이전트는 클라이언트 커넥터를 사용하여 원(origin) 서버에 대한 요청을 초기화한다. 원 서버는 서버 커넥터에 의존하여 사용자 에이전트에서 들어오는 요청을 처리하고 컬렉션이나 멤버 자원의 표현 상태로 응답한다.

Listing 5에서 인터페이스는 주어진 포트에 서버 컴포넌트를 추가하기 위해 하나의 메서드를 정의한 커넥터를 보여준다.


Listing 5. REST 커넥터 인터페이스
                    
public interface Connector
{
public ServerComponent addServer(int port);
}

factory 클래스는 프로토콜에 한정된 커넥터를 관리하는 데 유용하다. factory 클래스는 상황이 지시한 대로 factory 커넥터를 묶을 수 있다. Listing 6은 커넥터 factory 클래스의 예를 제공한다.


Listing 6. REST 커넥터 팩토리 예
                    ;
public static Connector getConnector(Protocol protocol)
throws ConnectorException
{
String key = protocol.getScheme();
Connector instance = instances.get(key);

if (instance == null)
{
if (key.equalsIgnoreCase(Protocol.HTTP.getScheme()))
{
instance = new HTTPConnector();
instances.put(key, instance);
}
else
{
throw new ConnectorException("Invalid protocol: "
+ protocol.getScheme());
}
}

return instance;
}

HTTP에 한정된 서버 커넥터를 위한 커넥터 인터페이스는 Listing 7처럼 특정 포트에 HTTP에 한정된 서버 컴포넌트를 추가하기 위한 속성과 메서드를 정의한다.


Listing 7. REST 서버 커넥터 예
                    
public ServerComponent addServer(int port)
{
return new HTTPServerComponent(port);
}

커넥터는 사용자 에이전트와 원 서버 사이의 통신을 가능하게 한다. 원 서버는 서버 컴포넌트에 의해 나타나며 멤버 자원을 포함하고 관리한다. 더 자세한 내용은 다음 절에서 다루겠다.


REST 자원 표현

사용자 에이전트가 요청을 하면 URI는 목표한 컬렉션이나 요청의 멤버 자원을 식별하는 원 서버로 전달된다. 원 서버를 나타내는 서버 컴포넌트가 요청을 받으면 요청에서 URI를 추출하고 요청을 처리하는 데 필요한 동작을 수행한다. 멤버 자원을 목표로 하는 요청은 자원의 표현 상태를 만들거나 읽거나 수정하거나 삭제할 수 있다. 그리고 나서 필요하다면 자원의 표현 상태 결과를 반환한다.

HTTP 요청을 처리할 컴포넌트 만들기

사용자 에이전트에서 원 서버로, 그리고 다시 사용자 에이전트로 HTTP 요청을 처리하는 데 필요한 컴포넌트를 만들고 접근하려면 다음 단계를 밟아야 한다.

  1. 서버 커넥터를 만들고 나서 이를 적절한 HTTP 서버 컴포넌트에 추가한다.
  2. 주어진 서버 컨텍스트를 위해 HTTP 컨텍스트를 추가하거나 이에 접근한다.
  3. 서버 컴포넌트는 들어오는 사용자 에이전트 요청을 받아 목표한 비즈니스 서비스로 보낸다.
  4. 비즈니스 서비스가 생성하는 자원을 나타내는 데이터 모델을 만들고 파퓰레이트한다. 그리고 이를 목표 자원의 표현 상태로 캡슐화한다.
  5. 자원의 표현 상태를 사용자 에이전트에 다시 보낸다.

그림 3은 이 과정을 보여준다.


그림 3. REST 프레임워크 제어 흐름
REST 프레임워크 제어 흐름

DataHandler 클래스 확장하기

JAF(JavaBean Activation Framework)는 멤버 자원의 상태를 효과적으로 나타낼 수 있는 컴포넌트인 javax.activation.DataHander 클래스를 제공한다. DataHandler 컴포넌트를 사용하여 자원을 표현내면 임의의 콘텐츠 타입이 들어 있는 이종 자원을 똑같은 일반적인 인터페이스로 나타낼 수 있다.

Listing 8에서 보여주는 추상 클래스는 DataHandler 클래스를 확장하여 멤버 자원을 나타내는 객체의 일반적인 기본 클래스를 제공한다.


Listing 8. 추상 Representation 클래스
                    
public abstract class Representation extends javax.activation.DataHandler
{
public Representation(DataSource dataSource)
{
super(dataSource);
}
}

본 튜토리얼의 목적처럼 아톰 피드 자원을 캡슐화하는 Representation 인터페이스의 특정 구현은 Listing 9처럼 만들 수 있다.


Listing 9. Representation 클래스
                    
public class FeedRepresentation extends Representation
{
public static final String MEDIA_TYPE = "application/atom+xml";
public static final String CONTENT_TYPE = MEDIA_TYPE + "; charset=utf-8";

public FeedRepresentation(File file)
{
super(new FeedDataSource(file, CONTENT_TYPE));
}

public String getContentType()
{
return CONTENT_TYPE;
}
}

Listing 9를 보면 피드 자원은 FeedRepresentation 클래스 내에서 캡슐화된다. 이 클래스는 FeedDataSource 클래스를 사용하여 파일 자원의 입출력 동작을 처리한다.


구현을 위해 속성과 메서드 정의하기

JAF 또한 임의의 컬렉션 데이터를 보여주는 자원에 의해 구현된 속성과 메서드를 정의하는 인터페이스인 javax.activation.DataSource를 제공한다. 인터페이스는 메서드를 제공하여 자원과 자원의 데이터에서 작동하는 입출력 스트림의 콘텐츠 타입을 보여준다. DataHandler는 객체를 사용하여 DataSource 인터페이스를 구현함으로써 똑같은 일반 인터페이스를 통해 기본적인 자원에 접근한다.

FeedDataSource의 각 구현은 기초를 이루는 자원 저장 메커니즘(예로 데이터베이스, 파일 시스템)에 의존하여 피드 데이터를 저장하고 찾아온다. Listing 10처럼 FeedDataSource 구현은 파일 시스템에 의존한다.


Listing 10. 파일 기반의 FeedDataSource 클래스
                    
public class FeedDataSource
implements DataSource
{
private File file = null;
private String contentType = "";

public FeedDataSource(File file, String contentType)
{
this.file = file;
this.contentType = contentType;
}

public String getContentType()
{
return contentType;
}

public InputStream getInputStream()
throws IOException
{
return new FileInputStream(file);
}

public String getName()
{
return file.getName();
}

public OutputStream getOutputStream()
throws IOException
{
return new FileOutputStream(file);
}

}

Listing 10에서 파일 자원은 FeedDataSource 클래스 내에서 캡슐화된다. 이 클래스는 주어진 파일 자원의 입/출력 기능을 수행한다.


FrontController 서블릿 구현하기

FrontController 서블릿을 구현하여 다양한 컴포넌트, 커넥터, 컨텍스트 클래스와 상호작용함으로써 사용자 에이전트로부터 들어오는 요청을 처리한다. 그리고 나서 서블릿이 나타내는 요청에 적합하면 서블릿은 비즈니스 서비스에서 생성된 자원의 표현 상태와 함께 사용자 에이전트에 응답한다.

Listing 11은 표준 HttpServlet doGet() 메서드를 처리하는 예를 보여준다. 목표를 요청하는 컨텍스트는 추출되고 요청 데이터를 처리하는 서버 컴포넌트를 찾아오는 데 쓰인다.


Listing 11. FrontController 서블릿 클래스를 위한 doGet() 메서드
                    
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,
IOException
{
HTTPConnector connector =
(HTTPConnector)Connectors.getConnector(Protocol.HTTP);
HTTPServerComponent serverComponent =
(HTTPServerComponent)connector.addServer(request.getServerPort());

String contextRootPath = this.getServletContext().getRealPath("/");
String contextPath = request.getContextPath() + "/"
+ this.getServletName();
serverComponent.addContext(contextRootPath, contextPath);

Representation representation = null;

try
{
representation =
serverComponent.getContext(request,
this).handleGet(new HTTPRequest(request));
}
catch (Exception e)
{
e.printStackTrace();
throw new ServletException("Error: " + e);
}

new HTTPResponse(response).write(representation.getContentType(),
representation.getInputStream());
}

Listing 11에서 FrontController 클래스의 doGet 메서드는 요청을 받고 처리하는 방법을 보여준다. HTTP 프로토콜의 커넥터는 서버 컴포넌트를 추가하고 찾아오는 데 사용된다. 목표한 자원 컨텍스트 객체는 서버 컴포넌트에서 찾아온다. 그러면 자원 컨텍스트 객체는 GET 요청을 처리하는 데 쓰인다. Resource 표현 객체는 자원 컨텍스트 객체에 의해 반환된다. 그리고 나서 표현 객체는 HTTPResponse 클래스를 사용하여 요청자에 다시 전달된다.


비즈니스 로직 구현하기

BusinessService 클래스를 사용하여 특정 APP 컬렉션의 멤버 자원을 나타내는 모델 데이터를 반환할 비즈니스 로직을 구현한다. BusinessService 클래스는 로직을 구체화한다. 이 때 로직은 특정 컬렉션과 자원 표현에서 컬렉션이나 멤버 자원의 아톰 피드에 적절한 javax.activation.DataHandler 클래스 구현과 javax.activation.DataSource 인터페이스로 작동한다.

Listing 12는 아톰 피드 자원의 컬렉션을 캡슐화하는 비즈니스 서비스의 예를 제공한다.


Listing 12. 피드 컬렉션 비즈니스-서비스 클래스
                    
public class CollectionService
implements BusinessService
{
// todo: read RESOURCES_DIR from config or system property
//
private static final String RESOURCES_DIR = "resources";

private String contextRootPath = "";

private String contextPath = "";

public CollectionService(String contextRootPath,
String contextPath)
{
this.contextRootPath = contextRootPath;
this.contextPath = contextPath;
}

protected String getContextPath()
{
return contextPath;
}

protected String filePathFromRequest(Request httpReq)
{
return contextRootPath + RESOURCES_DIR
+ ((HTTPRequest) httpReq).getRequest().getPathInfo();
}

protected void writeToFile(Request request,
File file,
boolean append)
throws IOException,
FileNotFoundException
{
System.out.println("writeToFile called");

InputStream inStream = ((HTTPRequest) request).getInputStream();
byte[] dataBuf = new byte[4096];

FileOutputStream outStream = new FileOutputStream(file, append);
int bytesRead = 0;
while ((bytesRead = inStream.read(dataBuf)) > 0)
{
System.out.println("Writing [" + bytesRead + " bytes]");
outStream.write(dataBuf, 0, bytesRead);
}

outStream.flush();
outStream.close();
}

public Representation create(Request request)
throws ServiceExecutionException
{
System.out.println("CollectionService.create()");

// todo: create feed from structured storage with DAO
//
String filePath = filePathFromRequest(request);
File file = new File(filePath);

try
{
boolean append = false;
writeToFile(request, file, append);
}
catch (Exception e)
{
e.printStackTrace();
throw new ServiceExecutionException(e);
}

FeedRepresentation representation = new FeedRepresentation(file);

return representation;
}

public Representation read(Request request)
throws ServiceExecutionException
{
System.out.println("CollectionService.read()");

// todo: read feed from structured storage with DAO
//
String filePath = filePathFromRequest(request);
File file = new File(filePath);
if (file.exists() == false)
{
throw new ServiceExecutionException("Feed file ["
+ filePath + "] does not exist.");
}

FeedRepresentation representation = new FeedRepresentation(file);

return representation;
}

public Representation update(Request request)
throws ServiceExecutionException
{
System.out.println("CollectionService.update()");

// todo: update feed from structured storage with DAO
//
String filePath = filePathFromRequest(request);
File file = new File(filePath);
FeedRepresentation representation = new FeedRepresentation(file);

try
{
boolean append = (file.exists() ? true : false);
writeToFile(request, file, append);
}
catch (Exception e)
{
e.printStackTrace();
throw new ServiceExecutionException(e);
}

return representation;
}

public void delete(Request request)
throws ServiceExecutionException
{
System.out.println("CollectionService.delete()");

// todo: delete feed from structured storage with DAO
//
String filePath = filePathFromRequest(request);
System.out.println("CollectionService.delete() - resolving feed file ["
+ filePath + "]");
File file = new File(filePath);
System.out.println("CollectionService.delete() - feed file resolved");
if (file.exists())
{
System.out.println("CollectionService.delete() - deleting feed file");

if (file.delete() == false)
{
System.out.println("CollectionService.delete() - feed deletion failed");
throw new ServiceExecutionException("Error deleting feed ["
+ filePath + "]");
}
System.out.println("CollectionService.delete() - feed deletion succeeded");
}
else
{
System.out.println("CollectionService.delete() - feed file ["
+ filePath + "] does not exist");
}
}
}

Listing 12는 아톰 피드 자원의 컬렉션을 캡슐화하는 비즈니스 서비스 클래스의 예를 제공한다. 이 클래스는 물리적 파일을 각 피드 자원의 소스로 사용한다. 각 피드 자원에 관한 동작은 "CRUD" 메서드(create, read, update, delete)에 의해 정의된다. 이 동작의 일반적인 추상화는 메서드 서명이나 서비스 인터페이스에 영향을 주지 않고 기초가 되는 물리적 소스를 변경할 수 있도록 한다.

특정 피드 자원의 컬렉션을 나타내는 비즈니스 서비스를 가지고 있다면 이제 사용자 에이전트는 적절한 비즈니스 서비스에 대한 요청을 처리하는 원 서버의 FrontController에 요청을 보낼 수 있다. 비즈니스 서비스는 목표한 자원에서 실행되면서 필요한 비즈니스 로직을 수행한다. 목표 자원은 논리의 명령에 따라 생성, 접근, 갱신, 삭제를 실행한다. 요청에서 발생되는 목표 멤버 자원의 표현 상태는 FrontController 서블릿에 다시 전달된다. 이 서블릿은 필요하다면 HTTP 응답 형태로 사용자 에이전트로 다시 중계된다.


요약하기

REST 는 기존 프로토콜과 표준을 사용하여 데이터나 서비스를 나타내는 자원에서 작동하는 분산 시스템을 만드는 소프트웨어 아키텍처 스타일이자 원칙의 집합이다. REST는 추가적인 시맨틱 레이어나 세션 관리에 의존하지 않고 HTTP 같은 프로토콜에 데이터를 전달하는 프레임워크를 기술하는 데 진화해 왔다.

APP는 데이터나 서비스를 나타내는 자원에 접근, 작성, 수정하는 HTTP 기반 프로토콜이다. APP는 HTTP 프로토콜이 제공하는 표준 동작(GET, PUT, POST, DELETE)을 사용하여 멤버 자원의 컬렉션을 인트로스펙트하고 블로그 엔트리, 이미지, 문서, 달력 엔트리 등을 나타내는 멤버 자원을 조작한다.)

본 튜토리얼에서는 기본 REST와 APP를 다뤘다. 또한 엔터프라이즈 시스템 내에 REST를 적용하는 법을 배웠다. 멤버 자원의 표현 상태와 컨테이너에 접근 및 조작을 수월하게 하기 위해 APP를 사용하여 자바 서블릿 기반 시스템을 통해 HTTP 요청과 응답을 주고 받았다.


필자소개

Jeff Hanson

Jeff Hanson은 윈도우 오픈독(Windows OpenDoc) 프로젝트의 선임 엔지니어 및 노벨 Route 66 프레임워크의 책임 아키텍트 등을 포함 20년 이상 소프트웨어 업계에서 경력을 쌓아왔다. 현재 Max International에서 CTO로 재직 중이며 웹 서비스, 마이크로 포맷, XML, 자바/J2EE, PHP, 모바일 기술, 윈도우 API를 사용해 시스템을 설계하고 만드는 업무를 담당하고 있다. Jeff는 .NET versus J2EE Web Services: A Comparison of Approaches, Pro JMX: Java Management Extensions, Web Services Business Strategies and Architectures 등 많은 저서와 기사를 쓰고 있다.


다 읽었다.. --;;






      Service Oriented  |  2009. 3. 9. 14:28



archidream's Blog is powered by Daum