잊지 않겠습니다.

Spring 3.1에서 강화된 @Configuration을 사용한 설정에서 재미있는 @EnableXX annotation을 이용한 @EnableOrm을 만들어보고자한다. 

먼저, 요구사항

1. 기본적으로 BoneCP를 이용
2. packagesToScan 을 통해서 entity가 위치한 package를 지정해줄 수 있어야지 된다.
3. Hibernate와 Hibernate를 이용한 JPA를 모두 지원하는 형태로 구성한다.
4. Ehcache를 사용할 수 있어야지 된다. 

기본적으로 구성되는 pom.xml의 구성은 다음과 같습니다. 

        <dependency>
            <groupId>com.jolbox</groupId>
            <artifactId>bonecp</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-sql</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>provided</scope>
        </dependency>

먼저, @EnableOrm interface는 다음과 같이 정의 될 수 있습니다. 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(OrmConfigurationSelector.class)
public @interface EnableOrm {
    String[] packagesToScan() default {};

    boolean enableCache() default false;

    boolean showSql() default false;

    OrmType ormType() default OrmType.Hibernate;
}

public enum OrmType {
    Hibernate, Jpa;
}

packageToScan과 enableCache, 그리고 console 창에 sql query문을 출력해야지 되는 경우를 기본적으로 고려해줄 수 있습니다.  그리고 OrmType을 통해서 기본 Hibernate를 이용한 Orm과 Jpa를 이용하는 두가지를 모두 사용할 수 있도록 합니다.  OrmType에 따라 각각 Load되는 Configuration이 바뀌어야지 되기 때문에 Import class는 ImportSelector를 구현한 객체여야지 됩니니다. 

ImportSelector를 구현한 객체는 다음과 같이 구성됩니다. 

public class OrmConfigurationSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> metadata = importingClassMetadata.getAnnotationAttributes(EnableOrm.class.getName());
        OrmType ormType = (OrmType) metadata.get("ormType");
        if (ormType == OrmType.Hibernate) {
            return new String[] { HibernateConfiguration.class.getName() };
        } else {
            return new String[] { JpaConfiguration.class.getName() };
        }
    }
}

그리고, Hibernate를 이용할 때와 JPA를 이용할 때의 공통 코드가 존재하는 abstract 객체를 하나 만들어주는 것이 좋을 것 같습니다. 기본적으로 Hibernate JPA를 사용할 예정이기 때문에 공통적으로 DataSource와 Hibernate Property는 완벽하게 중복되는 코드가 만들어질테니까요. 
공통 Configuration객체인 OrmConfiguration객체의 기능은 다음과 같습니다.

1. BoneCP datasource 제공
2. Hibernate Property의 제공
3. enableCache, packateToScan, showSql 등 protected 변수의 제공

OrmConfiguration 객체는 다음과 같이 구성될 수 있습니다. 

public abstract class OrmConfiguration implements ImportAware {
    public static final String HIBERNATE_DIALECT = "hibernate.dialect";
    public static final String CONNECT_USERNAME = "connect.username";
    public static final String CONNECT_PASSWORD = "connect.password";
    public static final String CONNECT_DRIVER = "connect.driver";
    public static final String CONNECT_URL = "connect.url";

    public static final String HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    public static final String ORG_HIBERNATE_CACHE_EHCACHE_EH_CACHE_REGION_FACTORY = "org.hibernate.cache.ehcache.EhCacheRegionFactory";
    public static final String HIBERNATE_CACHE_USE_QUERY_CACHE = "hibernate.cache.use_query_cache";
    public static final String HIBERNATE_CACHE_USE_SECOND_LEVEL_CACHE = "hibernate.cache.use_second_level_cache";
    public static final String HIBERNATE_CACHE_REGION_FACTORY_CLASS = "hibernate.cache.region.factory_class";

    @Autowired
    protected Environment env;

    protected boolean showSql;
    protected boolean enableCache;
    protected String[] packagesToScan;

