레이블이 restful인 게시물을 표시합니다. 모든 게시물 표시
레이블이 restful인 게시물을 표시합니다. 모든 게시물 표시

2010년 4월 4일 일요일

restful 서비스 클라이언트

JAX-RS는 RESTful 클라이언트에 대한 표준적인 방식을 제공하지 않는다.
CXF 프레임워크는 클라이언트 생성을 위한 두 가지 접근방식을 제공한다.
두 방식 모두 Spring을 이용하여 설정할 수 있다.

Proxy-based API


Proxy-based API는 RESTful서비스를 구현한 인터페이스가 있는 경우에 사용할 수 있다.
Proxy는 HTTP 요청을 생성하는 데 필요한 정보를 서비스 인터페이스를 이용한다.
org.apache.cxf.jaxrs.client.JAXRSClientFactory 클래스를 사용하며, proxy를 생성하기 위해 RESTful서비스 인터페이스를 입력값으로 전달해야 한다.
proxy가 생성되고 나면 RESTful서비스의 모든 메서드가 proxy에도 동일하게 존재하며, proxy의 메서드를 호출하면 RESTful 서비스에 메서드가 원격으로 호출된다.

HTTP centric clients


HTTP centric clients는 RESTful 서비스를 호출하기 위해 org.apache.cxf.jaxrs.client.WebClient 인스턴스를 이용한다. HTTP centric clients는 RESTful서비스 인터페이스를 알지 못해도 RESTful 서비스를 호출할 수 있지만 메서드의 I/O로 전달되는 도메인객체(model, VO)에 대한 정보는 알고 있어야 한다. 도메인 객체를 알지 못해도 처리는 가능하지만 XML을 파싱해야 하는 경우가 발생한다.

아래의 소스 파일에 WebClient를 이용하는 GET, POST method 예제와 JAXRSClientFactory를 이용하는 예제가 포함되어 있다.
눈여겨 볼 부분은 Proxy-based API의 서비스 인터페이스는 context path와 web.xml에서 설정되는 servlet url mapping 정보를 알 수 없기 때문에 예제에서와 같이 host, port 뿐만 아니라 추가 경로(/amfws/restful)에 대한 정보도 설정해 주어야 한다.
또한 WebClient는 매 요청마다 새롭게 만들어져야 한다. 그렇지 않으면 에러가 발생한다.

또 한가지 주의할 점은 RESTful 서비스의 리턴타입이 XML이 아닌 경우(예, String, boolean, int 등) JAXRSClientFactory의 proxy를 이용하는 경우에 RESTful 서비스의 메서드의 Produces에 반드시 text/plain으로 설정되어 있어야 한다. Produces가 설정되지 않은 경우에는 HTTP Status 406 에러가 발생한다.

RESTful client 예제
package com.archnal.amf.amfws.rs.client;

import java.io.IOException;
import java.io.InputStream;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.apache.cxf.jaxrs.client.WebClient;

import com.archnal.amf.amfws.biz.SampleUserBizService;
import com.archnal.amf.amfws.biz.SampleUserBizServiceImpl;
import com.archnal.amf.amfws.model.SampleUser;
import com.archnal.amf.amfws.rs.SampleUserRs;
import com.archnal.amf.amfws.rs.SampleUserRsImpl;

