튜토리얼#

목차#

  1. 구현체 등록하기 [1]
  2. 웹 페이지 내용 가져오기 [2]
  3. 웹 브라우저에 청취하기 [3]
  4. REST 아키텍처의 개요 [4]
  5. 컴포넌트, 가상 호스트와 애플리케이션 [5]
  6. 정적인 파일 제공하기 [6]
  7. 접근 로깅 [7]
  8. 에러 페이지 표시하기 [8]
  9. 민감한 자원에 대한 접근을 감시하기 [9]
  10. URI 재작성(rewriting)과 리다이렉트(redirection) [10]
  11. 라우터와 구조적인 URI [11]
  12. 대상 자원에 도달하기 [12]
  13. 결론 [13]

1. 구현체 등록하기 [#1]#

Restlet 프레임워크는 두가지 주요부분으로 구성되어 있다. 먼저, REST의 개념을 지원하고 클라이언트측과 서버측 애플리케이션 모두를 위한 호출을 다루는 중립적인 API인 "Restlet API"가 있다. 효과적으로 사용되도록 Restlet구현체가 API를 지원해야만 한다. 다중 구현체가 오픈소스 프로젝트나 상업적인 제품형태로 제공될수 있다.

1.png

API와 구현체간의 구분은 서블릿 API와 Jetty나 톰캣같은 웹 컨테이너, 또는 JDBC API와 구현된 JDBC 드라이버간의 구분과 유사하다. 최근에는 "Noelios Restlet Engine" (NRE)가 사용가능하고 참조 구현체처럼 작동한다. Restlet 배포판을 다운로드할때, API와 NRE는 번들형태로 함께 제공된다. 다른 구현체를 사용할 필요가 있다면 클래스패스에 해당 구현체 JAR파일을 추가하고 com.noelios.restlet.jar 라는 이름의 NRE JAR파일을 제거하라.

그러면 등록이 자동으로 이루어진다. 상세한 정보를 보기 위해서는 JAR specification 를 보라. 구현체가 로드되면 org.restlet.util.Engine.setInstance() 메소드를 자동으로 콜백한다.

2. 웹 페이지 내용 가져오기 [#2]#

소개 페이지 에서 언급한것처럼, Restlet프레임워크는 동시에 클라이언트 프레임워크도 되고 서버 프레임워크도 된다. 예를들어, NRE는 HTTP 클라이언트 연결자(connector)를 사용하여 원격 자원으로 쉽게 작동할수 있다. REST내 연결자는 대개 네트워크 프로토콜을 구현하여 컴포넌트간의 통신을 가능하도록 하는 소프트웨어 요소들이다. NRE는 현재 존재하는 오픈소스 프로제트에 기초하여 다양한 클라이언트 연결자 구현체를 제공한다. connectors 절은 사용가능한 클라이언트와 서버 연결자를 나열하고 그것들을 사용하는 방법과 설정법을 설명한다.

여기서 존재하는 자원의 표시를 JVM콘솔에 출력할것이다.

// 웹 페이지의 내용 출력하기
Client client = new Client(Protocol.HTTP);
client.get("http://www.restlet.org").getEntity().write(System.out);

위 예제가 일반적인 Client 클래스를 통해 호출을 하는 간단한 방법을 사용하고 있다. 좀더 유연한 방법은 새로운 Reqeust객체를 생성하고 이 객체를 다루도록 Client에 요청하는 것이다. 아래 예제는 호출에 대해 리퍼러(referrer) URI와 같은 몇가지를 셋팅하는 방법을 보여준다. 이것은 응답으로 받고자 하는 언어와 미디어 타입일수도 있다.

// request 객체 준비
Request request = new Request(Method.GET, "http://www.restlet.org");
request.setReferrerRef("http://www.mysite.org");

// HTTP 클라이언트 연결자를 사용하여 request객체 다루기
Client client = new Client(Protocol.HTTP);
Response response = client.handle(request);

// 콘솔에 응답 항목 쓰기
Representation output = response.getEntity();
output.write(System.out);

3. 웹 브라우저에 청취하기 [#3]#

지금, 우리는 Restlet프레임워크가 클라이언트 요청을 청취하고 클라이언트에 응답하는 방법을 보고자 한다. 여기서는 NRE HTTP 서버 연결자중 하나를 사용하고 일반 텍스트로 "Hello World!" 라는 간단한 문자열을 반환할것이다. 실제 애플리케이션에서는 익명 내부 클래스에 의존하는 것보다는 Restlet를 확장하는 개별 클래스를 아마도 생성할것이다.

Restlet 클래스는 Servlet과 유사하고 RESTful 애플리케이션에서 호출을 다룰때 제한적으로 도와준다. 나중에 추상화와 프로세스를 다루는 것을 단순화하는 프레임워크가 제공하는 많은 특성화된 하위 클래스와 Resource 클래스를 보게될것이다. 지금은 간단한 예제를 보자.

// "Hello World"를 반환하는 최소한의 Restlet를 생성하기
Restlet restlet = new Restlet() {
    @Override
    public void handle(Request request, Response response) {
        response.setEntity("Hello World!", MediaType.TEXT_PLAIN);
    }
};

// HTTP 서버를 생성하고 8182 포트에서 요청을 청취하기
new Server(Protocol.HTTP, 8182, restlet).start();

서버를 올리고 이 코드를 실행한다면, 웹 브라우저에서 http://localhost:8182 를 볼수 있다. 실제로 어떤 URI도 작동할것이다. http://localhost:8182/test/tutorial 페이지를 보라. 다른 머신에서 서버를 테스트하고자 한다면, URL에서 "localhost"를 서버에 할당된 IP주소나 도메인명으로 대체할 필요가 있다.

4. REST 아키텍처의 개요 [#4]#

조금 뒤로 물러나서 REST관점에서 전형적인 웹 아키텍처를 생각해보자. 아래의 다이어그램에서, 포트(선들을 연결하는 작은 사각형)는 큰 박스가 표시하는 컴포넌트간의 통신을 가능하게 하는 연결자를 나타낸다. 링크는 실제 통신을 위해 사용되는 특별한 프로토콜(HTTP, SMTP, 등등)을 표시한다.

2.png

같은 컴포넌트는 많은 수의 클라이언트를 가질수 있고 서버 연결자는 각각의 클라이언트에 붙게된다. 예를 들어, 웹서버 B는 사용자 에이전트 컴포넌트의 요청에 응답하기 위한 서버 연결자와 데이터베이스 서버, 메일 서버와 웹서버 A에 요청을 보내는 클라이언트 연결자를 모두 가진다.

5. 컴포넌트, 가상 호스트와 애플리케이션 [#5]#

앞에서 보여준 표준 REST 소프트웨어 아키텍처 요소를 지원하기 위해 추가적으로, Restlet프레임워크는 하나의 JVM내 다중 애플리케이션의 호스팅을 단순화하는 클래스들을 제공한다. 목표는 존재하는 서블릿 API에 대해 RESTful하고 이식가능하고 좀더 유연한 대안을 제공하는 것이다. 아래의 다이어그램에서, 이런 복잡한 경우를 다루기 위해 제공되는 Restlet의 3가지 타입을 볼수 있다. 컴포넌트는 여러개의 가상 호스트와 애플리케이션을 관리할수 있다. 가상 호스트는 유연한 설정을 지원한다. 예를 들어 같은 IP가 여러개의 도메인명으로 공유되거나 같은 도메인이 여러개의 IP로 로드밸런싱이 되는 것들을 지원한다. 마지막으로 관련 Restlet, 자원(Resource)과 표시(Representation)를 관리하기 위해 애플리케이션을 사용한다. 추가적으로 애플리케이션은 다른 Restlet구현물과 다른 가상 호스트에 대해 이식가능하고 설정가능하게 된다. 그리고 접근 로깅, 요청 항목의 자동 디코딩, 설정가능한 상태 상태 페이지 셋팅 등과 같은 중요한 서비스를 제공한다.

3.png

이러한 클래스를 보여주기 위해, 간단한 예제를 보자. 여기서 Component 객체를 생성하고 그 Component 객체에 HTTP 서버 연결자를 추가하고 나서 8182 포트로 청취한다. 그리고나서 간단한 추적(trace) Restlet을 생성하고 컴포넌트의 디폴트 VirtualHost에 추적 Reslet를 붙힌다. 디폴트 호스트는 선언된 VirtualHost(상세한 정보를 위해서는 Component.hosts프로퍼티를 보라)에 아직 라우팅되지 않은 요청들을 가져온다. 다음 예제에서, Application 클래스 사용법을 소개할것이다.

// 새로운 Restlet컴포넌트를 생성하고 그 컴포넌트에 HTTP 서버 연결자를 추가
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8182);

// 새로운 추적 Restlet를 생성
Restlet restlet = new Restlet() {
    @Override
    public void handle(Request request, Response response) {
        // Print the requested URI path
        String message = "Resource URI  : " + request.getResourceRef()
                '\n' "Root URI      : " + request.getRootRef()
                '\n' "Routed part   : "
                + request.getResourceRef().getBaseRef() '\n'
                "Remaining part: "
                + request.getResourceRef().getRemainingPart();
        response.setEntity(message, MediaType.TEXT_PLAIN);
    }
};

// 로컬호스트에 추적 Restlet를 붙이기
component.getDefaultHost().attach("/trace", restlet);

// 지금 시작해보자. 
// HTTP 서버 연결자는 자동으로 시작된다. 
component.start();

웹 브라우저의 주소창에 http://localhost:8182/trace/abc/def?param=123를 입력해서 테스트해보자. 다음은 그 결과이다.

Resource URI  : http://localhost:8182/trace/abc/def?param=123
Root URI      : http://localhost:8182/trace
Routed part   : http://localhost:8182/trace
Remaining part: /abc/def?param=123

6. 정적인 파일 제공하기 [#6]#

Javadoc과 같은 정적인 페이지를 제공하는 웹 애플리케이션의 일부를 가지고 있는가.? 좋아. 그렇다면 정적인 페이지를 위해 Apache서버를 셋업하는 작업은 필요하지 않다. 대신에 전용 Directory 클래스를 사용하라. 간단히 사용하는 방법을 보라.

// 컴포넌트 생성하기
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8182);
component.getClients().add(Protocol.FILE);

// 애플리케이션 생성하기
Application application = new Application(component.getContext()) {
    @Override
    public Restlet createRoot() {
        return new Directory(getContext(), ROOT_URI);
    }
};

// 애플리케이션을 컴포넌트에 붙이고 시작하기
component.getDefaultHost().attach("", application);
component.start();

짐작할수 있듯이 애플리케이션은 5절의 샘플 코드와는 달리 부모 컴포넌트의 컨텍스트를 전달해서 인스턴스화된다. The main reason resides in the fact the dispatching of client requests made by an application need to be handled by client connectors which are controlled by the component and shared between all the contained applications.

이 예제를 실행하기 위해, Restlet설치 위치에 따라 ROOT_URI를 위한 값을 명시할 필요가 있다. 디폴트로 이 값은 "file:///D:/Restlet/www/docs/api/"로 셋팅했다. 추가적인 설정이 필요하지 않다. 파일 확장과 메타데이터(미디어 타입, 언어 또는 인코딩) 사이의 매핑을 정의하거나 다른 인덱스명 Application의 "metadataService" 프로퍼티를 사용할수 있다.

7. 접근 로깅 [#7]#

웹 애플리케이션의 작동상태를 로깅하는 것은 아마도 공통적인 요구사항일것이다. Restlet 컴포넌트는 아파치 스타일의 로그나 사용자 정의한 형태의 로그를 생성하는 방법을 디폴트로 제공한다. JDK에 내장된 로깅 수단의 장점을 가짐으로써, 로거는 메시지를 필터링하거나 형태를 다시 만들거나 메시지를 보낼 위치를 명시하기 위해 표준 JDK 로그처럼 설정될수 있다. 로그의 순환또한 지원된다. 상세한 정보를 보기 위해서는 java.util.logging 패키지를 보라.

Component의 "logService" 프로퍼티를 변경하여 java.util.logging 프레임워크에 주어진 로거 이름을 임의로 변경할수 있다. 로깅 설정을 위해서 다음처럼 시스템 프로퍼티를 셋팅하여 설정파일을 선언할 필요가 있다.

System.setProperty("java.util.logging.config.file",
        "/your/path/logging.config");

설정 파일 포맷에 대한 상세정보를 위해 JDK의 LogManager 클래스를 살펴보라.

8. 에러 페이지 표시하기 [#8]#

다른 공통 요구사항으로는 호출을 다루는 동안 기대하지 않은 것이 발생했을때 상태를 알려주는 페이지를 설정하는 기능이다. 아마도 해당 자원을 찾을수 없거나 접속가능한 페이지가 사용가능하지 않을때인가..? 이 경우, 또는 어떤 다루기 힘든 예외가 발생했을때 Application이나 Component는 디폴트 상태를 알려주는 페이지를 자동적으로 제공할것이다. 이 서비스는 "statusService" 라고 불리는 Application 과 Component 프로퍼티처럼 접근가능한 org.restlet.util.StatusService 클래스에 관련된다.

디폴트 메시지를 설정하기 위해, StatusService의 하위 클래스를 생성하고 getRepresentation(Status, Request, Response) 메소드를 오버라이드할 필요가 있을것이다. 그리고 나서 적절한 "statusService" 프로퍼티에 당신이 정의한 서비스의 인스턴스를 셋팅한다.

9. 민감한 자원에 대한 접근을 감시하기 [#9]#

몇가지 Restlet에 접근하는 것을 보안적으로 제안할 필요가 있을때, 사용가능한 다양한 옵션이 있다. 공통적인 방법은 접근이 가능한지 판단하기 위해 클라이언트를 확인하고 애플리케이션 상태에 대해 주어진 사용자 ID나 세션 ID를 체크하기 위해 쿠키에 의존하는 것이다. Restlet은 RequestResponse에서 접근가능한 CookieCookieSetting 객체를 통해 쿠키를 지원한다.

표준 HTTP 인증 기법에 기초하여 다른 방법이 있다. Noelios Restlet 엔진은 최근에 기본적인 HTTP 구조에서 보내고 받는 인증정보와 아마존 웹 서비스 구조에서 받는 인증정보를 받는다.

호출을 받을때, 개발자는 Guard 필터를 통해 Request.challengeResponse.identifier/secret 에서 사용가능한 파싱된 인증정보를 사용할수 있다. 필터는 호출하고 Restlet를 첨부하기 전에 호출을 미리 처리하거나 첨부된 Restlet가 반환한 후에 호출뒤에 처리하는 특성화된 Restlet이다. 서블릿 API에 친숙하다면 개념은 Filter 인터페이스와 유사하다. Directory에 접근하는 것에 대해 보안적으로 적용하는 이전 예제를 변경하는 방법은 아래에서 볼수 있다.

// Guard 생성하기
Guard guard = new Guard(getContext(),
        ChallengeScheme.HTTP_BASIC, "Tutorial");
guard.getSecrets().put("scott""tiger".toCharArray());

// 파일의 구조를 반환하기 위한 Directory 생성하기
Directory directory = new Directory(getContext(), ROOT_URI);
guard.setNext(directory);
return guard;

4.png

인증(authentication)과 인가(authorization) 부분은 authenticate() 과 authorize() 메소드를 통해 사용자가 임의로 설정할수 있다. 어떤 사용자 정의 기법도 주어진 인증정보가 유효한지 인증받은 사용자가 Restlet를 계속적으로 첨부하도록 허가되었는지를 체크하기 위해 사용된다. 여기서 우리는 단일 사용자와 비밀번호를 직접 입력했다. 테스트를 진행하기 위해 클라이언트 측 Restlet API를 사용하자.

// Request 객체 준비하기
Request request = new Request(Method.GET, "http://localhost:8182/");

// 호출에 클라이언트측 인증 추가하기
ChallengeScheme scheme = ChallengeScheme.HTTP_BASIC;
ChallengeResponse authentication = new ChallengeResponse(scheme,
        "scott""tiger");
request.setChallengeResponse(authentication);

// 호출을 다루기 위해 HTTP 클라이언트 연결자에게 요청하기
Client client = new Client(Protocol.HTTP);
Response response = client.handle(request);

if (response.getStatus().isSuccess()) {
    // JVM콘솔에 응답 항목을 출력하기
    response.getEntity().write(System.out);
else if (response.getStatus().equals(Status.CLIENT_ERROR_UNAUTHORIZED)) {
    // 인증되지 않은 접근
    System.out.println("Access authorized by the server, " "check your credentials");
else {
    // 기대되지 않은 상태
    System.out.println("An unexpected status was returned: "
            + response.getStatus());
}

서버에서 보낸 응답을 체크하기 위해 이 테스트 클라이언트가 보낸 사용자 ID와 비밀번호를 변경할수 있다. 클라이언트를 실행하기 전에 Restlet 서버를 시작하는 것을 잊지말라. 다른 머신에서 서버를 테스트하고자 한다면 "localhost"를 서버의 IP주소나 도메인으로 변경할 필요가 있다. 디폴트로 모든 타입의 URI를 다룰수 있는 VirtualHost를 사용하기 때문에 어떤 조정작업도 필요하지 않다.

10. URI 재작성(rewriting)과 리다이렉트(redirection) [#10]#

Restlet 프레임워크의 또다른 장점은 멋진(cool) URIs 를 위한 내장된 지원이다. AlertBox 는 임의 URI 설계의 중요성을 잘 설명하였다. 멋진 URI을 다른 형태의 URI로 재작성하는 Redirector의 첫번째 사용가능한 툴은 다음처럼 자동(automatic) 리다이렉트이다. 다양한 타입의 리다이렉트가 지원된다. 클라이언트/브라우저를 통한 외부 리다이렉트와 프록시와 같은 행위를 하는 연결자 리다이렉트등이 있다. 아래의 예제에서, 구글에 기초하여 웹사이트("mysite.org" 라고 명명된)를 위한 검색 서비스를 정의할것이다. "/search" 라는 상대경로의 URI는 'kwd" 파라미터를 통해 몇가지 키워드를 받는 검색 서비스를 식별한다.

// Application 생성하기
Application application = new Application(component.getContext()) {
    @Override
    public Restlet createRoot() {
        // 구글 검색 서비스를 위한 Redirector 생성하기
        String target =
           "http://www.google.com/search?q=site:mysite.org+{keywords}";
        return new Redirector(getContext(), target,
                Redirector.MODE_CLIENT_TEMPORARY);
    }
};

// 컴포넌트의 디폴트 호스트에 애플리케이션을 첨부한다. 
Route route = component.getDefaultHost().attach("/search", application);

// 요청을 애플리케이션으로 보내는 동안 쿼리 파라미터를 추출한다.
// 예를 들면 아래값은
// http://localhost:8182/search?kwd=myKeyword1+myKeyword2
// 다음처럼 될것이다. 
// http://www.google.com/search?q=site:mysite.org+myKeyword1%20myKeyword2
route.extractQuery("keywords""kwd"true);

Redirector는 오직 3개의 파라미터만을 필요로 한다. 첫번째는 부모 컨텍스트, 두번째는 URI 템플릿에 기초하여 URI 재작성이 되는 방법을 정의한다. 이 템플릿은 Template 클래스가 처리할것이다. 세번째 파라미터는 리다이렉트 타입을 정의한다. 여기서 우리는 목적이 간단하기 때문에 클라이언트 리다이렉트를 선택한다.

또한, 호출이 애플리케이션에 전달되는 동안 초기 요청에서 쿼리 파라미터인 "kwd"를 추출하기 위해 Route클래스를 사용한다. 검색되는 파라미터가 있다면, 대상 URI를 만들때 Redirector가 사용할 "keywords" 라는 이름의 request속성에 복사된다.

11. Router 와 구조적인 URI [#11]#

Redirector에 추가적으로, 멋진 URI를 관리하기 위해 Router라는 또다른 도구를 가진다. Router는 Router에 첨부되는 다른 Restlet(예를 들어 Finder와 Filter)을 가질수 있고 URI template에 기초하여 호출을 자동으로 위임할수 있는 특성화된 Restlet이다. 대개 Application의 가장 상위 요소로 Router를 셋팅할것이다.

여기서 다음의 URI패턴을 다루는 방법을 설명하고자 한다.

  1. . /docs/ (정적인 파일을 보여주기 위해)
  2. . /users/{user} (사용자 계정을 보여주기 위해)
  3. . /users/{user}/orders (특정 사용자의 주문을 보여주기 위해)
  4. . /users/{user}/orders/{order} (특정 주문을 보여주기 위해)

사실 URI는 연결괄호사이에서 변수들을 포함하고 파일 확장자는 대개의 웹 컨테이너에서 그 변수들을 다루기 힘들게 만들기 때문에 사용되지 않는다. 여기서 URI템플릿을 사용하여 대상 Restlet를 Router에 첨부할 필요가 있다. At runtime, the route that best matches the request URI will received the call and be able to invoke its attached Restlet. 동시에, 요청 속성맵은 URI 템플릿 변수의 값으로 자동 수정될것이다.

5.png

아래의 구현체 코드를 보라. 실제 애플리케이션에서는, 여기서 사용하는 익명 클래스 대신에 명확히 구분되는 하위 클래스를 생성하고자 할것이다.

// Component객체 생성하기
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8182);
component.getClients().add(Protocol.FILE);

// Application객체 생성하기
Application application = new Application(component.getContext()) {
    @Override
    public Restlet createRoot() {
        // 가장 상위 Router객체 생성하기
        Router router = new Router(getContext());

        // 디렉토리에 안전하게 접근하기 위해 Guard객체 첨부하기
        Guard guard = new Guard(getContext(),
                ChallengeScheme.HTTP_BASIC, "Restlet tutorial");
        guard.getSecrets().put("scott""tiger".toCharArray());
        router.attach("/docs/", guard);

        // 파일 구조를 나타낼수 있는 Directory객체 생성하기
        Directory directory = new Directory(getContext(), ROOT_URI);
        guard.setNext(directory);

        // account 핸들러 생성하기
        Restlet account = new Restlet() {
            @Override
            public void handle(Request request, Response response) {
                // 요청된 URI 경로 출력하기
                String message = "Account of user \""
                        + request.getAttributes().get("user""\"";
                response.setEntity(message, MediaType.TEXT_PLAIN);
            }
        };

        // order 핸들러 생성하기
        Restlet orders = new Restlet(getContext()) {
            @Override
            public void handle(Request request, Response response) {
                // 요청된 order의 사용자명 출력하기
                String message = "Orders of user \""
                        + request.getAttributes().get("user""\"";
                response.setEntity(message, MediaType.TEXT_PLAIN);
            }
        };

        // order 핸들러 생성하기
        Restlet order = new Restlet(getContext()) {
            @Override
            public void handle(Request request, Response response) {
                // 요청된 order의 사용자명 출력하기
                String message = "Order \""
                        + request.getAttributes().get("order")
                        "\" for user \""
                        + request.getAttributes().get("user""\"";
                response.setEntity(message, MediaType.TEXT_PLAIN);
            }
        };

        // 가장 상위 router에 핸들러 첨부하기
        router.attach("/users/{user}", account);
        router.attach("/users/{user}/orders", orders);
        router.attach("/users/{user}/orders/{order}", order);

        // 가장 상위 router반환하기
        return router;
    }
};

// component에 application첨부하고 시작하기
component.getDefaultHost().attach("", application);
component.start();

12. 대상 자원에 도달하기 [#12]#

In the previous example, we took advantage of the flexible routing features of the framework to route the requests while extracting interesting parts from the target URI. But, we didn't pay attention to the request method, nor to the client preferences regarding the response that he expects. Also, how do we connect our Restlet handlers with the backend systems, the domain objects?

So far, we introduced features that go beyond the traditional Servlet API but we didn't justify the REST part in our Restlet name! If you haven't done so already, I recommend that you learn more about the REST architecture style and the best practices to follow when applying it to a Web application. There is a related FAQ entry that will give you some starting pointers and we also maintain a REST search engine (based on Google) that can be useful. If you have some experience with a traditional MVC framework, you can read more about the relationship to Restlets in this other FAQ entry.

To summarize, a request contains an URI that identifies the target resource that is the subject of the call. This information is stored in the Request.resourceRef property and serves as the basis of the routing as we saw. So the first goal when handling a request is to find the target resource which is in the framework... an instance of the Resource class or more precisely one of its subclasses. To help us in this task, we can use the dedicated Finder, a subclass of Restlet, which takes a Resource class reference as an argument and which will automatically instantiate it when a request comes in. Then it will dynamically dispatch the call to the newly created instance, actually to one of its handle*() methods (handleGet, handleDelete, etc.) depending on the request method. Of course, this behavior can be customized. There is even an attach() method on Router that can take two arguments, an URI template and a Resource class and that transparently creates the Finder for you! Now, let's have a look at this overall diagram, showing the relationship between the main framework classes involved in this example:

6.png

Back to the code, here is our refactored Application.createRoot() method. For simplicity purpose, we didn't keep the Directory serving static files as this part wouldn't change. You can notice the way that resource classes are directly attached to the router.

// Create a router
Router router = new Router(getContext());

// Attach the resources to the router
router.attach("/users/{user}", UserResource.class);
router.attach("/users/{user}/orders", OrdersResource.class);
router.attach("/users/{user}/orders/{order}",
        OrderResource.class);

// Return the root router
return router;

We will finally review one of the resource classes, the UserResource class. This class derives from org.restlet.resource.Resource and therefore has to override the three arguments constructor. This will initialize the useful "context", "request" and "response" properties on our instances. Then, we use the attribute "user" that is automatically extracted from the "/users/{user}" URI template and store its value in a convenient member variable. At this point, in a full application, we would lookup our associated "user" domain object. Finally, we declare which representation variants we want to expose, in our case only plain text. This will be used at runtime to transparently do some content negotiation, in order to select the preferred variant for each request handled, all this transparently.

public class UserResource extends Resource {
    String userName;

    Object user;

    public UserResource(Context context, Request request,
            Response response) {
        super(context, request, response);
        this.userName = (Stringrequest.getAttributes().get("user");
        this.user = null// Could be a lookup to a domain object.

        // Here we add the representation variants exposed
        getVariants().add(new Variant(MediaType.TEXT_PLAIN));
    }

    @Override
    public Representation getRepresentation(Variant variant) {
        Representation result = null;
        if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
            result = new StringRepresentation("Account of user \""
                    this.userName + "\"");
        }
        return result;
    }
}

You can have a look at the rest of the code in the tutorial package and test the application. You will obtain the same behavior as in Part11, with the difference that only GET requests will be accepted. If you want to enable PUT for example, you have to create an "allowPut()" method in UserResource that simply returns 'true' then add a "put(Representation)" method to process the call. You can check the Javadocs for further details.

13. Conclusion [#13]

We have already covered many aspects of the framework. Before you move on by yourself, let's take a step back and look at two hierarchy diagrams showing the main concepts covered in this tutorial and their relationships:

7.png

Now, here is the hierarchy with the core Representation classes:

8.png

Beside this tutorial, your best source of information will be the Javadocs available for the Restlet API, the Restlet Extensions and the NRE. Have also a look at the connectors section that lists all available client and server connectors and explain how to use and configure them, and the integrations section for a list of all available extensions providing pluggable features such as integration with servlet containers, generation of dynamic representations, etc. You can also post your questions and help others in our discussion list.

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
png
1.png 4.0 kB 1 20-May-2007 22:15 59.187.242.12
png
2.png 37.2 kB 1 20-May-2007 22:15 59.187.242.12
png
3.png 31.7 kB 1 20-May-2007 22:15 59.187.242.12
png
4.png 2.3 kB 1 20-May-2007 22:15 59.187.242.12
png
5.png 8.0 kB 1 20-May-2007 22:15 59.187.242.12
png
6.png 29.0 kB 1 20-May-2007 22:15 59.187.242.12
png
7.png 32.8 kB 1 20-May-2007 22:15 59.187.242.12
png
8.png 47.2 kB 1 20-May-2007 22:15 59.187.242.12
« This page (revision-13) was last changed on 05-Jun-2007 13:25 by 125.128.27.111