목차 >> 웹서비스 
+- RESTful

16장 웹서비스

이 장은 RESTful관련 Glue Framework과의 관계와 연결 방법에 대해 설명하는 것을 목적으로 한다. 각 단원 마다 예제 및 Sample Code를 삽입하여 개발자가 쉽게 따라 개발할 수 있도록 하였다.
Glue Framework를 사용하여 웹 서비스를 개발할 경우 필요한 내용들을 설명한다. 본 문서에서는 RESTful관련 개념이나 어노테이션(annotation)관련 부분은 꼭 필요한 부분만 설명하고 좀 더 구체적인 내용은 사이트를 참고하도록 한다.

RESTful

REST란 ROA(Resource Oriented Architecture)를 따르는 웹 서비스 디자인 표준으로 웹의 모든 리소스를 URI로 표현하고, 이를 구조적이고 유기적으로 연결하여, 비 상태 지향적인 방법으로 일관된 method를 사용하여 리소스를 사용한다.
REST 방식의 웹서비스는 잘 정의된 Cool URI로 리소스를 표현한다는 특징을 갖는다.
무분별한 파라미터의 남발이 아니라, 마치 오브젝트의 멤버변수를 따라가듯이예를 들면 아래와 같다.

http://www.glue.net/user/mk/age/32

기존의 서블릿을 이용한 URI는아래와 같은 방식 이었다.

http://www.glue.net/finduser.jsp?user=mk&age=32

일반적으로 ROA는

  • 웹의 모든 리소스를 URI로 표현하고
  • 모든 리소스를 구조적이고 유기적으로 연결하여
  • 비 상태 지향적인 방법으로
  • 정해진 method만을 사용하여 리소스를 사용하는 아키텍쳐

라고 4가지로 정의 한다.
ROA를 따르는 RESTful 웹서비스는 아래와 같은 4가지 속성을 갖는다.

  1. Addressability (주소로 표현 가능함) : 제공하는 모든 정보를 URI로 표시할 수 있어야 한다. 직접 URI로 접근할 수 없고 HyperLink를 따라서만 해당 리소스에 접근할 수 있다면 이는 RESTful하지 않은 웹서비스이다.
  2. Connectedness (연결됨) : 일반 웹 페이지처럼 하나의 리소스들은 서로 주변의 연관 리소스들과 연결되어 표현(Presentation)되어야 한다.
    다음은 독립적인 리소스 예제이며,
    <user>
    <name>MK</name>
    </user>
    

    다음은 관련 리소스(home, office)가 잘 연결된 리소스 예제이다.

    <user>
    <name>MK</name>
    <home>MK/home/</home>
    <office>MK/office</office>
    </user>
    
  3. Statelessness (상태 없음) : 현재 클라이언트의 상태를 절대로 서버에서 관리하지 않아야 한다. 모든 요청은 일회성의 성격을 가지며 이전의 요청에 영향을 받지 말아야 한다. 세션을 유지 하지 않기 때문에 서버 로드 발란싱이 매우 유리하다.
  4. Homogeneous Interface (동일한 인터페이스) : HTTP에서 제공하는 기본적인 4가지의 method와 추가적인 2가지의 method를 이용해서 리소스의 모든 동작을 정의한다.
    구분 method 추가설명
    리소스 조회 GET
    새로운 리소스 생성 PUT, POST 새로운 리소스의 URI를 생성하는 주체가 서버이면 POST를 사용
    존재하는 리소스 변경 PUT
    존재하는 리소스 삭제 DELETE
    존재하는 리소스 메타데이터 보기 HEAD
    존재하는 리소스의 지원 method 체크 OPTION

Server 파트

Spring 기반에서 RESTful방식으로 구현한다.
Spring의 REST를 위한 기능은 모두 Spring MVC를 기반으로 지원된다. 다양한 Annotation과 HTTP Request/Response Body 메세지 처리를 위한 HttpMessageConverter, Content Negotiation 지원을 위한 ViewResolver, 모든 HTTP method 사용을 위한 Filter, 그리고 REST 클라이언트 어플리케이션 개발에 도움을 주는 RestTemplate 등이 있다.
web.xml에 아래와 같이 확장자로 맵핑되지 않도록 DispatcherServlet을 등록한다.

