이번 테크팁에서는 Java에서 JAX-RS: Java API for RESTful Web Services (JSR-311) 사양을 따르는 RESTful 웹 서비스를 작성하는 방법과 그 참조 구현인 Jersey를 소개한다. REST(Representational State Transfer)의 기본 원리 몇 가지와 JAX-RS 및 Jersey에 대해 학습하게 된다.
이 팁에서는 JAX-RS 개념과 기술을 보여 주기 위해 샘플 애플리케이션을 사용한다. Jersey 다운로드 페이지에서 최신 버전의 Jersey 스냅샷을 다운로드하여 샘플을 구할 수 있다. 이 팁의 코드 예제는 다운로드 패키지에 포함된 샘플의 소스 코드에서 따온 것이다.
RESTful 웹 서비스 소개
REST는 WWW와 같은 분산형 시스템을 위한 소프트웨어 아키텍처 스타일 중 하나이다. 이 용어는 2000년에 Roy Fielding의 박사 논문에서 소개되었으며, 그 이후 네트워킹 커뮤니티에서 널리 사용되어 왔다. REST에서 중요한 개념 중 하나는 리소스의 존재인데, 각 리소스는 전역 식별자인 URI를 사용하여 참조할 수 있다. 이 리소스를 조작하기 위해 네트워크, 클라이언트 및 서버 구성 요소가 HTTP와 같은 표준화된 인터페이스를 사용하여 통신하고 이 리소스의 표현을 교환한다.
RESTful 웹 서비스는 RESTful 아키텍처 스타일을 사용하여 작성한 서비스이다. RESTful 방식의 웹 서비스 작성은 SOAP 기반 기술을 사용하여 인터넷에서 서비스를 배포하는 방법의 대안으로 새롭게 각광받고 있는데, 가벼울 뿐 아니라 직접 HTTP를 통해 데이터를 보낼 수 있기 때문이다.
RESTful 웹 서비스의 원리
RESTful 웹 서비스의 기본 원리는 다음과 같다.
- 리소스(Resource)와 표현(Representation). 특정 웹 서비스에 단 하나의 종단점을 제공하고 그 종단점이 여러 작업을 수행하게 하지 않고 리소스에 대한 액세스를 제공한다. 리소스는 여러분이 클라이언트로 하여금 액세스할 수 있게 해주는 웹 애플리케이션의 한 부분이다. 이 리소스는 네트워크를 통해 전송할 수 없으므로 "이 리소스를 제공한다"는 것은 그 상태의 표현을 전달하는 것을 의미한다.
- 주소 지정 가능성(Addressability)과 연결성(Connectedness). 리소스는 표현을 갖지만, 그 주소를 지정할 수 없다면 리소스의 표현을 전달하더라도 쓸모 없다. REST에서는 모든 리소스가 하나 이상의 주소, 즉 URI를 가져야 한다. 리소스 주소를 지정하려면 URI를 지정하면 된다. 이러한 개념을 "주소 지정 가능성"이라고 한다. 웹 애플리케이션을 게시하면 서로 연결된 많은 URI를 제시하게 된다. 그 연결성 덕분에 "부트스트랩 URI"라고 부르는 URI 하나만 클라이언트에게 전달하면 된다.
- 동일한 인터페이스(Uniform interface) URI와 표현을 통해 클라이언트와 서버 간에 리소스를 주고 받을 수 있게 하더라도 아직 통신을 설정할 수는 없다. 사용할 통신 프로토콜/인터페이스가 필요하다. REST 아키텍처에서는 그러한 인터페이스가 동일해야 한다. 즉 어떤 URI에 액세스하더라도 인터페이스가 같아야 한다. 예를 들어, WWW에서는 어떤 URI(리소스 주소)에 들어가더라도 웹 브라우저에서는
HTTP GET
메소드를 사용하여 해당 웹 페이지(리소스 표현)를 읽어들여 표시한다.
- 상태 비보존성(Statelessness). 상태 비보존성이란 웹 애플리케이션이 클라이언트의 상태에 대한 정보를 보관하지 않는다는 것이다. REST에서는 HTTP 세션의 개념을 수용하지 않는다. 클라이언트는 필요하다면 자신의 작업을 추적할 책임이 있다. 서비스는 리소스를 유지하면서 클라이언트에게 동일한 인터페이스를 제공한다.
JAX-RS와 Jersey
JAX-RS는 Java에서 RESTful 웹 서비스를 작성할 수 있는 표준화된 API를 제공한다. 이 API는 기본적으로 주석 그리고 관련된 클래스 및 인터페이스의 모음을 제공한다. 주석을 POJO(Plain Old Java Object)에 적용하면 웹 리소스를 공개할 수 있다. API는 아직 완성되지 않았다. 최종 버전은 Java EE 6에 포함될 것이다. JAX-RS에 대한 자세한 내용은 jsr311 프로젝트에서 확인할 수 있다.
Jersey는 JAX-RS의 참조 구현 중 하나이다. Jersey 프로젝트 다운로드 페이지에서 다운로드 가능한 Jersey 배포판을 구할 수 있다. 최신 Jersey 스냅샷을 선택하고 압축을 풀면 Jersey 구현이 그 사용법을 알려주는 몇 가지 예제와 함께 번들되어 있음을 알 수 있다. 그 예제 중 하나를 살펴보도록 하자.
JAX-RS 사용 예: 북마크 애플리케이션
Jersey와 함께 배포되는 예제 중 하나가 북마크 애플리케이션이다. examples/Bookmark
하위 디렉토리에 있는 이 애플리케이션에서는 JAX-RS API를 사용하여 사용자가 저장한 북마크에 대한 정보를 유지 관리한다. 이 애플리케이션을 실행하고 특정 사용자를 지정하면 그 애플리케이션은 다음과 비슷한 데이터를 반환한다.
{sdesc":"test desc","userid":"testuserid","uri":
"http://java.sun.com","ldesc":"long test description"}
이 애플리케이션은 JSON(JavaScript Object Notation) 형식으로 데이터를 반환한다.
examples/Bookmark 하위 디렉토리 아래의 resources 하위 디렉토리까지 이동하면 이 애플리케이션의 다음 리소스를 볼 수 있다.
UsersResource
: 사용자 목록을 나타낸다.UserResource
: 특정 사용자를 나타낸다.BookmarksResource
: 특정 사용자의 북마크 목록을 나타낸다.BookmarkResource
: 특정 북마크를 나타낸다.
다시 말하자면 REST에서 어떤 리소스를 주소 지정하려면 URI를 지정한다. 그러나 리소스와 통신하기 위해서는 HTTP와 같은 통신 프로토콜도 지정해야 한다. 다음은 북마크 애플리케이션의 리소스에 해당하는 URI와 HTTP 메소드이다.
리소스 |
URI 경로 |
HTTP 메소드 |
---|---|---|
UsersResource |
/users |
GET |
UserResource |
/users/{userid} |
GET, PUT, DELETE |
BookmarksResource |
/users/{userid}/bookmarks |
GET, POST |
BookmarkResource |
/users/{userid}/bookmarks/{bmid} |
GET, PUT, DELETE |
JAX-RS의 기본 원리를 이해하기 위해 이 리소스 중 두 가지를 살펴보자. UsersResource
와 UserResource
이다.
UsersResource
다음은 UsersResource
클래스 소스 코드 중 일부이다.
@UriTemplate("/users/")
public class UsersResource {
@HttpContext UriInfo uriInfo;
@PersistenceUnit(unitName = "BookmarkPU")
EntityManagerFactory emf;
/** Creates a new instance of Users */
public UsersResource() {
}
public List<UserEntity> getUsers() {
return emf.createEntityManager().createQuery(
"SELECT u from UserEntity u").getResultList();
}
@UriTemplate("{userid}/")
public UserResource getUser(@UriParam("userid")
String userid) {
return new UserResource(
uriInfo, emf.createEntityManager(), userid);
}
@HttpMethod("GET")
@ProduceMime("application/json")
public JSONArray getUsersAsJsonArray() {
JSONArray uriArray = new JSONArray();
UriBuilder ub = null;
for (UserEntity userEntity : getUsers()) {
ub = (ub == null) ?
uriInfo.getBuilder() : ub.clone();
URI userUri = ub.
path(userEntity.getUserid()).
build();
uriArray.put(userUri.toString());
}
return uriArray;
}
}
UsersResource
클래스에는 @UriTemplate("/users/")
주석이 있다. @UriTemplate
는 리소스의 URI 경로를 식별하는 JAX-RS 주석이다. 여기서 주석은 URI 경로를 /users/
로 식별한다.
@UriTemplate("/users/")
클래스에 @UriTemplate
주석을 추가하면 클래스는 "루트 리소스 클래스"가 된다. 또한 /users/
URI 경로에 액세스하는 클라이언트 요청의 경우 이 리소스가 적합한 응답을 제공해야 함을 의미한다. 또한 /users/
URI 경로가 북마크 웹 애플리케이션 전체에서 부트스트랩 URI 경로이다.
UsersResource
클래스에 포함된 또 다른 JSR-311 주석이 @HttpContext
이다.
@HttpContext UriInfo uriInfo;
이 주석은 클래스 필드 또는 메소드 매개 변수에 정보를 주입한다. UsersResource
클래스에서 @HttpContext
는 URI에 대한 정보를 uriInfo
변수에 주입하고, 이는 URI 관련 정보를 제공할 때 사용할 수 있다.
UsersResource
메소드
UsersResource
클래스는 getUser
와 getUsersAsJsonArray
라는 두 메소드를 갖는다. 일단 getUser
를 건너뛰고 getUsersAsJsonArray
를 집중적으로 살펴보도록 한다. getUsersAsJsonArray
메소드는 모든 사용자 리소스에 대한 URI를 반환한다. 이 메소드에는 두 개의 JSR 311 주석, 즉 @HttpMethod
와 @ProduceMime
이 추가되었다. @HttpMethod
주석은 주석이 추가된 메소드가 HTTP 요청 처리에 사용되어야 함을 의미한다. 또한 이 주석은 메소드가 응답할 HTTP 요청의 유형도 지정한다. 이 예제에서 이 주석은 getUsersAsJsonArray
메소드가 HTTP GET
요청을 서비스하도록 지정한다.
@HttpMethod("GET")
이와 같이 REST 요청을 서비스하는 메소드를 "리소스 메소드"라고 부른다.
@ProduceMime
주석은 메소드가 생성할 수 있는 MIME 형식을 지정한다. 여기서 이 주석은 getUsersAsJsonArray
메소드가 기존의 모든 사용자 리소스의 URI 어레이를 포함하는 JSONArray
개체를 반환하도록 지정한다.
@ProduceMime("application/json")
이 메소드는 다음과 같은 JSON
어레이 개체를 클라이언트에 반환한다.
["http://localhost:8080/Bookmark/resources/users/joe",
"http://localhost:8080/Bookmark/resources/users/mary"]
이 JSON
어레이는 URI, 즉 두 사용자 리소스인 joe
및 mary
의 링크를 포함한다.
getUser
메소드는 특정 사용자에 대한 정보를 가져온다. 예를 들어, 클라이언트가 사용자 joe에 대한 정보를 얻으려면 그 URI에서 리소스에 액세스한다. 여기서는 http://localhost:8080/Bookmark/resources/users/joe
이다. UsersResource
클래스는 joe의 URI 경로(/users/joe
)를 비롯해 /users/
로 시작하는 경로에 대한 모든 요청을 서비스한다.
여기서 getUser
메소드에 @UriTemplate("{userid}/")
주석이 붙는 것이 중요하다. 그러면 메소드가 "하위 리소스 로케이터"가 된다. 또한 getUser
메소드에는 @UriParam
주석이 붙는다. 따라서 getUser
메소드가 호출되면 현재 요청 URI 경로의 사용자 ID가 userid 매개변수에 주입되는 것이다.
getUser
메소드에는 @HttpMethod
주석이 연결되지 않는다. 따라서 이 메소드의 출력은 리소스 클래스 개체로 간주한다. 즉 요청 처리가 리소스 클래스에 위임되며 거기서 알맞은 @HttpMethod-annotated
메소드를 찾을 것이다. getUser
메소드는 UserResource
개체를 반환하므로
UserResource
언급한 대로 UsersResource
클래스의 getUser
메소드의 요청 처리는 새로 인스턴스화된 UserResource
개체의 메소드로 위임된다. 다음은 UserResource
클래스 코드 중 일부로 그 메소드 중 하나인 getUser
를 보여 준다.
@HttpMethod("GET")
@ProduceMime("application/json")
public JSONObject getUser() throws JSONException {
if (null == userEntity) {
throw new NotFoundException(
"userid " + userid + "does not exist!");
}
return new JSONObject()
.put("userid", userEntity.getUserid())
.put("username", userEntity.getUsername())
.put("email", userEntity.getEmail())
.put("password", userEntity.getPassword())
.put("bookmarks",
uriInfo.getBuilder().path("bookmarks").build());
}
이 메소드 역시 @HttpMethod("GET")
및 @ProduceMime("application/json")
주석이 붙는다. 여기서 getUsers
메소드는 HTTP GET
요청을 서비스하고 JSONObject
개체를 반환한다. JSONObject
개체는 특정 사용자의 표현(예: userid
가 joe
인 사용자의 표현)을 포함한다.
UserResource
에서 소스 코드의 나머지를 살펴볼 수 있다. @ConsumeMime
와 같이 메소드가 수용할 수 있는 MIME 형식을 식별하는 JSR 311 주석을 더 볼 수 있다.
샘플 코드 빌드 및 배포
이 팁의 샘플 코드는 NetBeans 프로젝트로 사용 가능하다. NetBeans IDE 또는 명령줄에서 샘플을 빌드하고 배포할 수 있다. 어떤 경우에서든
- GlassFish V2를 다운로드하지 않았다면 다운로드하여 설치한다.
- Jersey 다운로드 페이지에서 최신 Jersey 스냅샷을 다운로드하고 그 압축을 푼다. 새로 추출된 디렉토리가
<sample_install_dir>/jersey
로 나타나는데, 여기서 <sample_install_dir>
은 샘플 애플리케이션을 설치한 디렉토리이다. 예를 들어, Windows 시스템에서 C:\
에 압축을 풀었다면 새로 생성된 디렉토리는 C:\jersey
이다.
NetBeans에서의 샘플 코드 빌드 및 배포
- NetBeans 5.5.1 IDE가 없으면 다운로드하여 설치한다.
- NetBeans IDE를 시작한다. 아직 하지 않았다면 다음과 같이 NetBeans에서 GlassFish V2를 등록한다.
- Runtime 창에서 Servers 노드를 오른쪽 클릭한다.
- Add Server를 선택한다.
- 서버를 Sun Java System Application Server로 둔다.
- Next 버튼을 클릭한다.
- Browse 버튼을 클릭하고 GlassFish V2를 설치한 위치로 이동한다.
- Choose 버튼을 클릭한다.
- Next 버튼을 클릭한다.
- GlassFish에 다른 암호를 선택하지 않았다면 Admin Password를 기본값인 adminadmin으로 설정한다.
- Finish 버튼을 클릭한다.
- 다음과 같이 Bookmark 프로젝트를 연다.
- File 메뉴에서 Open Project를 선택한다.
- Bookmark 하위 디렉토리로 이동한다.
- Open Project Folder 버튼을 클릭한다.
- 다음과 같이 Bookmark 프로젝트를 빌드하고 배포한다.
- Projects 창에서 Bookmark 프로젝트 노드를 오른쪽 클릭한다.
- Deploy Project를 선택하거나 F6(기본 프로젝트 실행)을 누른다.
명령줄에서의 샘플 코드 빌드 및 배포
AS_HOME
환경 변수를 GlassFish v2 설치 디렉토리로 설정한다. 예를 들면 (여기서는 bash 구문임)
export AS_HOME= <GF_install_dir>
이다. 여기서 <GF_install_dir>
은 GlassFish v2를 설치한 디렉토리이다.
<sample_install_dir>/jersey
디렉토리 아래 /examples/Bookmark
디렉토리로 이동한다. 명령줄에 다음 명령을 입력하여 Bookmark 애플리케이션을 빌드한다(bash 구문).
AS_HOME/lib/ant/bin/ant run-on-glassfish
샘플 코드 실행하기
다음과 같이 배포된 Bookmark 애플리케이션을 명령줄 HTTP 도구인 Curl을 사용하여 실행할 수 있다.
- Curl을 다운로드하지 않았다면 다운로드한다.
- 명령줄에 다음 명령을 입력하여 새 사용자를 추가한다(포맷팅 때문에 지금과 이후 단계에서 명령은 여러 행으로 표시한다).
curl -i --data "{\"userid\":\"techtip\",\"username\":
\"TechTip User\",\"email\":\"techtip@example.com\",
\"password\":\"TEST\"}" -H Content-type:application/json
-X PUT
http://localhost:8080/Bookmark/resources/users/techtip/
그러면 HTTP GET
요청이 UsersResource
클래스의 getUser
메소드로 디스패치되고, 이는 새 UserResource
개체를 인스턴스화한다. 이 요청은 다시 putUser
메소드에 디스패치된다.
다음과 같은 출력이 나타나야 한다.
HTTP/1.1 204 No Content
X-Powered-By: Servlet/2.5
Server: Sun Java System Application Server 9.1_01
Date: Thu, 01 Nov 2007 14:31:53 GMT
- 명령줄에 다음 명령을 입력하여 사용자 목록을 가져온다.
curl -i -X GET
http://localhost:8080/Bookmark/resources/users/
그러면 UsersResource
클래스의 getUsersListAsJson
메소드를 호출한다.
다음과 비슷한 출력이 나타난다.
HTTP/1.1 200 OK
X-Powered-By: Servlet/2.5
Server: Sun Java System Application Server 9.1_01
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 01 Nov 2007 14:34:07 GMT
["http:\/\/localhost:8080\/Bookmark\/resources\/users\
/techtip"]
- 명령줄에 다음 명령을 입력하여 사용자의 표현을 가져온다.
curl -i -X GET
http://localhost:8080/Bookmark/resources/users/techtip/
그 결과 작업은 2단계와 비슷하다.
다음과 같은 출력이 나타나야 한다.
HTTP/1.1 200 OK
X-Powered-By: Servlet/2.5
Server: Sun Java System Application Server 9.1_01
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 01 Nov 2007 14:35:38 GMT
{"userid":"techtip","username":"TechTip User",
"email":"techtip@example.com","password":"TEST",
"bookmarks":"http:\/\/localhost:8080\/Bookmark\/resources
\/users\/techtip\/bookmarks"}
요약
이 팁에서는 Java에서 JSR-311(Java API for RESTful Web Services) 사양에 부합하는 RESTful 웹 서비스를 작성하는 방법을 소개했다. JAX-RS에 대한 자세한 내용은 jsr311 프로젝트에서 확인할 수 있다. JAX-RS의 참조 구현인 Jersey에 대해서는 Jersey 프로젝트에서 확인할 수 있다.
저자 정보
Jakub Podlesak는 Jersey 프로젝트 팀원이다. 그는 GlassFish v2 웹 서비스 스택인 Metro 개발에 참여한 바 있다.
Paul Sandoz는 JSR 311: Java API for RESTful Web Services의 공동 사양 책임자 겸 구현 책임자이다. 그는 W3C, ISO 및 ITU-T 표준화 기구에서 일한 바 있으며, GlassFish 웹 서비스 스택, 특히 Fast Infoset의 표준화, 구현, 통합 및 상호 운용성 분야에서 성능과 관련된 여러 기술 개발 및 향상에 기여했다.
이 아티클의 영문 원본은
http://java.sun.com/mailers/techtips/enterprise/2007/TechTips_Nov07.html
에서 볼 수 있습니다.
출처: http://blog.sdnkorea.com/blog/471