public class SampleUserRsClient {

public static void main(String[] args) throws Exception {
testProxyApi();

testWebClient();
}

private static void testProxyApi() throws Exception {
long userNo = 1L;
SampleUserRs proxy = null;
proxy = JAXRSClientFactory.create("http://localhost:8080/amfws/restful", SampleUserRs.class);
SampleUser user1 = proxy.getUser(userNo);
System.out.println("user1: " + user1.getName());

SampleUser newUser = createNewSampleUser();
proxy.register(newUser);
}

private static SampleUser createNewSampleUser() {
SampleUser newUser = new SampleUser();
newUser.setUserNo(System.currentTimeMillis());
newUser.setLoginId(System.currentTimeMillis() + "");
newUser.setName("new user");

return newUser;
}

private static void testWebClient() throws Exception {
WebClient webclient = WebClient.create("http://localhost:8080");
long userNo = 1L;
String path = null;

path = "/amfws/restful/sample/user/getUser/" + userNo;


//    SampleUser sampleUser = webclient.path(path).accept("application/xml").get(SampleUser.class);
SampleUser sampleUser = webclient.path(path).accept(MediaType.APPLICATION_XML_TYPE).get(SampleUser.class);
System.out.println(sampleUser.getName());

webclient = WebClient.create("http://localhost:8080");

path = "/amfws/restful/sample/user/register";
SampleUser newUser = createNewSampleUser();
Response response = webclient.path(path).accept("application/xml").post(newUser);
Object entity = response.getEntity();
System.out.println("entity: " + entity);
InputStream in = null;
try {
in = (InputStream) entity;
byte[] buf = new byte[in.available()];
in.read(buf);
System.out.println("content: '" + new String(buf) + "'");
} catch(IOException ex) {
ex.printStackTrace();
} finally {
if(in != null) {
in.close();
}
}

webclient = WebClient.create("http://localhost:8080");
//String value = webclient.path(path).accept("application/xml").post(newUser, String.class);
boolean value = webclient.path(path).post(newUser, Boolean.class);
System.out.println("value: " + value);
}
}



RESTful 인터페이스 예제
package com.archnal.amf.amfws.rs;

import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

import com.archnal.amf.amfws.model.SampleUser;
@javax.ws.rs.Path("/sample/user")
@javax.ws.rs.Produces({"application/json", "application/xml", "text/plain"})

public interface SampleUserRs {
@POST
@Path("/register")
@Consumes({"application/xml"})
@Produces("text/plain")
boolean register(SampleUser user);

@javax.ws.rs.GET
@javax.ws.rs.Path("/getUser/{userNo}")
@Produces({"application/xml"})
SampleUser getUser(@PathParam("userNo") long userNo);


javax.ws.rs.core.Response login(String loginId, String loginPassword);
List searchByName(String userName);


SampleUser getUser(String loginId);
}

restful 서비스와 http method

HTTP 메소드라고 하면 GET과 POST가 먼저 떠오른다.
HTTP 메소드가 어떻게 설계되었든간에 GET과 POST만 있으면 흔히 말하는 CRUD를 모두 처리할 수 있다.
form의 메서드로 GET으로 설정한다고 해도 문제될 게 없지만
일반적으로 GET메서드는 조회나 삭제를 할 경우와 같이 파라미터가 간단한 경우에 사용되고,
POST는 insert나 update처럼 다수의 파라미터를 사용할 경우에 사용한다.
하지만 restful 관련 문서를 읽다보면 일반적으로 많이 사용하지 않은 PUT가 DELETE를 사용하는 경우를 볼 수 있다.
아마도 HTTP 메서드 자체가 각각의 용도에 맞게 설계된 듯 하다.

GET: 조회
POST: INSERT용
DELETE: 삭제
PUT: UPDATE

우리나라에서 DELETE나 PUT을 사용하는 것을 고운 시선으로 볼 것으로 생각하지 않지만 장점이 있다면 적극 사용하고 싶다.
서블릿의 경우 doGet, doPost등이 나누어져 있지만 흔히 doGet() { doPost(req, res); } 이런식으로 사용한다. 서블릿을 단위 기능별로 구현하는 습관때문에 그런 것으로 생각된다. 하지만 하나의 서블릿에서 doGet, doPost, doDelete, doPut을 구현할 수 있다면 서비스 단위로 서블릿을 구현할 수 있다.
앞으로 Http 메서드를 접하면서 작업을 할 때에는 그동안 해왔던 습관을 버리고 접근해야 겠다.