    @Bean
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();
        dataSource.setUsername(env.getProperty(CONNECT_USERNAME));
        dataSource.setPassword(env.getProperty(CONNECT_PASSWORD));
        dataSource.setDriverClass(env.getProperty(CONNECT_DRIVER));
        dataSource.setJdbcUrl(env.getProperty(CONNECT_URL));
        dataSource.setMaxConnectionsPerPartition(20);
        dataSource.setMinConnectionsPerPartition(3);
        return dataSource;
    }

    @Bean
    public HibernateExceptionTranslator hibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

    @Bean
    public abstract PlatformTransactionManager transactionManager();

    protected Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put(HIBERNATE_DIALECT, env.getProperty(HIBERNATE_DIALECT));
        if (enableCache) {
            properties.put(HIBERNATE_CACHE_REGION_FACTORY_CLASS, ORG_HIBERNATE_CACHE_EHCACHE_EH_CACHE_REGION_FACTORY);
            properties.put(HIBERNATE_CACHE_USE_SECOND_LEVEL_CACHE, true);
            properties.put(HIBERNATE_CACHE_USE_QUERY_CACHE, true);
        }
        return properties;
    }

    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        if (env.getProperty(HIBERNATE_DIALECT) == null || env.getProperty(CONNECT_USERNAME) == null
                || env.getProperty(CONNECT_PASSWORD) == null || env.getProperty(CONNECT_DRIVER) == null
                || env.getProperty(CONNECT_URL) == null) {
            throw new IllegalArgumentException("properties is not completed! check properties (hibernate.dialect, "
                    + "connec.username, connect.password, connect.driver, connect.url)");
        }
        Map<String, Object> metaData = importMetadata.getAnnotationAttributes(EnableOrm.class.getName());
        enableCache = (boolean) metaData.get("enableCache");
        packagesToScan = (String[]) metaData.get("packagesToScan");
        showSql = (boolean) metaData.get("showSql");
    }
}

기본적인 Property들은 모두 Properties 파일에 정의되지 않으면 에러가 발생하도록 객체들을 구성하였습니다. 이제 HibernateConfiguration을 한번 구성해보도록 하겠습니다. 

@Configuration
@EnableTransactionManagement
public class HibernateConfiguration extends OrmConfiguration implements HibernateConfigurer {
    private static final String HIBERNATE_SHOW_SQL = "hibernate.show_sql";

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        setLocalSessionFactoryBean(sessionFactory);
        return sessionFactory;
    }

    @Override
    @Bean
    public PlatformTransactionManager transactionManager() {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setDataSource(dataSource());
        transactionManager.setSessionFactory(sessionFactory().getObject());
        return transactionManager;
    }

    @Override
    public void setLocalSessionFactoryBean(LocalSessionFactoryBean localSessionFactoryBean) {
        Properties properties = getHibernateProperties();
        if (showSql) {
            properties.put(HIBERNATE_SHOW_SQL, "true");
        }
        localSessionFactoryBean.setHibernateProperties(properties);
        localSessionFactoryBean.setPackagesToScan(packagesToScan);
    }
}


기본적으로 항시 사용되는 SessionFactory와 그에 대한 설정부분을 Load 시켜주고, PlatformTransactionManager를 return 시켜주는 매우 단순한 @Configuration class입니다. 

이제 JpaConfiguration입니다. 
@Configuration
@EnableTransactionManagementpublic class JpaConfiguration extends OrmConfiguration implements JpaConfigurer {
    @Override
    public void setEntityManagerFactoryBeanProperties(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        entityManagerFactoryBean.setPackagesToScan(packagesToScan);
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
        setEntityManagerFactoryBeanProperties(entityManagerFactory);
        entityManagerFactory.setDataSource(dataSource());
        entityManagerFactory.setJpaVendorAdapter(hibernateJpaVendorAdapter());
        entityManagerFactory.setJpaProperties(getHibernateProperties());
        return entityManagerFactory;
    }

    @Bean
    public HibernateJpaVendorAdapter hibernateJpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(showSql);
        return hibernateJpaVendorAdapter;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    @Override
    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setDataSource(dataSource());
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }
}


둘의 코드는 거의 완전히 동일합니다. Hibernate를 이용할 것인가, 아니면 Jpa를 이용할 것인가에 대한 기본적인 차이만이 존재합니다. 
테스트 코드 구성은 다음과 같습니다. 

