반응형



 안녕하세요. 오늘은 ENUM을 JSP에서 받아 사용하는 방법에 대해 포스팅하려고 합니다. 많은 분들이 공통적으로 자주 사용하는 코드 값들이나 타입들을 ENUM에 넣어서 주로 사용하실 텐데요. ENUM을 값 그대로 가져와서 JSP에서 쓸 수 있는 좋은 방법이 있어 포스팅을 하려고 합니다. 저 또한 최근에 ENUM값을 JSP에서 사용해야 하는 기능 개발 건이 있었는데 오늘 포스팅 하는 내용을 통해 쉽게 해결할 수 있었습니다.


먼저 ENUM을 어떻게 JSP에서 사용할 수 있었는지에 대해 간단한 절차를 적어보도록 하겠습니다.1. 사용하고자 하는 ENUM 클래스를 작성합니다.2. web.xml에 Context-param으로 해당 enum 클래스를 등록해 줍니다.3. EnumContextListner를 web.xml에 등록해 줍니다.4. EnumContextListner 클래스를 작성합니다.5. JSP에서 Context-param으로 등록한 ENUM을 가져다가 사용합니다.


1. 사용하고자하는 ENUM 클래스를 작성합니다.해당 내용은 포스팅을 위해 번호와 이메일을 임의로 작성하였습니다. 잘린 부분은 get,set메서드이기 때문에 생략하도록 하겠습니다.



2. web.xml에 Context-param으로 해당 enum 클래스를 등록해 줍니다.위에서 작성한 SmsServiceType 클래스를 Context-param의 param-value에 등록해 줍니다. 간단히 Context-param에 대해 설명하자면 보통 사용자의 요청(ex, www.nr.com/dongbeom)이 웹서버를 거쳐 웹 어플리케이션 서버(탐캣)에 들어왔을 경우 그에 해당하는 servlet을 생성하게 됩니다. Context-param은 사용자의 요청에 의해 생성되는 모든 servlet에서 Context-param으로등록 된 값을 사용할 수 있도록 하는 존재로 쉽게 전역 변수의 역할과 비슷하다고 생각하시면 될 것 같습니다.

밑의 그림에서는 Context-param의 이름이 enumServletContextConfig이고 com.nhncorp.echo.eme.model.SmsServiceType의 클래스를 값으로 가지고 있는 컨텍스트를 정의하고 있습니다.




3. EnumContextListner를 web.xml에 등록해 줍니다.
리스너는 컨텍스트에 정의되어 있는 것들을 모든 서블릿과 필터가 공유할 수 있도록 해줍니다.밑의 그림에서 EnumContextListener 클래스를 listener-class로 등록하고 있는 것을 볼 수 있습니다. 보통 컨텍스트의 값과 리스너들은 클라이언트에서 요청이 들어오지 않아도 web.xml이 읽혀질 때 호출되게 됩니다. (서버가 시작되는 시점에도 호출되게 됨)



4. EnumContextListner 클래스를 작성합니다.해당 클래스에 대해 간단히 설명하자면 web.xml에서 등록한 Context-param의 값들을 다 가져와서 MAP과 LIST로 만들어 JSP에서 사용할 수 있도록 ServletContext에 set을 해주는 역할을 합니다. 또한 loadEnum이라는 메소드를 보시면 ENUM 을 맵과 리스트로 만들어 Collections.unmodifiableMap, Collections.unmodifiableList로 변환해 read-only MAP과 LIST로 변경해 주는 작업도 진행 합니다. (혹시나 ENUM 값을 다른 곳에서 초기화하거나 값을 변경하는 불상사를 막을 수 있음)





5. JSP에서 Context-param으로 등록한 ENUM을 가져다가 사용합니다.

위와 같은 과정을 거치면 모든 JSP에서 해당 ENUM 값을 가져다가 사용할 수 있습니다. 사용하는 방

법에 대한 가이드를 참고 하셔서 사용하시면 됩니다.

[ 참고 가이드 ] 
1) web.xml에 리스너 등록 
2) web.xml의 context-param으로 enumServletContextConfig를 등록. value값에 사용할 enum클래스명을 기입 
3) jsp에서 등록한 enum을 ${enum명(소문자시작)} 형식으로 호출할 수 있다. ex) ${day.MONDAY} 
4) enum.values()의 경우 ${enum명 + List} 형식으로 호출할 수 있다. ex) ${dayList}


저 같은 경우에는 list형태로 다음과 같이 사용하였습니다. ${smsServiceTypeList}


포스팅은 여기서 마치고 이번 포스팅을 진행하다보니 이번 포스팅을 이해하기 위해 필요한 기본적인 내용들에 대해서(web.xml, servletContext.xml, applicationContext.xml 등등)는 아직 포스팅이 많이 안된 것 같은데 시간 나는 대로 다시 한 번 개념을 정리할 겸 포스팅하도록 하겠습니다.


감사합니다.





