|
RESTful WebService는 HTTP와 URI를 통해 접근할 수 있도록 해주는 아키텍처이다. REST란 용어는 Representational State Transfer의 약자이다. HTTP 프로토콜에 기반하므로 GET, POST, PUT, DELETE, TRACE, HEAD 명령을 이용한다. 그리고, 응답 받을 자원의 타입은 HTTP 헤더의 Accept 필드를 전송 하는 데이터 타입은 HTTP 헤더의 Content-Type을 이용한다. 예를 들어, /service/hello로 xml 형식의 자원을 HTTP GET 메소드로 요청한다면,
GET /service/helloworld HTTP/1.1
Accept: text/plain
같은 URL로 xml 형식의 요청 데이터를 HTTP PUT으로 전송한다면
PUT /service/helloworld HTTP/1.1
Content-Type: application/xml
<?xml version=1.0?>
...
RESTful 웹서비스의 메인 컨테이너는 메소드와 요청 URI, 데이터 타입(Accept, Content-Type)을 보고 배치된 웹서비스를 선택해 실행하고 응답을 보낸다.
Dynamic Web Project와 Jersey2
RESTful 웹서비스를 구현한 모듈로 jersey2를 이용할 것이다. maven 환경이라면 jersey2 jar 라이브러리를 다운 받을 필요 없이 pom.xml에 의존성 항복을 추가한다.
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.25.1</version>
</dependency>
<!-- Required only when you are using JAX-RS Client -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.25.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.25.1</version>
</dependency>
</dependencies>
Eclipse Dynamic web projec 환경이라면 jersey 사이트에서 다운 받은 jersey2의 *.jar 라이브러리를 WebContent\WEB-INF\lib에 복사한다. 라이브러리 자체가 웹 컨테이너 시작시 필요하기 때문에 lib 디렉토리에 복사해 두는 것이 좋다. 이클립스 프로젝트에서 빌드 패스에 jar 라이브러리를 추가하면 클래스의 컴파일은 되지만, 이클립스에서 설정한 타켓 서버를 시동해 테스트 할 때 ClassNotFound 예외를 만나게 될 것이다. 라이브러리가 준비된 후에 web.xml에 아래와 같은 서블릿 설정을 추가해야 한다.
<servlet>
<servlet-name>Jersey2</servlet-name>
<servlet-class>
org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>my.ex.rws</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey2</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
<init-parm>의 <param-value>는 RESTful 웹서비스로 제공될 클래스의 패키지 이름을 나타내고 <servlet-mapping>의 <url-pattern>은 접속할 웹서비스의 경로를 의미한다. 경로의 형식은
http://localhost:8080/<servlet-context>/<url-pattern>/<path>
이제 RESTful 웹서비스로 제공할 서비스 클래스를 작성한다. 아래 예는 간단히 Hellow 메시지를 제공하는 클래스이다(서블릿 클래스가 아니라 일반 자바 클래스 만들고 어노테이션을 추가한 것이다).
Example: HelloWorld WebService
package my.ex.rws;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("helloworld")
public class HelloWorld {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getTextHelloWorld() {
return "Hello World";
}
}
소스코드를 설명하면, @Path에는 접속할 URI 경로에 해당한다. @GET은 HTTP 메소드 GET, @Produces는 자원의 타입을 뜻한다. 클라이언트가 HTTP GET 메소드로 텍스타입(Accept Header filed에 표기됨) 자원을 요청했다면 getTextHelloWorld() 메소드가 RESTful 웹서비스에 의해 호출될 것이다. 소스 코딩을 완료하고 서버를 시작한 뒤에 브라우져에서 아래 경로로 접속하여 테스트해 본다.
http://localhost:8080/RESTfulWebService/service/helloworld
/RESTfulWebService는 서블릿 루트 컨텍스트(이클립스 프로젝트 이름)이고 /service는 web.xml에서 jersey를 위한 설정에서 지정했던 url이고 /helloworld는 클래스에 명시한 경로이다.
클라이언트 프로그램
클라이언트 역시 필요한 라이브러리는 maven 을 이용하던지 직접 jar 라이브러리를 빌드 패스에 추가하면 된다.
Example: HelloWorld Client
package my.ex.restfulclient;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class HelloWorldClient {
private static final String REST_SERVICE_URL =
"http://localhost:8080/RESTfulWebService";
public static void GetTextHelloWorld(){
Client client = ClientBuilder.newClient();
WebTarget target =
client.target(REST_SERVICE_URL).path("service/helloworld");
// TEXT_PLAIN 타입을 GET 요청
Response response = target.request(MediaType.TEXT_PLAIN_TYPE).get();
// 응답 출력
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
}
public static void main(String[] args) {
GetTextHelloWorld();
}
}
직관적으로 이해할 수 있을 것이다. URI로부터 WebTarget 객체를 얻고 이 객체를 이용해 요청 타입과 요청 메소드에 해당하는 메소드로 요청을 보낸다. 응답은 Response 객체로 리턴되며 getStatus()로는 HTTP 응답 코드를 readEntity()로는 응답 데이터를 다룬다. readEntity()의 파라미터는 응답 데이터를 String 클래스로 다룬다는 의미이다. HelloWorld Client가 보내는 메시지는
GET /service/helloworld HTTP/1.1
...
Accept: text/plain
이를 받은 웹 컨테이너는 서블릿 매핑(web.xml) /service/*에 의해 RESTful 웹서비스로 요청을 전달한다. RESTful 웹서비스는 /service/helloworld URI의 helloworld에 해당하는 HelloWorld 객체의 메소드를 타입에 따라 호출하고 리턴값을 클라이언트에게 전송한다.
@Produces, @Consumes - 요청/응답 타입
이미 살펴봤듯이 응답 타입은 @Produces에 명시하였다. 여기에 쓰는 타입은 텍스르로 나타낸 MIME 타입(ex, text/plain)이나 javax.ws.rs.core.MediaType(ex, MediaType.TEXT_PLAIN_TYPE)으로 지정한다. 웹 서비스에서 같은 요청 URL에 대해 여러 타입의 응답 타입을 제공하고 싶다면, 응답 타입 별로 메소드를 제공하면 된다. “text/plain”, “text/html”, “text/xml” 타입에 대해
@Path("helloworld")
public class HelloWorld {
@GET
@Produces("text/plain")
public String getTextHelloWorld() {
return "Hello World";
}
@GET
@Produces("text/xml")
public String getXMLHelloWorld() {
return "<?xml version=\"1.0\"?><hello>Hello World</hello>";
}
@GET
@Produces("text/html")
public String getHtmlHelloWorld() {
return "<html><title></title>"
+ "<body><h3>Hello World</h3</body></html>";
}
}
HelloWorld 클라이언트 예제 코드를 활용하여, “text/plain” 타입 대신 “text/xml” 타입을 요청하도록 수정하면
Response response = target.request(MediaType.TEXT_XML).get();
여러 응답 타입을 한 메소드에 지정할 수도 있다.
@GET
@Produces({"text/plain","text/html"})
public String getHelloWorld() {
...
}
@Consumes 어노테이션을 이용하여 요청 타입을 지정할 수 있다. 예를 들어, POST로 폼 데이터를 전송하다면 파라미터 형태로 전송되는 폼 데이터(x-www-form-urlencoded)나 멀티파트 포맷이 전송될 수 있다. RESTful 웹서비스에서 이 두 요청 데이터 타입에 대응하고자 한다면
@Path("formprocess")
public class FormPost {
@POST
@Consumes("application/x-www-form-urlencoded")
public String doPost1(MultivaluedMap<String, String> formParams) {
return "Hello World";
}
@POST
@Consumes("multipart/mixed")
public String doPost2(FormDataMultipart mimeMultipartData) {
return "Hello World";
}
}
폼데이터 처리 자체에 대해서는 이후에 살펴볼 것이다.
@Path, @PathParam
RESTful 웹서비스의 URL에 추가로 붙인 URI는 { }를 이용해 참조할 수 있다. HelloWorld 예제에서 웹 서비스의 전체 URL에 /honggildong을 붙였다고 하자.
http://localhost:8080/RESTfulWebService/service/helloworld/honggildong
뒤에 붙은 URI는 웹서비스 클래스의 @Path에서 { } 을 이용해 이름을 붙이고 @PathParam을 이용해 얻을 수 있다. URI 패스 파라미터를 다루도록 HelloWorld 웹서비스 예제를 수정하면
package my.ex.rws;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("helloworld/{username}")
public class HelloWorld {
@GET
@Produces("text/plain")
public String getTextHelloWorld(@PathParam("username") String userName){
return "Hello World " + userName;
}
}
@Path를 메소드에 쓰면 서브 패스가 지정된다. 예를 들어,
@Path("helloworld")
public class HelloWorld {
@GET
@Path("sub1")
public String getTextHelloWorld1(){
...
}
@GET
@Path("sub2")
public String getTextHelloWorld2(){
...
}
}
요청 URI가 helloworld/sub1이면 getTextHelloWorld1()을 helloworld/sub2면 getHelloWorld2()를 서비스한다.
GET과 POST-폼(Form) 파라미터 - @QueryParam, @FormParam
HTTP GET 요청의 파라미터는 @QueryParam 어노테이션을 이용해 얻는다. 비슷하게 HTTP POST로 전송되는 폼 파라미터는 @FormParam 어노테이션을 이용한다. 이 경우 폼 파라미터의 MIME 타입은 application/x-www-form-urlencoded이므로 @Consume 어노테이션으로 웹서비스가 받는 타입을 지정해 준다. 아래 예는 GET 혹은 POST로 전송되는 폼 파라미터를 다루는 예이다.
Example: 폼 파라미터를 처리하는 Contact 웹서비스
HTML 페이지
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Form</title>
</head>
<style>
label {
display: inline-block;
width: 120px;
text-align: right;
margin-right: 5px;
}
</style>
<body>
<h3>input a contact information</h3>
<!-- GET 테스트는 method="GET"으로 변경 -->
<form action="service/parametertest" method="POST">
<label>name</label><input type="text" name="name"><br>
<label>phone number</label><input type="text" name="phone"><br>
<label>email 1</label><input type="text" name="email"><br>
<label>email 2</label><input type="text" name="email"><br>
<input type="submit" value="Submit" />
</form>
</body>
</html>
웹서비스 클래스
package my.ex.rws;
import java.util.List;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
@Path("parametertest")
public class ParameterTest {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String doGetContact(@QueryParam("name") String name,
@QueryParam("phone") String phone,
@QueryParam("email") List<String> emails) {
String message = "your contact: " +
"name: " + name +
", phone: " + phone;
for(String email:emails) {
message += ", email: " + email;
}
return message;
}
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String doPostContact(@FormParam("name") String name,
@FormParam("phone") String phone,
@FormParam("email") List<String> emails) {
String message = "your contact: " +
"name: " + name +
", phone: " + phone;
for(String email:emails) {
message += ", email: " + email;
}
return message;
}
}
폼 파라미터를 다루는 다른 방식으로 @FormParam 대신 MultivaluedMap 클래스를 이용하는 방법이 있다.
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String doPostContact(MultivaluedMap<String, String> formParam){
List<String> name = formParam.get("name");
List<String> phone = formParam.get("phone");
List<String> emails = formParam.get("email");
String message = "your contact: " +
"name: " + name.get(0) +
", phone: " + phone.get(0);
for(String email:emails) {
message += ", email: " + email;
}
return message;
}
@QueryParam에 대해, @DefaultValue를 이용해 디폴트 값을 지정할 수도 있다.
public String doGetContact(
@DefaultValue("0") @QueryParam("min") String minimum)
요청 쿼리에 min이란 파라미터가 없다면 minmum=0이 할당된다.
웹브라우저 대신 폼 POST 요청을 보내는 클라이언트를 프로그램으로 작성해 보면 다음과 같다.
Example: POST Form 클라이언트
package my.ex.restfulclient;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class FormPostClient {
private static final String REST_SERVICE_URL = "http://localhost:8080/RESTfulWebService";
public static void testPostForm(){
Client client = ClientBuilder.newClient();
WebTarget target =
client.target(REST_SERVICE_URL).path("service/parametertest");
// 폼 파라미터
Form form = new Form();
form.param("name", "hong gil dong");
form.param("phone", "08-010-1234-3245");
form.param("email", "test@example1.com");
form.param("email", "test@example2.com");
// POST Request
// @Produces(MediaType.APPLICATION_FORM_URLENCODED)
Entity entity = Entity.entity(form,
MediaType.APPLICATION_FORM_URLENCODED);
// @POST
// @Consumes(MediaType.TEXT_PLAIN)
Response response = target.request(MediaType.TEXT_PLAIN).post(entity);
// Response
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
}
public static void main(String[] args) {
testPostForm();
}
}
주목할 부분은 Entity 객체를 생성하는 부분이다. 이전 HellowWorld 예제의 get() 요청에서는 사용하지 않았던 것이다. Entity.entity() 메소드 호출 부분을 보면 Form 객체를 이용해 만든 form과 폼 파라미터 타입 x-www-form-urlencoded을 지정한다. 이 엔티티는 post() 메소드의 파라미터로 넘겨지고 웹서비스로 전달되게 된다. 클라이언트 코드의 Entity.entitiy()와 target.request()에서 설정한 미디어 타입에 의해 웹서비스 URI “service/parametertest”에서 서비스를 수행할 메소드는 @Produces(MediaType.TEXT_PLAIN), @Consumes(MediaType.APPLICATION_FORM_URLENCODED)인 메소드가 될 것이다.
GET 요청을 보내는 클라이언트는 더 간단하다. 파라미터를 queryParam()으로 지정하면 된다.
public static void testGetParam() {
Client client = ClientBuilder.newClient();
WebTarget target = client.target(REST_SERVICE_URL);
WebTarget paramTarget = target
.queryParam("name", "hong gild dong")
.queryParam("phone", "08-010=1234-3245");
Response response = paramTarget.request(MediaType.TEXT_PLAIN).get();
// Response
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
}
컨텍스트 @Context
HTTP 요청에 대해 파라미터뿐만 아니라 패스와 헤더 정보에 접근하려는 경우 @Context를 이용한다. @Context로 지정할 수 있는 객체는 UriInfo와 HttpHeaders가 있다. 간단한 사용 법은
@GET
public String get(@Context UriInfo ui) {
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
MultivaluedMap<String, String> pathParams = ui.getPathParameters();
}
@GET
public String get(@Context HttpHeaders hh) {
MultivaluedMap<String, String> headerParams = hh.getRequestHeaders();
Map<String, Cookie> pathParams = hh.getCookies();
}
@Context로 서블릿 정보 ServletConfig, ServletContext, HttpServletResponse, HttpServletRequest도 이용할 수 있다.
POST 멀티파트 요청
jersey로 멀티파트 요청을 처리하기 위해서는 추가로 라이브러리가 필요하다. maven 프로젝트라면
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.25.1</version>
</dependency>
을 pom.xml에 추가한다.
package my.ex.rws;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
@Path("multipart")
public class MultiPart {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String doPostFileUpload(
@FormDataParam("file") InputStream in,
@FormDataParam("file") FormDataContentDisposition fileDetail) {
String message = "file name: " + fileDetail.getFileName()
+ "\nnote: " + note
+ "\nfile contents: \n";
StringBuffer strBuffer = new StringBuffer();
strBuffer.append(message);
byte [] buffer = new byte[1024];
int nread = 0;
try {
while((nread = in.read(buffer)) != -1) {
strBuffer.append(new String(buffer, 0, nread, "UTF-8"));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return strBuffer.toString();
}
}
XML 데이터 타입 처리
RESTful 웹서비스에서 XML를 다루기 위해서는 JAXB를 이용한다. 우선 다음과 같은 XML 형식의 문서를 서비스 한다고 하자.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<contact>
<name>hong gil dong</name>
<phone>08-010-1234-5678</phone>
<email>myaddr1@example.net</email>
<email>myaddr2@example.net</email>
</contact>
이러한 형식의 XML 문서를 자바 객체로 바인딩하기 위해서는 JAXB xjc 툴을 이용해 스키마로부터 바인딩에 이용할 클래스를 생성하거나, 직접 바인딩할 클래스를 작성해야 한다. 직접 작성해보면
Example: XML 바인딩 클래스
package my.ex.contact;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "contact")
public class Contact {
@XmlElement
protected String name;
@XmlElement
protected String phone;
@XmlElement
protected List<String> email;
@XmlAttribute
protected String priority;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getPhone() {
return phone;
}
public void setPhone(String value) {
this.phone = value;
}
public List<String> getEmail() {
if (email == null) {
email = new ArrayList<String>();
}
return this.email;
}
public String getPriority() {
if (priority == null) {
return "0";
} else {
return priority;
}
}
public void setPriority(String value) {
this.priority = value;
}
}
package my.ex.contact;
import javax.xml.bind.annotation.XmlRegistry;
@XmlRegistry
public class ObjectFactory {
public ObjectFactory() {
}
public Contact createContact() {
return new Contact();
}
}
Contact 클래스의 JAXB 어노테이션은 최소한의 어노테이션으로 객체와 XML 문서가 이름으로 매핑된다. 그리고, 서비스할 클래스는
Example: XML을 리턴하는 Contact 웹서비스
package my.ex.rws;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import my.ex.contact.Contact;
@Path("contact")
public class ContactService {
@GET
@Path("get")
@Produces(MediaType.APPLICATION_XML)
public Contact getContact(){
Contact contact = new Contact();
contact.setName("hong gil dong");
contact.setPhone("08-010-1234-5678");
List<String> emails = contact.getEmail();
emails.add("myaddr1@example.net");
emails.add("myaddr2@example.net");
return contact;
}
}
웹서비스 클래서에서는 바인딩 객체 Contact를 생성하고 이를 리턴한다. 브라우저에서 서비스에 접속해 보면 XML 문서를 볼 수 있다.
접속 URL: http://locahost:8080/RESTfulWebService/service/contact/get
접속 결과:
<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<contact><name>hong gil dong</name>
<phone>08-010-1234-5678</phone>
<email>myaddr1@example.net</email>
<email>myaddr2@example.net</email>
</contact>
웹서비스에서 객체를 리턴하는데도 XML 문서가 보이는 이유는 내부적으로 JAXB에 의해 Contact 객체를 XML 문서로 변환하기 때문이다. HTTP POST나 PUT을 이용한다면 파라미터에도 JAXB 바인딩 객체를 이용할 수 있다.
Example: XML을 파라미터로 받는 Contact 웹서비스
@POST
@Path("post")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public Contact addContact(Contact contact) {
// 그냥 받은 것을 돌려 줌.
return contact;
}
이 경우는 내부에서 파라미터로 전달된 XML 문서를 Contact 객체로 언마샬링하여 서비스 메소드에 전달되기 때문에, 객체를 이용할 수 있는 것이다.
아래는 브라우저 클라이언트 대신 어플리케이션 클라이언트에 대한 예이다.
Example: Contact Client
package my.ex.restfulclient;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import my.ex.contact.*;
public class ContactClient {
private static final String REST_SERVICE_URL = "http://localhost:8080/RESTfulWebService";
public static void requestXML() throws JAXBException{
String xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
+ "<contact><name>hong gil dong</name>"
+ "<phone>08-010-1234-5678</phone>"
+ "<email>myaddr1@example.net</email>"
+ "<email>myaddr2@example.net</email>"
+ "</contact>";
Client client = ClientBuilder.newClient();
WebTarget target =
client.target(REST_SERVICE_URL).path("service/contact/post");
Response response = target.request(MediaType.APPLICATION_XML)
.post(Entity.entity(xmlStr, MediaType.APPLICATION_XML));
// 응답 출력
Contact contact = null;
if(response.getStatus() == 200) {
// JAXB에 의해 응답 받은 XML 문서를 Contact로 언마샬링 한다.
contact = response.readEntity(Contact.class);
printContactToConsole(contact);
}
}
public static void printContactToConsole(Contact contact)
throws JAXBException{
String outStr = "";
outStr += "name: " + contact.getName() + "\n" +
"phone: " + contact.getPhone() +"\n";
for(String email:contact.getEmail()){
outStr += "email: " + email + "\n";
}
System.out.println(outStr);
}
public static void main(String[] args) throws JAXBException {
requestXML();
}
}
주목할 부분은 target.request()와 response.readEntity()이다. 우선 target.request()를 보면, 요청 타입은 MediaType.APPLICATION_XML로 HTTP Accept: “application/xml”에 해당한다. 이는 웹서비스에서 @Produces(MediaType.APPLICATION_XML)인 메소드에 대해 서비스를 요청한다는 의미이다. 그리고, post(Entity.entity()) 타입은 MediaType.APPLICATION_XML로 HTTP Content-Type: “application/xml”에 해당한다. 이는 웹서비스에서 @Consumes(MediaType.APPLICATION_XML)인 메소드에 대해 서비스를 요청한다는 의미이다. 그래서, 클라이언트와 웹서비스 사이에 전송되는 데이터는 XML이고 이 XML 문서는 JAXB에 의해 자바 객체 Contact로 마샬링(object to xml)하거나 언마샬링(xml to object)한다. 클라이언트 코드의 response.readEntity(Contact.class)에서 응답 받은 XML 문서를 Contact 객체로 언마샬링한다.
JSON 데이터 타입 처리
JAXB를 이용해 바인딩할 객체를 생성했다면 바로 JSON 데이터로도 바인딩할 수 있다. 이는 JAXB에 의해 알아서 수행된다. ContactService 예에서 JSON을 응답으로 보내도록 수정하는 것은 간단하다. 그냥 MediaType.APPLICATION_JSON을 @Produces에 추가하면 된다.
Example: Contact JSON 샘플
{"contact":[
{"name": "hong gil dong"},
{"phone": "08-010-1234-5678"},
{"email":"myaddr1@example.net"},
{"email":"myaddr2@example.net"}
]}
Example: JSON 타입의 응답을 보내는 Contact 웹서비스
...
@Path("contact")
public class ContactService {
@GET
@Path("get")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Contact getContact(){
Contact contact = new Contact();
contact.setName("hong gil dong");
contact.setPhone("08-010-1234-5678");
List<String> emails = contact.getEmail();
emails.add("myaddr1@example.net");
emails.add("myaddr2@example.net");
return contact;
}
...
}
코드는 XML 타입의 응답을 전송하는 contact/get 서비스에 그냥 MediaType.APPLICATION_JSON만 추가한 것이다. 클라이언트가 xml 타입을 요청하면 xml 타입으로 json 타입으로 요청하면 json 타입으로 Contact 객체를 변환한다. 클라이언트도 JAXB 바인딩 객체만 있으면 xml에서 json 타입 파라미터로 변경만 해도 동작한다.
Example: XML과 JSON 타입을 요청하는 클라이언트
public static void requestGetJSON() throws JAXBException {
Client client = ClientBuilder.newClient();
WebTarget target =
client.target(REST_SERVICE_URL).path("service/contact/get");
Response response = target.request(MediaType.APPLICATION_JSON).get();
// 응답 출력
Contact contact = null;
//String contact;
System.out.println(response.getStatus());
if(response.getStatus() == 200) {
// 응답을 Contact에 매핑
contact = response.readEntity(Contact.class);
printContactToConsole(contact);
}
}
public static void requestGetXML() throws JAXBException {
Client client = ClientBuilder.newClient();
WebTarget target =
client.target(REST_SERVICE_URL).path("service/contact/get");
Response response = target.request(MediaType.APPLICATION_XML).get();
// 응답 출력
Contact contact = null;
//String contact;
System.out.println(response.getStatus());
if(response.getStatus() == 200) {
// 응답을 Contact에 매핑
contact = response.readEntity(Contact.class);
printContactToConsole(contact);
}
}