@Configuration
@EnableOrm(ormType = OrmType.Hibernate, enableCache = true, packagesToScan = "com.xyzlast.domain.configs", showSql = true)
@PropertySource(value = "classpath:spring.properties")
@ComponentScan("com.xyzlast.domain.repositories")
public class TestHibernateConfiguration {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer configHolder = new PropertySourcesPlaceholderConfigurer();
        Properties properties = new Properties();
        properties.setProperty("org.jboss.logging.provier", "slf4j");
        configHolder.setProperties(properties);
        return configHolder;
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestHibernateConfiguration.class)
public class HibernateConfigurationTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Before
    public void setUp() {
        assertThat(applicationContext, is(not(nullValue())));
    }

    @Test
    public void dataSource() {
        DataSource dataSource = applicationContext.getBean(DataSource.class);
        assertThat(dataSource, is(not(nullValue())));
    }

    @Test
    public void transactionManager() {
        PlatformTransactionManager transactionManager = applicationContext.getBean(PlatformTransactionManager.class);
        assertThat(transactionManager, is(not(nullValue())));
    }

    @Test
    public void sessionFactory() {
        SessionFactory sessionFactory = applicationContext.getBean(SessionFactory.class);
        assertThat(sessionFactory, is(not(nullValue())));
    }
}

이제 Hibernate와 JPA에 따른 각각의 configuration을 따로 구성하지 않아도 되는 멋진 코드가 만들어졌습니다.
회사에서 다른 팀원들이 사용할 수 있도록 jar를 만들어서 사내 nexus 서버에 올려야지 되겠습니다. ㅋㅋ




Posted by Y2K
,
<mvc:annotation-driven>
이 설정될 경우, 여기에 따른 여러가지 설정이 많이 이루어지게 된다. 
먼저 Spring 문서에 따르면, 

 1. Support for Spring 3′s Type ConversionService in addition to JavaBeans PropertyEditors during Data Binding. A ConversionService instance produced by the org.springframework.format.support.FormattingConversionServiceFactoryBean is used by default. This can be overriden by setting the conversion-service attribute. 
2. Support for formatting Number fields using the @NumberFormat annotation 
3. Support for formatting Date, Calendar, Long, and Joda Time fields using the @DateTimeFormat annotation, if Joda Time is present on the classpath. 
4. Support for validating @Controller inputs with @Valid, if a JSR-303 Provider is present on the classpath. The validation system can be explicitly configured by setting the validator attribute. 
5. Support for reading and writing XML, if JAXB is present on the classpath. 
6. Support for reading and writing JSON, if Jackson is present on the classpath. 

와 같은 일들이 벌어지게 된다. 

여기서 주의할 점은 1번 항목. ConversionService가 default로 설정이 된다는것이다. ConversionService가 이미 설정이 되어 있기 때문에 Spring source에서 보면 다음과 같은 항목에 의하여 에러가 발생하게 된다.
        public void setConversionService( ConversionService conversionService ) {
               Assert .state ( this. conversionService == null, "DataBinder is already initialized with ConversionService");
               this .conversionService = conversionService ;
               if ( this .bindingResult != null && conversionService != null ) {
                      this .bindingResult . initConversion( conversionService );
               }
        }
따라서, Converter 를 이용한 @InitBinder는 사용할 수 없게 되고 에러를 발생시키게 된다. 이 경우에, @InitBinder를 이용하기 위해서는 PropertyEditorSupport를 상속받아 등록시키는 방법을 사용할 수 밖에 없게 된다. 을 통해서 설정되는 것을 xml로 표현하면 다음과 같다.
    
        
    
     
    
    

    
        
            
                
                
            
        
        
            
                
                
                    
                
                
                
                





            
        
    

    
        
        
            
                
            
        
    
위에서 주석처리 되어 있는 항목들은 해당 외부 라이브러리들이 존재하면 사용되게 된다.



Posted by Y2K
,

긁어온 내용. 출처는 > http://www.gurubee.net/pages/viewpage.action?pageId=26739591&

1. Apache HTTP 서버의 이해

1.1 개요

  • Apache HTTP 서버는 아파치 소프트웨어 재단(ASF:Apache Software Foundation)에서 개발하여 배포하고 있는 무료/오픈소스 웹 서버이다.
  • 아파치 HTTP 서버는 전세계 웹 서버 시장 점유율의 50% 이상을 차지하고 있으며 , 리눅스, 유닉스, BSD, 윈도우즈 등 다양한 플랫폼에서 사용이 가능하다.
  • 아파치 HTTP 서버는 빠르고 효율적이며, 이식성이 좋고 안정적이며, 기능이 다양하고 확장성이 좋다.

