메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

서블릿 2.3: 무엇이 추가되었는가?

한빛미디어

|

2001-05-18

|

by HANBIT

10,042

서블릿 API 규약의 완전 업데이트 2000년 10월, 썬은 서블릿 API 2.3의 "제안된 최종 안(Proposed Final Draft)"을 출시했다. 이 글에서는 서블릿 API 2.2와 2.3의 차이를 설명하고, 2.3으로 업데이트하게 된 이유와 2.3을 사용하여 서블릿을 작성하는 방법을 보여줄 것이다. By 제이슨 헌터(Jason Hunter) 2000년 10월, 썬 마이크로시스템은 서블릿 API 2.3 규약의 "제안된 최종 안(Proposed Final Draft)"을 출시했다(공식 규약 사이트로의 링크를 참조할 것). 비록 규약은 썬이 만들었지만, 서블릿 API 2.3은 실질적으로는 많은 개인과 기업이 JSR-053 전문가 그룹에서 작업을 하면서 개발되었다. 규약이 완결성이 뛰어난 것은 아니다. 제안된 최종 안은 공식적인 최종판과는 별개의 작업이고, 기술적인 세부 사항도 바뀔 여지가 있다. 하지만 바뀌는 부분들이 중요한 것은 아니다. 사실상 서버 판매자들은 이미 새로운 요소들을 구현하고 있다. 따라서 지금이 서블릿 API 2.3에서는 어떤 점이 바뀌었는지 배우기 시작할 적기이다. 이 글에서는 API 2.2와 API 2.3 사이의 차이점에 대해 상세히 설명할 것이다. 그리고 변화한 이유와, 새로운 요소들을 사용하여 서블릿을 작성하는 법도 다룰 것이다. 글이 논점에서 벗어나지 않도록 하기 위해, 독자가 서블릿 API의 이전 버전에서의 클래스와 메소드를 잘 알고 있다고 가정할 것이다. 이에 대해 잘 알지 못한다면, 링크를 클릭하여 자료들을 통독해 보기를 바란다. 서블릿 API 2.3에서는 서블릿의 핵심적인 부분은 거의 건드리지 않았다. 이것은 서블릿이 높은 수준의 성숙기에 도달했다는 것을 의미한다. 대부분의 액션은 새로운 요소를 핵심 부분 이외의 것에 덧붙이는 데에 관련이 있다. 변화한 사항은 다음과 같다.
  • 서블릿에는 JDK 1.2나 그 이후에 나온 버전이 필요하다.
  • 필터 메커니즘이 생겼다(드디어!)
  • 애플리케이션 수명 주기 이벤트가 추가되었다.
  • 새로운 국제화(internationalization) 지원이 추가되었다.
  • JAR 내부 의존도를 표현하는 기술이 정형화 되었다.
  • 클래스 로딩에 관련된 규칙이 분명해졌다.
  • HttpUtils 클래스가 없어졌다.
  • 다양하고 유용한 메소드가 추가되었다.
  • 다양한 DTD 행위가 확장되고 명확해졌다.
