[REST ②] Java Restlet을 이용한 RESTful 웹서비스의 구현

웹, 인터넷 2008. 5. 14. 17:40 posted by 무병장수권력자


Java Restlet을 이용한 RESTful 웹서비스의 구현

작성자 : 김문규
작성일 : 2008. 5.14

이번 포스트에서는 Restlet을 이용하여 RESTful 웹서비스를 구현하는 간단한 예제를 작성해 본다.

1. Restlet 이란?
Noelios Consulting이라는 회사에서 만든 Java 기반 REST 구현체이다.
자세한 튜토리얼이나 API 문서는
http://www.restlet.org/을 참조하길 바란다.
- 주요 기능
 . URI 입력과 서비스를 제공하는 클래스를 서로 연결
 . request의 method에 따라 해당 핸들러 함수를 호출
 . request와 response를 다루기 위한 라이브러리 클래스 제공
 . representation을 생성하기 위한 라이브러리 클래스 제공

2. 예제
 1) HTTP 서버 구동
 [방식 1]
 Restlet은 자체적으로 웹서버를 내장하고 있다. 따라서 개발 시 별도의 웹서버 없이 간단하게 테스트가 가능하다. Restlet 컴포넌트에 간단하게 HTTP 서버 connector를 연결함으로서 별도의 웹서버를 띄우지 않고도 간단하게 테스트가 가능하다. 여기서 Restlet에서 중요한 요소인 application 클래스가 등장한다. application 클래스는 web request를 확인하고 적절한 리소스를 연결해주는 역할을 한다. 태스크를 분배하는 관리자와 비슷하다고 할까?

<DefaultServer.java>
import org.restlet.Application;
import org.restlet.Component;
import org.restlet.data.Protocol;
public class DefaultServer{
  public static void main(String[] args) throws Exception {
    try {
      // Create a component
      Component component = new Component();
      component.getServers().add(Protocol.HTTP, 8182); // port number : 8182
      component.getClients().add(Protocol.FILE);
      // Create Application
      Application application = new DefaultApplication(component.getContext());
      // Attach the application to the component and start it
      component.getDefaultHost().attach(application);
      component.start();
    } catch (Exception e) {
      // Something is wrong.
      e.printStackTrace();
    }
  }
}

 [방식 2]
 WAS를 이용하여 HTTP 서버를 구동할 경우에는 다음과 같이 web.xml을 수정하면 된다.
 적색으로 표시된 부분만 본인의 서비스에 맞는 application과 연결하면 된다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
            xmlns="http://java.sun.com/xml/ns/j2ee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
   <display-name>first steps servlet</display-name>
   <!-- Application class name -->
   <context-param>
      <param-name>org.restlet.application</param-name>
      <param-value>
        
DefaultApplication
      </param-value>
   </context-param>

   <!-- Restlet adapter -->
   <servlet>
      <servlet-name>RestletServlet</servlet-name>
      <servlet-class>
         com.noelios.restlet.ext.servlet.ServerServlet
      </servlet-class>
   </servlet>

   <!-- Catch all requests -->
   <servlet-mapping>
      <servlet-name>RestletServlet</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>
</web-app>

 2)application 클래스 구현
 application 클래스는 web request를 확인하고 적절한 리소스를 연결해주는 역할을 한다. router 클래스가 실질적으로 이런 연결 작업을 해준다.

import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Restlet;
import org.restlet.Router;
public class DefaultApplication extends Application {
    public DefaultApplication(Context parentContext) {
        super(parentContext);
    }
    /**
     * Creates a root Restlet that will receive all incoming calls.
     */
    @Override
    public Restlet createRoot() {
        // Create a root router
        Router router = new Router(getContext());
        // Attach the handlers to the root router
        // 주소가 'service/item'이면 ResourceItems.class를 생성하고 적절한 핸들러 함수를 호출해라.
        router.attach("/service/item", ResourceItems.class);
       
        // Return the root router
        return router;
    }
};

 3) Resource 클래스의 구현
Resource 클래스는 실제 요청받은 작업들을 수행하는 곳이다. 어떤 데이터를 CRUD(create, read, update, delete)하고 그 결과를 HTTP response에 담아서 되돌려 주는 역할을 한다.

allowGet(), allowPost(), allowPut(), allowDelete() 함수는 web request의 허용 여부를 결정해준다. allowGet()은 기본적으로 true값을 가지기 때문에 별도의 overriding이 필요치 않다.

post(), put(), delete()는 해당 method에 대한 응답을 작성하는 핸들러 함수이다. getRepresentation()은 get method에 대해서 xml, xhtml, html representation을 생성하는 핸들러 함수이다. 각자 원하는 동작을 하도록 만들어 주면 된다.

import java.util.Iterator;
import java.util.List;
import javax.xml.xpath.XPathConstants;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.DomRepresentation;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.SaxRepresentation;
import org.restlet.resource.Variant;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import com.sec.nurisam.DefaultDatabase;
import com.sec.nurisam.billing.model.DataItem;
public class ResourceItems extends Resource{
 
  private DataItem _item;
 
  public ResourceItems(Context context, Request request, Response response) {
    super(context, request, response);
   
    // This representation has only one type of representation.
    getVariants().add(new Variant(MediaType.TEXT_XML));
  }
   
  @Override
  public boolean allowPut() {
    return true;
  }
   
  @Override
  public void put(Representation entity) {
    String name = null;
    int price = -1;
    SaxRepresentation sax = getRequest().getEntityAsSax();
    try {
      name = (String)sax.evaluate("/param/name", XPathConstants.STRING);
      price = Integer.parseInt((String)sax.evaluate("/param/price", XPathConstants.STRING));
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    if(name != null && price != -1) {
      _item = new DataItem(name, price);
      if(DefaultDatabase.getItemList().add(_item)) getResponse().setStatus(Status.SUCCESS_CREATED);
      else getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
    } else {
      getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
    }
  }
   
  @Override
  public Representation getRepresentation(Variant variant) {
    Representation representation = null;
    if(variant.getMediaType() == MediaType.TEXT_XML) {
      representation = this.getItems();
    } else {
      // do something
    }
    return representation;
  }
}

4) 정리
요약해보면, 하나의 서비스를 제공하기 위해서
 - application을 생성하고,
 - 여기서 router를 이용해서 URI-리소스를 매핑하며
 - 해당 리소스에는 각 요청 method에 대한 핸들러 함수를 설정하면 된다.
이 핵심 경로를 이해하면 다른 부분의 응용에 큰 문제가 없어 보인다.

3. 맺음말
개념에 관련된 내용만 책으로 한권을 읽었는데... 실제로 구현이 너무 쉽게 되는 것 같아서 다소 실망스러운 감이 없지 않을 것이다. 하지만, 실제로 서비스를 구현해 보면 REST의 철학과 관련되어 부딪히는 문제들이 속속 발생할 것이다. 이를 잘 해결하기 위해 Restlet보다 RESTful 웹 서비스에 대해 잘 이해하고 넘어가길 바란다.