1.2 Apache HTTP 서버 왜 필요한가?

  • 보안기능 (SSL, Proxy, ACL, Directory 접근제한등..)
  • 성능적인 측면(리소스 분산처리, Cache(Expires), HTTP 표준설정(ETag등), MPM(Multi-Processing Module), KeepAlive등..)
  • 가상호스트 기능(하나의 서버에 여러 도메인 운영, 서버호스팅등..)
  • 운영적인 측면 (ErrorDocument, Access Log등..)
  • 부가적인 기능 (여러가지 유용한 Apache Module등..)

1.3 설치

2. Apache HTTP 서버의 주요 설정

2.1 가상호스트 (VirtualHost)

  • VirtualHost(가상호스트)란 하나의 웹 서버에서 여러 개의 도메인 주소를 운영 및 관리 할 수 있는 기능이다.
  • Apache HTTP Server에서는 이름기반 가상호스트와 IP 기반 가상호스트 기능을 제공한다.
이름기반 가상호스트(Name-based VirtualHost) 설정
  • 하나의 IP 주소에 여러 개의 호스 도메인 주소 사용이 가능하다.
  • httpd-vhost.conf의 VirtualHost를 설정하여 이름기반의 가상 호스트를 사용 할 수 있다.
  • httpd.conf 파일에서 httpd-vhosts.conf 파일의 주석을 해제해야 한다.
Name-based VirtualHost 예제
Listen 80
NameVirtualHost *:80

<VirtualHost *:80>
  ServerName www.oracleclub.com
  ServerAlias oracleclub.com
  DocumentRoot C:\workspace\project\oracleclub
</VirtualHost>

<VirtualHost *:80>
  ServerName wiki.oracleclub.com
  DocumentRoot C:\workspace\project\wiki
</VirtualHost>
가상호스트 설정 실습
  • $APACHE_HOME/conf/extra/httpd-vhost.conf 파일을 열어 NameVirtualHost와 VirtualHost를 아래와 같이 설정한다.
  • ServerName은 test.apache.org로 입력하고, DocumentRoot는 아파치 $APACHE_HOME/htdocs로 설정한다.
httpd-vhost.conf
NameVirtualHost *:80

<VirtualHost *:80>
    ServerName test.apache.org
    DocumentRoot C:\dev\Apache2.2\htdocs    # Apache htdocs 디렉토리 
</VirtualHost>
  • 아래와 같이 C:\Windows\System32\drivers\etc\hosts 파일에 호스트 설정을 추가 한다.
  • hosts.zip 파일을 받아 바로가기를 만들어 사용하면 편하다.
C:WindowsSystem32driversetchosts
# 127.0.0.1  localhost
127.0.0.1  test.apache.org

2.2 Files, Directory, Location 섹션

Files (파일시스템 관점)
  • 특정 파일에 대한 접근제한을 설정한다.
  • 아래는 위치에 상관없이 private.html 파일에 대한 접근을 제한하는 예이다.
    <Files private.html>
    Order allow,deny
    Deny from all
    </Files>
  • 아래는 "/home/user/webapps" 경로아래에 있는 private.html 파일에 대한 접근을 제한하는 예이다.
    <Directory /home/user/webapps>
    <Files private.html>
    Order allow,deny
    Deny from all
    </Files>
    </Directory>
Directory (파일시스템 관점)
  • 운영체제 입장에서 디스크를 보는 관점이다. 운영체제 디렉토리에 대한 접근제한을 설정한다.
  • 아래 예제를 보면서 Allow,Deny에 대해 이해를 해보자
    • Order 절 순서대로 Allow와 Deny를 실행한다
    • 아래 예는 Allow를 먼저 수행하고, Deny를 수행한다.
    • 즉 모두 허용하고, 127.0.0, 192.168.123 두 개의 아이피 대역에 대해서 접근 제한을 설정한다 .
      # 특정 IP 대역의 IP 차단
      <Directory /usr/local/apache/htdocs>
          Order Allow,Deny
          Deny from 127.0.0 192.168.123
          Allow from all
      </Directory>
  • 위 예제를 이해하였다면, 아래 예제는 쉽게 이해할 수 있을 것이다.
    • Deny를 먼저 수행하고 Allow를 수행한다.
    • 127.0.0, 192.168.123 두 개의 아이피 대역만 접근이 가능할 것이다.
      # 특정 IP 대역의 IP 허용
      <Directory /usr/local/apache/htdocs>
          Order Deny,Allow
          Allow from 127.0.0 192.168.123
          Deny from all
      </Directory>
  • 아래는 Allow, Deny 설정을 잘못한 예제이다.
    • 어떻게 되겠는가?
    • 먼저 Allow를 하고, Deny from all을 하기 때문에 모두 차단하겠다라는 의미를 가진다.
      <Directory /usr/local/apache/htdocs>
         Order Allow,Deny
         Deny from all
         Allow from 192.168.123.1
      </Directory>