<servlet>
    <servlet-name>restful</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>restful</servlet-name>
    <url-pattern>/restful/*</url-pattern>
</servlet-mapping>

DipatcherServlet은 servlet-name-servlet.xml 을 필요로 하며, web.xml이 위와 같다면 restful-servlet.xml을 필요로 한다. 그 내용은 다음과 같이 설정한다.

<context:component-scan base-package="sample.controller" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/" />
    <property name="suffix" value=".jsp" />
</bean>
<bean name="ViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/views.xml"/>
</bean>

json으로 데이터를 주고 받을 경우 json데이터를 변환해줄 AnnotationMethodHandlerAdapter를 빈으로 등록하고 messageConverters 속성에 Json용 Converters 클래스를 추가해야 하는데 <mvc:annotation-driven />이 해당 bean 설정을 자동으로 설정해 준다. 해당 빈 설정을 변경해야 할 경우에는 <mvc:annotation-driven />을 사용하지 말고 AnnotationMethodHandlerAdapter등의 Bean을 직접 등록해주면 된다.
@controller 가 있는 Class를 자동 스캔 하기 위해서 <context:component-scan base-package=. . .>을 통해서 해당 package를 설정해 주어야 한다. 그래서 <context:component-scan base-package="sample.controller" />이 사용되었다.
ViewResolver에서는 views.xml을 필요로 하며, MappingJacksonJsonView를 사용할 수 있도록 해당 Bean을 다음과 같이 등록해야 한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <bean name="jsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</beans>

<context:component-scan base-package="sample.controller" /> 를 통해 자동 스캔되는 Class는 다음과 같은 형태이다.

@Controller
public class RestfulController
{
    /** logger */
    protected GlueLog logger = GlueLogFactory.getLogger(getClass());
    @RequestMapping(value = "/glue/{serviceName}", method = RequestMethod.GET)
    public ModelAndView doGlueServiceGet(@PathVariable String serviceName)
    {
        ModelAndView mav = new ModelAndView("jsonView");
        //비지니즈 로직 구현
        return mav;
    }
}

RestfulController의 @Controller 어노테이션은 해당 클래스가 컨트롤러의 역할을 한다는 것을 나타낸다. 스프링에서는 어떤 컨트롤러 기반 클래스도 확장할 필요가 없고 서블릿 API를 참조할 필요도 없다. 하지만 필요하다면 서블릿에 특화된 기능을 참조할 수 있다.
@Controller 어노테이션은 어노테이션이 붙은 클래스의 스테레오 타입처럼 동작하고 클래스의 역할을 나타낸다. 디스패처는 매핑된 메서드에 이러한 어노테이션이 붙은 클래스를 찾고 @RequestMapping 어노테이션을 탐지한다.
디스패처의 컨텍스트에 표준 스프링 빈 정의를 사용해서 어노테이션이 붙은 컨트롤러 빈을 명시적으로 정의할 수 있다. 하지만 클래스패스에서 컴포넌트 클래스를 탐지와 컴포넌트 클래스들을 위해 빈 정의의 자동등록에 대한 스프링의 일반적인 지원에 맞추어 @Controller 스테레오타입도 자동탐지(<context:component-scan . . . >)가 가능하다.
특정 URL을 매핑하려면 전체 클래스나 특정 핸들러 메서드에 @RequestMapping 어노테이션을 사용해야 한다. 보통 클래스수준의 어노테이션은 폼(form) 컨트롤러에 특정 요청 경로(또는 경로 패턴)을 매핑하고 추가적인 메서드 수준의 어노테이션은 특정 HTTP 요청 메서드("GET", "POST" 등)나 HTTP 요청 파라미터 상태로 매핑 범위를 좁힌다.

@Controller
@RequestMapping("/poscoict")
public class AppointmentsController
{
    @RequestMapping(method = RequestMethod.GET)
    public ModelAndViewget()
    {
        // 구현
    }
    @RequestMapping(value="/test", method = RequestMethod.GET)
    public ModelAndViewgetNew()
    {
        // 구현
    }
}