반응형
반응형

 


 안녕하세요 이번 포스팅에서는 Xss Filter에 대한 간단한 개념과 이번에 작업을 하다가 헤맸던 JSP에서 폼의 타입이 enctype ="multipart/form-data"일 때 XSS FILTER가 적용되지 않았던 문제에 대해 알아보고자 합니다. Xss Filter를 기존에 써봤다는 가정 하에 포스팅을 진행하겠습니다. ( 기본적인 XSS Filter를 적용하는 법에 대한 설명은 포함되어 있지 않음을 미리 말씀드립니다. )



 ▶ XSS Filter란???


 Xss Filter란 사용자들의 악의적인 스크립트 공격(ex, <script> alert("melong"); </script> )등을 막기 위해 사용하는 filter로 안정적인 웹서비스를 위해서는 필수적인 요소입니다. XSS는 Cross-Site Scripting를 의미한다. 보통 해당 filter를 사용하기 위해서는 web.xml에 필터를 정의해주고 거기에 해당하는 url을 매핑해주고 filter 클래스를 작성해주면 된다.( 이에 대한 내용은 생략합니다.) 



 ▶ 작업 중 문제 발생 ( JSP에서 form의 타입이 enctype="multipart/form-data"일 경우 XSS Filter를 타지 못함)


 보안팀으로부터 페이코 앱 고객센터의 문의를 넣는 페이지에 xss filter가 적용되어 있지 않아 보안에 취약하다는 메일을 한 통 받았습니다. 메일을 받고 가장 먼저 확인한 것은 "web.xml에 해당 문의를 넣을 때의 url이 XSS Filter url에 매핑이 되어있는가" 였습니다. 이를 확인한 결과 해당 url이 XSS Filter에 잘 매핑되어 있는 것을 확인 할 수 있었습니다. 여기서 되게 의아했습니다. 그래서 제가 직접 페이코 앱에서 스크립트( <script> alert("N"); </script> )를 제목 부분에 넣어 문의를 넣어 보았습니다.




 1:1 문의에 들어가서 문의를 넣고 내 문의 보기를 누르자 alert창이 뜨는 것을 볼 수 있었습니다. 도대체 왜 xss filter에 매핑이 되어 있는데 해당 스크립트의 내용이 안 걸러지는지 의아했습니다. 

                                                                           


 


 그림(보안상의 이슈로 모자이크 처리)을 보면 아시겠지만 /~/app/mail/addInquiry.nhn이 문의를 넣을 때 요청되는 url이고 잘 mapping되어 있는 것을 확인하실 수 있습니다. 잘 매핑은 되어 있는데 왜 필터링이 안될까하는 의문과 함께 filter와 wrapper클래스를 디버깅해 본 결과 폼에 의해 전송되는 값들을 제대로 호출해오지 못하는 점을 발견 하였습니다. 그래서 검색을 해본 결과 

 


 그림에서 보는 것과 같이 form 타입을 multipart/form-data로 폼을 넘길 경우 xss filter에서 해당 폼의 request의 값들을 제대로 읽어오지 못하는 것을 알 수 있었습니다. 해당 페이지가 기본적인 제목, 내용을 입력 받고 첨부파일 기능 또한 있는 페이지 이기 때문에 폼의 타입이 multipart/form-data일 수 밖에 없었습니다. 따라서 해당 타입으로 폼이 전송될 때 파일첨부 이외의 값들에 대해 XSS Filter를 통해 스크립트와 악의적인 공격들을 막아주어야하는데 XSS Filter가 폼의 값들을 제대로 읽어들이지 못하고 있었습니다. 문제는 multipart/form-data형식의 경우에는 파라미터를 읽을 때 getPart()와 getParts()라는 메소드를 사용하는데 해당 사용하고 있는 filter 클래스를 보면  getParameter와 getParameterValues()로 요청으로 부터 넘어온 값들에 대해 filter를 적용하는 것을 확인할 수 있었습니다. (밑의 그림)



 ▶ 문제 해결 방법


 위와 같이 form의 타입이 enctype="multipart/form-data"일 경우 XSS Fitler를 타지 못하는 문제를 해결 하기 위해서 XSS Filter 전에 "MultipartFilter"를 적용해 주어 먼저 multipartFile에 대해 필터를 적용해 주고 XSS Filter를 타게 해주어야 합니다. 


 


 그림(혹시나 해서 특정 부분은 모자이크 처리)과 같이 XSS 필터 위에 multipartFilter를 적용한 것을 보실 수 있습니다. 여기서 주의 사항은 multipartFilter에 url에 매핑을 시켜줬다고 해서 XSS Filter에서 url을 제거하면 안됩니다. ( 두 필터 모두에 url이 매핑되어 있어야함!!!! 저는 mutipartFilter에 매핑시켜줬으니까 xss에는 안해도 되나하고 안했다가 많은 시간을 낭비했다는 ㅠ,ㅠ) 이로 인해 form의 타입이 enctype="multipart/form-data"여도 XSS Filter가 잘 작동하는 것을 확인 할 수 있었습니다.



 ▶ multipartFilter를 XSS Filter위에 적용하기 전 requerst를 받아오는 모습과 적용 후 request 비교


먼저 multipartFilter 적용 전 request



다음 web.xml에 multipartFilter를 적용한 후



 request로 넘어오는 값들이 확연히 다른 것을 보실 수 있습니다. multipartParameters를 까보게 되면 해당 페이지에 기입한 여러 값들이 정삭적으로 잘 넘어오는 것을 확인할 수 있었습니다. 이로 인해 제목이나 본문에 스크립트가 들어간다고 해도 XSS Filter를 통해 잘 처리 할 수 있었습니다.

 




반응형
반응형

h3. Gatling ???

 

- Gatling은 오픈 소스 부하 테스트 프레임워크로 Scala로 개발되었으며, Async Http Client와 Netty, 그리고 Akka를 사용한다.

- Gatling은 여러 개의 테스트를 실행하는 것이 가능하며 복잡한 시뮬레이션도 테스트 가능하다.