Location (웹 경로 관점)
  • 웹서버가 제공하는 경로를 클라이언트가 보는 사이트의 관점이다
  • 아래는 /private 경로에 대해서 접근을 제한하는 예이다.
    • www.oracleclub.com/private 문자열로 시작하는 요청이 해당된다.
      <Location /private>
      Order Allow,Deny
      Deny from all
      </Location>
FilesMatch, DirectoryMatch, LocationMatch
  • 정규표현식을 사용할 수 있다.
  • 아래는 이미지 파일에 대한 접근을 제한하는 예이다.
    <FilesMatch \.(?i:gif|jpe?g|png)$>
    Order allow,deny
    Deny from all
    </FilesMatch>
  • 아래는 WEB-INF, META-INF 디렉토리에 접근을 금지하는 예이다.
    <DirectoryMatch "(^|/)META-INF($|/)">
      Order deny,allow
      deny from all
    </DirectoryMatch>
    
    <DirectoryMatch "(^|/)WEB-INF($|/)">
      Order deny,allow
      deny from all
    </DirectoryMatch>
Directory 설정 실습
  • 위의 Directory Allow, Deny 설정 예제를 직접 실습해 보자
  • 아래와 같이 특정 IP의 접근을 막는 Directory 옵션을 추가해 보자 (아래는 로컬 IP 주소의 접근을 막는 예제이다.)
  • Apache restart 후 http://test.apache.org 접속시 "Forbidden" 에러 메시지가 나오는지 확인 해 보자
    httpd-vhost.conf
    <VirtualHost *:80>
        ServerName test.apache.org
        DocumentRoot C:\dev\Apache2.2\htdocs    
        
        <Directory C:\dev\Apache2.2\htdocs>
            Order Allow,Deny
            Deny from 192.168  127.0.0
            Allow from all
        </Directory>    
    </VirtualHost>

2.3 ErrorDocument

ErrorDocument란
  • Apache HTTP Server에서는 ErrorDocument 지시자를 사용해 특정 에러발생시 특정 페이지로 redirect 할 수 있다.
  • ErrorDocumen를 활용하여 다양한 HTTP Status Code에 대해서 설정을 해놓으면 사용자에게 좀 더 편리하고 친절하게 메시지를 보여 줄 수 있다.
  • httpd.conf 파일에서 ErrorDocument 지시자를 설정하면 된다.
  • 아래와 같이 세가지 방법으로 설정 할 수 있다.
    ErrorDocument 설정 예제
    # 1. plain text 설정 방법
    # 500 메세지를 "The server made a boo boo." 로 변경해 준다.
    ErrorDocument 500 "The server made a boo boo."
    
    # 2. local redirects 방법
    # 404 발생시 missing.html의 내용을 보여준다.
    ErrorDocument 404 /missing.html
    
    # 3. external redirects 설정 방법
    # 404 발생시 외부 페이지로 redirect 한다.
    ErrorDocument 404 http://www.example.com/subscription_info.html
ErrorDocument 설정 실습
  • Directory 에서 실습한 접근권한 설정예제에서 403 접근 권한 오류가 발생 했을 때 Apache HTTP Server의 기본 메시지인 "Forbidden" 에러 메시지 대신 특정 HTML 이 나오게 실습 해보자
  • AccessDeny.html 파일을 "$APACHE_HOME/htdocs" 디렉토리에 저장한다.
  • 아래와 같이 ErrorDocument를 설정 한다.
    httpd-vhost.conf
    <VirtualHost *:80>
        ServerName test.apache.org
        DocumentRoot C:\dev\Apache2.2\htdocs    
    
        ErrorDocument 403 /AccessDeny.html   
        
        <Directory C:\dev\Apache2.2\htdocs>
            Order Allow,Deny
            Deny from 192.168.123 127.0.0
            Allow from all
        </Directory>    
    </VirtualHost>