다른 사항들도 더 명확해 졌지만, 대부분 서버 판매자에 관련된 것이지, 일반적인 서블릿 프로그래머와는 큰 관계가 없기 때문에 여기서는 생략하였다. 설명에 들어가기 전에, 버전 2.3은 초벌로 쓴 규약일 뿐이었다는 점을 지적하고 싶다. 여기서 논의된 대부분의 특징들은 아직 모든 서버에서 사용될 수 있는 수준은 아니다. 이러한 것들을 시험해 보고 싶다면, 공식 레퍼런스 구현 서버인 아파치 톰캣 4.0을 다운로드 받을 것을 권한다. 이것은 오픈 소스이며, 무료로 다운로드받을 수 있다. 톰캣 4.0은 현재 베타판이 출시되었다. API 지원이 더 나아지고 있지만, 아직 불완전하다. 톰캣 4.0을 다운받을 때에 같이 들어 있는 NEWS_SPECS.txt 파일을 읽으면 새로운 규약에서는 어느 정도까지 지원되는지 알 수 있을 것이다(톰캣에 대해 더 자세한 정보를 얻고 싶다면, Resources를 참고하라). J2SE와 J2EE에서의 서블릿 서블릿 API 2.3에서 주지해야 할 사항은, 서블릿은 지금 자바 2 플랫폼 스탠다드 판 1.2(Java 2 Platform, Standard Edition 1.2: J2SE 1.2 또는 JDK 1.2라고도 부른다.)에 의존한다는 것이다. 이 변화는 사소하지만 중요하다. 이제는 서블릿에서 J2SE 1.2의 요소를 사용할 수 있으며, 서블릿 컨테이너에서 서블릿이 확실히 동작하기 때문이다. 전에는 J2SE 1.2의 요소를 사용할 수는 있었지만 모든 서버에서 그것을 지원하지는 않았다. 서블릿 API 2.3은 자바 2 플랫폼 엔터프라이즈 판 1.3(J2EE 1.3)에서 핵심적인 부분이 될 것이다. 이전 버전인 서블릿 API 2.2는 J2EE 1.2의 일부분이었다. 눈에 띄는 차이점은 web.xml DTD의 상대적으로 모호한 J2EE 관련 배치 디스크립터 태그밖에 없다. J2EE 1.3 규약에 상세한 설명이 나와 있기 때문에, 서블릿을 작성하는 사람은 J2EE 태그 때문에 걱정할 필요가 없다. 필터 API에서 가장 중요한 부분은 필터를 추가한 것이다. 필터란, 요청을 변형하거나 응답을 변화시킬 수 있는 객체이다. 필터는 실제로 응답을 생성하지는 않기 때문에, 서블릿은 아니다. 필터는 요청이 서블릿에 도달하기 전까지는 전처리기(preprocessor)의 역할을 하고(하거나), 응답이 서블릿에서 나가면 후처리기(postprocessor)의 역할을 한다. 어떤 점에서 필터는 "서블릿 체인"이라는 개념이 더 성숙해 진 것이라고 할 수 있다. 필터로는 다음과 같은 일을 할 수 있다:
  • 서블릿이 소환되기 전에 호출을 차단한다.
  • 서블릿이 소환되기 전에 요청을 검사한다.
  • 실제 요청을 싸고 있는 요청 객체의 커스터마이즈된 버전을 공급함으로써 요청 헤더와 요청 데이터를 수정한다.
  • 서블릿이 소환된 후에 서블릿의 호출을 차단한다.
한 개나 여러 개의 서블릿에서 필터를 설정할 수 있다. 즉, 서블릿 한 개나 여러 개를 그룹으로 묶은 서블릿에는 필터를 설정하지 않거나, 여러 개의 필터를 설정할 수 있다는 것이다. 실제 사용되는 필터에는, 인증 필터, 로깅, 회계 검사 필터, 이미지 변환 필터, 데이터 압축 필터, 암호화 필터, 토크나이징 필터, 자원 접근을 유발하는 필터, XML 컨텐츠를 변형하는 XSLT 필터, MIME 타입 체인 필터 등이 있다. 필터는 javax.servlet.Filter를 구현하고 세 가지 메소드를 정의한다.
  • void setFilterConfig(FilterConfig config): 필터의 설정 객체를 설치한다.
  • FilterConfig getFilterConfig(): 필터의 설정 객체를 되돌린다.
  • void doFilter(ServletRequest req, ServletResponse res, FilterChain chain): 실제 필터링 작업을 수행한다.
서버는 필터가 서비스 상태로 되게 하기 위해 setFilterConfig()을 한 번 불러내고, 다양한 요청의 숫자에 상관 없이 doFilter()을 호출한다. FilterConfig 인터페이스는 필터 네임을 회수하기 위해 메소드를 가진다. 서버에서는 필터가 서비스 상태가 아니라는 것을 표시하기 위해 널(null)을 setFilterConfig()에 보낸다. 각 필터는 doFilter() 메소드에 현재의 요청과 응답을 받고, 아직 처리되어야 하는 필터를 담고 있는 FilterChain과도 요청과 응답을 받는다. doFilter() 메소드에서는, 필터가 요청과 응답을 사용하여 원하는 작업을 할 수 있다(아래에서 설명한 것과 같이, 필터를 사용하여 메소드를 호출하거나 객체를 래핑해서 거기에 새로운 행위를 부여할 수 있다). 그러면 필터는 다음 필터로 통제권을 이양한다. 그 호출이 반환되면, doFilter() 메소드의 끝부분에서, 필터가 응답에 대한 부가적인 작업을 수행할 수 있다. 예를 들어서 필터는 응답에 대한 정보를 로그할 수 있다. 만약 필터가 요청 처리 과정을 중단하고 응답을 완전히 통제하려고 한다면, 다음 필터를 의도적으로 호출하지 않을 수도 있다. 필터는 요청을 되돌리거나(되돌리고) 특정 메소드가 액션을 다루는 요청에 영향을 끼치기 위해 구현을 소환하는 것을 바꾸면서, 커스텀 행동을 공급하기 위해 객체에 응답을 보낼 수도 있다. API 2.3은 새로운 HttpServletRequestWrapper와 HttpServletResponseWrapper 클래스를 공급한다. 그것은 모든 요청과 응답 메소드의 디폴트 구현을 공급하고, 소환을 디폴트 값에 따라 원래의 요청이나 응답에 위임한다. 즉, 어느 한 메소드의 행동을 바꾸려면 래퍼(wrapper)를 늘리고 메소드 하나를 다시 구현기만 하면 된다. 래퍼는 필터가 요청을 다루고 응답을 생성하는 과정을 잘 통제할 수 있게 한다. 모든 요청의 지속 기간을 기록하는 간단한 로깅 필터에 대한 코드는 아래와 같다.
public class LogFilter implements Filter { 
  FilterConfig config; 
 