- Gatling은 무료 성능 측정 툴로 강력한 report기능을 제공해 주는 장점이 있습니다.

 

간단하게 사용법에 대해서 설명하도록 하겠습니다.

 

 

h5. \[ 시작전 설치 \]

 

[참고] Gatling을 사용을 위해서는 자바가 설치되어 있어야함[http://gatling.io/#/resources/download]

들어가서 zip파일을 받아 압축을 풀어줍니다.

 

 

 

h5. *1. Gatling 디렉터리 구조*

 

/bin 에는 스크립트를 생성(녹화)하는 recorder.sh와 스크립트를 실행하는 gatling.sh 파일이 있고

 

/conf에는 설정 파일이, /lib에는 라이브러리가,

 

/results에는 스크립트를 실행한 결과 페이지(HTML)가,

 

마지막으로 /user-files에는 녹화한 스크립트 파일(scala)과 데이터 파일이 들어 있습니다.

테스트 데이터 파일은 data 폴더에, 스크립트 파일은 simulations 폴더에 있고, 각각이 클래스 패스가 잡혀 있으니 구조에 맞춰 파일을 위치시키면 됩니다.

 

 

 

 

h5. *2. 스크립트 작성*

/user-files/simulations 안에서 동작시킬 스크립트를 작성합니다.

brooklynGatlingTest.scala 가 테스트를 위해 작성한 스크립트 입니다.

 

 

 

스크립트 내용은

{code:scala}

import io.gatling.core.Predef._

import io.gatling.http.Predef._

import scala.concurrent.duration._

 

class BrooklynGatlingTest extends Simulation {

   val httpProtocol = http

                .baseURL("http://10.161.0.28:8080")       // 테스트할 base URL

                .acceptCharsetHeader("ISO-8859-1,utf-8;q=0.7,*;q=0.7")

                .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

                .acceptEncodingHeader("gzip, deflate")

                .acceptLanguageHeader("en-US,en;q=0.5")

                .disableFollowRedirect

 

  val scn = scenario("Brooklyn DB select Test Scenario")

    .exec (

          http ("request_1" )

        .get ( "/randomAdid" )  // 테스트하고자하는 페이지 URI

         .check(status.is (200))

        )

 

     setUp(

     scn.inject(rampUsers(100) over (1 seconds))  // 1초에 100명의 유저

     ).protocols(httpProtocol)

}

{code}

자세한 옵션 및 사용법은 아래 링크를 참고하시면 좋을 것 같습니다.

\[ 참고링크 \][https://greencrayon00.wordpress.com/2015/11/01/%EA%B0%9C%ED%8B%80%EB%A7%81gatling%EC%9C%BC%EB%A1%9C-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0-1%EB%B6%80/]

 

 

 

 

h5. *3. 테스트하기*

작성한 스크립트로 실제 테스트를 진행해보도록 합시다.

테스트는 bin/ 디렉터리로 이동해 ./gatling.sh 스크립트를 실행합니다.

해당 스크립트를 실행하게되면 컴파일된 파일의 목록을 보여줍니다.

 

 

 

위와 같이 뜨면 brooklynGatlingTest가 0번째 목록에 있기때문에

콘솔에 0을 치고 enter.

 

Simulation BrooklynGatlingTest started가 뜰때까지 enter.

 

테스트가 수행되는 과정이 보이게 됩니다.

 

 

테스트가 완료되게 되면 테스트 결과를 리포트로 받아보실 수 있습니다.

 

 

 

h5. *4. 리포트확인*

리포트트는 result 디렉터리에서 확인가능하고 테스트별 디렉터리가 생겨납니다.

테스트 디렉터리내의 index.html을 열어보시며 테스트 결과를 볼 수 있습니다.

 

 

 

 

 

*자세한 설정 및 옵션은 아래 url 참고부탁드립니다.*

[http://gatling.io/docs/2.2.3/]

반응형
반응형

[ 작업 배경 ] 


현재 로그 시스템에서 여러 여러 파일 포맷(gz, parquet 등)사용하여 데이터 처리 작업이 이루어지고 있고


앞으로 추가적인 작업 및 dmp 운영에 통합된 데이터 포맷을 사용하여 관리가 필요함.


그 타겟으로 ORC, PARQUET 데이터 포맷을 검토해보게되었다. gz은 가급적 지양하도록 하자...



[ 테스트 시나리오 ]


1 case : text파일을 읽어 dataframe으로 변형 후 orc, parquet 파일포맷으로 저장하는 속도 비교


2 case : orc, parquet 파일포맷으로 저장되어 있는 파일을 읽어들여 row를 count하는 속도 비교


3 case : orc, parquet 파일포맷을 읽어들여 데이터 프레임을 table로 생성 후 spark sql로 adid컬럼만 추출하여 orc, parquet 파일포


맷으로 다시 저장하는 속도 비교



[ 테스트 환경 ]

- MacBook Pro (Retina, Mid 2014)

- Intel Core i5 2.6GHz 듀얼코어

- 8GB RAM 

- 256GB SSD

- Spark Version : 2.0.1

- Scala Version : 2.11.8



[ 테스트 데이터 사이즈 및 포맷 ]


- 총 row : 17685383

- DataFormat

0000040B-D236-4AF8-B95A-80B5FBA306A1 0 2 0

0000040F-91BF-4726-A3E9-B53836D3848D 1 6 2

0000067C-8719-4E41-928C-417965803D61 1 9 1

000006C6-2E07-4F84-AB8A-13A0D412C745 0 6 2

.

.

.



[ 테스트 코드 ]


scala code

  // 1 case : text 파일을 읽어 dataframe으로 변형 후 parquet 파일포맷으로 저장

  def textFileSaveToParquet(sc : SparkContext, sqlContext : HiveContext) : Unit = {

    import sqlContext.implicits._


    val demo_adids = sc.textFile("/Users/nhnent/Desktop/demo20170209.out")

    val demo_adids_df = demo_adids.map(_.split("\t") match { case Array(v1, v2, v3, v4) => (v1, v2, v3, v4)}).toDF("adid", "gender", "age", "married")


    val output = "/Users/nhnent/Desktop/spark_sample_data/testResult/parquet_result.out"

    demo_adids_df.toDF.write.format("parquet").save(output)


  }



  // text 파일을 읽어 dataframe으로 변형 후 orc 파일포맷으로 저장

  def textFileSaveToOrc(sc : SparkContext, sqlContext : HiveContext) : Unit = {

    import sqlContext.implicits._

    val demo_adids = sc.textFile("/Users/nhnent/Desktop/demo20170209.out")

    val demo_adids_df = demo_adids.map(_.split("\t") match { case Array(v1, v2, v3, v4) => (v1, v2, v3, v4)}).toDF("adid", "gender", "age", "married")



    val output = "/Users/nhnent/Desktop/spark_sample_data/testResult/orc_result.out"

    demo_adids_df.toDF.write.format("orc").save(output)

  }



  // parquet 파일을 읽어들여 해당 row를 count

  def readParquetFile(sc : SparkContext, sqlContext: SQLContext) : Unit = {

    val file_path = "/Users/nhnent/Desktop/spark_sample_data/testResult/parquet_result.out/part-*"

    val parquet_file = sqlContext.read.parquet(file_path)

    val count_parquet_file = parquet_file.count()

    log.warn(s"parquet count : $count_parquet_file")

  }

  


  // orc 파일을 읽어들여 해당 row를 count

  def readOrcFile(sc :  SparkContext, sqlContext: SQLContext) : Unit = {

    val file_path = "/Users/nhnent/Desktop/spark_sample_data/testResult/orc_result.out/part-*"

    val orc_file = sqlContext.read.orc(file_path)

    val count_orc_file = orc_file.count()

    log.warn(s"orc count : ${count_orc_file}")

  }



  // parquet 파일을 읽어들여 dataframe을 테이블로 생성 후 spark sql로 adid만 뽑아 낸 후 parquet 포맷으로 저장

  def readParquetAndSaveParquet(sc : SparkContext, sqlContext : SQLContext) : Unit = {

    val file_path = "/Users/nhnent/Desktop/spark_sample_data/testResult/parquet_result.out/part-*"

    val parquet_file = sqlContext.read.parquet(file_path)

    parquet_file.createOrReplaceTempView("info")


    val parquest_adid_file=sqlContext.sql("select adid from info")


    val output = "/Users/nhnent/Desktop/spark_sample_data/testResult/parquet_adid_result.out"

    parquest_adid_file.toDF.write.format("parquet").save(output)


  }



  // orc 파일을 읽어들여 dataframe을 테이블로 생성 후 spark sql로 adid만 뽑아 낸 후 orc 포맷으로 저장

  def readOrcAndSaveOrc(sc : SparkContext, sqlContext : SQLContext) : Unit = {

    val file_path = "/Users/nhnent/Desktop/spark_sample_data/testResult/orc_result.out/part-*"

    val orc_file = sqlContext.read.orc(file_path)


    orc_file.createOrReplaceTempView("info")


    val orc_adid_file=sqlContext.sql("select adid from info")


    val output = "/Users/nhnent/Desktop/spark_sample_data/testResult/orc_adid_result.out"

    orc_adid_file.toDF.write.format("orc").save(output)

  }




[ 테스트 결과 ]


1 case(parquet) : Execute textFileSaveToParquet : 34211

1 case(orc)     : Execute textFileSaveToOrc : 17173

2 case(parquet) : Execute readParquetFile : 1484

2 case(orc)     : Execute readOrcFile : 7161

3 case(parquet) : Execute readParquetAndSaveParquet : 30632

3 case(orc)     : Execute readOrcAndSaveOrc : 26512


1 case(parquet) : Execute textFileSaveToParquet : 31381

1 case(orc)     : Execute textFileSaveToOrc : 27624

2 case(parquet) : Execute readParquetFile : 924

2 case(orc)     : Execute readOrcFile : 5452

3 case(parquet) : Execute readParquetAndSaveParquet : 21010

3 case(orc)     : Execute readOrcAndSaveOrc : 18438



[ 결론 ]


< read&write 성능 >

파일 WRITE 성능 : *ORC > PARQUET*

파일 READ 성능  : *PARQUET > ORC*

파일 READ & SQL & 파일 WRITE 성능 : *ORC > PARQUET*


<파일 사이즈>

725M    원본파일(demo20170209.out)

560M orc_adid_result.out

582M orc_result.out

557M parquet_adid_result.out

572M parquet_result.out

*parquet > orc*


좀 더 큰 데이터로 알파 환경에서 확인이 필요하긴 하지만 로컬환경에서 테스트 결과 파일을 읽어들일 때의 속도는 parquet가 빨랐지


만 전체적인면으로 볼 때 ORC를 사용하는 것이 좋다고 판단된다.



반응형
반응형

 JSP/SERVLET 파트에서 SERVLET이 동작하는 흐름에 대해서 알아봤다. 컨테이너에서 서블릿이 초기화되고 그 해당 서블릿이 어떻게 DispatcherServlet에 의해 처리되어 뷰를 반환하게 되는지에 대해 살펴보도록 하겠다.


  DispatcherServlet에 대해 간단히 설명하자면, DispatcherServlet은 Controller로 향하는 모든 웹 요청의 진입점이며 웹 요청을 처리, 결과 데이터를 Client에게 제공하는 역할을 한다.네 맞습니다. DispatcherServlet은 우리가 각각 분리하여 만든 Model 파트와 Controller파트 View 파트를 조합하여 브라우저로 출력해주는 역할을 수행하는 클래스이며 DispatcherServlet으로 인해 web.xml의 역할을 획기적으로 축소시켜 주었다.

예전 DispatcherServlet이 있기전에는 모든 사용자의 요청 url을 일일이 해당 요청을 처리하는 servlet과 매핑시켜주는 작업을 지정해 주었어야만 했다.예를 들어, 기본URL/happy, 기본URL/contents, 기본URL/subject 등의 요청이 들어왔을 때 이에 해당하는 서블릿과의 매핑 작업을 web.xml에 모두 정의해주고 사용했어야 했던 만큼 web.xml 도 복잡해지고 손수 작업에 필요한 서블릿 정의와 매핑작업을 해줘야 했기 때문에 여간 귀찮은 일이 아니였다.

하지만 지금은 web.xml에

이렇게 DispacherServlet을 정의해주고 모든 URL에 대해 매핑을 해주게 되면 DispatcherServlet이 요청 url에 해당하는 컨트롤러를 찾아 매핑해주게 된다. 얼마나 고마운 일인가. 위의 소스코드에 대해 이해가 안가시는 분은  http://blog.naver.com/kim3zz/220273028176 servlet 매핑에 대한 설명 부분을 참고하시길 바란다.


DispatcherServlet은 이렇게 web.xml 을 획기적으로 축소시켜주었고 개발자에게 요청에 따른 컨트롤러를 일일이 매핑해야하는 번거로움 또한 덜어주었다. 이제 그럼 DispatcherServlet이 어떻게 동작하는지에 대해 알아보자.

[ 그림 1 ] 사용자가 특정 URL을 요청하게 되고 웹서와 컨테이너를 거쳐 해당 요청이 DispatcherServlet을 타게 된다.

[ 그림 2 ] DispatcherServlet은 요청에 대한 정보를 HandlerMapping에게 보내 요청을 처리할 수 있는 Controller을 찾아 DispatcherServlet에게 다시 반환하게 됩니다. HandlerMapping은 클라이언트 요청을 어떤 Controller가 수행할지의 여부를 결정해 주며 스프링의 디폴트 전략에 의해 BeanNameUrlHandlerMapping이 설정되어 있어 따로 설정을 하지 않아도 요청 url과 동일한 이름을 갖는 Controller을 찾아 매핑시켜 주게 된다.

[ 그림 3 ] DispatcherServlet은 해당 컨트롤러에게 요청에 대한 처리를 부탁하고 컨트롤러는 요청을 처리한 후에 응답받을 ModelAndView를 리턴하게 됩니다.

[ 그림 4, 5 ] ViewResolver는 컨트롤러가 반환한 ModelAndView를 받아 해당 view가 존재하는지 검색하게 됩니다. 해당 View가 있다면 전처리 과정을 거쳐 해당 View를 반환해 줍니다.

InternalResouceViewResolver를 보시면 name="prefix"와 "suffix"를 보실 수 있는데 컨트롤러가 던지 view 앞에 붙여지는 것이 prefix이고 뒤에 붙여지는것이 jsp가 되게 됩니다. 예를 들어 컨트롤러가 "board"라는 뷰 스트링 객체를 던졌다면 리졸버를 통해 /WEB-INF/views/board.jsp 가 되어 해당 뷰에서 요청이 보여지게 됩니다.

다음에는 web.xml에서는 어떠한 작업들이 이루어지는 지에 대해 살펴보도록 하겠습니다.

ref :https://kisukpark.wordpress.com/2013/08/29/spring-mvc-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90/http://egloos.zum.com/springmvc/v/504151




반응형
반응형


리눅스 소유자/그룹 변경 root 에서 irteam 으로~!소유자가 root일 경우에는 일반 계정으로 해당 스크립트 수정이 안되거나 빌드시 해당 스크립트에 접근이 안되는 문제가 있다. 따라서 톰캣, 아파치 등 설치할 때 sudo 계정이 아닌 일반 계정으로 설치하는 것이 좋다.

부득이하게 sudo 권한으로 설치를 했다면 다음과 같은 명령어로 권한 변경

sudo chown -R[옵션] [소유자:소유그룹] [디렉토리 or 파일명]> -R : 하위 디렉토리/파일에 모두 적용> 해당 파일의 모든 파일들을 순환하며 소유자 그룹 변경

그림에서 보면 logging.properties와 server.xml이 root로 되어있어서 일반 계정으로 수정 불가능

sudo chown -R irteam:irteam server.xml로 소유자 변경

변경된 것을 확인 할 수 있다.

서버 작업 시 특정 계정으로 스크립트를 실행했는데 접근이 안된다면 chown 명령어를 통해 소유자 및 그룹을 변경하도록 하자!!!




반응형
반응형



 안녕하세요. 오늘은 crontab에 대한 간단한 소개와 crontab을 통한 특정 기간 이상이 지난 log를 삭제하는 법에 대해서 살펴보도록 하겠습니다. 지나치게 불필요한 log를 많이 가지고 있게 되면 서버 메모리 공간에 여유가 부족해 문제를 일으킬 수도 있기에 일정 기간이 지난 로그 혹은 불필요 로그는 가지고 있지 않는 편이 원할한 서버 관리에 도움을 줄 수 있음을 상기하고 포스팅 시작해보도록 하겠습니다.

#CrontabCron은 원하는 시간에 특정 명령을 서버에서 실행 시키기 위한 데몬으로 특정 시간에 특정 작업이 필요할 경우 사용하게 됩니다.예를 들어 매일 자정에 그 날에 자료를 백업을 한다거나 매일 새벽 2시에 로그를 백업하는 작업이 필요한 경우 등 이렇게 특정 시간에 반복적으로 작업을 할 경우에 사용됩니다.한 마디로 일정 기간마다 특정 명령을 실행하도록 하는 것이다.

#Crontab 사용하기

crontab -l> 현재 crontab에 등록 된 작업보기

crontab -e> crontab 편집하기


crontab 등록




#Crontab 작업~ crontab -e를 열어 원하는 시간대에 작업이 이루어질 명령어를 등록해 줍니다.

제가 등록해준 작업은 #Tomcat log 쪽으로 매일 23시 59분에 rotatelog.sh를 실행하게 됩니다.

rotatelog.sh에서는 아파치로그, 프로젝트 로그, 톰캣로그를 삭제하는 스크립트가 작성되어 있습니다.



현재 3년 이상 된 로그들도 쌓여 있는 것을 확인 할 수 있는데 서비스 특성상 1년 까지만 보관하면 별문제 없을 것 같아 현재 날짜를 기준으로 일년이 지난 로그들을 삭제하도록 하였습니다.

#Crontab 등록 스크립트 작성시 find명령어 간단 정리

 

                                      이미지 출처 : http://dbrang.tistory.com/867



이상으로 포스팅을 마치겠습니다.




반응형
반응형


 안녕하세요. 이번에는 전에 한 번 포스팅 했던 '아파치 톰캣7 따라잡기 Part.1"에서 정리하지 못한 부분을 추가적으로 포스팅하도록 하겠습니다. 책을 정리하는 입장에서 제가 잘 몰랐던 부분이나 필요 부분을 문서화 시키는 작업임에 다소 설명적인 부분이 많이 생략될 수 있음을 이해해주시면 감사하겠습니다. 정리를 시작하도록 하겠습니다.


왜 아파치 HTTP 서버를 사용하는가?# 정적 컨텐츠를 효율적으로 제공톰캣도 정적 컨텐츠를 제공할 수는 있지만 아파치 HTTP 서버에 비해 반응 시간이 느리다.

# 10퍼센트 속도 증가아파치 HTTP 서버는 콤캣과 비교해 10퍼센트 정도 더 효율적으로 정적 컨텐츠를 제공한다. 사용자 부하가 높은 상황에서는 아파치를 통합하는 것이 좋다.

# 클러스터링아파치는 톰캣에 다중 인스턴스 연결을 안정적으로 제공하는 가장 효율적인 서버다.

# 보안아파치는 사용자와 호스트 기반 보안을 제공한다. 톰캣에서도 이 기능을 제공한다. 따라서 우리는 애플리케이션의 요구사항을 고려해 아파치와 톰캣 둘 중 누구의 보안을 활성화할 것인지 결정해야 한다.

# 다중 웹사이트 호스팅아파치 HTTP 서버의 가장 훌륭한 기능 중 하나는 다중 웹사이트 호스팅 기능이다. 다중 웹사이트 호스팅은 톰캣 7에서 처음 선보인 기능으로 http.conf를 이용해 32개가 넘는 가상 호스트를 설정할 수 있다. 32개 이상의 가상 호스트를 설정하려면 virtual.conf 파일을 별도로 만든 다음 http.conf에서 virtual.con를 포함하도록 설정해야 한다. (32개 이상이 아니더라도 virtual.conf 파일을 두어 사용하는 경우도 있음)


아파치 Jserv 프로토콜 (Apache JServ Protocol)Jserv는 평문 텍스트가 아닌 바이너리 형태의 데이터를 네트워크로 전송하도록 개발된 프로토콜이다. Jserv는 TCP와 패킷 기반 프로토콜을 사용하므로 웹 서버 성능이 증가한다. 보통 AJP라 부른다.AJP 프로토콜은 mod_jk와 mod_proxy로 이루어졌다. mod_jk와 mod_proxy는 브라우저를 통해 높은 컨텐츠 응답률을 전송할 때도 도움이 된다.

mod_jkmod_jk는 아파치느 IIS(Internet Information Service) 같은 웹 서버를 톰캣 7과 통합할 때 사용하는 AJP 커넥터다. mod_jk를 설치하지 않으면 톰캣에 프론트엔드 웹 서버를 제공할 수 없다. mod_jk는 프론트엔드 웹 서버 뒤에 톰캣을 숨기고 URL을 접근할 때 포트 번호를 제거하는 데 상당히 유용한 모듈이다.

mod_jk.conf 파일의 세부 정보모듈 경로아파치를 시작할 때 모듈을 로드할 위치를 정의한다.EX ) LoadModule jk_module modules/mod_jk.so

작업자 파일 경로작업자 파일 위치를 정의한다. 작업자 파일은 톰캣 인스턴스의 IP, 포트, 로드 분산 방법 등의 정보를 포함한다.EX ) JkWorkersFile conf/workers.properties

# 로그 파일로그 파일은 아파치 톰캣 통합 과정이 기록된다. 아파치/톰캣 간의 연결 양호 상태도 기록된다.EX ) JkLogFile logs/mod_jk.log

# URL 매핑URL 매핑은 아파치 컨텍스트 경로, 정의된 URL 요청 재전송 규칙을 정의한다. 다음과 같이 URL 매핑을 설정했다면 사용자가 http://localhost/sample라는 URL을 입력햇을 때 해당 요청을 톰캣 node1으로 재전송한다.EX ) JkMount /sample/* node1

# 로그 수준로그 수준 파라미터는 mod_jk에서 수행하는 다양한 이벤트를 logs가 어떻게 처리할지를 정의한다.EX ) JkLogLevel info

workers.properties의 세부 정보# 노드 이름호스트의 공통 이름worker.list = node1

# 톰캣의 AJP 포트 세부 정보톰캣에서 AJP 요청을 어떤 포트로 수락하는지worker.node1.port = 8009

# 톰캣 호스트 IP톰캣 인스턴스가 실행 중인 IP 주소worker.node1.host = 10.130.240.31

# 사용중인 프로토콜통신에 사용하는 프로토콜. 기본값은 AJPworker.node1.type = ajp13

# 부하 분산 방법라운드 로빈, 연결 지속 등worker.noe1.1bfactor = 1

톰캣 설정커넥터 포트기본적으로 톰캣은 HTTP 프로토콜 8080포트에서 실행된다. 모든 사람이 기본 포트 번호를 알고 있으므로 해커는 쉽게 포트에 접근해 서버 공격을 시도할 수 있다. 따라서 톰캣의 보안상 가능하면 항상 커넥터 포트와 AJP 포트(기본 값 8009)를 바꾸는 것이 좋다.

# 톰캣을 루트 사용자로 실항하는 것은 치명적인 결과를 초래할 수 있다. 따라서 루트가 아닌 새 사용자를 만들고 만든 사용자가 톰캣 서버를 실행할 수 있도록 권한을 부여하는 방식을 이용한다. 루트와 사용자 그룹에서 설정 파일에 접근할 수 있어야 하며, 이 사용자/그룹에 logs 등의 다른 디렉터리의 읽기/쓰기 권한도 줘야 한다.

# 톰캣 7의 SSL 설정 : 데이터 통신을 안전하게 보호하는 방버븡로 SSL(Secure Socket Layer)이  있다. SSL은 안전한 채널을 통해 데이터를 전송하는 암호 프로토콜이다. 서버가 암호키를 클라이언트 브라우저로 전송하면 클라이언트 브라우저가 암호키를 복호화 한 다음 서버오 클라이언트간에 핸드셰이크(handshake)가 이뤄진다. (이를 가리켜 안전 계층에서 두 방향 핸드셰이크가 일어났다고 한다.)언제 톰캿애 SSL이 필요한가?톰캣을 프론트엔드 서버로 사용할 때 SSL을 더 효과적으로 활용할 수 있다. 아파치나 IIS를 사용한다면 아파치나 IIS 서버에 SSL을 설치할 것을 권장한다.


다음은 마지막 톰캣에서 직면하는 문제들에 대한 해결책에 대해 책의 마지막 부분을 정리하도록 하겠습니다.




반응형
반응형

한가지. 메이븐이란 무엇인가?

메이븐을 한 마디로 정의해서 말을 하자면 개발자로 하여금 프로젝트 관리를 쉽게 도와주는 빌드 툴이라고  할 수 있습니다.

메이븐은 pom.xml이라는 파일에 기반합니다. POM(Project Object Model)이란 Xml 파일에 기반하여 반복적으로 진행되어 왔던 프로젝트 빌드, 리포팅, 문서화 작업등을 도와주는 문서라고 할 수 있습니다.

두가지. 많이 사용되고 있는 ANT와 MAVEN의 비교

아파치 앤트(Apache Ant)는 공통적인 프로젝트 디렉토리 구조와 같은 공식적인 관례를 가지고 있지 않으며, 개발자가 직접 소스코드의 위치와 소스코드의 빌드가 된 파일의 위치, 산출물을 어디에 위치시켜야 하는지 정확하게 지정해주어야 한다. 또한 앤트는 빌드를 실행하는 goal과 goal의 디펜던시를 정의할 수 있는 라이프 사이클을 가지고 있지 않다. 따라서 각 goal에 일일이 작업에 대한 순서를 지정해 주어야한다. 반면,

아파치 메이븐(Apache Maven)은 관례를 가지고 있다. 메이븐은 COC(Convetion over Configuration)의 개념에 입각한다. 설정보다 관례라는 뜻으로 앤트와는 다르게 하나 하나 작업에 대한 순서를 지정해주고 산출물에 대한 위치등을 정해줘야 한다면 메이븐은 어디에 소스코드가 위치하고, 소스코드 컴파일, 컴파일 된 소스코드의 산출물의 위치 등이 이미 다 지정되어 있어 따로 일일히 작업을 해주지 않아도 된다. 메이븐은 pom.xml 파일을 생성하고 기본 디렉토리 안에 소스를 위치시키는 것이 위의 빌드, 리포팅 등의 많은 작업들을 위해 해주어야하는 것의 전부이다.나머지는 메이븐의 특정 관례에 따라 처리하게 된다.

물론 설명으로만 보면 Maven이 Ant보다 훨씬 편해보이지만 그 어떤 것이 더 좋고 나쁘다고 말하기는 힘들 것 같다. 적적한 때에 적절한 툴을 사용하는 것이 중요하고 Maven이 Ant보다 더 많은 작업들을 개발자 입장에서 훨씬 쉽게 처리할 수 있지만 Ant보다 자유도가 떨어지기 때문에 세세한 컨트롤을 필요한 부분에 있어서는 Ant가 이점이 있다. 그러나 Ant는 너무나도 큰 자유도 때문에 script를 재사용하기 어렵고 문법 또한 훨씬 복잡하므로 개발을 하면 할수록 더욱 복잡해지고, 같은 내용을 반복적으로 사용해야하는 단점이 있다.

세가지. 메이븐의 장점

   1. 메이븐은 편리한 Dependency Libaray관리 기능을 제공한다.

프로젝트를 진행하다 보면 특정 기능을 구현하기 위해 jar파일 등의 라이브러리가 필요할 경우가 있다. 이 경우 메이븐 pom.xml에 단지 denpency만 추가시켜주면 알아서 해당 라이브러리를 다운받아 주게 된다.


이렇게 단순히 dependency만 추가하게 되면 junit을 사용할 수 있는 라이브러리를 다운 받아준다. 이러한 특징으로 인해 여러명이서 프로젝트 작업을 진행 할 경우 라이브러리 파일을 git이나 svn과 같은 도구를 통해 주고 받지 않고도 프로젝트에 필요한 라이브러리들을 pom.xml 하나로 쉽게 관리할 수 있는 큰 장점이 있다.

  2. 모든 프로젝트가 일관된 디렉토리 구조와 빌드 프로세스를 유지할 수 있다.


 설정보다 관례(Convention over Configuration)라는 아이디어에 따르면 옆과 같이 메이븐 프로젝트로 시작하게 되면 src/main/java, src/main/resouces, src/test/java, src/test/resources와 같은 구조를 발견할 수 있다. 이렇게 메이븐 프로젝트는 공통 구성을 가지게 되고누군가가 이 프로젝트를 봤을 경우 메이븐에 기반한 프로젝트라는 것을 쉽게 인식할 수 있을 뿐만 아니라 프로젝트의 전반적인 내용에 대해 어느정도 유추할 수 있게 된다. 이렇게 메이븐 템플릿 프로젝트로 프로젝트를 생성하면 프로젝트의 뼈대를 자동으로 생성할 수 있게 되는데 이 같은 기능을 아키타입(archetype)이라고 한다.

  3. 메이븐이 제공하는 다양한 플러그인을 활용할 수 있다.

  4. 신규프로젝트 세팅을 쉽고 빠르게 진행할 수 있다. (Archetype 기능을 통해)

사가지. Pom.xml의 기본 구성요소

Maven 프로젝트를 생성하면 pom.xml 파일이 프로젝트 루트 디렉터리에 생성된다. 이 pom.xml 파일은 Project Object Model 정보를 담고 있는 파일로서, 이 파일에서 다루는 주요 정보는 다음과 같다.

  1. 프로젝트 정보 : 프로젝트의 이름, 개발자 목록, 라이선스 등의 정보를 기술

  2. 빌드 설정 : 소스, 리소스, 라이프 사이클 별 실행할 플러그인 등 빌드와 관련된 설정을 기술

  3. 빌드 환경 : 사용자 환경 별로 달라질 수 있는 프로파일 정보를 기술

  4. POM 연관 정보 : 의존 프로젝트 (모듈), 상위 프로젝트, 포함하고 있는 하위 모듈 등을 기술


잘 안보이지만 왼쪽 위에 보이는 Group Id, Artifact Id, Packagin, Version 에 대해 설명하겠다.

Group Id : 프로젝트를 생성하는 조직의 고유 아이디어를 결정. 보통 Domain Name을 사용

Artifact Id : 프로젝트를 식별하는 유일한 아이디를 말한다. 보통 Project Name을 사용

Packaging : 프로젝트를 어떤 형태로 패키징 할지 결정한다. 진행하고 있는 프로젝트의 경우 웹 프로젝트이기 때문에 war로 설정되어 있다.

Version : 프로젝트 버전 구조 [메이버 버전.마이너 버전. 순차 버전 - 식별자]가 되겠다. 현재는 1.0.0-BUILD-SNAPSHOT으로 지정되어 있는 것을 볼 수 있다.

밑에 보이는 Overview, Dependencies, 등등이 있는 탭에 pom.xml을 눌러보면

과 같이 pom.xml에 표시되어 있는 것을 확인할 수 있다. pom.xml 은 workspace상에 여러 군데 있을 수 있고 hierarchy에 따라 overwrite 또는 상속을 받게 된다.

참고.

자바 세상의 빌드를 이끄는 메이븐 - 박재성 저

[포스팅을 하며]

글을 쓰는 일이 정말 쉽지 않다는 것을 다시 한 번 느낀다. 앞으로 현업에서 새롭게 습득한 지식이나 어려움을 겪었던 경험들에 대해서도 포스팅 해볼 생각이다. 아직은 많이 부족하지만 꾸준히 지식 습득에 노력을 기울이고 학습한 내용에 대해 포스팅 하며 더욱 지식을 굳건히 할 수 있도록 해야 겠다. 미흡하거나 잘못 된 부분의 경우 댓글을 통해 얘기해주면 감사하겠습니다.



반응형

+ Recent posts