위 예제에서 @RequestMapping를 여러 곳에서 사용했다. 먼저 클래스 수준에서 사용했는데 이것은 컨트롤러의 모든 핸들링 메서드는 /poscoict 경로에 상대적이라는 것을 나타낸다. get() 메서드의 @RequestMapping의 method 만을 정의했는데, 이것은 GET 요청만 받아들인다는 것을 의미한다. 즉, /poscoict에 대한 HTTP GET 요청만 이 메서드를 실행한다. POST요청 처리를 위해서도 마찬가지 방식으로 구현할 수 있다. getNew()은 HTTP 메서드와 경로를 함께 사용했으므로 poscoict/new에 대한 GET 요청을 이 메서드가 처리한다.
Method 정의시 일부 오래된 장비나 서버의 경우 GET,POST만 허용하는 경우도 있으므로 주의해야 한다.
클래스 수준의 @RequestMapping는 필수가 아니다. 클래스에 @RequestMapping를 사용하지 않으면 모든 경로는 상대경로가 아니라 절대경로가 된다.
스프링 MVC에서 URI 템플릿 변수의 값에 바인딩하려고 메서드 인자에 @PathVariable 어노테이션을 사용할 수 있다.

@RequestMapping(value="/glue/{loginid}", method=RequestMethod.GET)
public String findOwner(@PathVariable(“loginid”) String id) {
    // 구현
}

@PathVariable 어노테이션을 처리하려면 스프링 MVC가 이름과 일치하는 URI 템플릿 변수(loginid)를 찾아야 한다. 어노테이션에서 이를 지정할 수 있다.
또는 URI 템플릿 변수 이름이 메서드 인자의 이름과 일치한다면 생략할 수 있다.
메서드는 다수의 @PathVariable 어노테이션을 가질 수 있다.
@PathVariable 인자는 int, long, Date 등의 간단한 어떠한 타입이라도 될 수 있다. 스프링이 자동으로 적절한 타입으로 변환하거나 타입변환에 실패한다면 TypeMismatchException를 던진다.

Client 파트

다음은 jquery를 이용한 호출 예제이다.

$.ajax({
   type: 'GET',
   url: "http://localhost:8080/GlueSample/restful/glue/sample01-service/",
   data: formdata,
   success: function(resultdata){
      //비즈니스 로직 구현
      // resultdata은 해당 웹서비스에서 넘어온 값
   }
});

다음은 RestTemplate을 사용한 예제이다.

RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject ( "http://localhost:8080/GlueSample/restful/glue/sample01-service/" , String.class );

위와 같이 가급적 RestTemplate 클래스의 빈으로 등록하여 사용하는 것을 권장한다.
GET방식의 요청일 경우에는 getForObject(String, Class, String...)을 사용하고 POST방식일 경우에는 postForLocation(String, Object, String...)을 PUT방식일 경우에는 put(String, Object, String...)을 DELETE방식일 경우에는 delete(String, String...)를 사용한다.

Glue에서제공하는 RESTful용 Controller

Glue에서는 특정 서비스를 실행 시키고 결과 값을 JSON형태로 반환하는 RestfulController를 제공하고 있다.
servlet.xml 에 Controller 스캔 Package를 다음과 같이 정의해준다.

<context:component-scan base-package="com.poscoict.glueframework.web.control.restful" />

Controller는 다음과 같은 URI 패턴을 처리할 수 있다.

  • /glue/serviceName : URI의serviceName과 Service명이일치하는 Glue Servic가실행된다.
  • /glue/serviceName/data/gluedata : URI의serviceName과 Service명이일치하는 Glue Service가 실행되며 URI의gluedata값은“gluedata”를 Key로GlueContext에저장된다.

RequestMethod 로는 GET,POST,PUT,DELETE 방식이 지원되며 어떤 방식의 요청이었는지는 “action”을 Key로 GlueContext에 소문자로 저장된다. 해당 요청이 Key로도 등록되므로 아래와 같이 Default Router로 분기도 가능하다.

그림 : Default Router분기
Default Router분기

POST방식은 RequestParam으로 넘어온 값을 모두 GlueContext에 전달하며, PUT 방식의 경우에는 RequestBody로 넘어온 값을 모두 GlueContext에 전달한다.
Glue에서제공하는 RESTful용 Controller가 사용되었다면, Client부분에서는 ajax방식이나 RestTemplate을 사용하여 서비스를 호출 할 수 있으면 JSON 형식의 데이터가 Return된다.

^L

Prev Home Next