IE ErrorDocument 설정 기준
  • error page 의 사이즈가 아래 기준보다 작으면 IE 메세지를 보여준다.(브라우저가 알아서 에러 페이지가 성의 없다고 판단함)
CodeDescriptionFile Size
400Bad Request512 bytes
403Forbidden256 bytes
404Not Found512 bytes
500Internal Server Error512 bytes

참고자료


Apache HTTP 서버와 Tomcat서버의 연동

1. Apache와 Tomcat을 연동하는 이유

  • Tomcat 서버는 본연의 임무인 서블릿 컨테이너의 역할만 하고, Apache HTTP Server는 웹서버의 역할을 하도록 각각의 기능을 분리하기 위해 연동을 할 수 있다.
  • Apache HTTP Server에서 제공하는 편리한 기능을 사용하기 위해서 연동을 할수 있다.
  • 대규모 사용자가 사용하는 시스템을 구축할 때 웹 서버인 아파치와 연동을 하면 부하 분산의 효과를 가질 수 있다. mod_jk의 Load Balancing과 FailOver 기능을 사용하여 안정적으로 운영 할 수 있다.

2. Apache와 Tomcat 연동하기

  • 아래 내용은 윈도우 OS를 기준으로 설명하였다.
2.1 mod_jk 다운로드 및 설정
httpd.conf
# jk_module 추가
LoadModule jk_module modules/mod_jk.so
2.2 workers.properties 파일 설정
  • apache와 tomcat를 연동하기위해서는 workers.properties 파일을 설정해야 한다.
  • $APACHE_HOME/conf/workers.properties 파일을 아래 예제와 같이 생성한다.
  • workers.properties 파일은 일반적으로 httpd.conf 파일과 같은 디렉토리에 위치하게 설정한다.
workers.properties
worker.list=sample

# 톰캣 server.xml 파일 AJP/1.3 Connector의 Port를 입력한다.
worker.sample.port=8009

# 톰탯 server 호스트
worker.sample.host=localhost

# 아파치 + 톰캣 통신 프로토콜
worker.sample.type=ajp13
  • ※참고 Tomcat Worker
    • 톰캣 워커는(Tomcat worker) 웹서버로부터의 서블릿 요청을 톰캣 프로세스(Worker)에게 전달하여 요청을 처리하는 톰캣 인스턴스이다.
    • 대부분 하나의 worker를 사용하나, load 밸런싱이나 site 파티셔닝을 위해 여러개의 worker를 사용 할 수 있다.
    • 워커 타입에는 ajp12, ajp13, jni, lb 등이 있다.
2.3 workers.properties 경로 지정
  • httpd.conf 파일에 workers.properties 파일 경로를 지정한다.
httpd.conf
# workers.properties 파일 추가
JkWorkersFile conf/workers.properties
2.4 VirtualHost 설정 변경
  • $APACHE_HOME/conf/vhosts/extra/httpd-vhost.conf 파일의 VirtualHost의 DocumentRoot를 Tomcat 디렉토리로 변경하자
  • JkMount 설정을 추가하자
httpd-vhost.conf 파일 설정
NameVirtualHost *:80

<VirtualHost *:80>
    ServerName test.apache.org
    DocumentRoot C:\dev\apache-tomcat-6.0.32webapps\ROOT 
 
 # URL중 jsp로 오는 Request만 Tomcat에서 처리 함
 # sample은 workers.properties에서 등록한 worker이름
JkMount  /*.jsp  sample   

# servlet 예제 실행을 위해서 추가
JkMount  /examples/* sample
</VirtualHost>
2.5 Tomcat의 server.xml 수정
  • <Context> 태그의 docBase 디렉토리를 Apache HTTP Server 설정과 동일하게 Tomcat 서버의 webapps/ROOT 디렉토리를 절대경로로 지정하자.
원하는 디렉토리를 Document Root로 사용
<Host name="localhost"  appBase="webapps"
           unpackWARs="true" autoDeploy="true"
           xmlValidation="false" xmlNamespaceAware="false">
 <Context path="" docBase="C:\dev\apache-tomcat-6.0.32\webapps\ROOT" reloadable="true"/>
 ..
</Host>

3. Apache HTTP Server와 Tomcat의 연동 테스트

Posted by Y2K
,