  public void setFilterConfig(FilterConfig config) { 
    this.config = config; 
  } 
 
  public FilterConfig getFilterConfig() { 
    return config; 
  } 
 
  public void doFilter(ServletRequest req, 
                       ServletResponse res, 
                       FilterChain chain) { 
    ServletContext context = getFilterConfig().getServletContext(); 
    long bef = System.currentTimeMillis(); 
    chain.doFilter(req, res); // no chain parameter needed here 
    long aft = System.currentTimeMillis(); 
    context.log("Request to " + req.getRequestURI() + ": " + (aft-bef)); 
  } 
}
서버가 setFilterConfig()를 호출하면, 필터는 config 변수에 config에 대한 레퍼런스를 저장한다. 이는 나중에 doFilter() 메소드에서 ServletContext를 검색하는 데에 사용된다. doFilter()에 있는 논리 회로는 간단하다. 요청을 다루는 데에 걸리는 시간과 프로세싱이 일단 완료된 시간을 로그한다. 이 필터를 사용하려면, 아래에서 설명한 것과 같이 web.xml 배치 디스크립터에서 태그를 사용하여 밝혀 주어야 한다.
 
   
    log 
   
   
    LogFilter 
   

이는 "log"라는 필터가 LogFilter 클래스에서 구현되었다는 것을 서버에 전달한다. 태그를 사용하여, 등록된 필터를 특정 URL 패턴이나 서블릿 네임에 적용할 수 있다.
 
  log 
  /* 

이것은 필터가 (정적, 동적) 서버로 가는 모든 요청을 조정하도록 설정한다. 간단한 페이지에 접속한다면, 로그 아웃풋은 다음과 같을 것이다. Request to /index.jsp: 10 수명주기 이벤트(Lifecycle events) 서블릿 API 2.3에서 일어난 두 번째 중요한 변화는 애플리케이션 수명주기 이벤트를 첨가했다는 점이다. 이를 통해 "청취자(listener)" 객체가 서블릿 컨텍스트와 세션이 생기거나 없어진 시간을 알 수 있으며, 컨텍스트나 세션으로부터 속성이 추가되거나 제거된 때도 알 수 있다. 서블릿 수명주기 이벤트는 스윙(Swing) 이벤트와 비슷한 역할을 한다. ServletContext 수명주기에 관심이 있는 사람이라면 누구나 ServletContextListener 인터페이스를 구현할 수 있다. 인터페이스에는 두 가지 메소드가 있다.
  • void contextInitialized(ServletContextEvent e): 웹 애플리케이션이 처음 요청을 처리할 준비가 되었을 때 불러내 진다. 요청은 메소드가 반환될 때까지 처리되지 않을 것이다.
  • void contextDestroyed(ServletContextEvent e): 웹 애플리케이션이 종료되려고 할 때에 호출된다. 이 메소드가 호출 전에 요청 처리가 중지될 것이다.
이러한 메소드에 전달되는 ServletContextEvent 클래스는 생성되거나 없어지는 컨텍스트가 반환하는 getServletContext() 메소드만을 가진다. ServletContext 속성 생명주기를 관찰하는 데에 관심이 있는 사람이라면 ServletContextAttributesListener 인터페이스를 구현할 수 있다. 이 인터페이스에는 세 가지 메소드가 있다.
  • void attributeAdded(ServletContextAttributeEvent e): 속성이 서블릿 컨텍스트에 추가될 때 호출된다.
  • void attributeRemoved(ServletContextAttributeEvent e): 속성이 서블릿 컨텍스트에서 제거될 때 호출된다.
  • void attributeReplaced(ServletContextAttributeEvent e): 속성이 서블릿 컨텍스트에 있는 다른 속성에 의해 대체될 때 호출된다.
ServletContextAttributeEvent 클래스는 ServletContextEvent를 확장하고, getName()과 getValue() 메소드를 추가한다. 따라서 사용자들은 속성이 어떻게 변화하는지 배울 수 있다. 애플리케이션 상태를 데이터베이스 등과 동조해야 하는 웹 애플리케이션이 한 장소에서 그러한 일을 할 수 있기 때문에 그것은 유용하다. 세션 청취자 모델은 컨텍스트 청취자 모델과 비슷하다. 세션 모델에서는 두 가지 메소드를 가진 HttpSessionListener 인터페이스가 있다.
  • void sessionCreated(HttpSessionEvent e): 세션이 만들어지면 호출된다.
  • void sessionDestroyed(HttpSessionEvent e): 세션이 없어지면 호출된다.
메소드는 생성되거나 없어지는 세션을 반환하기 위해 getSession() 메소드가 있는 HttpSessionEvent 인스턴스를 수용한다. 웹 애플리케이션에 있는 모든 활성 사용자를 추적하는 관리 인터페이스를 구현할 때 이 메소드를 모두 사용할 수 있다. 세션 모델도 세 가지 HttpSessionAttributesListener 메소드가 있는 인터페이스를 가진다. 이 메소드들은 속성이 변화하거나 사용할 수 있는 상태일 때, 예를 들어서 세션에 있는 프로필 데이터를 데이터베이스에 동조시킬 수 있는 애플리케이션에 의해 청취자에게 알린다.
  • void attributeAdded(HttpSessionBindingEvent e): 속성이 세션에 추가될 때 호출된다.
  • void attributeRemoved(HttpSessionBindingEvent e): 속성이 세션에서 제거되었을 때 호출된다.
  • void attributeReplaced(HttpSessionBindingEvent e): 속성이 세션에 있는 다른 속성으로 대체되었을 때 호출된다.
예상했겠지만, HttpSessionBindingEvent 클래스는 HttpSessionEvent를 확장하고 getName()과 getValue() 메소드를 추가한다. 약간 특이한 점은 이벤트 클래스의 이름이 HttpSessionAttributeEvent가 아니라 HttpSessionBindingEvent라는 것이다. 이는 단지 전통 때문이다. API에는 이미 HttpSessionBindingEvent 클래스가 있기 때문에 그 이름을 다시 사용하였다. 이 점은 API에서 혼동을 주는 부분이지만, 최종 배포판에서는 해소될 것이다. 생명주기 이벤트는 컨텍스트 청취자가 관리하는 공유 데이터베이스 연결로 사용할 수 있다.
  
   
    com.acme.MyConnectionManager 
   
 
서버는 클래스 구현을 어떤 청취자 인터페이스가 담당하는지 결정하기 위한 내성의 사용을 수신하기 위해 청취자 클래스의 인스턴스를 생성한다. 청취자는 배치 디스크립터 안에서 설정되기 때문에, 코드를 변화하지 않고도 새로운 청취자를 덧붙일 수 있다는 점을 기억해 두자. 청취자 자체를 다음과 같이 작성할 수 있다.
public class MyConnectionManager implements ServletContextListener { 
 
  public void contextInitialized(ServletContextEvent e) { 
    Connection con = // create connection 
    e.getServletContext().setAttribute("con", con); 
  } 
 
  public void contextDestroyed(ServletContextEvent e) { 
    Connection con = (Connection) e.getServletContext().getAttribute("con"); 
    try { con.close(); } catch (SQLException ignored) { } // close connection 
  } 
}
청취자는 데이터베이스 연결이 새로운 서블릿 컨텍스트에서 모두 사용 가능하도록 해 주며, 모든 연결은 컨텍스트가 종료되면 닫힌다. API 2.3에서의 또 다른 새로운 청취자 인터페이스인 HttpSessionActivationListener 인터페이스는 한 서버에서 다른 서버로 옮겨가려는 세션을 다루기 위해 고안된 것이다. HttpSessionActivationListener을 구현하는 청취자는 어떤 세션이 비활성화 되려고 할 때와 제 2의 호스트에서 활성화되려 할 때 통보를 받는다. 이러한 메소드를 사용하면 애플리케이션은 시리얼라이즈할 수 없는 데이터를 JVM 사이에서 영속화 하거나, 시리얼라이즈된 객체를 이동하기 전, 후의 객체 모델에 붙이거나 떼어낼 수 있다. 이러한 인터페이스에는 두 가지 메소드가 있다.
  • void sessionWillPassivate(HttpSessionEvent e): 이 세션은 곧 비활성화되려고 하는 것이다. 호출이 만들어지면 곧 사용할 수 없게 된다.
  • void sessionDidActivate(HttpSessionEvent e): 이 세션은 활성화된 것이다. 호출이 만들어졌을 때 아직은 서비스가 가능한 영역이 아니다.
이 청취자를 다른 것들과 마찬가지로 등록할 것이다. 하지만 다른 것들과는 달리 passivate와 활성 호출은 다른 서버에서 일어날 것이다. 문자 인코딩 선택하기 API 2.3에서는 외국어를 처리하는 새로운 메소드가 있으며, 서버의 요청 문자 인코딩을 알려줄 것이다. 그리고 새로운 메소드인 request.setCharacterEncoding(String encoding)이 있는데, 이것은 요청한 문자 인코딩이 무엇인지 서버에 알려줄 것이다. charset이라고도 불리는 문자 인코딩은, 바이트를 문자로 나타내는 방법으로 알려져 있다. 서버는 파라미터를 올바로 파싱하고 데이터를 POST하는 데에 특정 문자열을 사용할 수 있다. 디폴트 값에서 서버는 파라미터를 일반적인 Latin-1(ISO 8859-1) 문자열을 파싱한다. 하지만 불행히도 이는 서구 유럽 언어에서만 작동한다. 브라우저가 다른 문자열을 사용한다면, 원래는 요청 컨텐트 타입 헤더에 인코딩 정보를 보내도록 되어 있다. 하지만 실제로 브라우저에서는 그런 일이 거의 일어나지 않는다. 이 메소드를 사용하면 서블릿은 서버에 어떤 문자열이 사용중인지 알리며, 그 나머지 부분은 서버에서 책임진다. 예를 들어서 Shift_JIS 코드 형식으로 된 일본어를 전송받는 서블릿은 다음과 같은 파라미터를 읽을 수 있을 것이다.
  // Set the charset as Shift_JIS 
  req.setCharacterEncoding("Shift_JIS"); 
 
  // Read a parameter using that charset 
  String name = req.getParameter("name");
getParameter()이나 getReader()을 불러내기 전에 인코딩을 설정해야 한다는 점을 기억하자. 인코딩이 지원되지 않을 경우 setCharacterEncoding() 호출에서 java.io.UnsupportedEncodingException이라는 문구가 뜰 것이다. JAR 의존도 WAR 파일(Web application archive file)이 서버에서 존재하고 올바로 작동하려면, 다른 JAR 라이브러리가 필요할 때도 있다. 예를 들어서 ParameterParser 클래스를 사용하는 웹 애플리케이션에는 클래스패스에 cos.jar이 필요하고, WebMacro를 사용하는 웹 애플리케이션에는 webmacro.jar이 필요하다. API 2.3의 이전 버전에서는 의존도가 문서화되거나 웹 애플리케이션에서 필요한 jar 파일을 WEB-INF/lib 디렉토리에 포함하고 있어야 했다. 서블릿 2.3을 사용하면 WAR의 META-INF/MANIFEST.MF 엔트리를 사용하여 WAR 안에서 JAR 의존도를 표시할 수 있다. 이것이 jar 파일이 의존성을 선언하는 표준적인 방법이었다. 하지만 API 2.3에서는 WAR 파일이 공식적으로 동일한 메커니즘을 지원해야 한다. 만약 의존성이 충족되지 않는다면, 서버는 런타임에 모호한 에러 메시지를 띄우지 않고 웹 애플리케이션을 배치 시간에서 빼버리기도 한다. 예를 들어서 사용자는 옵션이 있는 패키지의 특정 버전에 대한 의존성을 표현할 수 있고, 서버는 검색 알고리즘으로 어떤 것이 옳은지 찾아야 한다. (버전을 알아내는 모델이 어떻게 작동하는지 자세히 알고 싶으면 링크를 참고하라.) 클래스 로더 작은 변화가 큰 효과를 가져왔다. API 2.3에서는 서블릿 컨테이너에서 웹 애플리케이션에 있는 클래스가 서버의 구현 클래스를 보는 것을 허용하지 않는다. 다시 말해서, 클래스 로더가 분리되어 있어야 한다. 이것을 큰 변화라고 생각하지 않을 수도 있다. 하지만 이는 웹 애플리케이션 클래스와 서버 클래스 사이에 충돌이 일어날 수 있는 가능성을 없애 준다. 둘 사이에 충돌이 일어나면, XML 파서에 문제가 생기기 때문에 심각한 문제가 생겼다. 각 서버에는 web.xml 파일을 파싱하기 위한 XML 파서가 필요하며, 많은 웹 애플리케이션에서 XML 데이터를 읽거나 다루거나 쓰는 것을 통제하기 위해 XML 파저를 사용한다. 파저가 다른 DOM이나 SAX 버전을 지원한다면, 복구가 불가능한 충돌이 일어날 수도 있다. 클래스 영역을 분리하면 이러한 문제를 깔끔하게 해결할 수 있다. 새로운 에러 속성 이전의 API 버전인 서블릿 API 2.2에서는 규칙의 타겟과 같이 작용하는 서블릿과 JSP에서 사용할 수 있는 여러 요청 속성을 소개하였다. 만약 규칙을 기억하지 못한다면, 서블릿 API 2.2에서는 어떤 에러 상태 코드나 예외 타입을 통해 특정 페이지가 나오도록 설정할 것이다.
 
     
     
         
            404 
         
         
            /404.html 
         
     
     
         
            javax.servlet.ServletException 
         
         
            /servlet/ErrorDisplay 
         
     
     

규칙을 위해 에 있는 서블릿은 다음 세 가지 속성을 수신할 수 있다.
  • javax.servlet.error.status_code: 에러 상태 코드를 말해 주는 정수이다.
  • javax.servlet.error.exception_type: 에러가 생기게 된 예외 형을 지적해 주는 클래스 인스턴스이다.
  • javax.servlet.error.message: 예외 메시지를 말해주는 스트링이며, 예외 컨스트럭터로 보내어 진다.
이러한 속성을 사용하여, 서블릿은 에러에 때라 각각 다른 에러 페이지를 생성할 수 있다. 페이지 내용은 다음과 같다.
import java.io.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 
 
public class ErrorDisplay extends HttpServlet { 
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException { 
    res.setContentType("text/html"); 
    PrintWriter out = res.getWriter(); 
 
    String code = null, message = null, type = null; 
    Object codeObj, messageObj, typeObj; 
 
    // Retrieve the three possible error attributes, some may be null 
    codeObj = req.getAttribute("javax.servlet.error.status_code"); 
    messageObj = req.getAttribute("javax.servlet.error.message"); 
    typeObj = req.getAttribute("javax.servlet.error.exception_type"); 
 
    // Convert the attributes to string values 
    // We do things this way because some old servers return String 
    // types while new servers return Integer, String, and Class types. 
    // This works for all. 
    if (codeObj != null) code = codeObj.toString(); 
    if (messageObj != null) message = messageObj.toString(); 
    if (typeObj != null) type = typeObj.toString(); 
 
    // The error reason is either the status code or exception type 
    String reason = (code != null ? code : type); 
 
    out.println(""); 
    out.println("" + reason + ": " + message + ""); 
    out.println(""); 
    out.println("

" + reason + "

"); out.println("

" + message + "

"); out.println("
"); out.println("Error accessing " + req.getRequestURI() + ""); out.println(""); } }
하지만 만약 에러 페이지가 예외 스택 흔적이나 정말로 문제를 일으킨 서블릿의 URI를 포함하고 있다면 어떠한가? API 2.2에서는 불가능했지만, API 2.3에서는 두 가지 속성이 새로 추가되었기 때문에 이러한 정보를 얻을 수 있다.
  • javax.servlet.error.exception: 실제 예외가 없어지면 버릴 수 있는 객체이다.
  • javax.servlet.error.request_uri: 문제를 일으킨 리소스의 URI를 말해주는 스트링이다.
이러한 속성을 통해 에러 페이지에서는 예외의 스택 흔적과 문제를 일으킨 리소스의 URI를 포함하게 된다. 아래에 있는 서블릿은 새로운 속성을 사용하기 위해 재작성 되었다(이러한 코드가 존재하지 않을 때에는 후방과 양립할 수 없기 때문에 수행에 실패할 것이다).
import java.io.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 
 
public class ErrorDisplay extends HttpServlet { 
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException { 
    res.setContentType("text/html"); 
    PrintWriter out = res.getWriter(); 
 
    String code = null, message = null, type = null, uri = null; 
    Object codeObj, messageObj, typeObj; 
    Throwable throwable; 
 
    // Retrieve the three possible error attributes, some may be null 
    codeObj = req.getAttribute("javax.servlet.error.status_code"); 
    messageObj = req.getAttribute("javax.servlet.error.message"); 
    typeObj = req.getAttribute("javax.servlet.error.exception_type"); 
    throwable = (Throwable) req.getAttribute("javax.servlet.error.exception"); 
    uri = (String) req.getAttribute("javax.servlet.error.request_uri"); 
 
    if (uri == null) { 
      uri = req.getRequestURI(); // in case there"s no URI given 
    } 
 
    // Convert the attributes to string values 
    if (codeObj != null) code = codeObj.toString(); 
    if (messageObj != null) message = messageObj.toString(); 
    if (typeObj != null) type = typeObj.toString(); 
 
    // The error reason is either the status code or exception type 
    String reason = (code != null ? code : type); 
 
    out.println(""); 
    out.println("" + reason + ": " + message + ""); 
    out.println(""); 
    out.println("

" + reason + "

"); out.println("

" + message + "

"); out.println("
"); 
    if (throwable != null) { 
      throwable.printStackTrace(out); 
    } 
    out.println("
"); out.println("
"); out.println("Error accessing " + uri + ""); out.println(""); } }
새로운 보안 속성(attribute) 서블릿 API 2.3에는 서블릿이 HTTPS 접속을 안전하게 다루는 방법에 관련된 정보를 얻게 해 주는 두 가지의 새로운 요청 속성이 추가되었다. 서버는 HTTPS를 사용하여 만들어진 요청에, 다음과 같은 새로운 요청 속성을 제공할 것이다.
  • javax.servlet.request.cipher_suite: HTTPS가 있다면, HTTPS에서 사용되는 사이퍼 수트(cipher suite)를 나타내는 스트링이다.
  • javax.servlet.request.key_size: 알고리즘이 있을 경우, 알고리즘의 비트(bit) 크기를 나타내는 정수이다.
서블릿에서는 이러한 속성들을 사용하여, 연결을 계속 진행할 수 있을 정도로 안전한지 프로그램적으로 판단할 수 있다. 어떤 애플리케이션에서는 소량의 비트 사이즈나 신뢰성 없는 알고리즘이 있을 경우 접속을 거부한다.
public boolean isAbove128(HttpServletRequest req) { 
  Integer size = (Integer) req.getAttribute("javax.servlet.request.key_size"); 
  if (size == null || size.intValue() < 128) { 
    return false; 
  } 
  else { 
    return true; 
  } 
}
미묘한 변화들 API 2.3에서는 작은 변화들이 있었다. 우선, 클라이언트를 확인하기 위해 사용된 인증형을 반환하는 getAuthType() 메소드가 정의되었다. 그 목적은 HttpServletRequest 클래스 안에 있는 새로운 정적(static) 최종 스트링 상수인 BASIC_AUTH, DIGEST_AUTH, CLIENT_CERT_AUTH, FORM_AUTH 를 반환하기 위한 것이다. 이를 통해 다음과 같이 더 단순화된 코드를 만들 수 있다.
if (req.getAuthType() == req.BASIC_AUTH) { 
  // handle basic authentication 
}
물론 네 가지 상수는 여전히 전통적인 값을 갖는다. 따라서 API 2.2에서 사용했던 다음 코드도 역시 사용 가능하지만, 이것을 사용하면 속도가 위의 것만큼 빠르지 않고, 더 깔끔하게 처리되지도 않는다. getAuthType()이 공(null)으로 반환되면, NullPointerException 을 피하기 위해 의 equals()역을 취해야 한다는 것을 기억하자.
if ("BASIC".equals(req.getAuthType())) { 
  // handle basic authentication 
}
API 2.3에서는 "일반에 절대로 공개되어서는 안된다"고 알려진 HttpUtils을 사용할 수 없게 되었다. HttpUtils은 정적인 메소드를 모아 놓은 이상한 것이기 때문에 두드러져 보였다. 이것은 때로는 유용한 것을 호출하지만, 다른 곳에 배치하는 것이 더 좋을 것 같았다. 재호출하지 않는 경우를 고려하여, 클래스는 요청 객체로부터 원래의 URL을 재구성하기 위해, 그리고 파라미터 데이터를 해시테이블(hashtable)로 파싱하기 위해 메소드를 포함하고 있었다. API 2.3에서는 이 함수를 요청 객체로 옮겼는데, 이 부분에 더 잘 맞는 것 같았다. 그리고 HttpUtils는 사용 불가능하게 만들었다. 요청 객체에 있는 새로운 메소드는 다음과 같다.
  • StringBuffer req.getRequestURL(): 요청 정보에서 재구축되었으며, 원래의 요청 URL을 포함하는 StringBuffer를 반환한다.
  • java.util.Map req.getParameterMap(): 요청 파라미터의 변하지 않는 맵을 반환한다. 파라미터 네임은 키의 역할을 하며, 파라미터 값은 맵 값으로 작용한다. 다중 값을 가진 파라미터가 어떻게 다루어지는지는 결정되지 않았지만, 모든 값은 String[]으로 반환될 가능성이 크다. 이러한 메소드는 character conversions를 다루기 위해 새로운 req.setCharacterEncoding() 메소드를 사용한다.
API 2.3에는 새로운 두 가지 메소드가 ServletContext에 추가되어, 컨텍스트 네임과 리소스 리스트를 모두 얻을 수 있다.
  • String context.getServletContextName(): 컨텍스트 네임을 web.xml 파일에서 선언된 대로 반환한다.
  • java.util.Set context.getResourcePaths(): 컨텍스트에서 사용할 수 있는 모든 리소스 패스를 스트링 객체의 고정 집합으로서 반환한다. 각 스트링에는 슬래쉬 표시가 있으며, 컨텍스트 루트와 관련되어 있다.
프로그래머의 응답 버퍼에 대한 통제력을 높이기 위해 응답 객체에 새로운 메소드가 추가되었다. API 2.2에서는 응답을 리셋하고 응답의 바디, 헤더, 상태 코드 등을 클리어하기 위해 res.reset() 메소드를 도입하였다. API 2.3에서는 응답 바디만을 클리어해 주는 res.resetBuffer()가 추가되었다.
  • void res.resetBuffer(): 헤더나 상태 코드를 클리어하지 않고도 응답 버퍼를 클리어해준다. 응답이 이미 커밋되었다면, 여기서는 IllegalStateException라는 문구를 불러온다.
마지막으로, 전문가들이 오랫동안 논의한 후, 서블릿 API 2.3은 최종적으로 루트가 아닌 컨텍스트(non-root context) 내부에서 실행되는 서블릿을 요청하는 res.sendRedirect("/index.html")에서 정확히 어떤 일이 일어나는지 명확히 하였다. 중요한 것은 서블릿 2.2에서는 서블릿 컨테이너에 의해 완전한 패스로 번역되려면 "/index.html"와 같은 불완전한 패스가 필요했지만, 컨텍스트 패스가 어떻게 다루어져야 하는지는 정하지 않았다. 만약 호출을 하는 서블릿이 "/contextpath"라는 패스에 있는 컨텍스트에 있다면, URI 번역을 컨테이너루트(http://server:port/index.html)나 컨텍스트 루트(http://server:port/contextpath/index.html)에 따라 리다이렉트해야 하는가? 이식성을 최대한 높이기 위해, 행위를 반드시 정의해야 한다. 오랫동안 논쟁한 끝에, 전문가들은 컨테이너 루트에 비교하여 번역하기로 결정했다. DTD의 명료화 마지막으로, 서블릿 API 2.3은 web.xml 배치 디스크립터 행위와 관련하여 해결되지 않은 부분들을 확실히 하였다. 이제는 사용하기 전에 web.xml 파일에 있는 텍스트 값을 조절해야 한다. 이러한 규칙 때문에 다음 두 가지 엔트리가 동일하게 다루어질 수 있다.
hello
 
  hello 

그리고 API 2.3은 규칙을 허용해서, "*"라는 특별한 값이 모든 역할을 허용하는 와일드카드로 사용될 수 있다. 이것을 통해서 다음과 같은 규칙을 작성할 수 있는데, 모든 사용자들이 웹 애플리케이션에서 어떠한 역할에라도 속해서 적절한 확인 과정을 거치자 마자 엔터(enter)할 수 있다.
 
  *  

마지막으로, 규칙에 의해 isUserInRole() 메소드의 파라미터로서 선언된 규칙 이름을 사용할 수 있다는 점은 분명히 밝혀져 있다. 다음의 web.xml 엔트리를 예로 들 수 있다.
 
     
        secret 
     
     
        SalaryViewer 
     
     
         
            mgr  
         
         
            manager  
         
     
 
 
 
 
 
     
        manager 
     

secret이라는 서블릿으로 isUserInRole("mgr")이나 isUserInRole("manager")을 호출할 수 있다. 둘 다 동일한 행위를 줄 것이다. 기본적으로 security-role-ref 는 별명을 만들기 위해 동작하지만, 반드시 필요한 것은 아니다. API 2.2 규약에서는 별명 규칙에서 명시적으로 선언된 역할만을 사용할 수 있다는 것을 암시하는 것으로 해석될 수 있다(이 점이 납득이 가지 않아도 걱정할 필요는 없다. 단지 지금은 모든 것이 제대로 잘 된다는 점만 알고 있으면 된다). 결론 위에서 언급했듯이, 서블릿 API 2.3에는 흥미로운 새 필터 메커니즘과 확장된 생명주기 모델, 그리고 국제화를 지원하기 위한 새로운 함수, 에러 처리, 보안 연결, 사용자 역할 등이 포함되었다. 규약 도큐먼트에서도 크로스 플랫폼 배치에서 나타날 수 있는 모호성을 제거하기 위해 내용을 좀 더 다듬었다. 전체적으로 15개의 클래스가 추가되었으며, 4 가지 메소드가 현재 있는 클래스에 덧붙여졌고, 네 가지 새로운 상수가 있으며, 클래스 하나는 빠져 있다. 2.2에서 2.3으로 어떤 변화가 있는지에 대한 컨닝 페이퍼를 보고 싶다면, sidebar를 참고하라.
작가에 관하여 제이슨 헌터(Jason Hunter)는 오픈 소스 스타일 협업을 지원하는 툴과 서비스를 공급하는 CollabNet의 선임 기술자이다. 그는 『자바 서블릿 프로그래밍』을 저술하였으며, Servlets.com이라는 사이트를 운영하며, 서블릿/JSP와 JAXP API 개발과 관련 있는 전문가 그룹의 멤버이기도 하다.
TAG :
댓글 입력
자료실

최근 본 상품0