hamcrest 대신 FEST Assert 를 사용해 보자

기본

hamcrest 의 단점 중 하나는 많은 수의 static import 가 필요해 이클립스에서 자동완성 기능을 사용하기 어렵다는 것이죠. Intellij는 메소드 자동완성도 가능하지만 여전히 어떤 이름의 메소드가 있는지 모르거나 해당 매처가 어떤 타입을 지원하는지 모르면 사용하기 어렵긴 매한가지 입니다.

FEST Assert의 경우 빌더 패턴을 통해 이 문제를 매우 운치있게 풀어나갔습니다.

// String specific assertions
assertThat(frodo.getName()).startsWith("Fro").endsWith("do")
                           .isEqualToIgnoringCase("frodo");

// collection specific assertions
assertThat(fellowshipOfTheRing).hasSize(9)
                               .contains(frodo, sam)
                               .excludes(sauron);

hamcrest 나 jUnit assertThat() 과는 다르게 assertThat() 에 검증할 파라미터만 넘기고 매처는 메소드 체이닝으로 뙇! 넘긴 파라미터의 타입에 따라 매처가 뙇! static import 를 딱 하나만 해주면 되는거죠. 괜찮지 않나요? 여기에 2.x 버전 대라 그런지 hamcrest 로 할 수 있는 것 + 타입에 따라 다양하게 제공되는 매처들이 많아 편하네요. 뿐만 아니라 Joda Time, Guava 용 모듈이 따로 있어 최적화된 테스트가 가능합니다.

한 가지 문제점은 FEST Assert 가 기존 jUnit 의 assertThat() 과 같은 이름의 static method 를 사용하기 때문에 같이 사용한다기 보단 대체한다는 생각으로 접근해야 합니다.  그래서 그런지 jUnit 에서 마이그레이션 하는 방법도 만들어 놨더군요.

안 써본 분들은 한 번 써보시길.. 🙂

Spring Framework 3.2 – Themes And Trends 일부 정리

기본

Spring Framework 3.2 Webinar 인 Themes And Trends 영상을 보고 Spring Framework 3.2 에 대한 내용 중 일부를 정리한 포스트입니다:

Meta 어노테이션

빈을 주입하거나 @Bean 메소드에서 @Autowired, @Value 어노테이션을 메타 어노테이션으로 사용할 수 있습니다. 즉, 커스텀 어노테이션을 만들면서 @Value, @Autowired, 그리고 커스텀 어노테이션을 추가하면 해당 기능이 추가됩니다. 그리고 @Lazy 등을 이용해 기능을 더 추가할 수도 있다고 하네요.

비동기 지원

request.startAsync()

request.startAsync()

코드를 사용해서 명시적으로 비동기 코드를 사용할 수 있습니다. 위와 같이 지정하면..

  • 컨테이너 쓰레드가 종료되고 새로운 쓰레드로 처리되며 완료 후, 다시 컨테이너로 디스패치 됩니다.

java.util.concurrent.Callable<?>

Spring MVC 핸들러에서 Callable<?>을 리턴값으로 사용 가능합니다. Callable을 리턴할 경우 새로운 쓰레드에서 실행됩니다.

@RequestMapping(&amp;quot;…&amp;quot;)
public @ResponseBody Callable callable() {
  return new Callable() {
    @Override
    public String call() throws Exception {
      // Long Running DB job, 3rd party REST API call, etc.
      return &amp;quot;Hello world&amp;quot;;
    }
  }
}

org.springframework.web.*.DeferredResult<?>

Spring MVC가 제어하지 않는 쓰레드를 통해 비동기 처리(JMS, AMQP, Redis 등)를 합니다. 먼저 DeferredResult를 저장하고, 외부 이벤트 발생 시, 실행하게 됩니다. 이때, 스프링의 AsyncTask를 사용할 수도 있습니다:

// save
@RequestMapping(&amp;quot;…&amp;quot;)
public @ResponseBody DeferredResult deferredResult() {
  DeferredResult result = new DeferredResult();
  this.responseBodyQueue.add(result);
}

// process
@Scheduled(fixedRate=2000)
public void processQueues() {
  for (DeferredResult result : this.responseBodyQueue)  {
    result.setResult(&amp;quot;Deferred result&amp;quot;);
    this.responseBodyQueue.remove(result);
  }
}

서버단 롱폴링 처리 프로세스

  1. 브라우저 요청
  2. 핸들러 메소드가 DeferredResult 리턴
  3. 서블릿 컨테이너 종료(응답은 살아있음)
  4. JMS, AMQP, Redis 같은 외부 이벤트 발생
  5. DeferredResult 세팅
  6. 요청이 서블릿 컨테이너로 돌아감
  7. 처리가 다시 진행되고 요청 완료

서블릿 3 에서 비동기 설정

서블릿과 모든 필터에 비동기 지원 플래그 추가
<async-supported>true</async-supported>

<filter-mapping> 자식 엘리먼트인 <disaptcher> 엘리먼트에 ASYNC 추가
<dispatcher>REQUEST, ASYNC, FORWARD</dispatcher>

Java Config 에서 설정을 도와주는 WebApplicationInitializer  추가됨

public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class&amp;lt;?&amp;gt;[] getRootConfigClasses() {
    return new Class&amp;lt;?&amp;gt;[] { RootConfig.class };
  }

  @Override
  protected Class&amp;lt;?&amp;gt;[] getServletConfigClasses() {
    return new Class&amp;lt;?&amp;gt;[] { WebMvcConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { &amp;quot;/*&amp;quot; };
  }

  @Override
  protected void customizeRegistration(Dynamic registration) {
    registration.setAsyncSupported(true);
  }

}

컨텐츠 네고시에이션

file extension, Accept header, request parameter, default value, custom strategy 를 기반으로 컨텐츠 네고시에이션이 가능하고, 전략의 우선순위도 설정 할 수 있습니다.

REST 에러 보고

전역 @ExceptionHandler 를 지정할 수 있는 @ControllerAdvice 어노테이션이 추가됐습니다.

@ControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler
  public @ResponseBody String handleBusinessException(BusinessException ex) {
    return &amp;quot;Error details&amp;quot;;
  }

  // May also add globally applicable @InitBinder(e.g. Date) and @ModelAttribute methods
}

Matrix 변수

URI path에 세미콜론을 사용하면.. GET /pets/42;q=21 다음과 같은 코드로 가져올 수 있습니다.

@PathVariable String petId, @MatrixVariable int q

Matrix 변수 설정을 사용하기 위해선 RequestMappingHandlerMapping 의 removeSemicolonContent 프로퍼티의 값을 false 로 지정해야 합니다.(디폴트 true)

TestContext 프레임워크에서 Web Application 지원

// default to &amp;quot;file:src/main/webapp&amp;quot;
@WebAppConfiguration

// detects &amp;quot;WacTests-context.xml&amp;quot; in same package
// or static nested @Configuration class
@ContextConfiguration
public class WacTests {
  //...
}

Spring MVC Test Framework

발표자가 생각하는 좋은 테스트 이디엄(idiom)을 소개해 주고 있습니다.

// Server-side test
@Runwith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(&amp;quot;servlet-context.xml&amp;quot;)
public class SampleTests {
  @Autowired
  private WebApplicationContext wac;
  private MockMvc mvc;

  @Before
  public void setup() {
    this.mvc = webAppContextSetup(this.wac).build();
  }

  @Test
  public void getFoo() throws Exception {
  this.mvc.perform(get(&amp;quot;/foo&amp;quot;).accept(&amp;quot;application/json&amp;quot;))
          .andExpect(status().isOk())
          .andExpect(content().mimeType(&amp;quot;application/json&amp;quot;))
          .andExpect(jsonPath(&amp;quot;$.name&amp;quot;).value(&amp;quot;Lee&amp;quot;));
  }
}

// Client-side test
RestTemplate restTemplate = new RestTemplate();

MockRestServletServer mockServer = MockRestServiceServer.createServer(restTemplate);

mockServer.expect(requestTo(&amp;quot;/greeting&amp;quot;))
    .andRespond(withSuccess(&amp;quot;Hello world&amp;quot;, &amp;quot;text/plain&amp;quot;));

// use RestTemplate …

mockServer.verify();

Spring MVC 3.1에서 ObjectId를 Jackson 매퍼에서 시리얼라이즈 할 때 toString() 하게끔 하기

기본

MongoDB는 컬렉션에 넣는 데이터에 기본적으로 ObjectId를 할당합니다. 일종의 PK라고 볼 수 있죠. Java에서 ObjectId 또한 클래스이고 그 안에 여러가지 필드를 가지고 있는데요. @ResponseBody 어노테이션으로 JacksonMapper를 사용해 ObjectId가 json으로 파싱되면 MongoDB에서 사용하는 문자열 값이 아니라 ObjectId 객체의 필드들이 JSON 형태로 만들어 집니다. 원하는 건 이게 아닌데 말이죠 :-/

그래서 Jackson에서 제공해 주는 @Serializer 어노테이션으로 커스텀 시리얼라이저를 만들어 봤지만 씨알도 안먹히더군요. 그래서 이 포스트를 참고해 CustomObjectMapper 도 만들어 봤지만 <annotation-driven /> 에서 Jackson 매퍼를 빈으로 등록하기 때문에 파라미터로 세팅해 줄수가 없네요.

위 포스팅에선 @PostConstruct 를 사용해 빈으로 등록된 Jackson 매퍼를 찾아서 커스텀 매퍼를 세팅해 주고 있는데요. 위 방법은 Spring 3.1 에서 동작하지 않습니다. 그 이유는 더이상 AnnotationMethodHandlerAdapter를 사용하지 않기 때문이죠. 예제에서 AnnotationMethodHandlerAdapter를 RequestMappingHandlerAdapter로 변경하면 잘 동작합니다!

[번역] WebWork in Action – Appendix WebWork Architecture

기본

WebWork in Action

Appendix: WebWork Architecture

처음 배웠던 챕터1 “웹워크 오버뷰”로 돌아가보자, 웹워크는 두 부분으로 만들어져 있다고 했었다. 웹워크의 근간은 일반적인 커맨드 패턴 프레임워크인 XWork이다. 웹워크는 XWork를 감싼 형태로 MVC 웹 애플리케이션 프레임워크를 구현했다.

프레임워크의 주 컨셉인 액션(Action), 인터셉터(Interceptor), 리절트(Result)는 XWork에 정의돼 있다. 웹워크는 이러한 기본 컨셉을 확장해 웹 애플리케이션 개발을 지원할 수 있도록 한다. 이 부록에선 XWork와 웹워크의 설계에 대해 논의한다.

챕터 1에 XWork와 웹워크를 나눠놨지만 이 부록에서는 XWork와 웹워크의 각 요소에 대해서 깊게 살펴보지 않고 프레임워크의 아키텍처에 대해서 논의할 것이다.

A.1 커맨드 패턴 구현하기

웹워크의 핵심은 커맨드 패턴 구현체란 것이다. 커맨드 패턴은 코드 단위(일반적인 함수나 OOP에서 말하는 메소드)를 커맨드(command) 또는 액션(action)이라고 불리는 클래스에 놓고, 모두 공통된 인터페이스를 구현한다. 그림 A.1에서 보는바와 같이, 액션 인스턴스는 다른 코드에 의해 생성되거나 넘겨져 실행될 수 있는데, 커맨드 인터페이스를 구현했다면 어떤 클래스가 해당 인스턴스를 실행시키는지 알 필요가 없다.

The simplest possible Command pattern implementation

그림 A.1 가장 간단한 커맨드 패턴 구현체

만약 당신이 커맨드를 사용하는 프레임워크를 사용한다면 프레임워크는 액션의 실행을 캡슐화 할 수 있다. 왜냐하면 당신이 프레임워크를 통해 액션을 호출하면, 프레임워크 설정을 통해 해당 호출에 다른 서비스를 추가할 수 있기 때문이다. 예를들어, 커맨드 객체를 저장해 다시 실행하거나 나중에 되돌리기 기능을 구현한다던지, 로그를 위해 저장한다던지, 다른 곳으로 보내 실행되도록 할 수도 있을것이다.

Calling commands through a Command pattern framework allows the client code to be decoupled from the command implementation and lets the framework add other services transparently

그림 A.2 커맨드 패턴 프레임워크를 통해 커맨드를 호출하면, 커맨드 구현체로부터 클라이언트 코드를 분리시켜 프레임워크가 다른 서비스 추가를 쉽게할 수 있습니다.

그림 A.2를 보면 프레임워크를 통해 액션을 실행시키려는 클라이언트는 어떠한 서비스가 호출되는지, 심지어 어떠한 액션 클래스가 실행되는지에 대해서 알아야 할 필요가 없다.

왜냐하면 클라이언트가 직접 클래스를 호출하는 것이 아니라, 프레임워크를 통해 호출이 이루어지기 때문에, 커맨드 클래스의 변경 없이도 프레임워크의 설정을 통해 커맨드의 행동을 바꾸는 것이 가능하기 때문이다. 어떻게 프레임워크 설정을 하는지 그리고 무슨 기능(Service)이 사용가능한지에 대한 구체적인 구현체에 대해서는 챕터 3 “웹워크 세팅하기”에서 논의했었고, 웹워크 액션은 xwork.xml 에서 설정 가능했었다.

A.1.1 커맨드 패턴 기본 기능

웹워크는 다른 커맨드 프레임워크에서는 찾아볼 수 없는 고급 기능 뿐만 아니라 커맨드 패턴 프레임워크의 표준 기능들도 제공한다. 커맨드 패턴 구현체의 표준 기능은 다음을 포함한다:

  • 액션 클래스를 별칭(alias name) 기반으로 실행하게 해서 커맨드 객체 구현체로부터 클라이언트를 분리시킨다. 이것은 xwork.xml 설정 파일에서 할 수 있으며, 챕터3에서 자세히 살펴봤었다.
  • 액션이 실행되기 전, 환경설정 시점(configuration-time)에서 인스턴스에 파라미터 설정이 가능하다. 이 파라미터는 xwork.xml에서 설정 가능하며, 더 자세한 내용은 챕터 3과 챕터 5의 “인터셉터로 기능 추가하기”를 보자.
  • 특정 요청에(request-specific) 필요한 파라미터를 액션이 실행되기 직전에 세팅한다. 이것은 인터셉터를 통해 할 수 있으며 챕터 5에서 논의했었다.
  • 커맨드가 반환하는 리턴코드(return code)와 리절트(Result 인스턴스)간에 매핑을 제공한다. 이것 또한 xwork.xml에서 매핑 설정을 하며, 챕터 3에서 논의했었다.
  • 여러 액션을 조합해 다수의 커맨드를 하나로 묶을 수 있도록 지원한다. 이것은 xwork.xml의 리절트와 인터셉터를 통해 조합할 수 있다. 액션의 구성에 대해선 챕터 7 “리절트 사용하기”에 자세히 나와있다.

A.1.2 커맨드 패턴 고급 기능

웹워크는 다른 커맨드 프레임워크에서는 볼 수 없는 여러 고급 기능들도 제공한다:

  • 액션 인스턴스에 프로퍼티를 세팅할 때, 각각의 클래스와 프로퍼티에 대해 타입 변환을 지원한다. 이 타입 변환은 유연해서 스트럿츠의 String FormBeans나 다른 프레임워크에서 하지 못하는 다양한 타입의 프로퍼티를 가지는 도메인 객체를 사용 가능하게 한다. 타입 변환에 대해선 챕터 12에서 자세히 다루고 있다.
  • 액션 클래스 계층을 기반으로한 메시지 상속 모델로 지역화된 메시지를 지원한다. 국제화 지원에 대해선 챕터 14에서 다루고 있다.
  • 에러 메시지를 클래스 인스턴스와 필드 수준에서 지원한다. 따라서 에러 메시지를 저장했다가 사용자에게 액션이나 각 필드 별로 보여줄 수 있다. 에러 메시지를 다루는 방법은 챕터 4 “웹워크 액션 구현하기”에서 다루었다.
  • 인터셉터 설정으로 액션 인스턴스 실행 전, 후에 기능을 추가할 수 있다. 인터셉터는 xwork.xml에서 설정하며, 챕터 5에서 자세히 논의했다.
  • XML 메타데이터 주도(metadata-driven) 검증 프레임워크는 액션과 도메인 객체의 값을 검증한다. 직접 검증을 정의할 수도 있어, 만들어 둔 코드 외부에서 다양한 사용 시나리오에 따라 다른 검증을 할 수 있다. 검증은 챕터 13에서 다루고 있다.

A.2 액션

웹워크가 동작하는데 있어서 가장 핵심적인 기능은 액션이다. 액션은 섹션 A.1에서 논의했던 커맨드 패턴의 커맨드 객체이며, 웹워크의 구현체다. 액션은 com.opensymphony.xwork.Action 인터페이스를 구현하며, 딱 하나의 메소드를 가진다:

public String execute() throws Exception

이 메소드는 액션 클래스를 실행시키기 위한 기본 엔트리 포인트(default entry point;액션을 실행하기 위해 프레임워크가 호출하는 메소드)이다.

액션 별칭 설정을 하면 execute() 메소드 대신 다른 메소드를 호출할 수도 있으며, 다수의 액션 별칭으로 하나의 액션 클래스에서 다른 메소드를 호출하게 할 수도 있다. 이때, 각 별칭별로 인터셉터나 리절트 등 또한 다르게 설정 가능하다.

com.opensymphony.xwork.ActionSupport 클래는 Action 인터페이스와 여러가지 선택적인 인터페이스를 구현하고 있어, 이를 상속해 자신의 액션 클래스의 베이스 클래스로 사용할 수 있을 것이다. 챕터 4에서, 우리는 액션 클래스를 직접 구현해봤고,  ActionSupport에 의해서 제공되는 것들에 대해서도 살펴봤다.

A.3 인터셉터

웹워크의 인터셉터는 코드를 캡슐화 시켜 액션 실행 전/후에 실행될 수 있게한다. 이것은 부가 기능으로 당신의 액션을 실행할 때 커맨드 패턴 프레임워크에 의해 투명하게(transparently;실제로는 있지만 캡슐화가 돼있어 없는것처럼 보인다는 의미) 제공된다. 인터셉터는 액션 클래스의 외부에 정의되며, 액션에 접근하지는 않고, 액션 실행 환경에서, 부품식으로 조립돼 관심사의 분리(separation of concerns)를 하게된다.

횡단 코드(cross-cutting code)는 액션을 실행할 때 데이터베이스 커넥션이나 트랜잭션 같은 리소스를 설정하거나 액션 실행 후에 뒷처리를 하는 것까지 어떤 것이든 가능하다. 액션 인스턴스에 프로퍼티를 세팅하는 것 같은 주요 기능을 포함한 많은 웹워크 기능들은, 인터셉터로 구현된 것이다. 인터셉터는 당신의 액션에서 많이 사용하게 될 것이다. 하지만 복사-붙이기나 복잡한 클래스 상속구조가 액션에서 자주 보인다고 강제로 적용하는 것 보다는, 액션을 실행할 때 필요한 것을 가져다 쓰는 모듈식으로 접근하자.

이것이 관점 지향 프로그래밍(Aspect-Oriented Programming)같이 들린다고 생각한다면 그것이 맞다. 행위에 대한 아이디어를 AOP에서 가져왔기 때문에 많은 컨셉을 공유한다. 하지만 AOP와는 다르게 어떠한 전처리나 바이트코드 조작은 필요로 하지 않는다. 왜냐면 프레임워크 내부에서 액션 호출을 가로채기 때문에 호출하는 쪽과 호출된 액션을 분리시키기 때문이다. 우리는 챕터 5에서 인터셉터에 대해 자세히 살펴봈으며, 이 부록의 뒤에서 ActionProxy와 ActionInvocation 내부에서 무슨 일이 벌어지는지에 대해 토론할 때, 액션 호출이 발생하는 것에 대해 시퀀스 다이어그램을 검토할 것이다.

A.4 리절트

com.opensymphony.xwork.Result 인터페이스는 액션 실행에 대한 결과물을 나타낸다. 이 리절트는 기본적으로 액션이 실행된 후에 대해 당신이 원하는 어떤 것이라도 될 수 있다. Result 인터페이스는 하나의 메소드 만을 정의한다:

public void execute(ActionInvocation invocation) throws Exception

리절트는 액션 실행으로부터 어떠한 종류의 결과물(웹 페이지 보여주기, 보고서 만들기, 이메일 보내기 등)을 만들어야 할 때 사용될 수 있다. 웹워크는 서블릿 디스패치(렌더링을 위해 JSP에서 사용되던 것), 서블릿 리다이렉트, 벨로시티, 프리마커, JasperReports(PDF, CSV, XML 등을 만들게 해줌), XSLT 렌더링, 그리고 ActionChainResult(현재 액션에서 다른 액션을 연결해 처리하는 것)를 제공한다.

리절트는 xwork.xml 액션에 설정된 결과 코드와 매핑된다. 우리는 이것을 챕터 3에서 자세히 논의했었는데, 지금 당신이 알아야 하는 것은 웹워크는 액션의 execute() 메소드의 결과 코드에 따라 다른 리절트를 실행한다는 것이다. 각각의 결과 코드는 자신과 매핑된 리절트를 실행시킬 것이다. 리절트에 대해선 챕터 7에서 자세히 논의하였다.

A.5 밸류스택

밸류 스택은 XWork와 웹워크 같은 동적인 컨텍스트 주도 환경의 중심이다. 이 객체들의 스택은 표현식(프로퍼티 값을 동적으로 찾는데 사용되며, 평가될 수 있다)에 대해 해당 프로퍼티의 이름을 가지는 객체가 있는지 첫 번째 객체(스택의 꼭대기부터 바닥까지)를 검색한다. 웹워크는 액션이 스택에 푸싱하는 것을 실행하는 동안 밸류 스택을 구성한다.

많은 웹워크 JSP 태그들과 Velocity 매크로는 밸류 스택에 접근해 객체를 집어 넣거나 꺼내올 수 있다. 밸류 스택은 객체 그래프 네비게이션 언어(OGNL)로 만들어졌고 OGNL의 단일 객체 루트 컨셉(single object root concept)을 확장해 다수의 객체 스택(multiple-object stack)을 지원한다. OGNL과 OGNL 그리고 밸류 스택 사이의 인터랙션에 대해서 다음 섹션에서 더 논의할 것이다.

A.5.1 OGNL

OGNL(http://www.opensymphony.com/ognl)은 평가할 수 있는 표현식을 통해 자바빈의 프로퍼티를 세팅하거나 가져올 수 있도록 한다. 또한 OGNL은 static 또는 인스턴스 메소드 실행, 컬렉션 내부 접근 그리고 표현식 재사용을 위한 람다 표현식 같은 고급 표현식 기능을 제공한다.

OGNL은 또한 XWork와 웹워크가 확장한 풍부한 타입 변환 모델을 제공한다. 타입 변환은 챕터 12에서 다루었다.

OGNL 언어의 기초는 단순함이며 90%는 흔히 쓰이는 용법이다. 빈 프로퍼티의 접근하기 위한 기초는 프로퍼티 이름을 사용하는 것이다. 예로, count 라는 표현식은 count 프로퍼티의 getCount() 같은 게터를 찾는 것으로 평가된다. 비슷하게, address.street라는 표현식은 getAddress().getStreet()를 호출하며 프로퍼티를 가져오거나 getAddress().setStreet()를 호출해 세팅할 수도 있다. OGNL의 다른 기능도 비슷하다. 한 예로, hashCode()라는 표현식은 현재 OGNL 컨텍스트에 있는 객체의 hash code 메소드를 호출할 것이다.

우리는 OGNL과 웹워크가 확장한 OGNL 문법에 대해 챕터8에서 자세히 논의했다. 하지만 지금은 예제를 설명하기 위한 용도로 사용됐고 그렇지 않을 경우 커멘트를 표시했다.

A.6 ActionProxy/ActionInvocation

ActionProxy는 액션을 실행하기 위한 클라이언트 코드를 관리하기 위해 제공된다. ActionProxy가 제공되는 이유는 당신이 액션 인스턴스를 스스로 실행하는 대신 프레임워크를 통해 실행을 하면, 인터셉터, 리절트 등과 같은 기타 기능을 캡슐화 할 수 있기 때문이다. 그림 A.3은 ActionProxy와 ActionInvocation, 액션 인스턴스, 인터셉터, 리절트 그리고 ActionContext(다음 섹션에서 다룰것이다) 사이의 관계에 대해 보여주고 있다.

Class diagram of the classes involved in executing an ActionProxy

그림 A.3 ActionProxy 실행과 관련된 클래스들의 클래스 다이어그램

ActionProxy는 현재 액션의 실행 상태를 나타내는 ActionInvocation을 가지고 있다. ActionInvocation은 액션 인스턴스, (순서 상) 적용되어질 인터셉터, 리절트 맵(리턴 코드와 리절트 인스턴스) 그리고 ActionContext(다음 세션에서 더 살펴볼 것이다)를 가진다.

ActionProxy는 ActionProxyFactory static 인스턴스를 사용해 디스패처(웹워크의 ServletDispatcher 또는 FilterDispatcher)에 의해 다음과 같이 생성된다:

ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, context);

ActionProxy가 context(요청 파라미터, 애플리케이션 맵, 세션 맵, 로케일 그리고 ServletRequest와 ServletResponse를 포함)를 가지고 생성된 뒤, Dispatcher는 ActionProxy를 execute() 메소드를 호출해 실행시킨다. 그림 A.4는 DefaultActionProxy(ActionProxy 인터페이스의 기본 구현체)의 execute() 메소드가 호출되는 과정을 보여주고 있다.

Sequence diagram of the internals of DefaultActionProxy.execute()

그림 A.4 DefaultActionProxy.execute() 시퀀스 다이어그램

ActionProxy는 ActionInvocation(ActionContext에 대해 논의할 때 더 살펴볼 것이다)을 위해 실행 컨텍스트를 세팅하고 ActionInvocation의 invoke() 메소드를 호출한다. ActionInvocation의 invoke() 메소드는 아직 실행되지 않았고, 다음에 실행되어야 하는 인터셉터를 찾아서 intercept()를 호출한다. 인터셉터는 ActionInvocation.invoke()를 다시 호출하기 전에 ActionInvocation을 사용해서 필요한 전처리를 할 수 있다. 이런 재호출(reentrant) 행동은 ActionInvocation을 다소 혼란스럽게 만든다. ActionInvocation을 유지하기 위해선 어떤 인터셉터가 실행됐는지를 알아야하며, 아직 실행이 안된 인터셉터가 남아있다면, intercept()를 호출해서 다음 인터셉터를 실행한다. 만약 더이상 호출할 인터셉터가 남아있지 않다면, 액션 인스턴스가 실행된다. 액션에서 반환된 리턴 코드는 리절트를 찾는데 쓰이며, 찾아낸 리절트를 실행한다. 마침내, invoke() 메소드가 리턴되고, 스택에 있는 마지막 인터셉터에게 제어권이 넘어간다. 인터셉터는 필요한 후처리를 하고 invoke() 메소드로부터 리턴되면 스택에 있는 이전 인터셉터는 필요한 후처리를 한다. 이 작업은 모든 인터셉터가 리턴될 때까지 반복된다. 끝으로, ActionProxy가 몇 가지 정리 작업을 하고 리턴된다.

ActionInvocation이 인터셉터 안으로 넘겨지면서 발생하는 결과 하나는 ActionInvocation의 인터셉터 처리 여부가 다른 인터셉터에 의존된다는 것이고 따라서 다른 인터셉터 처리를 넘겨 액션을 빠르게 실행하는 것도 가능하며, 인터셉터가 액션을 계속 수행할지 선택해 바로 리절트 코드를 리턴할 수도 있다. 이것은 예를 들어, 특정 사용자가 적절한 권한이 없다면 SecurityInterceptor에서 액션의 실행을 막을 수 있도록 허용한다.

ActionProxyFactory/ActionProxy/ActionInvocation 아키텍처 또한 액션 실행을 위한 다른 전략을 허용한다. 예로, 이 아키텍처를 사용해 비동기 액션 처리가 가능하고 필요한 파라미터를 가지고 서버쪽에서 액션을 실행한 다음 사용자를 위해 렌더링 하는 리치 클라이언트를 허용하는 자바 메시지 서비스(JMS) 디스패처 를 만들 수도 있다. 이미 MessageWork 프로젝트가 이를 구현해 최근에 http://dev.java.net에 추가됐다.

A.7 ActionContext

ActionContext는 ThreadLocal Map으로 애플리케이션과 세션 맵, ActionInvocation, 요청 파라미터, 로케일 등의 미리 정의된 값을 가져오거나 저장하는 도우미 역할을 한다. ActionContext는 특정한 ActionInvocation/ActionProxy의 쌍과 연관돼 있으며, 이 쌍은 ActionInvocation이 실행되는 동안의 쓰레드와 연관이 있다.

이전 섹션에서, 우리는 ActionProxy가 ActionInvocation이 호출되기 전 어떠한 상태를 설정하고 호출 후에는 정리한다고 했었다. 이 설정은 ActionContext와 현재 쓰레드 즉, ActionInvocation이 실행되는 동안과 관련이 있다. 자, ActionInvocation이 호출되기 전/후에 ActionProxy가 무엇을 하는지 그림 A.5를 살펴보자.

Sequence diagram of the ActionContext setup and teardown done by DefaultActionProxy before and after invoking the ActionInvocation

그림 A.5 ActionInvocation 호출 전/후 DefaultActionProxy에 의한 ActionContext의 설정과 해체를 나타낸 시퀀스 다이어그램

그림 A.5는 ActionInvocation의 invoke() 메소드 호출 전/후 DefaultActionProxy에 의해 만들어지는 호출의 결과를 보여준다. ActionContext는 ActionInvocation과 ActionProxy가 생성되는 동안 만들어지며 ActionProxy가 실행되기 전까지 연관된 쓰레드에 의해 저장된다. 인터셉터, 액션 그리고 리절트가 실행되는 동안 당신은 ActionContext.getContext() 메소드로 ThreadLocal에서 ActionContext를 가져올 수 있고 현재 실행 컨텍스트를 얻게된다.

ActionContext에는 실행 중인 환경에 대한 중요한 정보들을 담고있다. 이러한 컨텍스트 값들은 ActionProxy가 만들어 지기 전 Dispatcher에 의해 세팅되고 extraContext Map에서 ActionProxyFactory의 createActionProxy()로 넘겨진다. ActionContext에서 발견할 수 있는 프로퍼티들은 다음과 같다:

  • ActionInvocation – getActionInvocation()은 이 ActionContext와 연관된 현재 ActionInvocation에 접근할 수 있게한다.
  • Application – getApplication()은 ServletContext를 사용한 애플레케이션 스코프를 감싸고 있는 java.util.Map 구현체를 넘겨준다. 이 맵을 사용하면 애플리케이션 스코프에서 읽기와 쓰기를 할 수 있다.
  • ConversionErrors – getConversionErrors()는 타입-변환 에러가 난 모든 필드의 이름과 값의 필드 쌍을 가지는 Map을 반환한다. 우리는 이것을 챕터 12에서 자세히 논의했었다.
  • Locale – getLocale은 현재 요청의 로켈을 가져온다. 이 로케일은 지역화된 텍스트 메시지를 찾는데 사용되며, 챕터 14에서 자세히 볼 수 있다.
  • Name – getName()은 현재 액션의 이름을 가져온다. xwork.xml에 정의된 액션의 이름에 대응하며 실행될 액션의 식별자이자 요청에 포함된 정보의 한 부분이다.
  • Parameters – getParameters()는 요청에 포함된 파리미터들의 Map을 반환한다. 이 Map은 String으로 된 파라미터 이름과 String[] 으로 된 값을 가지는데, 그 이유는 HttpServletRequest의 파라미터들은 하나의 파라미터 이름에 여러 값을 가질 수 있기 때문이다.
  • Session – getSession()은 HttpSession 속성을 감싼 java.util.Map 구현체를 반환한다. 이 Map을 사용해 세션에 값을 저장하거나 가져올 수 있다.
  • ValueStack – getValueStack()은 현재 요청의 밸류 스택을 반환한다.

미리 정의된 값에 덧붙여, ActionContext의 put() 그리고 get() 메소드를 사용하면 당신이 사용하고 싶은 값을 메소드의 파라미터로 넘겨줄 필요 없이 ActionContext에 저장했다가 나중에 사용할 수 있다. 예를 들어, 인터셉터가 처리되는 동안 데이터베이스 커넥션과 연관된 작업을 하고 싶을 때 당신은 ActionContext ThreadLocal을 사용할 수 있을 것이다. 이런식으로, 모든 메소드 호출을 할 때 파라미터를 넘겨줄 필요 없이, 어느 코드에서나 데이터베이스 커넥션을 받아올 수 있다. 또한 쓰레드-세이프하기 위해 필요할지 모르는 정적 저장소 관련 문제가 없다. 왜냐하면 ActionContext는 하나의 쓰레드 하고만 연관되기 때문이다.

A.7.1 ThreadLocal 저장소

ActionContext는 ThreadLocal을 구현하고 있다. 따라서 메소드를 호출할 때마다 파라미터를 넘겨줄 필요 없이 항상 사용가능하다. ThreadLocal(java.lang.ThreadLocal)은 Java 1.2에서 추가됐다. ThreadLocal은 각각의 쓰레드에 저장공간을 제공한다. ThreadLocal의 값을 가져오는 게터는 static 메소드가 가능하기 때문에, ThreadLocal의 값을 어디에서나 가져올 수 있으며 메소드 파라미터로 무언가를 넘겨줄 필요가 없다.
Listing A.1은 ActionContext에 있는 ThreadLocal 관련 코드를 보여준다.

ActionContext's ThreadLocal specifit code

Listing A.1 ActionContext에 있는 ThreadLocal 관련 코드

getContext()와 setContext() 메소드를 보면, ThreadLocal의 set(), get() 메소드를 사용한 것을 볼 수 있다. ThreadLocal의 set() 메소드는 ThreadLocal에서 나중에 가져올 수 있게 객체를 저장한다. ThreadLocal get() 메소드는 현재 쓰레드의 ThreadLocal에서 저장된 객체를 가져온다.

한 가지 다른 구현체가 있는데 바로 내부 클래스 ActionContextThreadLocal 이다. 이것은 ThreadLocal의 서브 클래스로 initialValue()를 오버라이드해 디폴트 값을 세팅해 ThreadLocal의 get() 메소드가 처음 호출 될 때 null을 반환하지 않게 한다.

ThreadLocal 사용에 대해 하나 첨부한다: ThreadLocal은 단위 테스트 하기 어렵기로 소문이 나있다. 단위 테스트 전/후에, 셋업과 티어다운에 주의하고, 테스트가 다른 테스트에 간섭하지 않는지 확인하라. 만약 XWork와 웹워크의 단위 테스트를 본다면, 뜻밖의 상호작용으로 인한 어떠한 부작용도 없게끔 노력이 들어간 것을 볼 수 있을 것이다.

A.8 디스패처

Dispatcher는 웹워크 요청의 주 엔트리 포인트이다. 서블릿은 일반적으로 확장자(*.action이나 *.jspa가 웹워크 커뮤니티에서 널리 사용된다)와 매핑된다. Dispatcher는 요청 경로로 어떤 액션이 실행될지 결정하는데 이와 관련해서는 섹션 3.2.2 네임스페이스에서 논의했었다. Dispatcher는 서블릿의 HTTP 요청/응답과 웹워크 그리고 일반적인 커맨드 패턴인 XWork의 액션/리절트 사이의 어댑터 역할을 한다.

Dispatcher는 액션 실행을 위한 컨텍스트(애플리케이션, 세션 그리고 파라미터 스코프의 속성을 감싼 java.util.Map 구현체)를 만든다. 그다음 ActionProxyFactory를 사용해 ActionProxy를 만들거나 요청에 맞는 액션이 없다면 에러를 반환한다. 끝으로, Dispatcher는 ActionProxy를 실행하는데 그 뜻은 앞에서 봤듯이, 관련된 인터셉터, 액션 그리고 액션이 반환하는 리턴 코드와 매핑되는 리절트를 모두 포함한다는 의미다. 따라서 이 실행 결과는 웹 페이지를 보여주는 것일 수도 있고 PDF 문서를 만드는 것일 수도 있다. 또한 Dispatcher는 멀티파트 파일업로드 요청(챕터 4에서 논의)이나 에러 코드를 다루며, 일반적으로 웹워크와 함께 밀접하게 돌아간다.

A.9 요약

핵심은, 웹워크는 커맨드 패턴 구현체(XWork)를 MVC 웹 애플리케이션 프레임워크(웹워크)로 감싸고 있다는 것이다. 프레임워크는 커맨드 실행을 다루며, 호출되는 코드를 구상 액션 클래스로부터 분리시켜 액션 실행 부근에 기능 추가를 할 수 있게 한다. 이러한 기능은 웹워크에서 인터셉터 형태로 제공되며 많은 프레임워크의 핵심 기능을 다룰 수 있다.

이러한 기능을 추가하기 위해, 프레임워크는 ActionProxy/ActionInvocation 쌍에 액션 실행을 캡슐화한다. ActionProxy/ActionInvocation은 액션을 감싸며, 인터셉터와 리절트 그리고 실행되는 동안 각각의 호출을 관리한다. 인터셉터는 순서대로 호출되며 ActionInvocation(다른 인터셉터 포함)의 나머지 부분이 실행되기 전 후에 필요한 것을 무엇이든 할 수 있다. 액션이 실행되는 동안, 액션과 인터셉터 그리고 리절트는 ActionProxy에 의해 관리되고 ActionProxy.execute() 메소드가 실행중인 동안만 사용할 수 있는 ActionContext라고 불리는 ThreadLocal 저장소에 접근할 수 있다.

ActionProxy 인스턴스는 Dispatcher가 static ActionProxyFactory 인스턴스를 사용해 생성한다. Dispatcher는 HTTP 요청을 액션 네임스페이스와 액션 별칭으로 매핑하며, ActionProxyFactory가 ActionProxy를 생성하기 위해  사용된다. 그다음 Dispatcher는 ActionProxy를 실행시키고, 인터셉터 실행, 액션 그리고 리절트(웹 페이지나 PDF 같은)를 처리한다.

트위터 API 플랫폼과 OAuth – Access Token 얻기

기본

이 글은 트위터 API 플랫폼과 OAuth – Twitter for Websites에서 이어지는 글입니다.

OAuth?

OAuth Logo

OAuth 로고. 저는 볼때마다 치과가 떠오르네요..

OAuth에 관한 자세한 설명은 네이버 개발자센터의 소개글을 한 번 읽어보시면 좋습니다.

OAuth 간단 요약

먼저 API를 제공해 주는 곳을 서비스 프로바이더(트위터)라고 부릅니다. 그리고 서비스 프로바이더의 API를 사용하는 쪽을 컨슈머(여러분의 애플리케이션)라고 부르죠. 그리고 사용자란 컨슈머의 서비스를 사용하는 사람들 입니다. 컨슈머는 서비스 프로바이더에 자신의 애플리케이션과 관련된 정보를 입력한 후, 컨슈머 키와 시크릿을 발급 받습니다. 이것은 OAuth 이전의 API Key와 비슷한 거라고 생각하셔도 되겠습니다. 애플리케이션에 관한 정보를 입력할 때 주의하실 점은 나중에 OAuth 인증진행 중, 사용자에게 노출이 된다는 점입니다.

Create an application

애플리케이션 등록 화면

Application description

등록한 정보는 이런식으로 사용자에게 노출됩니다

위 그림은 애플리케이션 등록 화면인데요. 여기서 약간 애매한 부분이 웹사이트 URL과 콜백 URL입니다. 트위터는 완전한 형태의 URL이 필요하기 때문에 테스트 용도로 사용하기 위한 http:://localhost:8080/ 형태는 사용이 불가능합니다. 그래서 구글링을 통해 약간의 꼼수를 부린건데요. 물론, hosts파일에 127.0.0.1 local.dev 식으로 사용할 것을 만들어 주셔야 합니다. 이 방법이 귀찮다면 그냥 127.0.0.1로 하셔도 됩니다 🙂

컨슈머 키와 시크릿이 있는 상태에서 OAuth 인증 플로우가 시작됩니다:

OAuth flow for webapplication

callback이 있는 OAuth 인증 과정

컨슈머는 먼저 컨슈머 키와 시크릿을 사용해서 Request Token(트위터에선 oauth token이라고도 쓰이며 토큰은 키와 값의 쌍이며 Request Token의 경우 매번 달라집니다)을 발급 받습니다. Request Token은 해당 컨슈머를 대변한다고 보시면 되겠습니다. 자~ 이제 컨슈머는 됐으니 사용자를 대변할 Access Token을 받아야겠죠? 결국 서비스 프로바이더의 API를 사용한다는 것은 특정 사용자를 대신해 컨슈머가 서비스 프로바이더에게 해당 사용자 대신 API 요청을 하는 것이고, 그렇다면 해당 사용자가 정말 이 컨슈머에게 권한을 위임했는지 서비스 프로바이더가 알아야 할 것입니다. 이 역할을 하는 것이 Access Token 되겠습니다.

트위터가 Request Token을 발급해 주면 컨슈머는 사용자를 서비스 프로바이더로 보내 인증을 받고, Access Token을 얻기 위해 verifier 값을 얻어야 합니다. 그래서 위의 두 번째 그림처럼 컨슈머의 계정 사용 여부를 물어보는 것이죠. 만약 로그인 정보 쿠키가 남아있다면 두 번째 그림이 아니라 컨슈머를 허용할지에 대한 화면이 나타납니다.

Authorize consumer to use your account?

로그인 정보가 있다면 간단히 컨슈머 허용 여부만 물어봅니다

만약 예전에 허용을 했었고(트위터 마이페이지 applications 탭에서 확인 가능), 로그인 정보도 있다면 바로 verifier 값을 얻을 수 있으며, 이 verifier 값과 Request Token 정보를 트위터에 보내서 최종적으로 Access Token을 발급받게 됩니다.

트위터의 OAuth

트위터는 현재 OAuth 1.0a 스펙을 구현 중이며, 조만간 2.0을 제공할 것 같습니다. OAuth가 기존 인증 절차와 어떻게 다른지는 moving-from-basic-auth-to-oauth에 잘 설명돼 있습니다.

제 생각에 OAuth는 두 가지 큰 특징을 가지는 것 같습니다:

  • 사용자 계정 정보를 애플리케이션에서 관리하지 않는다.
  • 인증 절차를 서비스 프로바이더에게 위임한다.

즉, 컨슈머의 사용자는 적합한 절차를 거쳐 서비스 프로바이더에게 컨슈머가 자신을 대신해 해당 서비스를 이용할 수 있다는 권한을 위임하게 되며, 이를 확인하기 위해 기존의 사용자 계정 정보 대신 Access Token을 이용합니다. 컨슈머는 Access Token을 가지고 해당 사용자 대신 서비스 프로바이더의 API를 호출합니다.

Access Token은 한 번 발급 받으면 다른 정보(로그인 정보, Request Token 등)가 전혀 필요없이 바로 API 호출이 가능하게 되며, 사용자가 계정 정보를 변경(패스워드 변경 등)했다고 하더라도 이 Access Token은 유효합니다.

Access Token의 유효기간은 서비스 프로바이더의 정책에 따라 다른 부분이 있는데, 트위터 OAuth faq를 확인해 보면 현재 Access Token의 유효기간은 없다(!)고 명시하고 있습니다. 따라서 토큰 보관에 주의를 하실 필요가 있을겁니다. Access Token의 유효기간이 없긴 하지만 사용자가 자신의 계정 설정에서 애플리케이션을 명시적으로 거부하거나 관리자가 제한한 경우에 Access Token이 만료될 수 있습니다.

내 계정에 접근할 수 있는 애플리케이션 목록

내 계정에 접근할 수 있는 애플리케이션 목록

애플리케이션 종류에 따른 OAuth 인증

트위터에서는 OAuth 인증에 관해서 여러가지 방법을 제공해 주고 있습니다. 그렇다고 OAuth 스펙을 무시하는 것은 아니며 기본 흐름은 같되 세부적인 방법이 다른 것인데요. 특정 플랫폼에서 사용이 권장되는 것과 제한되는 것이 있습니다. 관련 정보는 공식 문서인 Which authorization path should I choose?를 읽어보세요. 아래는 이 문서를 요약한 것입니다:

웹 애플리케이션

콜백 방식의 OAuth를 사용해야 합니다. xAuth 방식은 사용할 수 없습니다.

써드 파티애플리케이션 이용

OAuth Echo라고 부르며 Twitpic, yfrog 같은 트위터 에코 시스템에 속하는 써드 파티 애플리케이션을 통해서 인증 및 API 호출을 위임하는 것을 말합니다.

위 두가지 방식이 웹 애플리케이션에서 권장됩니다. 모바일과 데스크탑 애플리케이션의 경우, 콜백 URL 방식을 사용하기 어렵거나 사실상 불가능 할 수도 있기 때문에 트위터에선 아래 두 가지 방법을 제공해 줍니다.

Out-of-band/PIN 코드 인증(oob)

콜백 URL OAuth 방식과 거의 흡사한데요. 콜백을 하는 대신에 컨슈머에게 특정 URL을 리턴해 줍니다. 접속하면 PIN 코드라 불리는 몇 자리 숫자가 나타나는데요. 이 숫자를 복사해서 다시 서비스 프로바이더에게 보내면 Access Token을 발급해 줍니다. 아마 핸드폰으로 소액결제 해보셨으니 그리 낯설지 않은 방식이라고 생각되네요 🙂

xAuth

xAuth 인증 방식은 우리가 많이 사용하고 있는 방식으로 사용자 계정 정보를 이용해서 Access Token을 발급받게 됩니다. xAuth 또한 OAuth 플로우를 거치며, 단지 Request Token을 얻는 부분과 사용자 승인 과정이 없을 뿐입니다. xAuth는 XAuth와는 다르며, 데스크탑과 모바일에서만 사용 가능합니다. 콜백 방식과 PIN코드 사용이 어려울 때, api@twitter.com에 메일을 보내 사용 동의를 얻어야 합니다.

Callback URL OAuth 자세히 살펴보기

애플리케이션 종류에 따른 OAuth 인증에서도 살펴 봤듯이 트위터에서는 OAuth 인증 방식에 몇 가지 옵션을 주고 있지만, 제가 웹 개발자라서 Callback URL을 사용하는 OAuth를 조금 더 자세히 살펴보겠습니다. 사실 다른 OAuth 방법들도 여기서 크게 벗어나지 않습니다.

1단계. Request Token 얻기

먼저, 앞서 살펴봤듯이 자신의 애플리케이션을 등록해 컨슈머 키/시크릿을 발급받습니다. 다음으로 Request Token을 발급 받기위해 http://dev.twitter.com/oauth/request_token을 POST로 호출합니다. 이때 컨슈머 키, 시크릿, OAuth 콜백 URL 등을 같이 보내야 합니다.

Get request token header

Request Token 요청을 위한 헤더의 예

그러면 트위터에서 Request Token(또는 OAuth Token)을 발급해 줍니다.

Response request token header

Request Token을 생성한 후 보내주는 응답 헤더의 예

2단계. 사용자 리다이렉트 하기

Request Token 발급 후, 사용자의 인증을 받기 위해 https://api.twitter.com/oauth/authenticate로 리다이렉트 해야합니다. 트위터 REST API OAuth 부분을 보면 /oauth/authenticate 외에 /oauth/authorize도 제공되는데요. authorize의 경우 사용자가 기존에 컨슈머 애플리케이션을 인증 했다고 하더라도 다시 인증 화면이 나타납니다. 반면에 authenticate는 이전에 사용자가 인증을 했다면 이 부분을 바로 넘어가 버립니다.  OAuth 1.0 스펙에서는 사용자가 반드시 해당 애플리케이션에게 자신의 권한을 줄 것인지 아닌지에 대해서 선택을 할 수 있게하라고 명시돼 있습니다. authenticate는 편의를 위해 이 부분을 대체한 것이라고 보면 되겠습니다.

로그인 정보가 없다면 로그인 및 애플리케이션 사용 동의 창이 나타나며, 로그인을 했다면 간단히 동의 페이지가 나타납니다. 이렇게 사용자 인증까지 마치면 트위터는 oauth_verifier라는 값을 넘겨줍니다.

Sign in OAuth Step2

사용자 인증 및 verifier 얻어오기

3단계. Request Token을 Access Token으로 교환하기

Request Token과 oauth_verifier 값을 가지고 https://api.twitter.com/oauth/access_token을 POST 방식으로 호출하면 Access Token을 얻게 됩니다.

Sign in OAuth Step3

드디어 Access Token을.. ;ㅁ;

트위터 API 라이브러리

트위터에서 제공하는 공식 OAuth 라이브러리는 없으며, REST API만을 제공하고 있습니다. 비공식적으로는 다양한 언어를 기반으로 하는 여러가지 구현체가 있으며, 그 중 트위터 공식 홈페이지에 등록된 Java 구현체는 총 3개 입니다. Java 구현체 중, Scribe의 경우 트위터 REST/Stream API 구현체는 아니지만 OAuth인증에 관한 범용적인 라이브러리를 제공합니다. Scribe의 예제 코드를 보면 다양한 SNS를 볼 수 있는데, 이 예제 중 트위터쪽은 PIN 코드 방식이므로 궁금하시면 한 번 돌려보세요.

트위터 파일럿 프로젝트

처음으로 Github에 프로젝트를 공유해 보네요. Spring Framework 3.0 기반에 Scribe 라이브러리를 사용했습니다. 관심 있으신 분들은 한 번 돌려보시고 참여도 좀.. 🙂

https://github.com/ethdemor/social-api-pilot

개발 시 고려사항

파일럿 프로젝트를 만들다 알게된 사실인데, 사용자가 계정 설정에서 해당 컨슈머를 revoke 한 경우, Access Token이 파기됩니다. 그런데 컨슈머 쪽에서는 Access Token이 파기 됐다고 연락 같은거 못받으니 문제가 생길 수 있습니다. 바로 만료된 Access Token으로 트위터 API를 호출한 경우인데요. 문제는 트위터쪽에선 무조건 200 OK 응답 코드를 준다는 것입니다. 만약 타임라인을 가져오려고 할 경우 사용자 입장에서 에러는 안뜨는데 목록은 하나도 안보이는 경우가 발생할 수 있겠죠. 트위터는 응답 코드는 200 OK지만 status 코드에 에러 정보를 넣어주니 이를 확인해서 대처해야 합니다.

Access Token이 사용 가능한지는 다음 API를 호출해 보면 됩니다:

GET account/verify_credentials

만약 현재 Access token이 사용할 수 없는 것이라면 HTTP 응답 헤더 status 코드가 401 Unauthorized로 세팅됩니다. 따라서 status 값이 401일 때, 다시 OAuth 인증을 받게 하면 이 문제를 해결 할 수 있을 것 같습니다.

트위터 API 플랫폼과 OAuth – Twitter for Websites

기본

이 포스팅은 원래 트위터 OAuth만 다룰려고 했었지만 트위터 API 플랫폼과 얽힌 부분이 많기 때문에 좀 더 포괄적으로 적어보려고 합니다. 단, 이 포스팅은 소개 정도에 그치기 때문에 자세한 내용은 해당 문서를 꼭 읽어 보시길 바랍니다.

트위터 API 플랫폼 훑어보기

트위터 개발자 페이지에 있는 슬라이드입니다:

Developing for @twitterapi (Techcrunch Disrupt Hackathon)

슬라이드를 보면 트위터 API 플랫폼에 대해 간단한 설명이 나와있습니다. OAuth는 API 사용을 위한 사용자 인증에 쓰이며 현재는 OAuth 1.0a를 구현중인데 곧, OAuth 2.0을 공개할 예정인가 보네요. 참고로 페이스북은 OAuth 2.0을 구현하고 있습니다.

트위터 API를 사용하는 두 가지 방식

슬라이드에서도 나와있듯이 트위터 API를 사용하는 방법은 크게 두 가지로 구분할 수 있습니다. 첫 번째는 트위터에서 제공해주는 웹용 컴포넌트를 가져다 쓰는 방식(Twitter for Websites)이고 두 번째는 트위터 API를 직접 사용하는 방식(REST/Stream API)입니다. 이번 포스팅에서는 Twitter for Websites에 대해서 살펴보겠습니다.

Twitter for Websites

트위터에서 웹 사이트를 위해 제공하는 여러 컴포넌트로 직접 API를 호출하지 않고 트위팅, 팔로우 버튼 등을 웹 페이지에 넣으면 사용자의 로그인 정보(정확하게는 .twitter.com에 저장된 쿠키)를 사용해 트위팅, 팔로우 기능을 하게 됩니다. 만약 로그인 정보가 없다면 로그인 창이 나타나게 됩니다. 컴포넌트라고 말은 거창하지만 사실 태그 하나와 약간의 자바스크립트로 이루어져 있고, 이것을 페이지에 붙여 넣으면 끝입니다. 참 쉽죠? 트위터에서 제공하는 컴포넌트는 다음과 같습니다:

Tweet ButtonFollow Button

기본 트윗 버튼들

제공되는 기본 버튼의 종류

말그대로 트윗과 팔로우 버튼입니다. 편리하게 트윗 카운팅도 제공해 주며 자신의 웹 사이트 정보나 언어 등을 입력해 제공되는 기본 버튼을 커스터마이징 할 수도 있습니다. 언어를 한국어로 설정하면 한국어로된 버튼도 만들어 줍니다. 이 버튼을 클릭하면 다음과 같은 화면이 나옵니다:
Tweet Button Clicked

트윗 버튼 클릭하면 이런 창이 나옵니다

익숙한 창이죠? 한 가지 재미있는 건 여러 파라미터들을 설정해 줄 수 있다는 겁니다. 파라미터 설정을 통해 버튼 커스터마이징을 할 수도 있고, 어떤 텍스트를 트윗창에 넣을 것인지, 어떤 URL을 공유할 것인지, 해시태그로 논의를 이어갈 것인지, 누구를 멘션할 것인지 등을 설정해 버튼을 생성할 수도 있고, 자신만의 버튼을 만드는 것도 가능합니다.

Embedded Tweets

임베디드 트윗은 트위터 사이트의 특정 부분을 아예 가져다가 붙일 수 있습니다. UI도 그대로 가져오니 상당히 편리하겠네요. 사용자들도 동일한 경험을 할 수 있겠죠?

Embedded Tweets

트윗 임베드하기

트위터 문서대로라면 트윗 세부 페이지마다 임베드 버튼이 노출되어야 하는데 어째서인지 관련 버튼이 보이질 않네요;;

Embed This Tweet Button

나는 이런 버튼 안보이는데?

저 버튼을 누르면 유튜브에서 동영상 퍼가듯이 관련 코드가 나오고 복사 붙이면 됩니다. 그런데 이렇게 복사 붙이기를 하지 않고 json을 통해 임베드 트윗에 관련된 데이터를 받아온 후, 동적으로 보여줄 수 있는 방법도 제공하고 있는데요. 이를 oEmbed라고 한다네요.

Web Intents & Javascript callback events

Web Intents는 팝업 창으로 이루어진 트위터 API 사용 방법으로 웹 페이지에 끼워 넣어도  따로 다른 페이지에 왔다갔다 할 필요도 없고, 말그대로 웹에 최적화된 방법이라고 할 수 있습니다. Web Intents 역시 위의 방법들처럼 사용자의 로그인 정보가 담긴 쿠키를 사용해 API를 사용하게 되며(만약 로그인 정보가 없다면 팝업으로 로그인 창이 뜨게 됩니다) tweet, reply, retweet, favorite, follow의 기능을 사용할 수 있습니다. 트위터의 거의 핵심적인 기능들을 사용할 수 있다는 거죠. 반대로 이정도 기능을 사용할꺼면 굳이 REST/Stream API를 사용할 필요가 없다는 말도 됩니다. 뒤에서 더 설명하겠지만 API를 사용할 경우 할 수 있는건 더 많아지지만 앱 등록부터 OAuth 인증까지 신경써야 하는 부분이 많아집니다.
Web Intents를 사용하는 방법은 매우 쉽습니다.

1.

<script type=”text/javascript” src=”http://platform.twitter.com/widgets.js”></script&gt; 태그를 임포트 하구요.

2. 사용하고 싶은 기능을 <a> 태그로 만들어 주면 됩니다. 버튼과 마찬가지로 href 속성에 쿼리스트링 형태로 파라미터를 넘기는 것도 가능합니다.

링크를 클릭하면 스크립트가 알아서 적당한 크기의 팝업 창을 띄워줍니다. 그리고 이미지 리소스들도 잔뜩 지원해주니 맘에 드는 새 한마리 가져다가 쓰시면 됩니다 🙂

Web Intents나 Tweet/Follow Button을 사용할 때, 이벤트 콜백을 지원하는데요. 이것을 Intent Events라고 부르고 다음과 같은 종류가 있습니다:

  • widgets.js 파일 로딩이 완료 됐을 때
  • tweet, follow, retweet, favorite 기능이 완료 됐을 때
  • 트윗, 트윗 카운트, 팔로우, 유저 이름을 클릭했을 때

Share Bookmarklet

북마클릿. 아마 이건 즐겨찾기가 우리한테 더 친숙할  겁니다. 즐겨찾기에 자바스크립트를 한 줄 넣어놓고, 트윗하고 싶은 페이지에서 한 번 누질러 주면 요런 팝업창이 뜹니다:

Share Bookmarklet

맘에 드는 페이지에서 즐겨찾기를 누르면 바로 공유할 수 있습니다

즐겨찾기에 등록해놓고 써보세요:

javascript:(function(){window.twttr=window.twttr||{};var D=550,A=450,C=screen.height,B=screen.width,H=Math.round((B/2)-(D/2)),G=0,F=document,E;if(C&gt;A){G=Math.round((C/2)-(A/2))}window.twttr.shareWin=window.open('http://twitter.com/share','','left='+H+',top='+G+',width='+D+',height='+A+',personalbar=0,toolbar=0,scrollbars=1,resizable=1');E=F.createElement('script');E.src='http://platform.twitter.com/bookmarklets/share.js?v=1';F.getElementsByTagName('head')[0].appendChild(E)}());

 @Anywhere

아마 웹 사이트 중에 트위터로 로그인(Connect to Twitter)을 해서 서비스를 이용해 보신 적이 있으실 텐데요. 바로 @Anywhere의 기능 중 하나입니다. 이 외에도 username을 찾아서 트위터 링크를 만들어 준다던지, 해당 username에 마우스를 올려다 놓으면 정보 창이 뜬다던지, 팔로우 버튼, 트윗 폼 같은 기능이 제공됩니다.

사용법은 기존보다 조금 더 복잡한 편인데 바로 컨슈머 키(또는 API key)가 필요하기 때문입니다. 컨슈머는 OAuth의 용어로 서비스 프로바이더(트위터)를 이용하는 클라이언트(여러분의 웹 애플리케이션)를 말합니다. 컨슈머 키를 얻기 위해선 자신의 애플리케이션 정보를 등록해야 합니다. 참고로 REST/Stream API를 사용하기 위해서도 이 등록절차를 거쳐야 합니다.

사용하는 방법은 위의 방법들과는 다르게 자바스크립트를 하나 임포트 한 후, jQuery 스타일로 CSS 셀렉터로 엘리먼트를 가져와 필요한 기능을 적용시키게 됩니다. 예를 들어, 트위터 사용자 유저네임이 있을 때 해당 유저의 트위터 페이지로 링크를 시키는 기능(linkify)을 적용하고 싶다면..

@Anywhere linkify HTML

간단한 마크업을 한 후..

@Anywhere linkify result

링크가 된 부분이 보이시나요?

Web Intents 보다 더 강화된, 트위터 친화적인 사이트를 만들고 싶으시다면 @Anywhere가 제격일 것 같네요.

 

트위터 OAuth와 Access Token 얻기로 이어집니다.