<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tech on</title><link>https://taetaetae.github.io/categories/tech/</link><description>Recent content in Tech on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 04 Apr 2021 18:54:00 +0900</lastBuildDate><atom:link href="https://taetaetae.github.io/categories/tech/index.xml" rel="self" type="application/rss+xml"/><item><title>공모주 알리미 개발 후기 - 3부</title><link>https://taetaetae.github.io/posts/public-offering-notice-3/</link><pubDate>Sun, 04 Apr 2021 18:54:00 +0900</pubDate><guid>https://taetaetae.github.io/posts/public-offering-notice-3/</guid><description>&lt;p>　﻿&lt;a href="https://t.me/PublicOfferingNotice" target="_blank" rel="noopener noreffer ">공모주 알리미&lt;/a>라는 토이 프로젝트 개발기의 마지막 포스팅이다. 토이 프로젝트를 왜 시작하게 되었고 어떻게 설계하게 되었으며 데이터는 어떤 식으로 수집하고 그 데이터를 어떤 방법으로 사용자들에게 알림을 보내기까지 알아보았다. 이제는 이러한 일련의 &amp;lsquo;파이프라인&amp;rsquo;을 자동화해야 할 시간이다. 사람이 직접 수동으로 로컬 컴퓨터에서 위 파이프라인을 실행하는 것이 아니라 별도의 서버에 해당 애플리케이션이 등록되어 있고 이를 어떤 무언가에 의해 트리거링을 해주는 방식으로 말이다.﻿&lt;/p>
&lt;ul>
&lt;li>1부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-1" rel="">프로젝트 설계, 데이터 수집&lt;/a>&lt;/li>
&lt;li>2부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-2" rel="">수집한 데이터 알림 발송&lt;/a>&lt;/li>
&lt;li>3부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-3" rel="">서버 선정 및 릴리즈&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="서버-선정">서버 선정&lt;/h2>
&lt;p>　﻿1부에서 이야기했던 것처럼 &lt;a href="https://www.heroku.com" target="_blank" rel="noopener noreffer ">heroku&lt;/a>라는 PaaS(Platform as a service)를 사용하면 될 것 같았다. 무료 플랜으로도 설계했던 서비스 내용을 모두 소화 가능했기 때문이다. 앞서 만든 Spring Boot Application 을 heroku에 배포를 해보자.&lt;/p>
&lt;p>　heroku에서 새로운 &amp;lsquo;App&amp;rsquo;을 생성한다. 아래에서 보여주고 있는 화면대로 App name을 지정하고 만들기만 하면 끝. 그러면 배포 방법이 여러 가지가 나오는데 heroku에서 제공하는 CLI를 사용하는 방법, 그리고 Github 과 연동하거나, 컨테이너 레지스트리를 활용하는 방법 총 3가지가 있다. 여기서 필자는 Github을 활용해서 연동하는 방법을 소개해 보고자 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-3/1.jpg" title="/images/public-offering-notice-3/1.jpg" data-thumbnail="/images/public-offering-notice-3/1.jpg" data-sub-html="&lt;h2>heroku 에서 app을 생성하자.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-3/1.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-3/1.jpg, https://taetaetae.github.io/images/public-offering-notice-3/1.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-3/1.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-3/1.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">heroku 에서 app을 생성하자.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿로컬에서 만든 애플리케이션을 Github에 push 하면 Github Repository 가 생기고 작업 파일들이 정상적으로 업로드된 것을 확인할 수 있다. 그다음 heroku에서 만들었던 App 페이지에서 Deploy 탭을 클릭하면 아래와 같이 3가지 방법으로 Deploy를 할 수 있다고 나오고, 이 중에 &amp;ldquo;Connect to Github&amp;quot;을 선택하면 Github 과 연동할 수 있는 버튼이 생기고 이를 누르면 자동으로 본인의 Github 내 Repository를 등록할 수 있도록 화면이 바뀐다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-3/2.jpg" title="/images/public-offering-notice-3/2.jpg" data-thumbnail="/images/public-offering-notice-3/2.jpg" data-sub-html="&lt;h2>heroku 와 github 연동&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-3/2.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-3/2.jpg, https://taetaetae.github.io/images/public-offering-notice-3/2.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-3/2.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-3/2.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">heroku 와 github 연동&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿그다음 위에서 Github에 push 했던 Repository 이름을 적고 검색하면 조회가 되고 &amp;lsquo;Connect&amp;rsquo;를 누르면 자동으로 연결이 된 것을 확인할 수 있다. 연동된 Repository 브랜치에 코드가 푸시 되면 자동으로 heroku에 배포가 되도록 자동화 설정도 가능하고 그 아래에 보면 브랜치를 선택해서 배포를 수동으로 할 수 있기도 하다. 수동으로 푸시를 눌러보면 이런저런 빌드 로그가 나오고 최종적으로 배포가 되어 &lt;code>{Appname}.herokuapp.com&lt;/code> 을 접속해보면 서버에 배포가 되어있는 것을 확인할 수 있다.&lt;/p>
&lt;ul>
&lt;li>sample Github Code : &lt;a href="https://github.com/taetaetae/heroku/blob/master/src/main/java/com/taetaetae/helloworld/heroku/HelloWorldController.java#L11" target="_blank" rel="noopener noreffer ">taetaetae@heroku#HelloWorldController.java#L11&lt;/a>&lt;/li>
&lt;li>sample heroku app : &lt;a href="https://taetaetae-test.herokuapp.com/" target="_blank" rel="noopener noreffer ">https://taetaetae-test.herokuapp.com/&lt;/a>&lt;/li>
&lt;/ul>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-3/3.jpg" title="/images/public-offering-notice-3/3.jpg" data-thumbnail="/images/public-offering-notice-3/3.jpg" data-sub-html="&lt;h2>수동으로 배포를 해보자.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-3/3.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-3/3.jpg, https://taetaetae.github.io/images/public-offering-notice-3/3.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-3/3.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-3/3.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">수동으로 배포를 해보자.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿heroku에서는 이렇게 몇 번의 클릭만으로 간단하게 애플리케이션을 배포할 수 있는 기능을 제공하고 있고 서버 내 로그도 아래 화면처럼 보여주고 있기 때문에 쉽고 간단하게 서버를 구성하고 싶은 사용자들에게 매력적으로 보이는 것 같다. 단, 무료 플랜의 제한사항들을 자세히 살펴보고 사용할 것을 추천한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-3/4.jpg" title="/images/public-offering-notice-3/4.jpg" data-thumbnail="/images/public-offering-notice-3/4.jpg" data-sub-html="&lt;h2>서버 로그도 볼 수 있다!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-3/4.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-3/4.jpg, https://taetaetae.github.io/images/public-offering-notice-3/4.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-3/4.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-3/4.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">서버 로그도 볼 수 있다!&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="호출-테스트-문제의-시작">호출 테스트, 문제의 시작&lt;/h2>
&lt;p>　﻿앞서 만들었던 텔레그램 채널에는 아무도 가입을 하지 않았기에 배포한 heroku web endpoint를 호출하면 텔레그램 봇을 통해 알림이 오는 걸 테스트하고 싶었다. 그런데 아무리 호출을 해도 서버는 타임아웃이라는 에러 응답을 뱉기 일쑤였고, 로직이 문제인지 한참을 리팩토링하며 원인을 파악하는데 꽤 오랜 시간을 삽질하였다. 왜 타임아웃이 발생할까? heroku는 web에서 바로 실행할 수 있는 console 페이지를 제공하고 있었다. 그래서 &amp;lsquo;크롤링을 하기 위한 페이지&amp;rsquo;와 &amp;lsquo;구글&amp;rsquo;을 비교하기 위해 단순하게 curl 해서 가져오는 테스트를 해보니 아래처럼 확연히 결과가 달랐다. 결국은 heroku 와 크롤링 하는 서버 간의 네트워크 타임아웃이 문제였던 것.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-3/5.jpg" title="/images/public-offering-notice-3/5.jpg" data-thumbnail="/images/public-offering-notice-3/5.jpg" data-sub-html="&lt;h2>오류가 나고 원인을 찾는 과정이 가장 어려운 것 같다. 어플리케이션의 문제가 아닌 네트워크 자체의 문제&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-3/5.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-3/5.jpg, https://taetaetae.github.io/images/public-offering-notice-3/5.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-3/5.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-3/5.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">오류가 나고 원인을 찾는 과정이 가장 어려운 것 같다. 어플리케이션의 문제가 아닌 네트워크 자체의 문제&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="그래서-어떻게-해결-했나">그래서 어떻게 해결 했나?&lt;/h2>
&lt;p>　﻿heroku에서 타임아웃이 발생하는 문제를 해결하려 여러 구글링을 통해 찾아봤지만 방법을 찾을 수 없어서 결국 heroku를 사용하는 것을 포기하고 다른 방법을 찾아봐야만 했다. 앞서 이야기했지만 토이 프로젝트를 무료로 운영하고 싶었기 때문에 이런저런 방법을 찾다 보니 AWS 와 비슷하게 Google에서도 GCP라는 서비스를 알게 되었다. 이곳에서도 1년 동안은 무료이지만 이후 AWS처럼 과금이 드는 문제가 아쉬워서 좀 더 찾아보니 아주 작은 서버에 특정 조건을 맞춘다면 평생 무료로 사용이 가능하다는 걸 알게 되었고 이를 사용하기로 마음먹는다. (참고 사이트 : &lt;a href="https://nhj12311.tistory.com/317" target="_blank" rel="noopener noreffer ">https://nhj12311.tistory.com/317&lt;/a>)﻿&lt;/p></description></item><item><title>공모주 알리미 개발 후기 - 2부</title><link>https://taetaetae.github.io/posts/public-offering-notice-2/</link><pubDate>Sun, 28 Mar 2021 11:41:33 +0900</pubDate><guid>https://taetaetae.github.io/posts/public-offering-notice-2/</guid><description>&lt;p>　﻿혹시 이 포스트를 처음 읽는 독자라면 &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-1" rel="">지난 포스팅&lt;/a>을 읽고 오는 것을 추천한다. 정리하자면, 지난 포스트에서는 &lt;a href="https://t.me/PublicOfferingNotice" target="_blank" rel="noopener noreffer ">토이 프로젝트&lt;/a>를 시작하게 된 계기와, 어떤 식으로 만들지에 대한 설계. 그리고 데이터를 수집하는 과정에 대해 이야기했었다. 지난 포스팅에서 수집한 데이터를 이제 사용자들에게 알려주는 부분에 대해 정리하고자 한다.&lt;/p>
&lt;ul>
&lt;li>1부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-1" rel="">프로젝트 설계, 데이터 수집&lt;/a>&lt;/li>
&lt;li>2부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-2" rel="">수집한 데이터 알림 발송&lt;/a>&lt;/li>
&lt;li>3부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-3" rel="">서버 선정 및 릴리즈&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="데이터-정의">데이터 정의&lt;/h2>
&lt;p>　﻿java 라이브러리 중에 jsoup라는 것을 사용하여 웹사이트를 크롤링 하였고, 필요한 데이터를 파싱을 하였다. 아래는 &amp;lsquo;공모주&amp;rsquo;라는 자바 모델을 정의해 보았다. 이렇게 자바 &amp;lsquo;모델&amp;rsquo;로 정의를 하는 이유는 필요한 데이터가 무엇인지 다시 한번 정리를 하기 위함이기도 하고 map 같은 형태의 임시 변수(?)보다 더 직관적이기에 이후 코드를 작성하는데 가이드 역할의 효과도 얻을 수 있을 것 같았기 때문이다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">PublicOffering&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 종목명&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalDate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">startDate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 일정 시작일&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalDate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">endDate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 일정 마감일&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalDate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">listingDate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 상장일&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">publicOfferingPrice&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 확정 공모가&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">expectedOfferingPrice&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 희망 공모가&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Underwriter&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 주간사&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">detailUrl&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 상세URL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">competitionRate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 청약경쟁률&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>　﻿초기에는 위에서 정의한 모델처럼 공모주의 기본 정보만을 서비스해야겠다 생각했고, 관련 뉴스라든지 기타 추가적인 정보나 다른 분들의 요구 사항(?)들이 추가될 경우 점진적으로 설계를 하고서 확장시켜 나가는 방향으로 계획했다. 우선은 기능들이 부족하더라고 돌아가는 서비스를 만들고 싶었기에.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-2/agile.png" title="/images/public-offering-notice-2/agile.png" data-thumbnail="/images/public-offering-notice-2/agile.png" data-sub-html="&lt;h2>애자일 방법론! 출처 : https://m.blog.naver.com/keycosmos3/221267522930&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-2/agile.png"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-2/agile.png, https://taetaetae.github.io/images/public-offering-notice-2/agile.png 1.5x, https://taetaetae.github.io/images/public-offering-notice-2/agile.png 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-2/agile.png" />
 &lt;/a>&lt;figcaption class="image-caption">애자일 방법론!&lt;br> 출처 : &lt;a href="https://m.blog.naver.com/keycosmos3/221267522930" target="_blank" rel="noopener noreffer ">https://m.blog.naver.com/keycosmos3/221267522930&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="텔레그램-봇채널-생성">텔레그램 봇/채널 생성&lt;/h2>
&lt;p>　﻿텔레그램 봇을 만드는 과정은 가볍게 검색을 해보면 너무나 쉽게 찾을 수 있지만, 보다 하나의 글 안에 모든 내용을 담고 싶어 텔레그램 봇을 만들고 → 텔레그램 채널을 만든 다음 → 텔레그램 봇을 이용해서 텔레그램 채널에 메시지를 보내는 걸 이야기해 보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>﻿아, 여기서 왜 꼭 &amp;lsquo;텔레그램&amp;rsquo;을 선택했는가에 대한 이유는 개인적으로 다른 메신저 (카카오톡, 라인 등)보다도 api를 활용하여 메시지를 보내는 과정이 단순하면서도 빠르고 쉽게 느껴졌기 때문이다. 혹시 텔레그램 이 아닌 다른 메신저로 보내달라는 요청이 있을 경우 그때 가서 고민해 보려 한다.&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>텔레그램 봇 생성&lt;/li>
&lt;/ul>
&lt;p>　﻿먼저 텔레그램 메신저에서 &amp;lsquo;BotFather&amp;rsquo;라는 사용자를 찾고 &amp;lsquo;/start&amp;rsquo;를 누르면 아래와 같이 사용할 수 있는 명령어가 나온다.&lt;/p>
&lt;p>　﻿그다음 우리는 봇을 만들 것이기 때문에 &amp;lsquo;/newbot&amp;rsquo;을 누르고 봇의 이름을 작성하고 그 봇의 사용자 이름을 지정한다. &amp;lsquo;_bot&amp;rsquo;으로 끝나야 한다고 하기에 이름 뒤에 붙여서 만들면 그걸로 끝. 다음으로 친절하게 HTTP API를 사용할 수 있는 토큰이 발급되는데 이 토큰으로 봇을 컨트롤 가능하기 때문에 잘 간직하고(?) 있어야 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-2/newbot.jpg" title="/images/public-offering-notice-2/newbot.jpg" data-thumbnail="/images/public-offering-notice-2/newbot.jpg" data-sub-html="&lt;h2>친절한 봇 아버지&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-2/newbot.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-2/newbot.jpg, https://taetaetae.github.io/images/public-offering-notice-2/newbot.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-2/newbot.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-2/newbot.jpg" width="70%" />
 &lt;/a>&lt;figcaption class="image-caption">친절한 봇 아버지&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿이후 해당 토큰을 이용해서 봇의 상태를 확인해보자. 아래의 url에 토큰 경로만 변경하여 입력하면 json 응답을 받을 수 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">https://api.telegram.org/bot{token}/getUpdates
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">e.g. https://api.telegram.org/bot17...42:AAH...cQU/getUpdates
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>텔레그램 채널 생성&lt;/li>
&lt;/ul>
&lt;p>　﻿1:N으로 채널에 가입한 사람들에게 메시지를 일방적으로 보내야 하기 때문에 사용할 채널을 만들어 보자. 텔레그램 UI만 봐도 간단하게 생성하기 쉽게 되어있다. 더불어 이 채널에 메시지를 보내야 하기 때문에 위에서 만들었던 봇을 추가하고 관리자로 승격 시키자.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-2/newchannel.jpg" title="/images/public-offering-notice-2/newchannel.jpg" data-thumbnail="/images/public-offering-notice-2/newchannel.jpg" data-sub-html="&lt;h2>채널 생성 &amp;gt; 봇을 관리자로&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-2/newchannel.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-2/newchannel.jpg, https://taetaetae.github.io/images/public-offering-notice-2/newchannel.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-2/newchannel.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-2/newchannel.jpg" width="70%" />
 &lt;/a>&lt;figcaption class="image-caption">채널 생성 &amp;gt; 봇을 관리자로&lt;/figcaption>
 &lt;/figure>
&lt;ul>
&lt;li>﻿텔레그램 봇으로 텔레그램 채널에 메시지 보내기&lt;/li>
&lt;/ul>
&lt;p>　이 부분에서 약간 헤맸는데 결국 위에서 얻은 토큰과 채널의 특정 id를 알아야 메시지를 보낼 수 있다. 앞서 만들었던 채널에 아무 메시지나 작성을 하고 위에서 호출했던 &amp;lsquo;getUpdates&amp;rsquo; api를 다시 호출해보면 아래처럼 채널의 id를 구할 수 있게 된다.&lt;/p></description></item><item><title>공모주 알리미 개발 후기 - 1부</title><link>https://taetaetae.github.io/posts/public-offering-notice-1/</link><pubDate>Sun, 21 Mar 2021 19:13:49 +0900</pubDate><guid>https://taetaetae.github.io/posts/public-offering-notice-1/</guid><description>&lt;p>　﻿작년부터 시작된 &amp;lsquo;동학 개미 운동&amp;rsquo;에 언제부터인가 필자도 주린이로써 동참을 하게 되었다. 최근에는 &amp;lsquo;공모주 청약&amp;rsquo;이라는 걸 알게 되었는데 따라 해보고 정신 차려보니 치킨 한 마리 정도의 수익을 얻는 기적이 일어났다. 공모주란 정해진 일자에 청약을 하고 배정을 받으면 해당 주식이 상장을 하기 전에 미리 살 수 있다는 &amp;lsquo;기회&amp;rsquo;로 이해했다. (주린이라 이해의 범위가 여기까지다&amp;hellip;) 공모주 배정이 로또처럼 엄청난 큰 수익률을 가져다주는 건 아니지만 앞서 이야기 한 것처럼 언제 있을지 모르는 공모주 청약을 꼬박꼬박 챙겨서 하게 된다면 맛있는 치킨을 먹을 수 있겠다는 기대감이 부풀었다. (치킨은 역시 교촌 허니콤보&amp;hellip;)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/public-offering-notice-1/stock.jpg" title="/images/public-offering-notice-1/stock.jpg" data-thumbnail="/images/public-offering-notice-1/stock.jpg" data-sub-html="&lt;h2>주린이는 계속 자야 할까 싶다. 출처 : https://b-s-d.tistory.com/8&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/public-offering-notice-1/stock.jpg"
 data-srcset="https://taetaetae.github.io/images/public-offering-notice-1/stock.jpg, https://taetaetae.github.io/images/public-offering-notice-1/stock.jpg 1.5x, https://taetaetae.github.io/images/public-offering-notice-1/stock.jpg 2x"
 data-sizes="auto"
 alt="/images/public-offering-notice-1/stock.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">주린이는 계속 자야 할까 싶다.&lt;br> 출처 : &lt;a href="https://b-s-d.tistory.com/8" target="_blank" rel="noopener noreffer ">https://b-s-d.tistory.com/8&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿치킨이 머릿속에 맴도는 시간도 잠시. 필자의 머리를 스치는 하나의 생각. 그러면 공모주 청약은 언제 하는 거지? 청약하니까 준비하라고 누가 알려주면 좋을 텐데&amp;hellip; 그러면서 이런저런 검색을 해보니 안드로이드 앱은 이미 있었고, IOS 앱은 없었다. 음? 그럼 이걸 내가 만들어보면 어떨까?&lt;/p>
&lt;p>　﻿결론부터 말하자면, 텔레그램을 활용하여 자동화 공모주 알림봇을 만들게 되었다. 혹시 공모주에 관심이 있다면 필자가 만든 &lt;a href="https://t.me/PublicOfferingNotice" target="_blank" rel="noopener noreffer ">텔레그램 채널&lt;/a>을 가입하는 것도 좋을 것 같다.﻿&lt;/p>
&lt;p>　﻿이번 글에서는 필자의 새로운 토이 프로젝트인 &amp;lsquo;공모주 알리미&amp;rsquo;를 만들게 된 배경과 설계, 그리고 개발부터 릴리즈까지에 대해 이야기를 해보고자 한다. 크게 아래의 목차로 이야기하게 될 것 같다.&lt;/p>
&lt;ul>
&lt;li>1부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-1" rel="">프로젝트 설계, 데이터 수집&lt;/a>&lt;/li>
&lt;li>2부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-2" rel="">수집한 데이터 알림 발송&lt;/a>&lt;/li>
&lt;li>3부 : &lt;a href="https://taetaetae.github.io/posts/public-offering-notice-3" rel="">서버 선정 및 릴리즈&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>　﻿자칫 너무 간단한데~, 이런 걸 굳이 왜 만들어?라는 시각이 있을 수 있겠지만 토이 프로젝트를 해야지 하고 마음을 먹었지만 막상 시작을 못하고 있는 어느 누군가에게는 도움이 될 내용인 것 같아서 꽤 자세히 정리를 하려 한다. 물론 이러한 정리는 필자 자신을 위해서가 더 크긴 하다.&lt;/p>
&lt;h2 id="프로젝트-설계">프로젝트 설계&lt;/h2>
&lt;p>　﻿과거에 토이 프로젝트로 진행했던 &lt;a href="http://daily-devblog.com/" target="_blank" rel="noopener noreffer ">기술블로그 구독 서비스&lt;/a>의 경험을 되새기면서 처음부터 황소처럼 달려드는 것보단 충분에 충족을 더해 충만해질 때까지 고민을 오랫동안 해보기로 했다. (그래봤자 하루 정도&amp;hellip;?^^)
　﻿
우선 데이터를 어딘가에서 가져오고 가져온 데이터를 DB에 저장할 것인지 아니면 저장하지 않고 휘발성으로 조회후 버리는(?) 형태로 할 것인지를 고민해야 했다. 공모주라는 데이터의 특성상 한번 정해진 메타 데이터가 상황에 따라 변경이 될 수도 있다고 했기에(일정이 변경되거나 공모가가 변경되거나 등) DB에 저장을 하게 되면 이를 동기화(Sync) 하는 비용이 추가로 생길 것 같아서 알림을 보내기 직전에만 조회하고 버리는 형태를 생각했다.&lt;/p>
&lt;p>　그렇게 데이터를 조회했다면 이를 입맛에 맞게 가공하고서 사용자에게 알림을 줘야 한다. 알림을 발생시키는 방법은 매우 다양한데 뭔가 적은 비용으로 구성하고 싶었다. 즉, 알림을 받는 사용자가 10명, 100명, 1000명 이 되어도 (그렇게 될지는 모르겠지만;;) 내가 만든 서비스에서 알림 수신인이 늘어나는 경우를 고려하지 않아도 되었으면 했다. 그에 생각한 게 메신저 API. 그중에서도 텔레그램 API가 뭔가 이런 형식으로 딱일 것 같았기 때문이다. 결국 데이터를 메시지 형태에 맞춰 한 번만 발송하게 되면 1:N 형식(Broadcast)으로 텔레그램 채널을 구독하고 있는 사용자들에게 전송이 될 테니 안성맞춤이었다.&lt;/p>
&lt;p>　그럼 언제 어떤 정보를 알려주는 게 좋을까? 청약이 보통 오전에 시작하기 때문에 대략 매일 오전 9시에 관련 정보들을 보내주면 될 것 같았다. 3일 전에 청약을 시작하게 되니 미리 준비하라는 알림. 그리고 청약 날짜가 도래해서 잊지 말고 청약을 신청하라는 알림. 마지막으로 공모주가 상장을 하게 되는 알림. 이 세 가지 알림만 잘 챙긴다면 필자 같은 주린이들도 충분히 공모주 청약으로 치킨을 먹을 수 있을 거라 생각했다.&lt;/p>
&lt;p>　마지막으로 이 모든 내용을 개발한 어플리케이션을 어느 곳에 배포해야 하는지를 결정해야 했다. 항상 머릿속에는 있었지만 한 번도 안 해본 클라우드 Paas 인 &lt;a href="https://dashboard.heroku.com/" target="_blank" rel="noopener noreffer ">heroku&lt;/a>가 딱일 거라 생각했다. (실제로 해보지 않았던 지금까지는 그랬다&amp;hellip;) 다들 토이 프로젝트 서버로 잘 활용한다는 소리를 들었고 가장 큰 장점은 &amp;lsquo;무료&amp;rsquo;라는 점에서 heroku를 선택하지 않을 수 없었다.&lt;/p></description></item><item><title>Elastic Stack으로 코로나19 대시보드 만들기 - 2부 : 대시보드</title><link>https://taetaetae.github.io/posts/make-dashboards-from-elasticstack-2/</link><pubDate>Wed, 17 Feb 2021 16:53:49 +0900</pubDate><guid>https://taetaetae.github.io/posts/make-dashboards-from-elasticstack-2/</guid><description>&lt;p>　&lt;a href="https://taetaetae.github.io/posts/make-dashboards-from-elasticstack-1/" rel="">지난 포스팅&lt;/a>에서는 ﻿데이터를 수급하며 전처리 과정을 거쳤고, Filebeat와 Logstash를 거쳐 Elasticsearch에 인덱싱 하는 것까지 알아보았다. 앞선 포스팅에서 이야기했지만 단순하게 데이터를 시각화 도구를 이용해서 대시보드를 만드는 게 아니라 데이터가 추가되면 만들어둔 대시보드에 자동으로 반영되는 흐름을 만들고 싶었다. 마침 파이프라인을 이틀 전에 만들었기 때문에 그동안의 빠진 데이터를 추가해야 하는 상황이다. 이 경우 Filebeat-Logstash-Elasticsearch 가 실행 중이라면 앞서 작성했던 파이썬 스크립트만 한번 실행해 주면 이틀 치 데이터가 파이프라인을 거쳐 Elasticsearch로 인덱싱이 된다. 즉, 별도로 데이터를 가져와서 재 가공하고 추가하는 다소 까다로운 작업이 미리 만들어둔 파이프라인 덕분에 한 번의 스크립트 실행으로 손쉽게 처리가 됨을 알 수 있다.&lt;/p>
&lt;p>　이제는 쌓여있는 데이터를 가지고 시각화를 해볼 차례이다. ElasticStack에서는 Kibana라는 강력한 시각화 도구를 제공하는데 이번 포스팅에서는 Kibana를 이용해서 대시보드를 만드는 방법에 대해 알아보려 한다.&lt;/p>
&lt;h2 id="visualize">Visualize&lt;/h2>
&lt;p>　﻿Elasticsearch에 인덱싱 되어있는 데이터들은 기본으로 제공되는 REST API를 통해서 조회할 수 있고 JSON 형태로 결과가 나오기 때문에 이를 가지고 다양하게 시각화를 할 수도 있다. 하지만 Kibana에서는 데이터를 조회하고 UI로 표현하는 일련의 모든 행위를 클릭 몇 번으로 할 수 있게 해주기 때문에 전문가가 아니더라도 조금만 만져보면 누구나 만들 수 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/visualize-main.jpg" title="/images/make-dashboards-from-elasticstack-2/visualize-main.jpg" data-thumbnail="/images/make-dashboards-from-elasticstack-2/visualize-main.jpg" data-sub-html="&lt;h2>New Visualizaion!!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/visualize-main.jpg"
 data-srcset="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/visualize-main.jpg, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/visualize-main.jpg 1.5x, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/visualize-main.jpg 2x"
 data-sizes="auto"
 alt="/images/make-dashboards-from-elasticstack-2/visualize-main.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">New Visualizaion!!&lt;/figcaption>
 &lt;/figure>
&lt;p>　버전업이 되면서 비쥬얼라이즈를 만드는 첫 화면 또한 변화가 생겼다. 기존에는 어떤 유형의 비쥬얼라이즈를 선택할 것인지에 대해 선택하는 화면부터 나왔는데 만드는 걸 보다 편리하게 도와주는 &lt;code>Lens&lt;/code>, &lt;code>TSVB&lt;/code> 같은 기능들이 먼저 반겨준다. 이 기능을 통해서 만드는 방법도 괜찮지만 보다 명시적으로 만들고 싶으니 하단에 &lt;code>Aggregation based&lt;/code>을 선택해서 원하는 비쥬얼라이즈의 타입을 선택해 보자. 이후 생성되어 있는 인덱스를 선택하면 본격적으로 비쥬얼라이즈를 그릴 수 있는 화면이 나오는데 대시보드 화면 기준으로 만들어야 할 항목별로 살펴보자.&lt;/p>
&lt;h3 id="전체-수">전체 수&lt;/h3>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/1.jpg" title="/images/make-dashboards-from-elasticstack-2/1.jpg" data-thumbnail="/images/make-dashboards-from-elasticstack-2/1.jpg" data-sub-html="&lt;h2>.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/1.jpg"
 data-srcset="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/1.jpg, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/1.jpg 1.5x, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/1.jpg 2x"
 data-sizes="auto"
 alt="/images/make-dashboards-from-elasticstack-2/1.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿확진자, 사망자, 격리 해제의 총합을 표현하려 한다. 이렇게 &amp;lsquo;숫자&amp;rsquo;를 표현하려 하는 경우 &lt;code>Metric&lt;/code>을 활용하곤 한다. 우측에서 Aggregation 방법을 &amp;lsquo;sum&amp;rsquo;으로 설정하고 필드는 유형별로 각각 선택해 주자. 아래 &amp;lsquo;Add&amp;rsquo;버튼을 눌러 확진, 사망, 격리 해제 수를 모두 표시한 다음 저장을 눌러준다. Label을 지정하지 않으면 어떤 형태로 Aggregation을 했는지를 Label 영역에 보여주는데 그게 보기 싫다면 원하는 텍스트로 지정해 주는 것도 방법이다.&lt;/p>
&lt;h3 id="최근-수">최근 수&lt;/h3>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/2.jpg" title="/images/make-dashboards-from-elasticstack-2/2.jpg" data-thumbnail="/images/make-dashboards-from-elasticstack-2/2.jpg" data-sub-html="&lt;h2>.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/2.jpg"
 data-srcset="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/2.jpg, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/2.jpg 1.5x, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/2.jpg 2x"
 data-sizes="auto"
 alt="/images/make-dashboards-from-elasticstack-2/2.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿확진자, 사망자, 격리 해제의 &amp;lsquo;최근 데이터&amp;rsquo;를 보여주는 게 목적이다. 이 경우 Aggregation을 Top Hit으로 선택하면 필드를 선택할 수 있게 되는데 하루의 데이터가 총 18 row이기 때문에 (서울, 부산, &amp;hellip;, 제주, 검역) 18 row 을 전부 더한 값이 하루 기준의 합계가 된다. 여기서 정렬을 날짜 기준 내림차순으로 해줘야 가장 최근 데이터의 합계가 되는 점도 신경 써야 한다.&lt;/p>
&lt;h3 id="각-타입별-합계">각 타입별 합계&lt;/h3>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/3.jpg" title="/images/make-dashboards-from-elasticstack-2/3.jpg" data-thumbnail="/images/make-dashboards-from-elasticstack-2/3.jpg" data-sub-html="&lt;h2>.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/3.jpg"
 data-srcset="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/3.jpg, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/3.jpg 1.5x, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/3.jpg 2x"
 data-sizes="auto"
 alt="/images/make-dashboards-from-elasticstack-2/3.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿지역별로 타입별 수를 보기 위해 &lt;code>Pie&lt;/code> 타입으로 선택하여 진행한다. 타입별(예로 들어 확진이면 confirmed)로 합계를 구하기 위해 Aggregation을 &amp;lsquo;sum&amp;rsquo;으로 설정하면 빈 원이 나오지만 각 지역별로 차트를 잘라서 봐야 하기에 하단의 Buckets의 Add를 누르고 regieon의 필드를 Terms Aggregation 한다. 18 row의 데이터가 전부 보여야 하기에 정렬 개수를 늘리고 option 탭에서 보는 취향에 알맞게 설정값들을 바꿔준다.&lt;/p>
&lt;h3 id="타입별-추이">타입별 추이&lt;/h3>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/4.jpg" title="/images/make-dashboards-from-elasticstack-2/4.jpg" data-thumbnail="/images/make-dashboards-from-elasticstack-2/4.jpg" data-sub-html="&lt;h2>.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/4.jpg"
 data-srcset="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/4.jpg, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/4.jpg 1.5x, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-2/4.jpg 2x"
 data-sizes="auto"
 alt="/images/make-dashboards-from-elasticstack-2/4.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿확진, 사망, 격리 해제 중에 사망을 제외하고 나머지 둘은 데이터의 크기가 크고 변화량이 비슷하기 때문에 x축은 시간으로 설정해두고 사망은 막대로, 나머지 둘은 라인으로 한 화면에서 표현하면 이 3가지 데이터를 한눈에 보기 좋을 것 같았다. &lt;code>Vertical bar&lt;/code> 을 선택하고 x축(Buckets &amp;gt; X-axis)은 데이터 타입인 convert_date로 설정한다. 다음으로 사망은 매일 몇 명 사망했는지 뚜렷하게 보기 위해 그냥 sum으로, 나머지 둘은 누적 합계가 더 의미 있어 보일 것 같아 Cumulative Sum으로 Aggregation을 한다. 한 화면에서 3가지의 데이터를 보여주기엔 다소 y 축의 크기가 맞지 않으므로 확진, 격리 해제를 별도의 y 축(Y-axes)으로 &amp;lsquo;Metrics &amp;amp; axes&amp;rsquo;탭에서 설정해 준다. 다음으로 각 항목의 범례를 위로 표시하여 보다 보기 편하게 배치한다.&lt;/p></description></item><item><title>Elastic Stack으로 코로나19 대시보드 만들기 - 1부 : 파이프라인 구성</title><link>https://taetaetae.github.io/posts/make-dashboards-from-elasticstack-1/</link><pubDate>Mon, 15 Feb 2021 17:50:12 +0900</pubDate><guid>https://taetaetae.github.io/posts/make-dashboards-from-elasticstack-1/</guid><description>&lt;p>﻿　얼마 전에 필자의 블로그를 보고 어느 교육 기관에서 ElasticStack에 대한 강의 요청이 들어왔다. 사실 관련 기술에 대해 지식이 아주 깊고 해박한 게 아니라서 약간의 반감부터 들었지만 ElasticStack을 전혀 모르는 사람들 기준으로 어떻게 돌아가는지에 대해서만 간단하게 소개하는 정도로 하면 된다고 하여 조심스럽지만 떨리는 마음으로 열심히 준비를 하기 시작했다. 그런데, 이런저런 이유로 갑자기 강의를 할 수 없게 되었고 그간 준비했던 내용들이 너무 아쉽지만 아무 소용이 없게 되어버렸다. 그냥 중단하기엔 아쉬운 마음이 너무 커서 준비했던 내용 중에 &amp;lsquo;데이터를 가지고 대시보드를 만드는 부분&amp;rsquo;은 누군가에겐 도움이 될까 싶어 블로그에 정리를 해보려 한다.&lt;/p>
&lt;blockquote>
&lt;p>﻿강의를 준비한 올해 1월 중순엔 Elasticsearch 버전이 7.10.2이었는데 블로그를 쓰고 있는 지금은 벌써 7.11으로 버전 업 되었다. 내가 아는 오픈소스 중에 버전업이 가장 빠른데 그렇다고 기능이 확 바뀌거나 습득하기 어렵게 바뀌진 않았다. 그만큼 사용자가 무엇을 원하는지 명확히 알고 작은 단위로 조금씩 바뀌어 가는 모습이 꽤 인상적이다.&lt;/p>&lt;/blockquote>
&lt;p>　﻿작년 초부터 코로나19 바이러스가 전 세계적으로 퍼지기 시작했고 아직까지도 진행 중이다. 나도 전염되는 건 아닐까 하는 두려움에 어디에서 얼마나 발생했는지를 확인하기 어렵던 시절 우리나라의 뛰어난 개발자들은 누가 시키지도 않았는데 정말 감사하게도 그 현황을 한눈에 볼 수 있도록 여러 유형으로 코로나19 바이러스 대시보드를 만들기 시작한다. 그 덕분에 좀 더 현황을 보기에 편해졌고 더욱 조심하게 되는 계기가 되었다고 생각한다. 이제는 포털사이트나 각종 매체를 통해 손쉽게 코로나19 바이러스의 현황을 볼 수 있지만 이러한 데이터를 가지고 검색엔진이지만 대시보드를 구축하는데 훌륭한 기능을 가지고 있는 ElasticStack을 활용해서 &amp;lsquo;나만의 대시보드&amp;rsquo;를 만드는 걸 정리해보고자 한다. 본 포스팅의 일회성으로 데이터를 가지고 대시보드를 만드는 것에서 끝나는 게 아니라 지속적으로 데이터가 업데이트된다는 가정하에 전반적인 &amp;ldquo;파이프라인&amp;quot;을 구축한 뒤 대시보드를 만들어 두고 데이터만 갱신하면 자동으로 대시보드 또한 업데이트되는 것을 목적으로 한다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/pipeline.png" title="/images/make-dashboards-from-elasticstack-1/pipeline.png" data-thumbnail="/images/make-dashboards-from-elasticstack-1/pipeline.png" data-sub-html="&lt;h2>전체 흐름&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/pipeline.png"
 data-srcset="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/pipeline.png, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/pipeline.png 1.5x, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/pipeline.png 2x"
 data-sizes="auto"
 alt="/images/make-dashboards-from-elasticstack-1/pipeline.png" width="100%" />
 &lt;/a>&lt;figcaption class="image-caption">전체 흐름&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;blockquote>
&lt;p>﻿글을 모두 작성하고 보니 양이 생각보다 길어져서 데이터를 조회하고 필터링하여 Elasticsearch에 인덱싱 하는 대시보드를 만들기 위한 일종의 &amp;ldquo;데이터 파이프라인&amp;quot;을 구성하는 부분과 만들어진 데이터 기반으로 Kibana의 다양한 기능을 활용하여 대시보드를 만드는 2개의 포스팅으로 나누어 정리해보겠다.&lt;/p>&lt;/blockquote>
&lt;p>﻿　최종적으로 만들게 될 대시보드의 모습은 다음과 같다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png" title="/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png" data-thumbnail="/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png" data-sub-html="&lt;h2>최종 목표!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png"
 data-srcset="https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png 1.5x, https://taetaetae.github.io/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png 2x"
 data-sizes="auto"
 alt="/images/make-dashboards-from-elasticstack-1/kibana-dashboard.png" width="100%" />
 &lt;/a>&lt;figcaption class="image-caption">최종 목표!&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;h2 id="대시보드-구성-준비">대시보드 구성 준비&lt;/h2>
&lt;p>　﻿예전에는 Elasticsearch, Logstash, Kibana 3가지를 가지고 ELK라 불리다 Beat라는 경량 수집기 제품이 등장하며 이 모든 걸 ElasticStack라 부르기 시작했다. (&lt;a href="https://www.elastic.co/kr/elastic-stack" target="_blank" rel="noopener noreffer ">공식 홈페이지 참고&lt;/a>) 먼저 어떤 목표와 어떤 순서로 대시보드를 구성할 것인지에 대해 정리해봐야겠다.﻿&lt;/p>
&lt;h3 id="데이터">데이터&lt;/h3>
&lt;p>　﻿데이터는 &lt;a href="https://data.go.kr" target="_blank" rel="noopener noreffer ">공공데이터 포털&lt;/a>에서 가져오려다 조회를 해보니 누락되는 날짜도 있었고 원하는 데이터의 품질이 생각보다 좋지 않아서 다른 곳을 찾아봐야 했다. 그러다 간결하게 정리한 데이터가 &lt;a href="https://github.com/jooeungen/coronaboard_kr" target="_blank" rel="noopener noreffer ">깃헙&lt;/a>에 공개가 되어 있어서 그것을 사용하려 한다. 해당 데이터는 &lt;a href="https://coronaboard.kr/" target="_blank" rel="noopener noreffer ">https://coronaboard.kr/&lt;/a> 에서도 사용되는 데이터라고 한다. ﻿&lt;/p>
&lt;h3 id="데이터-전처리preprocessing">데이터 전처리(preprocessing)&lt;/h3>
&lt;p>　﻿원하는 데이터는 위 깃헙에서 제공하는 데이터 중에 지역별 발생 현황. 해당 &lt;a href="https://raw.githubusercontent.com/jooeungen/coronaboard_kr/master/kr_regional_daily.csv" target="_blank" rel="noopener noreffer ">데이터&lt;/a>를 살펴보면 요일별로 데이터가 &amp;lsquo;누적&amp;rsquo;되어 저장되어 있다. 즉, 서울지역 기준으로 2020년 2월 17일에 14명이 발생했고 2020년 2월 18일에 한 명도 발생하지 않았는데 14명으로 &amp;lsquo;누적&amp;rsquo;되어 저장되어 있다. 사실 이대로 해도 큰 문제는 없지만 어디까지나 별도의 가공 없이 최대한 원본 데이터(raw) 가 있어야 데이터 분석 시 다양하게 활용이 가능하기에 데이터를 분석하기 전에 전처리 과정이 필요했다. 정리하면, 집계 수가 누적되지 않고 날짜 기준으로 집계된 수만 있는 데이터를 원했다.﻿&lt;/p>
&lt;p>　﻿필자는 주로 java를 가지고 개발을 하지만 가끔 간단한 스크립트성 개발은 python을 활용하는 편이기에 다소 이쁜 코드는 아니지만 데이터를 조작하려 아래와 같은 코드를 작성하였다.&lt;/p></description></item><item><title>Swagger와 Spring Restdocs의 우아한 조합 (by OpenAPI Spec)</title><link>https://taetaetae.github.io/posts/a-combination-of-swagger-and-spring-restdocs/</link><pubDate>Tue, 22 Dec 2020 10:41:40 +0900</pubDate><guid>https://taetaetae.github.io/posts/a-combination-of-swagger-and-spring-restdocs/</guid><description>&lt;p>　﻿MSA 환경에서의 API 문서화는 어떤 식으로 구성하는 걸까? 예컨대, 모듈이 10개 있다고 하면 각 모듈마다 API 문서가 만들어질 테고 API 문서를 클라이언트에 제공하기 위해서 각각의 (10개의) URL를 전달해야 할 텐데 이게 과연 효율적일까? 물론 기능별로 URL이 분리된다는 장점이 있고 굳이 모아보자면 각 API 문서를 다시 한번 크롤링 하여 검색할 수 있도록 제공하는 것도 하나의 방법이 될 수 있다. 하지만 이러한 방법들은 요구 사항을 위한 별도의 작업을 하게 되니 일을 위한 일이 되는 것 같아 뭔가 아쉬웠다. 좋은 방법이 없을까?&lt;/p>
&lt;h2 id="고민의-시작">고민의 시작&lt;/h2>
&lt;p>　한창 궁금증이 머릿속에서 지워지지 않았을때 &lt;a href="https://www.facebook.com/groups/springkorea/permalink/2731936460251299/" target="_blank" rel="noopener noreffer ">Spring 한국 스프링 사용자 모임 페이스북 그룹&lt;/a>에 문의도 해가며 방법을 찾아가고 있었다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" title="/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" data-thumbnail="/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" data-sub-html="&lt;h2>﻿닉네임이나 프로필 사진은 그들의 개인 정보를 위해 임의로 지정하였다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png"
 data-srcset="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png 1.5x, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-1.png 2x"
 data-sizes="auto"
 alt="/images/a-combination-of-swagger-and-spring-restdocs/line-1.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿닉네임이나 프로필 사진은 그들의 개인 정보를 위해 임의로 지정하였다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿필자와 함께 개발자의 인생을 시작한 멋진 친구들에게 정확히 올해 6월 초에 고민을 털어놓으며 좋은 방법이 없을지에 대한 논의를 했던 적이 있다. 그런데 친구 중 한 명이 잊고 있었던 그 이슈에 대해서 다시 꺼내며 URL 하나를 던져준다. 참 고마운 친구들.&lt;/p>
&lt;blockquote>
&lt;p>Shout out 34. &lt;a href="http://asuraiv.tistory.com/" target="_blank" rel="noopener noreffer ">asuraiv&lt;/a>, &lt;a href="https://black9p.github.io/" target="_blank" rel="noopener noreffer ">black9p&lt;/a>&lt;/p>&lt;/blockquote>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" title="/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" data-thumbnail="/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" data-sub-html="&lt;h2>언 반년이 지났으나 필자도 잊고 있었던 이슈를 그는 기억하고 있었다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png"
 data-srcset="https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png 1.5x, https://taetaetae.github.io/images/a-combination-of-swagger-and-spring-restdocs/line-2.png 2x"
 data-sizes="auto"
 alt="/images/a-combination-of-swagger-and-spring-restdocs/line-2.png" width="40%" />
 &lt;/a>&lt;figcaption class="image-caption">언 반년이 지났으나 필자도 잊고 있었던 이슈를 그는 기억하고 있었다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿올해 NHN FORWARD에서 진행했던 세션 중에서 &lt;a href="https://forward.nhn.com/session/3" target="_blank" rel="noopener noreffer ">MSA 환경에서 API 문서 관리하기: 생성부터 배포까지&lt;/a>라는 제목의 내용이었고, 정확하게 필자가 고민했던 부분을 콕! 집어서 해결해 준 사례였다. 역시 세상엔 엄청난 고수들이 내가 고민했던 부분들을 이미(혹은 이후에라도) 고민하고 해결한 경우가 많다는 것을 느끼고 공유의 힘이 이렇게도 대단하구나 하며 놀라움을 금치 못하였다.﻿&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/qguXHW0s8RY?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
 &lt;/div>

&lt;p>　﻿이번 포스팅에서는 OpenAPI Spec 을 활용하여 Spring Restdocs로 만들어지는 문서를 Swagger UI에서 보는 흐름을 실제로 구현해 보고자 한다. 즉, Swagger 나 Spring Restdocs 뭐로 만들든 간에 OpenAPI Spec에 맞춰서만 만든다면 한곳에서 볼 수 있겠다는 희망이 보였다. 며칠 전 작성한 &lt;a href="https://taetaetae.github.io/posts/openapi-and-swagger-ui-in-spring-boot/" rel="">OpenAPI 와 Swagger-ui 포스팅&lt;/a>을 본 독자들은 지금의 포스팅을 작성하기 위한 밑거름이었다는 사실을 눈치챘을 수도 있을 것 같다.&lt;/p>
&lt;blockquote>
&lt;p>﻿좋은 내용을 공유해 주신 (저의 고민을 완벽하게 해결해 주신) NHN FORWARD 발표자분께 이 포스팅을 빌어 감사의 인사를 보냅니다. :) 당장 팀 내에도 적용해봐야겠어요!!&lt;/p>&lt;/blockquote>
&lt;h2 id="spring-restdocs에서-openapi-spec-추출">﻿Spring Restdocs에서 OpenAPI Spec 추출&lt;/h2>
&lt;p>　﻿누가 또 친절하게 오픈소스로 만들어놨다. &lt;a href="https://github.com/ePages-de/restdocs-api-spec" target="_blank" rel="noopener noreffer ">https://github.com/ePages-de/restdocs-api-spec&lt;/a> 에서 관련 내용을 확인할 수가 있는데 해당 링크에서는 gradle 버전이고 &lt;a href="https://github.com/BerkleyTechnologyServices/restdocs-spec" target="_blank" rel="noopener noreffer ">https://github.com/BerkleyTechnologyServices/restdocs-spec&lt;/a> 는 maven 버전이라고 한다. 마침 필자의 Github에 Maven 버전으로 SpringRestdocs를 세팅해둔 &lt;a href="https://github.com/taetaetae/SpringRestDocs-In-SpringBoot" target="_blank" rel="noopener noreffer ">Repository&lt;/a> 가 있어서 이를 활용해보고자 한다.﻿&lt;/p>
&lt;h3 id="pomxml-추가">pom.xml 추가&lt;/h3>
&lt;p>　﻿관련 dependency를 추가하자. jcenter라고 bintray.com 에서 운영되는 Maven Repository에 올려진 오픈소스이니 repository 도 추가해 주자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;restdocs-api-spec.version&amp;gt;&lt;/span>0.10.0&lt;span class="nt">&amp;lt;/restdocs-api-spec.version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;restdocs-spec.version&amp;gt;&lt;/span>0.19&lt;span class="nt">&amp;lt;/restdocs-spec.version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;repositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;repository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;id&amp;gt;&lt;/span>jcenter&lt;span class="nt">&amp;lt;/id&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;url&amp;gt;&lt;/span>https://jcenter.bintray.com&lt;span class="nt">&amp;lt;/url&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/repository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/repositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.epages&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>restdocs-api-spec&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${restdocs-api-spec.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.epages&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>restdocs-api-spec-mockmvc&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${restdocs-api-spec.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿위의 dependency에서 제공해 주는 모듈로 테스트의 SpringRestdocs를 만들었다면 OpenAPI Spec 을 만들어 주는 plugin 또한 추가해 주자&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;pluginRepositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;pluginRepository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;id&amp;gt;&lt;/span>jcenter&lt;span class="nt">&amp;lt;/id&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;url&amp;gt;&lt;/span>https://jcenter.bintray.com&lt;span class="nt">&amp;lt;/url&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/pluginRepository&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/pluginRepositories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.github.berkleytechnologyservices.restdocs-spec&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>restdocs-spec-maven-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${restdocs-spec.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;executions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;execution&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;goals&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;goal&amp;gt;&lt;/span>generate&lt;span class="nt">&amp;lt;/goal&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/goals&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;specification&amp;gt;&lt;/span>OPENAPI_V3&lt;span class="nt">&amp;lt;/specification&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;format&amp;gt;&lt;/span>JSON&lt;span class="nt">&amp;lt;/format&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;outputDirectory&amp;gt;&lt;/span>${project.build.directory}/classes/static/docs&lt;span class="nt">&amp;lt;/outputDirectory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/execution&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/executions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿위 plugin 설정을 보면 format 을 JSON으로 한 것을 볼 수 있는데 YAML로도 만들 수 있다. 자세한 사용방법은 위에서 명시한 링크를 참고해보는 게 좋을 것 같다.&lt;/p></description></item><item><title>OpenAPI 와 Swagger-ui 적용하기</title><link>https://taetaetae.github.io/posts/openapi-and-swagger-ui-in-spring-boot/</link><pubDate>Sun, 20 Dec 2020 11:42:40 +0900</pubDate><guid>https://taetaetae.github.io/posts/openapi-and-swagger-ui-in-spring-boot/</guid><description>&lt;p>　﻿API를 개발하고 사용방법에 대한 명세를 작성하는 방법은 여러 가지가 있다. 가장 심플하게 개발 코드와는 별도로 직접 수기로 작성하여 파일 혹은 문서 링크를 전달하는 방법이 있다. 하지만 개발 코드와 별도로 직접 작성을 한다는 점에서 오타/실수가 발생할 수 있고 최신화가 안되는 여러 가지 문제가 발생한다. 그에 등장한 API 문서화 자동화 툴의 양대 산맥인 SpringRestDocs 와 Swagger.&lt;/p>
&lt;p>　﻿과거 &lt;a href="https://taetaetae.github.io/2020/03/08/spring-rest-docs-in-spring-boot/" rel="">SpringRestDocs 에 대한 포스팅&lt;/a>을 했기에 이번엔 Swagger에 대한 사용방법에 대해 정리해보고자 한다. 이 둘의 장단점은 너무 뚜렷하기에 API문서를 제공하는 상황에 따라 적절하게 선택하여 사용할 수 있었으면 좋겠다.&lt;/p>
&lt;h2 id="springboot에-swagger-적용">﻿SpringBoot에 Swagger 적용&lt;/h2>
&lt;p>　기본 SpringBoot 가 셋팅되어 있다는 가정하에 Swagger 관련 dependency를 추가해주자. 아참, 이제부터의 프로젝트 셋팅은 Gradle로 하려한다. (물론 Maven으로 해도 무방하지만&amp;hellip;)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gradle" data-lang="gradle">&lt;span class="line">&lt;span class="cl">&lt;span class="n">dependencies&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">implementation&lt;/span> &lt;span class="s2">&amp;#34;io.springfox:springfox-boot-starter:3.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>　﻿이후 JavaConfig 을 아래와 같이 설정하는데 아래 내용은 아주 기본 세팅이니 자세한 내용은 &lt;a href="https://springfox.github.io/springfox/docs/current/#springfox-spring-mvc-and-spring-boot" target="_blank" rel="noopener noreffer ">공식 도큐문서&lt;/a>를 참고해 보면 좋을 것 같다. (물론 샘플 프로젝트를 만들며 필요할 것 같은 내용은 아래에서 설명하겠다.)﻿&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@EnableSwagger2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">SwaggerConfig&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Docket&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">api&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Docket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DocumentationType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">SWAGGER_2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">select&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">apis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RequestHandlerSelectors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">any&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">paths&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PathSelectors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">any&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>　﻿테스트할 컨트롤러를 아래처럼 심플하게 작성하고(사칙연산&amp;hellip;) 실행을 시킨 후 &lt;code>/swagger-ui/&lt;/code>에 접속을 해보면 swagger 관련 javaConfig 하나만 추가했는데 문서가 만들어진 것을 확인할 수 있다. (http method는 편의상 다양하게 작성했으니 왜 DELETE 인가라는 의문은 접어두자.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">SampleController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/addition&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">addition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@PostMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/subtraction&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">subtraction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@PutMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/multiplication&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">multiplication&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@DeleteMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/division&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">division&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">num2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" title="/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" data-thumbnail="/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" data-sub-html="&lt;h2>기본 셋팅만 했는데 이런 화면이 나타났다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png"
 data-srcset="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png 1.5x, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/default-config.png 2x"
 data-sizes="auto"
 alt="/images/openapi-and-swagger-ui-in-spring-boot/default-config.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">기본 셋팅만 했는데 이런 화면이 나타났다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿위에서 했던 설정들 중 몇 가지만 좀 더 자세히 살펴보자.&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>설정&lt;/th>
 &lt;th>설명&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Docket&lt;/td>
 &lt;td>﻿Springfox 프레임 워크의 기본 인터페이스가 될 빌더로 구성을 위한 여러 가지 기본값과 편리한 방법을 제공하고 있다. 이후 &lt;code>select()&lt;/code>로 ApiSelectorBuilder를 반환받아 사용할 수 있도록 해준다.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>apis&lt;/td>
 &lt;td>﻿어떤 위치에 있는 API들을 가져올 것인가에 대한 정의. &lt;code>RequestHandlerSelectors.any()&lt;/code>이라고 했으니 SpringBoot에서 기본으로 제공하는 &lt;code>basic-error-controller&lt;/code> 도 API 문서로 만들어진 것을 확인할 수 있다. 특정 패키지만 적용하기 위해서는 &lt;code>RequestHandlerSelectors.basePackage(&amp;quot;com.taetaetae.swagger.api&amp;quot;)&lt;/code> 와 같은 형식으로 지정하면 해당 패키지 하위에 있는 Controller를 기준으로 문서를 만들어 준다﻿.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>paths&lt;/td>
 &lt;td>﻿이름에서도 눈치를 챌 수 있듯이 특정 path만 필터링해서 문서를 만들어 준다.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>useDefaultResponseMessages&lt;/td>
 &lt;td>﻿기본 http 응답 코드를 사용해야 하는지를 나타내는 플래그&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>﻿이외에도 security 나 공통으로 사용되는 파라미터 등 다양한 옵션을 설정할 수 있으니 가능하면 상황에 맞게 설정을 변경해 보는 것도 좋을 것 같다. 다른 설정들을 추가시켜서 좀 더 친절하게 만들어 보면 아래처럼 만들 수 있고 해당 코드는 &lt;a href="https://github.com/taetaetae/Swagger-In-SpringBoot" target="_blank" rel="noopener noreffer ">Github&lt;/a>에서 확인 가능하다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" title="/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" data-thumbnail="/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" data-sub-html="&lt;h2>API 문서화는 최대한 친절하게!!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png"
 data-srcset="https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png 1.5x, https://taetaetae.github.io/images/openapi-and-swagger-ui-in-spring-boot/change-config.png 2x"
 data-sizes="auto"
 alt="/images/openapi-and-swagger-ui-in-spring-boot/change-config.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">API 문서화는 최대한 친절하게!!&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="openapi">OpenAPI&lt;/h2>
&lt;p>　﻿Swagger 공식 홈페이지를 이리저리 둘러보면 OpenAPI라는 내용이 많이 나온다. 그렇다면 OpenAPI는 무엇일까? 문서에 나와있는 내용을 직역해보면 Swagger 사양으로 알려져 있으며 RESTful 웹 서비스를 설명, 생성, 소비 및 시각화하기 위한 기계 판독 가능 인터페이스 파일에 대한 사양이라고 한다. 즉, API 자체를 설명하는 인터페이스 스펙이라고 이해를 해볼 수 있다. 위에서 만들어졌던 Swagger를 보면 http://localhost:8080/v2/api-docs?group=Test API 라고 나와있는데 이를 클릭해보면 아래와 같이 json 형태로 보인다. 다시 보면 우리가 이제까지 Swagger으로 만든 API 문서를 설명하는 인터페이스 스펙으로 이해할 수 있다. (꽤 길어서 덧셈 API를 제외하고 지웠다.) 그렇다면 이 JSON 파일을 어떻게 활용할 수 있을까?﻿&lt;/p></description></item><item><title>Jenkins Job을 병렬로 실행해서 속도를 개선해보자. (by. Pipeline)</title><link>https://taetaetae.github.io/posts/jenkins-job-parallel-processing-by-pipeline/</link><pubDate>Sun, 06 Dec 2020 20:19:47 +0900</pubDate><guid>https://taetaetae.github.io/posts/jenkins-job-parallel-processing-by-pipeline/</guid><description>&lt;p>﻿　관리하는 URL이 &lt;code>200&lt;/code>응답을 주고 있는지 모니터링을 한다고 가정해보자. 다양한 방법이 생각나겠지만 가장 처음으로 떠오른 건 단연 &lt;code>Jenkins&lt;/code>. 간단하게 사용할 언어에 맞춰 &lt;code>Execute Script&lt;/code>를 작성하고 스케줄링을 걸어 놓으면 큰 수고 없이 모니터링을 구성할 수 있게 된다. 아래는 python script로 작성해 보았다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://모니터링url&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿　하지만 모니터링을 해야 하는 URL이 1개에서 여러 개로 늘어난다면 어떻게 될까? 단순하게 작성한 Script를 아래처럼 약간 수정하면 되긴 하지만 URL마다 응답속도가 다를 경우 순차적으로 실행하다 보니 실행 속도는 느릴 수밖에 없다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://모니터링url-3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>﻿
　이러한 경우, 빠른 속도를 보장하기 위해서는 병렬로 실행을 해야 한다는 건 누구나 다 알지만 그렇다고 Thread를 사용하기엔 벌써부터 덜컥 부담이 된다. 그렇다고 Job을 URL 개수만큼 늘리기에는 배보다 배꼽이 더 커버리고&amp;hellip; 그러다 발견한 기능이 바로 Jenkins Pipeline!&lt;/p>
&lt;p>　이번 포스팅에서는 Jenkins Job을 동시에 여러 번 사용해야 하는 경우를 Pipeline을 통해서 개선한 내용에 대하여 공유해보려 한다. Jenkins Pipeline에 대해 들어만 봤는데 이번에 실제로 사용해보니 생각보다 쉽게 개선할 수 있었고 옵션들을 상황에 맞게 조합을 잘 한다면 상당히 활용성이 높아 보이는 기능인 것 같다.&lt;/p>
&lt;h2 id="기존상황">기존상황&lt;/h2>
&lt;p>　테﻿스트를 위해 임의로 느린 응답을 생성하도록 URL을 구성하고 위에서 이야기했던 것처럼 Job 하나에 아주 심플하게 Python script를 작성하고 실행해보도록 하자. 임의로 느린 응답은 &lt;a href="http://slowwly.robertomurray.co.uk/" target="_blank" rel="noopener noreffer ">http://slowwly.robertomurray.co.uk/&lt;/a> 에서 제공하는 기능을 활용하였다.﻿&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/0/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/100/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/200/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/500/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/1000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/2000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/5000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/10000/url/https://www.naver.com/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;http://slowwly.robertomurray.co.uk/delay/20000/url/https://www.naver.com/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답성공 : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그래서 실행해보면 50초가 소요되었다. 자, 이제 개선을 해보자!&lt;/p>
&lt;h2 id="개선을-해보자">개선을 해보자&lt;/h2>
&lt;p>　﻿전체적인 개선의 흐름은 하나의 Job에 모니터링하고자 하는 url을 파라미터로 받아서 처리할 수 있도록 설정하고, 이를 Jenkins Pipeline 을 통해 여러 URL을 동시에 모니터링하게 구성하는 것이다. 그러면 두 개의 Job(파라미터로 받아 모니터링하는 Job, Jenkins Pipeline Job) 만으로 보다 빠르고 효율적인 구성을 할 수 있을 것으로 상상을 하고.&lt;/p>
&lt;h3 id="job을-범용적으로-jenkins-paramters-활용">Job을 범용적으로 (Jenkins paramters 활용)&lt;/h3>
&lt;p>　﻿위에서 샘플로 작성하였던 Python script는 url 이 늘어날수록 Job 안에 script를 수정해야 한다. 그렇게 해도 무방하지만 이번 개선의 목표는 하나의 Job을 Pipeline 이 병렬로 컨트롤하도록 설정해야 했기 때문에 Jenkins Job에 파라미터를 받을 수 있도록 아래처럼 Jenkins Job 설정에 파라미터를 설정하고 Python script 또한 수정해 주자.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" data-sub-html="&lt;h2>﻿Job &amp;gt; 구성 &amp;gt; 이 빌드는 매개변수가 있습니다&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-parameter-option.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Job &amp;gt; 구성 &amp;gt; &lt;code>이 빌드는 매개변수가 있습니다&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;url&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">status_code&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답 실패 :&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">, status : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status_code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;응답성공 : &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="병렬-실행을-위한-jenkins-설정">병렬 실행을 위한 Jenkins 설정&lt;/h3>
&lt;p>　﻿Jenkins Job 을 생성하면 기본적으로 Job마다의 대기열(Queue)이 있어 Job이 실행 중이라면 시작된 시간 순서대로 기다렸다가 앞선 Job이 종료가 되면 이어서 실행되는 구조이다. 하지만 우리는 Job을 병렬로 실행해야 했기에 Job 설정 중 &lt;code>필요한 경우 concurrent 빌드 실행&lt;/code> 옵션을 켜줘서 기다리지 않고 병렬로 실행될 수 있도록 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" data-sub-html="&lt;h2>﻿Job &amp;gt; 구성 &amp;gt; 필요한 경우 concurrent 빌드 실행&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-concurrent-build-option.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Job &amp;gt; 구성 &amp;gt; &lt;code>필요한 경우 concurrent 빌드 실행&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;p>　﻿또한 Jenkins Job 자체는 병렬로 실행되도록 설정되었다 해도 기본적으로 Jenkins 자체의 대기열은 한정되어 있기 때문에 적당히 늘려줘서 여러 개의 Job이 대기 열 없이 동시에 실행될 수 있도록 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" title="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" data-thumbnail="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" data-sub-html="&lt;h2>﻿Jenkins &amp;gt; Jenkins 관리 &amp;gt; 시스템 설정 &amp;gt; of executors&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg 1.5x, https://taetaetae.github.io/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-job-parallel-processing-by-pipeline/jenkins-executors.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿Jenkins &amp;gt; Jenkins 관리 &amp;gt; 시스템 설정 &amp;gt; &lt;code>of executors&lt;/code>&lt;/figcaption>
 &lt;/figure>
&lt;h3 id="jenkins-pipeline">Jenkins Pipeline&lt;/h3>
&lt;p>　﻿Job 을 Pipeline으로 만들고 Pipeline scirpt를 작성하는데 눈여겨봐야 할 옵션들을 짚고 넘어가고자 한다.&lt;/p></description></item><item><title>기술블로그 개편기 (by HUGO)</title><link>https://taetaetae.github.io/posts/blog-reorganization-by-hugo/</link><pubDate>Sun, 29 Nov 2020 18:12:15 +0900</pubDate><guid>https://taetaetae.github.io/posts/blog-reorganization-by-hugo/</guid><description>&lt;p>　웹서비스 개발자라면 나만의 블로그쯤은 있어야지 하며 기술 블로그를 시작한 지도 어느덧 4년이 되었다. 처음엔 그저 새로 알게 된 기술이나 삽질하며 경험한 것들 중에 핵심만을 적어놓는 수준이었다. (지금 다시 보면 뭔가 오글거리는 건 기분 탓이겠지&amp;hellip;) 그렇게 계속 글을 써오면서 글쓰기라는 것에 관심을 갖게 되고 내 글이 누군가에게 도움이 될 거라는 기대에 조금이라도 글을 잘 써보고자 단순 기록 용이 아닌 하나의 &amp;lsquo;글&amp;rsquo;을 쓰려고 노력해 온 것 같다.&lt;/p>
&lt;p>　일주일에 한 개는 써야지. 한 달에 한 개는 써야지. 하며 자꾸 나 자신과의 타협을 하다가 최근에는 회사에서 운영하는 서비스 개편 때문에 정신없이 바쁘다는 핑계로 &amp;lsquo;블로그&amp;rsquo;에 &amp;lsquo;ㅂ&amp;rsquo;자도 생각하지 못하게 된다. 무엇이 문제일까?라는 생각은 결국 내 기술 블로그도 회사 서비스처럼 &amp;lsquo;개편&amp;rsquo;을 해보자는 생각으로 도달하게 되었고 간단할 것만 같았던 기술 블로그 개편 작업은 꽤 오랫동안 + 다양한 삽질들로 작업을 하게 된다.&lt;/p>
&lt;p>　이번 포스팅에서는 기술 블로그를 개편하며 겪었던 내용들에 대해 정리해보고자 한다. 기존에 기술 블로그를 운영하시는 분들이나 이번에 새롭게 시작하시는 분들께 도움이 될 거라 기대한다. 더불어 서비스 &amp;lsquo;출시&amp;rsquo; 가 아닌 개편&amp;rsquo;이라는 과정 속에서 느끼게 되었던 인사이트도 간략하게 작성해볼까 한다.&lt;/p>
&lt;h2 id="기술블로그-플랫폼-선택">기술블로그 플랫폼 선택&lt;/h2>
&lt;p>　처음 블로그를 쓰기 시작했을 때 포털서비스의 글쓰기 플랫폼을 사용하지 않은 이유는 단 하나다. &amp;lsquo;글쓰기&amp;rsquo; 뿐만 아니라 개발자이기에 웹사이트(블로그)를 내 입맛에 맞게 커스터마이징 하기 위해서. 그 이유로 &lt;a href="https://hexo.io/" target="_blank" rel="noopener noreffer ">hexo&lt;/a> 라는 프레임워크에 github의 호스팅을 사용하여 운영을 해왔다. 그렇게 블로그를 운영해오면서 느꼈던 불편했던 부분들과 개편을 하며 기대하는 부분들을 정리하면 아래와 같다.&lt;/p>
&lt;ul>
&lt;li>테마(UI)가 이뻐야 하고 기능들이 많으면 좋겠다.&lt;/li>
&lt;li>기술 블로그인 만큼 코드가 많이 삽입되니 코드 표현 또한 이뻐야 한다.&lt;/li>
&lt;li>테마 또는 프레임워크의 커뮤니티가 활발해야 한다.&lt;/li>
&lt;li>페이지 생성 또는 만들어진 웹페이지의 성능이 좋아야 한다.&lt;/li>
&lt;li>글을 작성하고 배포하는 과정이 심플하고 깔끔해야 한다.
﻿&lt;/li>
&lt;/ul>
&lt;p>　위와 같은 이유를 기반으로 검색을 해보다 SSG(쓱 쇼핑몰 아님, Static site generators)를 깔끔하게 정리해 놓은 &lt;a href="https://jamstack.org/generators/" target="_blank" rel="noopener noreffer ">사이트&lt;/a>를 발견한다. 정말 다양한 플랫폼들을 살펴보며 필자에게 맞는 게 어떤 건지 고민하다 결국 &lt;a href="https://gohugo.io/" target="_blank" rel="noopener noreffer ">hugo&lt;/a> 를 선택하게 된다. hugo를 선택한 이유는 go라는 언어를 사용한다는 것과 (간접적으로라도 다른 언어를 경험해보고 싶어서 + go 언어가 빠르다는 소리를 어디선가 들어서) &lt;a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreffer ">테마들&lt;/a>이 너무 다양했기 때문이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg" title="/images/blog-reorganization-by-hugo/hugo_homepage.jpg" data-thumbnail="/images/blog-reorganization-by-hugo/hugo_homepage.jpg" data-sub-html="&lt;h2>﻿아주 대놓고 빠르다고 하니&amp;hellip; 쓰고 싶어진다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg"
 data-srcset="https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg, https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg 1.5x, https://taetaetae.github.io/images/blog-reorganization-by-hugo/hugo_homepage.jpg 2x"
 data-sizes="auto"
 alt="/images/blog-reorganization-by-hugo/hugo_homepage.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">﻿아주 대놓고 빠르다고 하니&amp;hellip; 쓰고 싶어진다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　결국 hugo에 &lt;a href="https://hugo-ranking-trend.com/" target="_blank" rel="noopener noreffer ">hugo-ranking-trend&lt;/a>라는 사이트에서 상위에 랭크가 되어있고 기술 블로그 성격에 적합할 것 같은 &lt;a href="https://github.com/dillonzq/LoveIt" target="_blank" rel="noopener noreffer ">LoveIt&lt;/a>이라는 테마를 사용하기로 결정하였다. 자 그럼 시작해볼까?!&lt;/p>
&lt;h2 id="hugo-는-어떻게-쓰는거야">hugo 는 어떻게 쓰는거야?&lt;/h2>
&lt;p>﻿　대부분의 오픈소스는 hello world 혹은 quick start 같이 처음 접하는 사람들을 위한 도큐먼트가 있기 마련. hugo도 마찬가지로 &lt;a href="https://gohugo.io/getting-started/quick-start/" target="_blank" rel="noopener noreffer ">quick-start&lt;/a>가 있었고 이를 천천히 따라 하면 생각보다 쉽게 초기 세팅을 할 수 있었&amp;hellip; 을꺼라 기대했지만 약간 초기 설정 과정이 어려워서 남겨 두고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>참고로 필자는 윈도 10 환경에서 구성하였다. mac이라면 더 쉽게 설정할 수 있는 것 같은데 이 부분은 OS의 차이에서 생겨나는 어쩔 수 없는 약간의 장벽이라 생각한다. 이쁜 테마와 새로운 환경을 사용할 수 있다는 기대감으로 꾹 참아본다.&lt;/p>&lt;/blockquote>
&lt;h3 id="기본설정">기본설정&lt;/h3>
&lt;p>　﻿git이 설치되어 있다는 가정하에 우선 hugo는 go 언어기반으로 돌아가기에 우선 go를 설치해야 한다. &lt;a href="https://golang.org/dl/" target="_blank" rel="noopener noreffer ">다운로드&lt;/a>페이지에서 환경에 맞는 설치 파일을 다운로드하고 설치를 해준다. 다음으로 패키지 관리자인 chocolatey 또한 설치가 필요하다. &lt;a href="https://chocolatey.org/install" target="_blank" rel="noopener noreffer ">공식 홈페이지&lt;/a>페이지에서 나와있는 순서대로 진행하면 설치 완료. 필자는 여기서 진행이 잘 안됐었는데, &amp;lsquo;관리자 권한&amp;rsquo;으로 PowerShell 을 실행시켜야지만 성공을 할 수 있었다.﻿&lt;/p>
&lt;p>﻿　위 설정이 완료되었으면 드디어 hugo를 설치해 주고 초기화를 해준 뒤 샘플로 글 하나를 만들고 서버를 띄우면 끝.&lt;/p></description></item><item><title>빌드/테스트는 내가 해줄게. 너는 코딩에 집중해 (by GitHub Pull Request Builder)</title><link>https://taetaetae.github.io/2020/09/07/github-pullrequest-build/</link><pubDate>Mon, 07 Sep 2020 10:09:56 +0000</pubDate><guid>https://taetaetae.github.io/2020/09/07/github-pullrequest-build/</guid><description>&lt;p>　git 은 분산 버전 관리 시스템 중 가장 잘 알려져 있다고 해도 과언이 아닐 정도로 대부분의 시스템에서 사용되고 있는 것 같다. 이를 웹서비스에서 보다 편하게 사용할 수 있도록 한 시스템이 Github. &lt;!--more -->Github 을 사용하는 이유 중에 가장 큰 이유를 하나만 이야기해보자면 바로 온라인상에서 코드 리뷰를 할 수 있는 pullRequest라는 기능 때문이 아닐까 조심스럽게 생각을 해본다.&lt;/p>
&lt;p>　pullRequest는 work branch에서 작업한 내용을 base branch로 merge 전 꼭 코드 리뷰가 아니더라도 작업한 내용에 대해서 다양한 검사를 자동화할 수 있는 강력한 기능들이 많다. 이러한 자동화는 CI(지속적 통합) 관점에서 매우 중요한데 코드에 대해 체크해야 할 부분들(빌드, 테스트, 정적 분석 등)을 &amp;ldquo;알아서&amp;rdquo; 해준다면 작업자는 오롯이 비즈니스 로직 개발에 대해서만 신경 쓸 수 있으니 생산성 절약 측면에서 엄청난 효과를 볼 수 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/car.gif" title="/images/github-pullrequest-build/car.gif" data-thumbnail="/images/github-pullrequest-build/car.gif" data-sub-html="&lt;h2>내가 하는일에만 집중할 수 있게! 출처 : https://www.clien.net/service/board/park/10453442&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/car.gif"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/car.gif, https://taetaetae.github.io/images/github-pullrequest-build/car.gif 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/car.gif 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/car.gif" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">내가 하는일에만 집중할 수 있게! &lt;br> 출처 : &lt;a href="https://www.clien.net/service/board/park/10453442" target="_blank" rel="noopener noreffer ">https://www.clien.net/service/board/park/10453442&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>이번 포스팅에서는 그중에서도 아주 간단한 설정만으로 work branch의 빌드 상태를 검사해 볼 수 있는 Jenkins의 Github Pull Request Builder를 설치 및 활용해 보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>사실 최근 팀에서 CI 서버를 이전해야 했었다. 머릿속에서는 어떻게 하면 되겠지 싶었지만 막상 해보려니 Jenkins 버전업도 되었고 뭐부터 해야 할지 허둥대는 필자가 부끄러웠다. 이참에 정리를 해보며 다시 한번 리마인드 하는 시간을 가져보고자 한다. (이래서 기억보다 기록이 중요하다.)&lt;/p>&lt;/blockquote>
&lt;h2 id="준비물">준비물&lt;/h2>
&lt;p>　전체적인 흐름은 아래 그림처럼 흘러가기 때문에 당연히 서버에 Jenkins 가 설치되어 있어야 한다. Jenkins 설치는 필자의 포스팅(&lt;a href="https://taetaetae.github.io/2018/12/02/jenkins-install/" target="_blank" rel="noopener noreffer ">Jenkins 설치 치트키&lt;/a>)를 참고해 보는 것도 좋을 것 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg" title="/images/github-pullrequest-build/programmer-github-jenkins.jpg" data-thumbnail="/images/github-pullrequest-build/programmer-github-jenkins.jpg" data-sub-html="&lt;h2>전체적인 흐름&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg, https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/programmer-github-jenkins.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/programmer-github-jenkins.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">전체적인 흐름&lt;/figcaption>
 &lt;/figure>
&lt;p>　참고로 필자는 GitHub Enterprise 버전에서 사용했는데 일반 Github에서도 동일한 방법으로 사용 가능하다.&lt;/p>
&lt;h3 id="github과-jenkins의-연동을-위한-2가지-설정">Github과 Jenkins의 연동을 위한 2가지 설정&lt;/h3>
&lt;p>　Github 과 Jenkins 가 통신이 되도록 설정해 줘야 한다. 그래야 Github의 코드를 받아서 Jenkins 가 빌드를 하고 그 빌드 결과를 다시 Github에 리포트가 가능해지기 때문이다. 먼저 첫 번째로 ssh 설정으로 Github의 코드를 가져오도록 ssh 설정을 해두자. ssh 설정하는 방법은 필자의 포스팅(&lt;a href="https://taetaetae.github.io/2018/02/08/github-with-jenkins/" target="_blank" rel="noopener noreffer ">Github과 Jenkins 연동하기&lt;/a>)편을 확인해보면 될 것 같다.&lt;/p>
&lt;p>　그다음으로 아래에서 이야기할 &lt;code>GitHub Pull Request Builder&lt;/code>라는 Jenkins plugin 이 빌드가 끝난 뒤에 결과를 리포팅 해줄 수 있는 인증 토큰을 발급받아두자. Github &amp;gt; Settings &amp;gt; Developer settings &amp;gt; Personal access tokens 화면에서 키를 생성하고 만들어진 키를 저장해 둔다. (이 키는 보안에 유의해야 하고, 화면 경고(?)에서도 볼 수 있듯이 키는 생성 시 한 번밖에 볼 수 없기 때문에 미리 저장해 둬야 한다.)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg" title="/images/github-pullrequest-build/github-access-token.jpg" data-thumbnail="/images/github-pullrequest-build/github-access-token.jpg" data-sub-html="&lt;h2>인증토큰을 미리 받아두자.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg, https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/github-access-token.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/github-access-token.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">인증토큰을 미리 받아두자.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="jenkins-설정">Jenkins 설정&lt;/h2>
&lt;p>　Jenkins &amp;gt; 관리 &amp;gt; pluginManager에 들어가 &lt;code>GitHub Pull Request Builder&lt;/code>를 검색 후 설치해 준다. 그러고 나서 Jenkins &amp;gt; 관리 &amp;gt; 환경설정에 들어가 보면 아래와 같이 &lt;code>GitHub Pull Request Builder&lt;/code> 항목이 생긴 것을 확인할 수 있고 위에서 설정한 인증토큰을 아래처럼 등록 후 저장을 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg" title="/images/github-pullrequest-build/add-github-access-token.jpg" data-thumbnail="/images/github-pullrequest-build/add-github-access-token.jpg" data-sub-html="&lt;h2>credentials 을 위에서 발급받은 인증토큰으로 등록해준다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg, https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/add-github-access-token.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/add-github-access-token.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">credentials 을 위에서 발급받은 인증토큰으로 등록해준다.&lt;/figcaption>
 &lt;/figure>
&lt;p>　Jenkins job을 하나 만들고 pullRequest 가 발생했을 때 자동으로 실행될 수 있도록 설정을 해준다. 먼저 General 탭에 &lt;code>Github project&lt;/code>에 Github url 을 적어주고&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg" title="/images/github-pullrequest-build/jenkins-general.jpg" data-thumbnail="/images/github-pullrequest-build/jenkins-general.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg"
 data-srcset="https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg, https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg 1.5x, https://taetaetae.github.io/images/github-pullrequest-build/jenkins-general.jpg 2x"
 data-sizes="auto"
 alt="/images/github-pullrequest-build/jenkins-general.jpg" width="50%" />
 &lt;/a>
&lt;p>　소스 코드 관리 탭에서 ssh 주소를 적고 위에서 미리 설정한 ssh 키로 credentials 값을 넣어준다. 전에도 이야기했지만 이 부분에서 오류가 발생하면 빨간색 글씨로 오류 내용이 나오고 아래 화면처럼 오류가 없다면 아무것도 안 나온다. Refspec 에 &lt;code>+refs/pull/*:refs/remotes/origin/pr/*&lt;/code> 라고 적어주고 브랜치 설정은 파라미터로 받아와서 pullRequest를 발생시킨 브랜치를 빌드 할 수 있도록 &lt;code>${sha1}&lt;/code> 라고 적어주자.&lt;/p></description></item><item><title>스프링 부트에 필터를 '조심해서' 사용하는 두 가지 방법</title><link>https://taetaetae.github.io/2020/04/06/spring-boot-filter/</link><pubDate>Mon, 06 Apr 2020 23:59:36 +0000</pubDate><guid>https://taetaetae.github.io/2020/04/06/spring-boot-filter/</guid><description>&lt;p>웹 어플리케이션에서 필터를 사용하면 중복으로 처리되는 내용을 한곳에서 처리할 수 있다거나 서비스의 다양한 니즈를 충족시키기에 안성맞춤인 장치인것 같다. 필터란 무엇인가 에 대한 내용은 워낙에 다른 블로그나 공식 도큐먼트에서 자세하게 그리고 다양하게 설명하고 있기에 기본 개념에 대해서는 설명하지 않도록 하려 한다. &lt;!--more -->
이번 포스팅에서는 스프링 부트를 사용하면서 어노테이션이라는 간편함에 취해(?) &amp;ldquo;돌격 앞으로, 닥공&amp;rdquo; 의 자세로 개발을 하려했던 필자를 보고 &amp;ldquo;반성&amp;quot;의 자세로 필터를 등록하는 방법에 대해 명확하게 정리를 하고자 한다. 마지막으로는 아주 간단하면서도 엄청나게 위험한 필터 설정 사례에 대해서도 짚고 넘어가보자.
그냥 넘어가면 아쉬우니, 한번이라도 &amp;lsquo;spring&amp;rsquo; 이라는 framework 를 접해본 사람이라면 봤을법한 그림을 첨부하는것으로 필터란 무엇인가 에 대한 설명을 대신하는게 좋겠다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg" title="/images/spring-boot-filter/spring-request-lifecycle.jpg" data-thumbnail="/images/spring-boot-filter/spring-request-lifecycle.jpg" data-sub-html="&lt;h2>출처 : https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg, https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-filter/spring-request-lifecycle.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-filter/spring-request-lifecycle.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : &lt;a href="https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/" target="_blank" rel="noopener noreffer ">https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>방법을 설명하기 전에 동일하게 사용될 필터와 컨트롤러 코드를 보면 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>필터&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">MyFilter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">implements&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Filter&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FilterConfig&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterConfig&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;init MyFilter&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">doFilter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ServletRequest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">servletRequest&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletResponse&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">servletResponse&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FilterChain&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterChain&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IOException&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;doFilter MyFilter, uri : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">HttpServletRequest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">servletRequest&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">getRequestURI&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">filterChain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">doFilter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">servletRequest&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">servletResponse&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">destroy&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;destroy MyFilter&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>테스트 할 컨트롤러&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">SampleController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/test&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;test&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/filtered/test&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">filteredTest&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;filtered&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="방법-1--filterregistrationbean">방법 1 : FilterRegistrationBean&lt;/h2>
&lt;p>아주 간단하게, 일반 url 하나와 필터에 적용할 url 두개를 만들고 설정하려 한다. FilterRegistrationBean 을 이용해서 위에서 만들었던 필터를 아래처럼 등록해보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@SpringBootApplication&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Method1Application&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">SpringApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Method1Application&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FilterRegistrationBean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setFilterRegistration&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">FilterRegistrationBean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterRegistrationBean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">FilterRegistrationBean&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MyFilter&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="c1">// filterRegistrationBean.setUrlPatterns(Collections.singletonList(&amp;#34;/filtered/*&amp;#34;)); // list 를 받는 메소드&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">filterRegistrationBean&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">addUrlPatterns&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/filtered/*&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// string 여러개를 가변인자로 받는 메소드&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">filterRegistrationBean&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 주석에도 적었지만 filterRegistrationBean 의 &amp;ldquo;setUrlPatterns&amp;rdquo; 와 &amp;ldquo;addUrlPatterns&amp;rdquo; 의 차이는 별거 없다. list 자체를 받을건지 아니면 가변인자로 계속 추가 할것인지. 이렇게 되면 &amp;ldquo;/filtered/&amp;ldquo;으로 &amp;ldquo;시작&amp;quot;하는 패턴의 url의 요청이 오게 되면 등록한 필터를 통과하게 된다.&lt;/p>
&lt;ul>
&lt;li>실행 : 필터 생성&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl"> /&lt;span class="se">\\&lt;/span> / ___&lt;span class="s1">&amp;#39;_ __ _ _(_)_ __ __ _ \ \ \ \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">( ( )\___ | &amp;#39;&lt;/span>_ &lt;span class="p">|&lt;/span> &lt;span class="s1">&amp;#39;_| | &amp;#39;&lt;/span>_ &lt;span class="se">\/&lt;/span> _&lt;span class="sb">`&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="se">\ \ \ \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="se">\\&lt;/span>/ ___&lt;span class="o">)&lt;/span>&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>_&lt;span class="o">)&lt;/span>&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="o">(&lt;/span>_&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="o">)&lt;/span> &lt;span class="o">)&lt;/span> &lt;span class="o">)&lt;/span> &lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39; |____| .__|_| |_|_| |_\__, | / / / /
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> =========|_|==============|___/=/_/_/_/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> :: Spring Boot :: (v2.2.6.RELEASE)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:01.225 INFO 14672 --- [ main] c.t.s.method1.Method1Application : No active profile set, falling back to default profiles: default
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.153 INFO 14672 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.168 INFO 14672 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.168 INFO 14672 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.33]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.361 INFO 14672 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.362 DEBUG 14672 --- [ main] o.s.web.context.ContextLoader : Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.362 INFO 14672 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1082 ms
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.391 DEBUG 14672 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: filterRegistrationBean urls=[/filtered/*] order=2147483647, characterEncodingFilter urls=[/*] order=-2147483648, formContentFilter urls=[/*] order=-9900, requestContextFilter urls=[/*] order=-105
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.391 DEBUG 14672 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.409 DEBUG 14672 --- [ main] o.s.b.w.s.f.OrderedRequestContextFilter : Filter &amp;#39;&lt;/span>requestContextFilter&lt;span class="s1">&amp;#39; configured for use
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.409 DEBUG 14672 --- [ main] s.b.w.s.f.OrderedCharacterEncodingFilter : Filter &amp;#39;&lt;/span>characterEncodingFilter&lt;span class="s1">&amp;#39; configured for use
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">// 필터가 생성 되었다!
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.410 INFO 14672 --- [ main] c.t.springbootfilter.method1.MyFilter : init MyFilter
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.410 DEBUG 14672 --- [ main] o.s.b.w.s.f.OrderedFormContentFilter : Filter &amp;#39;&lt;/span>formContentFilter&lt;span class="s1">&amp;#39; configured for use
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2020-04-06 23:45:02.544 INFO 14672 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService &amp;#39;&lt;/span>applicationTaskExecutor&lt;span class="err">&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>일반 url&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.526 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : GET &lt;span class="s2">&amp;#34;/test&amp;#34;&lt;/span>, &lt;span class="nv">parameters&lt;/span>&lt;span class="o">={}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.528 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.taetaetae.springbootfilter.method1.SampleController#test&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.548 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Using &lt;span class="s1">&amp;#39;text/html&amp;#39;&lt;/span>, given &lt;span class="o">[&lt;/span>text/html, application/xhtml+xml, image/webp, image/apng, application/xml&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, application/signed-exchange&lt;span class="p">;&lt;/span>&lt;span class="nv">v&lt;/span>&lt;span class="o">=&lt;/span>b3&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, */*&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.8&lt;span class="o">]&lt;/span> and supported &lt;span class="o">[&lt;/span>text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.548 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Writing &lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;test&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:27.555 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-1&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : Completed &lt;span class="m">200&lt;/span> OK
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>필터링 url&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">// 필터에 들어온것 확인!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.455 INFO &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> c.t.springbootfilter.method1.MyFilter : doFilter MyFilter, uri : /filtered/test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.456 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : GET &lt;span class="s2">&amp;#34;/filtered/test&amp;#34;&lt;/span>, &lt;span class="nv">parameters&lt;/span>&lt;span class="o">={}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.456 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.taetaetae.springbootfilter.method1.SampleController#filteredTest&lt;span class="o">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.457 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Using &lt;span class="s1">&amp;#39;text/html&amp;#39;&lt;/span>, given &lt;span class="o">[&lt;/span>text/html, application/xhtml+xml, image/webp, image/apng, application/xml&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, application/signed-exchange&lt;span class="p">;&lt;/span>&lt;span class="nv">v&lt;/span>&lt;span class="o">=&lt;/span>b3&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.9, */*&lt;span class="p">;&lt;/span>&lt;span class="nv">q&lt;/span>&lt;span class="o">=&lt;/span>0.8&lt;span class="o">]&lt;/span> and supported &lt;span class="o">[&lt;/span>text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.457 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> m.m.a.RequestResponseBodyMethodProcessor : Writing &lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;filtered&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2020-04-06 23:45:37.459 DEBUG &lt;span class="m">14672&lt;/span> --- &lt;span class="o">[&lt;/span>nio-8080-exec-2&lt;span class="o">]&lt;/span> o.s.web.servlet.DispatcherServlet : Completed &lt;span class="m">200&lt;/span> OK
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="방법-2--webfilter--servletcomponentscan">방법 2 : @WebFilter + @ServletComponentScan&lt;/h2>
&lt;p>@ServletComponentScan 어노테이션을 @Configuration 어노테이션이 설정되어 있는곳에 걸어준 다음 위에서 설정한 필터에 @WebFilter 어노테이션을 설정해주면 아주 간단하게 끝이 난다.&lt;/p></description></item><item><title>조금 더 괜찮은 Rest Template 2부 - Circuit-breaker</title><link>https://taetaetae.github.io/2020/03/29/better-rest-template-2-netflix-hystrix/</link><pubDate>Sun, 29 Mar 2020 23:09:16 +0000</pubDate><guid>https://taetaetae.github.io/2020/03/29/better-rest-template-2-netflix-hystrix/</guid><description>&lt;p>&lt;a href="https://taetaetae.github.io/2020/03/22/better-rest-template-1-retryable/" rel="">지난 포스팅&lt;/a>에서는 Retryable 를 활용해서 간헐적인 네트워크 오류를 &amp;ldquo;재시도&amp;quot;를 함으로써 아주 간단하면서도 강력하게 해결할 수 있는 방법에 대해 알아보았다. 실제로 필자가 운영하는 서비스 에서도 Retryable 를 이용하기 전과 후를 비교해보면 간헐적인 네트워크 오류의 빈도수가 확실히 줄어든것을 확인할 수 있었다. &lt;!--more -->&lt;/p>
&lt;p>이렇게 &amp;ldquo;재시도&amp;quot;를 해서 요청했을때 성공 응답을 받을 경우엔 문제가 안되지만 네트워크 오류가 아닌 실제로 호출을 받는 해당 서버에서 문제가 발생했다면 어떨까? 예컨대, 해당 서버에서 DB를 조회하는 API를 호출한다고 가정했을때 DB 자체에서 어떠한 오류가 난다면. 이런 경우는 단순히 &amp;ldquo;재시도&amp;quot;로 해결할 수 없는 문제다.&lt;/p>
&lt;p>물론 Retryable 의 Recover 어노테이션을 활용했기 때문에 클라이언트 즉, 사용자에게는 오류응답이 발생을 안했겠지만 호출 받는 서버 자체에서의 에러가 발생하는데 이런식의 재시도를 계속 시도한다면 호출 받는 서버 입장에서는 이 &amp;ldquo;재시도&amp;rdquo; request 또한 &amp;ldquo;부하&amp;rdquo; 로 받게 되고 결국 2차, 3차 장애가 이어질 수 밖에 없다.&lt;/p>
&lt;p>기존 한덩어리로 관리되던 Monolithic Architecture 에서는 자체적으로 관리하기 때문에 이러한 에러 컨트롤 또한 자체적으로 관리를 할 수 있지만, 모듈이 모듈을 호출하게 되는 Microservice Architecture 로 바뀌다보니 이런 &amp;ldquo;연쇄 장애(?)&amp;rdquo; 같은 현상이 발생하게 되는 경우가 있다. 호출을 받는 서버의 상태가 이상하면 (에러응답이 지정한 임계치를 벗어나는 수준으로 맞춰서 발생한다면) 적절하게 호출을 하지 않고 (2차 장애를 내지 않도록 호출 자체를 하지 않고) 어느정도 기다리다 클라이언트에게는 에러응답이 아닌 미리 정해둔 응답을 내려주고, 에러가 복구되면 다시 호출하도록 하는 &amp;ldquo;무언가&amp;rdquo; 가 필요하지 않을까?&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif" title="/images/better-rest-template-2-netflix-hystrix/domino.gif" data-thumbnail="/images/better-rest-template-2-netflix-hystrix/domino.gif" data-sub-html="&lt;h2>연쇄 장애. 제발 멈춰&amp;hellip; 출처 : http://dpg.danawa.com/mobile/community/view?boardSeq=175&amp;listSeq=4066389&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif"
 data-srcset="https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif, https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif 1.5x, https://taetaetae.github.io/images/better-rest-template-2-netflix-hystrix/domino.gif 2x"
 data-sizes="auto"
 alt="/images/better-rest-template-2-netflix-hystrix/domino.gif" width="30%" />
 &lt;/a>&lt;figcaption class="image-caption">연쇄 장애. 제발 멈춰&amp;hellip; &lt;br> 출처 : &lt;a href="http://dpg.danawa.com/mobile/community/view?boardSeq=175&amp;amp;listSeq=4066389" target="_blank" rel="noopener noreffer ">http://dpg.danawa.com/mobile/community/view?boardSeq=175&amp;listSeq=4066389&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>지난 포스팅에 이어 이번 포스팅 에서는 그 &amp;ldquo;무언가&amp;rdquo;. 즉, Circuit-breaker 에 대해 알아보고 직접 구현 및 테스트 하면서 돌아가는 원리에 대해 이해 해보고자 한다. 막상 개념은 머릿속에 있지만 직접 구현해보지 않으면 내것이 아니기에, 직접 구현하고 설정값들을 바꿔가면서 언젠가 필요한 순간에 꺼내서 사용할 수 있는 나만의 &amp;ldquo;무기&amp;rdquo; 를 만들어 보고자 한다.&lt;/p>
&lt;h2 id="circuit-breaker-">Circuit breaker ?&lt;/h2>
&lt;p>(한국 발음으로) 서킷브레이커를 검색해보면 주식시장 관련된 내용이 꽤 나온다. (앗, 잠깐 눈물좀&amp;hellip;) &lt;a href="http://bitly.kr/kSm6Y" target="_blank" rel="noopener noreffer ">서킷 브레이커&lt;/a>. 이 용어는 다양한 곳에서 사용되는데 &amp;ldquo;회로 차단기&amp;rdquo; 라고도 검색이 된다. 해당 내용을 발췌해보면 다음과 같다.&lt;/p>
&lt;blockquote>
&lt;p>회로 차단기는 전기 회로에서 과부하가 걸리거나 단락으로 인한 피해를 막기 위해 자동으로 회로를 정지시키는 장치이다. 과부하 차단기와 누전 차단기로 나뉜다. 퓨즈와 다른 점은, 차단기는 어느 정도 시간이 지난 뒤, 원래의 기능이 동작하도록 복귀된다.&lt;/p>&lt;/blockquote>
&lt;p>여기서 가장 중요한 문장은 &amp;ldquo;피해를 막기 위해 자동으로 회로를 정지시키는&amp;rdquo;, &amp;ldquo;어느정도 시간이 지난뒤 원래의 기능이 동작하도록 복귀된다&amp;rdquo; 이 부분이 가장 중요한 것 같다. 시스템 구성이 점점 Microservice Architecture 로 바뀌어 가는 시점에서 이러한 &amp;ldquo;서킷브레이커&amp;quot;는 자동으로 모듈간의 호출 에러를 감지하고 위에서 말한 &amp;ldquo;연쇄 장애&amp;quot;를 사전에 막을 수 있는 아주 중요한 기능이라 생각된다.&lt;/p>
&lt;p>&amp;ldquo;circuit breaker spring&amp;rdquo; 이라는 키워드로 검색해보면 이러한 고민을 이미 Netflix 라는 회사에서 &lt;a href="https://github.com/Netflix/Hystrix" target="_blank" rel="noopener noreffer ">Hystrix&lt;/a> 라는 이름으로 개발이 된것을 알 수 있다. 이 core 모듈을 Spring 에서 한번 더 감싸서 Spring Boot 에서 사용하기 좋게 spring-cloud-starter-netflix-hystrix 라는 이름으로 만들어 둔 것이 있는데 이것을 활용해 보기로 하자.&lt;/p>
&lt;h2 id="구현">구현&lt;/h2>
&lt;p>늘 그랬듯이 SpringBoot 프로젝트를 만들고 테스트할 Controller 를 만들어 주자. 원래대로라면 호출을 하는 모듈과 호출을 받는 모듈, 2개의 모듈을 만들어서 테스트 해야 하지만 편의를 위해 하나의 모듈에서 두개의 Controller 을 만들고 테스트 해보는 것으로 하자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">MainController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">index&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">MainController&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">mainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mainService&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">MainService&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getForObject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:8080/target?key=&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">MainService&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">restTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">TargetContoller&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/target&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">target&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;input key : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">equals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;taetaetae&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">throw&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RuntimeException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Invalid key&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>사실 설명할 부분도 없긴 하지만 그래도 적어보면, &lt;code>/index&lt;/code>라는 주소와 &lt;code>key&lt;/code>라는 파라미터로 요청을 하면 &lt;code>/target&lt;/code>이라는 컨트롤러가 받아서 &lt;code>key&lt;/code> 값에 따라 정상 응답을 줄지 아니면 에러를 응답하는 코드이다. 목표로 하는건, 파라미터를 일부러 잘못줘서 에러를 발생하는데 Hystrix를 적용해서 설정한 기준에 따라 응답은 미리 정해둔 응답을, &lt;code>TargetContoller&lt;/code>에서는 잠시동안 요청을 받지 않다가 조금있다가 다시 호출하면 요청을 받는 시나리오로 작성해 보려 한다.
우선 필요한 dependency를 추가해준다.&lt;/p></description></item><item><title>조금 더 괜찮은 Rest Template 1부 - Retryable</title><link>https://taetaetae.github.io/2020/03/22/better-rest-template-1-retryable/</link><pubDate>Sun, 22 Mar 2020 15:30:35 +0000</pubDate><guid>https://taetaetae.github.io/2020/03/22/better-rest-template-1-retryable/</guid><description>&lt;p>웹 어플리케이션을 만들면서 꼭 한번 쯤 만나게 되는 &amp;ldquo;RestTemplate&amp;rdquo;. 접근 가능한 외부 HTTP URL(보통 API)을 호출하는 방법중에 하나로 springframework 에서 제공해주는 모듈이다. 특히 큰 한덩어리로 관리되던 Monolithic Architecture 에서 요청을 하고(client) 응답을 주는(server) &lt;!--more -->즉, Endpoint가 작은 단위로 분리되는 Microservice Architecture 로 바뀌면서 각 서비스간 호출방식이 HTTP 일 경우 자주 사용되곤 하는 것 같다. (webClient 등 다른 여러 호출 방법들이 있다.)
만약, 요청을 하는 클라이언트 입장에서 응답을 주는 서버의 상태가 불안정 하다고 가정했을때, 어떤식으로 처리해야 할까? 예컨대, 요청 10번에 한번은 어떠한 이슈로 응답이 지연되거나 서버에러가 발생한다고 하면 클라이언트를 사용하는 사용자 입장에서는 간헐적인 오류응답에 답답함을 호소할 수도 있다. 그럼 잠시 눈을 감고 생각해보자.
가볍게 생각하면 아래처럼 아주 간단하게 &amp;ldquo;예외처리&amp;quot;를 이용할 수도 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="c1">// http call&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="c1">// 서버에러가 아닌 약속된 에러응답을 리턴&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>하지만 이것도 정답이 아닐수 있는게, &amp;ldquo;간헐적인 오류&amp;quot;로 인해 사용자는 오류화면을 봐야하기 때문에 클라이언트에 대한 신뢰를 저버릴 수밖에 없다. 그럼 어떻게 해야할까? 여러가지 해결방법이 있겠지만 간단하면서도 강력하다고 생각되는 방법이 바로 &amp;ldquo;재시도&amp;rdquo; 라고 생각한다. 클라이언트를 사용하는 사용자가 눈치 못챌만큼 빠르게 재시도를 한다면 에러가 나도 다시한번 호출해서 성공할 수 있는 가능성이 높기 때문이다. (그치만 근본적인 원인은 해결해야&amp;hellip;)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png" title="/images/better-rest-template-1-retryable/retry.png" data-thumbnail="/images/better-rest-template-1-retryable/retry.png" data-sub-html="&lt;h2>실제로 조금있다 해보면 되는 경우가 많으니 안될때는 조금 (천천히) 시도해보자. 출처 : http://www.segye.com/newsView/20200302504384&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png"
 data-srcset="https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png, https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png 1.5x, https://taetaetae.github.io/images/better-rest-template-1-retryable/retry.png 2x"
 data-sizes="auto"
 alt="/images/better-rest-template-1-retryable/retry.png" width="40%" />
 &lt;/a>&lt;figcaption class="image-caption">실제로 조금있다 해보면 되는 경우가 많으니 안될때는 조금 (천천히) 시도해보자. &lt;br>출처 : &lt;a href="http://www.segye.com/newsView/20200302504384" target="_blank" rel="noopener noreffer ">http://www.segye.com/newsView/20200302504384&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>이번 포스팅에서는 RestTemplate 를 이용할때 &amp;ldquo;재시도&amp;rdquo; 할 수 있는 방법에 대해 알아보고자 한다. 아주 간단할지 모르지만 노력에 비해 효과가 상당하다고 생각하기 때문에 정리해 두고 싶었다.&lt;/p>
&lt;h2 id="spring-retry">Spring Retry&lt;/h2>
&lt;p>&lt;a href="https://github.com/spring-projects/spring-retry" target="_blank" rel="noopener noreffer ">공식 Github&lt;/a>에 소개를 빌리자면, Spring 어플리케이션에 대한 재시도 지원을 제공한다고 한다. 위에서 이야기 했던 &amp;ldquo;RestTemplate&amp;quot;과는 사실 무관하고, 이를 활용해서 재시도 하는 &amp;ldquo;RetryRestTemplate&amp;quot;를 구현해보려 하는것이다. 우선 이 &amp;ldquo;Spring-Retry&amp;quot;의 예제를 보면 아주 심플하게 사용할 수 있다. 우선 pom에 구현에 필요한 dependency 를 추가하고 아래 코드를 보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.retry&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-retry&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-aop&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@EnableRetry&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Application&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Service&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">service&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Service&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Service&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Retryable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RemoteAccessException&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">service&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// ... do something&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Recover&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">recover&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RemoteAccessException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// ... panic&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>@EnableRetry 어노테이션을 @Configuration을 지정한 클래스 중 하나에 추가한다.&lt;/li>
&lt;li>재시도 하려는 메소드에 @Retryable 어노테이션을 지정해준다.&lt;/li>
&lt;li>재시도가 완료되는 시점에서 실행하고 싶을때 선언하는 어노테이션, @Retryable 동일한 클래스에서 선언되어야 하고 return type 은 @Retryable을 지정한 메소드와 동일해야 한다.&lt;/li>
&lt;/ol>
&lt;h2 id="retry-rest-template">Retry Rest Template&lt;/h2>
&lt;p>이렇게 springframework 에서 제공해주는 spring-retry 를 이용해서 이번 포스팅의 목표인 재시도를 하는 Retry Rest Template 를 구성해보자. 우선, RestTemplate 를 Bean 으로 등록하고, 위에서 이야기 한 어노테이션들로 구성해보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@EnableRetry&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">RetryableRestTemplateConfiguration&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">retryableRestTemplate&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">SimpleClientHttpRequestFactory&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SimpleClientHttpRequestFactory&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setReadTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">2000&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setConnectTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">500&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestTemplate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">clientHttpRequestFactory&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Retryable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestClientException&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">maxAttempts&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">backoff&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nd">@Backoff&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delay&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1000&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ResponseEntity&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">exchange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">URI&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpMethod&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpEntity&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">requestEntity&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Class&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">responseType&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RestClientException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">exchange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">requestEntity&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">responseType&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> 
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Recover&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ResponseEntity&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">exchangeRecover&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RestClientException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ResponseEntity&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">badRequest&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">body&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;bad request T.T&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>SimpleClientHttpRequestFactory 를 만들고 각 타임아웃을 설정해준 다음 RestTemplate 파라미터로 넘겨준다.&lt;/li>
&lt;li>사용하는 곳에서 exchange 메소드를 이용할 것이므로 해당 메소드를 오버라이드 해준다. 먼저 해당 메소드에서 &amp;ldquo;RestClientException&amp;quot;이 발생할 경우 Retry 로직을 수행한다고 정해주고, 최대 시도는 3번, backoff 설정중 delay를 1000ms(1초)로 지정해서 재시도가 진행되도록 해준다.&lt;/li>
&lt;li>2 에서 지정한 재시도가 끝나면 (재시도를 전부 다 하면) 해당 메소드를 수행하게 되어있고, 임의로 응답에 지정한 문구를 넘겨준다.&lt;/li>
&lt;/ol>
&lt;p>이렇게 하고 실제로 사용하는 로직에서 일부러 잘못된 URL을 호출해 보도록 하자. 그리고서 로그를 자세히 보도록 application.properties 에 &amp;ldquo;debug=true&amp;rdquo; 설정을 해준다.&lt;/p></description></item><item><title>SpringRestDocs를 SpringBoot에 적용하기</title><link>https://taetaetae.github.io/2020/03/08/spring-rest-docs-in-spring-boot/</link><pubDate>Sun, 08 Mar 2020 23:16:59 +0000</pubDate><guid>https://taetaetae.github.io/2020/03/08/spring-rest-docs-in-spring-boot/</guid><description>&lt;p>API를 개발하고 제공하기 위해서는 그에 해당하는 API 명세를 작성해서 사용하는 곳에 전달하게 된다. 어떤 URL에 어떤 파라미터를 사용해서 어떻게 요청을 하면 어떤 결과를 응답으로 내려주는지에 대한 관련 정보들. 이러한 &amp;ldquo;API 문서&amp;rdquo; 를 제공하는 방식은 상황에 따라 다양한 방법으로 사용되곤 한다. &lt;!--more -->&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg" title="/images/spring-rest-docs-in-spring-boot/api-document.jpg" data-thumbnail="/images/spring-rest-docs-in-spring-boot/api-document.jpg" data-sub-html="&lt;h2>API 코드와 해당 문서의 동기화가 자동으로 되어야 조금 편해질것 같다는 생각이 들었다. 출처 : https://dribbble.com/shots/3386291-API-Documentation&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg, https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg 1.5x, https://taetaetae.github.io/images/spring-rest-docs-in-spring-boot/api-document.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-rest-docs-in-spring-boot/api-document.jpg" width="50%" />
 &lt;/a>&lt;figcaption class="image-caption">API 코드와 해당 문서의 동기화가 자동으로 되어야 조금 편해질것 같다는 생각이 들었다. &lt;br>출처 : &lt;a href="https://dribbble.com/shots/3386291-API-Documentation" target="_blank" rel="noopener noreffer ">https://dribbble.com/shots/3386291-API-Documentation&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>필자는 주로 &amp;ldquo;위키&amp;rdquo;(또는 일반 문서)를 활용해서 전달하곤 했었는데 API의 형태가 달라질 때마다 해당 위키를 수정해야만 하는 번거로움이 있었다. API 수정하면 위키도 수정하고. 깜박하고 위키 수정을 안하게 될 경우 왜 API 명세가 다르냐는 문의가&amp;hellip; 그러다 알게된 Spring Rest Docs. (아무리 좋은 기술, 좋은 툴 이라 해도 실제로 본인이 필요로 하고 사용을 해야하는 이유가 생길때 비로소 빛을 발하는것 같은 느낌이다.)&lt;/p>
&lt;blockquote>
&lt;p>이 포스팅에서는 swegger 와 비교하는 내용은 제외할까 한다. 워낙 유명한 두 양대 산맥(?)이라 검색해보면 각각의 장단점이 자세히 나와있기에&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;p>최근 들어 TestCode 의 중요성을 절실하게 느끼고 있었고, TestCode 를 작성하면 자연스럽게 문서를 만들어 주는 부분이 가장 매력적이라고 생각이 들었다. 이를 반대로 생각하면, TestCode 가 실패할 경우 빌드 자체가 안되기에 어쩔수 없이 TestCode를 성공시켜야만 하고, 자연스럽게 정상적인(최신화 된) API 문서가 만들어지게 된다.&lt;/p>
&lt;p>이번 포스팅에서는 다음과 같은 목표를 두고 실무에서 언제든지 활용이 가능한 약간의 &amp;ldquo;가이드&amp;rdquo; 같은 내용으로 작성해 보고자 한다.&lt;/p>
&lt;ul>
&lt;li>Spring Boot 최신 버전에서 Spring Rest Docs 를 설정한다.&lt;/li>
&lt;li>임의의 API 를 만들고 그에 따른 TestCase 를 작성한다.&lt;/li>
&lt;li>Spring.profile 에 따라 Spring Rest Docs Url 을 접근 가능/불가능 할 수 있게 한다.&lt;/li>
&lt;/ul>
&lt;p>물론 필자의 방법이 다를수도 있지만, 이러한 방법을 토대로 보다 더 우아하고 아름다운 방법을 알아갈수 있지 않을까 하는 기대로.&lt;/p>
&lt;h2 id="spring-boot-에-spring-rest-docs-셋팅하고-testcase-작성하기">Spring Boot 에 Spring Rest Docs 셋팅하고 TestCase 작성하기&lt;/h2>
&lt;p>우선 Spring Boot 프로젝트를 만든다. &lt;a href="https://start.spring.io/" target="_blank" rel="noopener noreffer ">https://start.spring.io/&lt;/a> 에서 만들어도 되고 IDE 에서 제공하는 툴로 만들어도 되고. 만드는 방식은 무방하다. 그 다음 필요한 dependency 를 추가해 준다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.restdocs&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-restdocs-mockmvc&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>임의로 API를 작성하고&lt;/p>
&lt;ul>
&lt;li>모델&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Getter&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Setter&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>컨트롤러&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">BookController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/book/{id}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getABook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nd">@PathVariable&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Integer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setTitle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;spring rest docs in spring boot&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setAuthor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;taetaetae&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>해당 컨트롤러에 대한 TestCase 를 작성하자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@WebMvcTest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BookController&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@AutoConfigureRestDocs&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (1)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">BookControllerTest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Autowired&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MockMvc&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">mockMvc&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (2)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test_책을_조회하면_null이_아닌_객체를_리턴한다&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">mockMvc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">perform&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/book/{id}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">accept&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MediaType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">APPLICATION_JSON&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andDo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MockMvcResultHandlers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">print&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MockMvcResultMatchers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">status&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">isOk&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andDo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">document&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;book&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (3)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">pathParameters&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">parameterWithName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;book unique id&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (4)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">responseFields&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">fieldWithPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;book unique id&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">fieldWithPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">fieldWithPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;author&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;author&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">jsonPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;$.id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">notNullValue&lt;/span>&lt;span class="p">())))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// (5)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">jsonPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;$.title&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">notNullValue&lt;/span>&lt;span class="p">())))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">andExpect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">jsonPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;$.author&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">notNullValue&lt;/span>&lt;span class="p">())));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>(1) Spring Boot 에서는 해당 어노테이션으로 여러줄에 걸쳐 설정해야 할 Spring Rest Docs 관련 설정을 아주 간단하게 해결할 수 있게 된다. (&lt;a href="https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-docs" target="_blank" rel="noopener noreffer ">참고&lt;/a>)&lt;/p>
&lt;p>(2) &lt;a href="https://docs.spring.io/spring-restdocs/docs/2.0.4.RELEASE/reference/html5/#getting-started-sample-applications" target="_blank" rel="noopener noreffer ">공식 도큐먼트&lt;/a> 에서는 4가지 방식을 말하고 있는데 이 포스팅 에서는 &amp;ldquo;MockMvc&amp;rdquo; 을 사용하고자 한다.&lt;/p>
&lt;p>(3) &amp;ldquo;book&amp;rdquo; 이라는 identifier 를 지정하면 해당 TestCase 가 수행될때 snippets 가 생성되는데 해당 identifier 묶음으로 생성이 된다.&lt;/p></description></item><item><title>Jupyter 설치하고 원격접속까지 (for 파.알.못)</title><link>https://taetaetae.github.io/2020/02/09/jupyter-install/</link><pubDate>Sun, 09 Feb 2020 20:06:15 +0000</pubDate><guid>https://taetaetae.github.io/2020/02/09/jupyter-install/</guid><description>&lt;p>파이썬이라는 언어는 다른 프로그래밍 언어들에 비해 쉽고 직관적이라 그런지 프로그래밍을 처음 시작하는 사람들에게 더욱이 주목을 받고 있는것 같다. 정말 다양한 모듈들이 많아 여러분야에서 활용되고 있고 &lt;!--more -->특히 언제부터인가 핫! 해진 분야(?)라 해도 과언이 아닐정도인 &amp;ldquo;머신러닝&amp;rdquo; 분야에서도 다양하게 사용되고 있는것 같다.&lt;/p>
&lt;p>마침 필자가 속해 있는 팀 내에 머신러닝 스터디가 시작이 되었고, 그에 파이썬을 이용하여 스터디를 해야하는 상황. 하지만 스터디를 하는 팀원 절반 이상이 파이썬을 이용한 개발 경험이 없었고, 서로 배운것을 공유를 하면서 스터디를 하면 더 좋겠다는 생각이 들때 즈음. 언제 어디선가 봤던것이 머릿속을 스쳐 지나간다. 그건 바로 Jupyter(이하 주피터).&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg" title="/images/jupyter-install/jupyter_logo.jpg" data-thumbnail="/images/jupyter-install/jupyter_logo.jpg" data-sub-html="&lt;h2>출처 : https://jupyter.org/&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg"
 data-srcset="https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg, https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg 1.5x, https://taetaetae.github.io/images/jupyter-install/jupyter_logo.jpg 2x"
 data-sizes="auto"
 alt="/images/jupyter-install/jupyter_logo.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : &lt;a href="https://jupyter.org/" target="_blank" rel="noopener noreffer ">https://jupyter.org/&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>&lt;a href="https://jupyter.org/" target="_blank" rel="noopener noreffer ">주피터&lt;/a>는 수십 개의 프로그래밍 언어에서 대화 형 컴퓨팅을위한 오픈 소스 소프트웨어, 오픈 표준 및 서비스를 개발하기 위한 툴이라고 한다. 이 포스트를 작성하기 전까지만 해도 &amp;ldquo;주피터 == 파이썬 웹 개발툴&amp;rdquo; 이라고만 알고있었는데 좀더 찾아보니 &lt;a href="https://github.com/jupyter/jupyter/wiki/Jupyter-kernels" target="_blank" rel="noopener noreffer ">다양한 언어를 지원&lt;/a>하는것 같다.&lt;/p>
&lt;p>그럼 이러한 주피터를 특정 서버에 설치하고 로컬에 파이썬을 설치하지 않아도 원격으로 파이썬 코딩을 해보면 좀더 스터디에 도움이 되지 않을까 하는 마음이 들었다. 또한 학교에서 운동장에 잔디를 깔아서 맘껏 뛰놀수 있게 하는 느낌으로 팀원들을 위해 설치를 해두고 원격으로 접속할 수 있게 해두면 모두가 편하고 쉽게 파이썬에 대해 경험을 해볼 수 있지 않을까 하는 마음으로 주피터를 설치를 해 보고자 한다.&lt;/p>
&lt;p>본 포스팅의 목표는 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>환경 : CentOS 7.4 64Bit, python 2.7 (기본)&lt;/li>
&lt;li>목표
&lt;ul>
&lt;li>anaconda 를 활용하여 시스템 기본 파이썬을 건드리지 않는 가상환경을 구축한다.&lt;/li>
&lt;li>주피터를 설치하고 원격으로 접속할 수 있도록 설정한다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>여기까지 보면 필자가 엄청나게 파이썬에 대해 잘 아는것처럼 보일수도 있어 미리 말하지만 필자는 찐 자바 개발자이면서 파이썬 개발 수준은 기본적인 스크립트를 작성하는 정도이다. 그러니 이 포스트를 읽고 있는 필자같은 파알못(?) 분들도 충분히 설치가 가능하다. (최대한 따라할수 있을 정도의 치트키 수준으로 작성 하고자 한다.)&lt;/p>
&lt;h2 id="아나콘다-설치-덤으로-설치되는-주피터">아나콘다 설치 (덤으로 설치되는 주피터)&lt;/h2>
&lt;p>우선 아나콘다를 설치하자. 아나콘다는 Anaconda(이전: Continuum Analytics)라는 곳에서 만든 파이썬 배포판으로, 수백 개의 파이썬 패키지를 포함하고 있다고 한다. 즉, 아나콘다를 설치하고 만들어진 가상환경에서 파이썬 개발을 하면 다양한 모듈이 이미 설치되어 있기 때문에 편리하다는 이야기.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jupyter-install/ananconda.jpg" title="/images/jupyter-install/ananconda.jpg" data-thumbnail="/images/jupyter-install/ananconda.jpg" data-sub-html="&lt;h2>출처 : https://www.anaconda.com/&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jupyter-install/ananconda.jpg"
 data-srcset="https://taetaetae.github.io/images/jupyter-install/ananconda.jpg, https://taetaetae.github.io/images/jupyter-install/ananconda.jpg 1.5x, https://taetaetae.github.io/images/jupyter-install/ananconda.jpg 2x"
 data-sizes="auto"
 alt="/images/jupyter-install/ananconda.jpg" width="40%" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : &lt;a href="https://www.anaconda.com/" target="_blank" rel="noopener noreffer ">https://www.anaconda.com/&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>더불어 시스템에 기본으로 설치되어 있는 파이썬을 건드리면 여러 복잡한 문제가 발생할 수 있기에. 아나콘다를 활용하여 파이썬 3을 사용하는 가상환경을 만들어 보자.
설치는 아주 간단하다. 아나콘다 설치파일을 다운받고 이를 실행하면 끝.
(user 레벨이 root 면 sudo 명령어를 생략해도 된다.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ wget https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo bash Anaconda3-2019.10-Linux-x86_64.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Welcome to Anaconda3 2019.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">In order to &lt;span class="k">continue&lt;/span> the installation process, please review the license
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">agreement.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Please, press ENTER to &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Anaconda End User License &lt;span class="nv">Agreement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">===================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Copyright 2015, Anaconda, Inc.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~~~ 중략 ~~~
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you accept the license terms? &lt;span class="o">[&lt;/span>yes&lt;span class="p">|&lt;/span>no&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>no&lt;span class="o">]&lt;/span> &amp;gt;&amp;gt;&amp;gt; yes &lt;span class="c1"># yes!!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Anaconda3 will now be installed into this location:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/root/anaconda3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Press ENTER to confirm the location
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Press CTRL-C to abort the installation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Or specify a different location below
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>/root/anaconda3&lt;span class="o">]&lt;/span> &amp;gt;&amp;gt;&amp;gt; /home/anaconda3 &lt;span class="c1"># 설치될 경로를 설정해주고 기본 설정값에 설치하려면 그냥 엔터&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~~~뭐가 엄청 설치된다. 물 한잔 먹고 오자.~~~
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">installation finished.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Do you wish the installer to initialize Anaconda3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">by running conda init? &lt;span class="o">[&lt;/span>yes&lt;span class="p">|&lt;/span>no&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>no&lt;span class="o">]&lt;/span> &amp;gt;&amp;gt;&amp;gt; yes &lt;span class="c1"># yes!!&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 되면 설치는 끝. 환경변수를 설정해서 기본 파이썬 환경을 아나콘다에 의해 설정되도록 맞춰주자.&lt;/p></description></item><item><title>스프링 부트로 멀티모듈 셋팅하기</title><link>https://taetaetae.github.io/2020/01/19/spring-boot-maven-multi-module/</link><pubDate>Sun, 19 Jan 2020 10:29:03 +0000</pubDate><guid>https://taetaetae.github.io/2020/01/19/spring-boot-maven-multi-module/</guid><description>&lt;p>서비스를 처음 만들기 시작할때면 각 직군별로 생각하는 포인트가 다양하다. 설계, 기획, 디자인, 개발. 여기서 개발은 프로젝트 셋팅을 어떻게 해야하지? 하는 고민을 하기 마련이다. 아주 간단하게 하나의 모듈로 모든 기능을 담당하도록 만들 수 있지만 기능별로 모듈을 나눠서 셋팅하는게 관리측면에서 장점이라 생각한다.&lt;!--more -->&lt;/p>
&lt;p>예를 들어보자. 도서관의 들어온 책 정보를 외부에 제공하는 &amp;ldquo;API&amp;rdquo;, 주기적으로 책 정보를 업데이트 하는 &amp;ldquo;Batch&amp;rdquo;. 이렇게 크게 두가지의 모듈이 있어야 한다고 가정했을때 어떤식으로 모듈을 설계할 수 있을까?&lt;/p>
&lt;p>이번 포스팅에서는 스프링 부트와 메이븐을 활용해서 하나의 프로젝트(컴포넌트)에서 여러 모듈을 관리할 수 있는 &lt;a href="https://spring.io/guides/gs/multi-module/" target="_blank" rel="noopener noreffer ">Spring Multi Module&lt;/a>을 셋팅하는 방법에 대해 알아보고자 한다. 필자도 셋팅하기 전에는 &amp;ldquo;그냥 하면 되는거 아니야?&amp;ldquo;라며 우습게 보다 아주 사소한 부분들에서 엄청난 삽질을 해서 그런지 꼭 포스팅으로 남겨놔야 겠다고 다짐했고 이렇게 정리를 할 수 있게 되어서 다행이라 생각한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png" title="/images/spring-boot-maven-multi-module/team_structure.png" data-thumbnail="/images/spring-boot-maven-multi-module/team_structure.png" data-sub-html="&lt;h2>어쩌면 우리가 있는 팀도 멀티모듈이 아닐까? 출처 : https://bcho.tistory.com/813&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png"
 data-srcset="https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png, https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png 1.5x, https://taetaetae.github.io/images/spring-boot-maven-multi-module/team_structure.png 2x"
 data-sizes="auto"
 alt="/images/spring-boot-maven-multi-module/team_structure.png" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">어쩌면 우리가 있는 팀도 멀티모듈이 아닐까? &lt;br>출처 : &lt;a href="https://bcho.tistory.com/813" target="_blank" rel="noopener noreffer ">https://bcho.tistory.com/813&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="왜-멀티모듈로-셋팅할까">왜 멀티모듈로 셋팅할까?&lt;/h2>
&lt;p>위에서 예시로 이야기 한것처럼 현재 우리가 셋팅해야할 모듈은 크게 두가지 이다.&lt;/p>
&lt;ul>
&lt;li>API : 외부에 도서관에 들어온 책 정보를 알려주는 모듈&lt;/li>
&lt;li>Batch : 주기적으로 도서관의 책 정보를 갱신하는 모듈&lt;/li>
&lt;/ul>
&lt;p>한번 생각을 해보자. 위에서 말한 모듈들 중에 동시에 사용할것만 같은 정보가 있다. &amp;ldquo;책 정보&amp;rdquo;. 각 모듈마다 &amp;ldquo;책 정보&amp;quot;를 가져오는 로직을 작성하는것 보다 한곳에서 해당로직을 구현하고 이를 여러곳에서 사용하는게 사용하는게 중복코드를 방지할수 있는 방법이란건 쉽게 알아차릴수 있다. 그렇다면 어떻게 모듈을 분리할수 있을까?&lt;/p>
&lt;p>필자의 경험으로 미루어 볼때 크게 두가지 방법이 있는것 같다.&lt;/p>
&lt;ul>
&lt;li>공통으로 사용하는 모듈을 jar로 만들고 이를 메이븐 원격 저장소에 deploy, 사용하는 모듈에서 디펜던시에 추가하여 사용&lt;/li>
&lt;li>멀티모듈로 구성하고 사용하는 모듈에서 디펜던시에 추가하여 사용&lt;/li>
&lt;/ul>
&lt;p>첫번째 방법의 가장 큰 단점은, 공통으로 사용하는 모듈이 변경될때마다 버전을 바꿔주고 (안바꿔도 되지만 사용하는 모듈에서 캐시 갱신을 해야하는 불편함이 생긴다.) 메이븐 원격 저장소에 deploy를 해줘야 한다. 그에 반해 두번째 방법은 이런과정없이 함께 빌드만 해주면 끝나고 IDE에서 개발시 한 모듈에서 동시에 수정과 사용이 가능하기 때문에 훨씬 편리하다.&lt;/p>
&lt;p>&lt;a href="https://johngrib.github.io/wiki/No-Silver-Bullet/" target="_blank" rel="noopener noreffer ">은총알은 없다&lt;/a> 라는 말처럼, 정답은 없다. 하지만 이런저런 방법들을 미리 알아두면 적시적소에 사용할 수 있는. 필자가 다른글들에서도 언급을 자주하던 &amp;ldquo;나만의 무기&amp;quot;가 되지 않을까?&lt;/p>
&lt;h2 id="멀티모듈-셋팅하기">멀티모듈 셋팅하기&lt;/h2>
&lt;p>위에서 이야기 했던 &amp;ldquo;API&amp;rdquo;, &amp;ldquo;Batch&amp;quot;와는 별도로 공통으로 사용하는 모듈인 &amp;ldquo;Core&amp;rdquo; 이렇게 총 3개의 모듈을 만들예정이다.&lt;/p>
&lt;blockquote>
&lt;p>다른 이야기지만, 공통으로 사용할 것 &amp;ldquo;같아서&amp;rdquo; 미리 공통로직을 작성하는 습관은 좋지 않는것 같다. 그러다보면 쓸데없이 공통로직이 무거워지므로 실제로 사용하면서 중복코드가 발생할때 그때 공통로직으로 리펙토링 해도 늦지 않는것 같다. (꼰데인가&amp;hellip;)&lt;/p>&lt;/blockquote>
&lt;p>구현하는 환경은 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>Spring Boot 2.2.3&lt;/li>
&lt;li>Maven&lt;/li>
&lt;li>IntelliJ&lt;/li>
&lt;/ul>
&lt;p>우선 IDE의 힘을 빌려 하나의 스프링 부트 프로젝트를 생성해본다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg" title="/images/spring-boot-maven-multi-module/spring_boot_init.jpg" data-thumbnail="/images/spring-boot-maven-multi-module/spring_boot_init.jpg" data-sub-html="&lt;h2>다음 &amp;gt; 다음 &amp;gt; 다음&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg, https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-maven-multi-module/spring_boot_init.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-maven-multi-module/spring_boot_init.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">다음 &amp;gt; 다음 &amp;gt; 다음&lt;/figcaption>
 &lt;/figure>
&lt;p>그 다음 만든 프로젝트에서 우클릭 후 새로운 모듈을 선택. Maven 모듈을 선택하고 적당한 이름을 적어준다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg" title="/images/spring-boot-maven-multi-module/new_module.jpg" data-thumbnail="/images/spring-boot-maven-multi-module/new_module.jpg" data-sub-html="&lt;h2>다음 &amp;gt; 다음 &amp;gt; 다음 222&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg, https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-maven-multi-module/new_module.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-maven-multi-module/new_module.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">다음 &amp;gt; 다음 &amp;gt; 다음 222&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;p>&amp;ldquo;API&amp;rdquo;, &amp;ldquo;Batch&amp;rdquo;, &amp;ldquo;Core&amp;rdquo; 라는 모듈을 추가하고 실제 모듈이 되는 &amp;ldquo;API&amp;rdquo;, &amp;ldquo;Batch&amp;quot;에 &lt;code>parent&lt;/code> 와 &lt;code>dependencies&lt;/code> 을 설정해주자. 그렇게 하고 각 Pom.xml을 보면 아래와 같다. (&amp;ldquo;API&amp;rdquo; 모듈에 대해서만 집중적으로 이야기 하려 한다. &amp;ldquo;Batch&amp;rdquo; 모듈도 동일한 형식으로 작성하기 때문.)&lt;/p>
&lt;ul>
&lt;li>최 상위 Pom.xml (library)
modules 하위에 멀티모듈로 설정한 모듈들의 이름이 들어가 있는것을 확인할 수 있다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span class="nt">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;packaging&amp;gt;&lt;/span>pom&lt;span class="nt">&amp;lt;/packaging&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modules&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;module&amp;gt;&lt;/span>api&lt;span class="nt">&amp;lt;/module&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;module&amp;gt;&lt;/span>core&lt;span class="nt">&amp;lt;/module&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;module&amp;gt;&lt;/span>batch&lt;span class="nt">&amp;lt;/module&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/modules&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-parent&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>2.2.3.RELEASE&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;relativePath/&amp;gt;&lt;/span> &lt;span class="c">&amp;lt;!-- lookup parent from repository --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;name&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;description&amp;gt;&lt;/span>Demo project for Spring Boot&lt;span class="nt">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;java.version&amp;gt;&lt;/span>1.8&lt;span class="nt">&amp;lt;/java.version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-web&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-test&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>test&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;exclusions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;exclusion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">					&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.junit.vintage&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">					&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>junit-vintage-engine&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;/exclusion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/exclusions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>API Pom.xml
parent 부분이 설정되어 있는 모습을 볼수 있고, core 모듈을 사용하기 위해 dependency 에 추가를 해준다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span class="nt">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;packaging&amp;gt;&lt;/span>jar&lt;span class="nt">&amp;lt;/packaging&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>api&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>core&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;finalName&amp;gt;&lt;/span>library-api&lt;span class="nt">&amp;lt;/finalName&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">				&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-maven-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;/plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>Core Pom.xml
Core 모듈은 &amp;ldquo;jar&amp;quot;로 패키징 되어 다른 곳에서 사용되어야 하기 때문에 packaging 만 설정해준다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>library&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.taetaetae&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span class="nt">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;packaging&amp;gt;&lt;/span>jar&lt;span class="nt">&amp;lt;/packaging&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>core&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하고서 Core 모듈에 공통으로 사용될 로직을 작성하고, API 모듈에서 이를 사용하는 로직을 작성한뒤, 빌드를 해보면 에러 없이 정상 작동을 하는 모습을 볼 수 있다.&lt;/p></description></item><item><title>더이상 기다리지 않아도 되는 배치 무중단 배포</title><link>https://taetaetae.github.io/2019/10/13/batch-nondisruptive-deploy/</link><pubDate>Sun, 13 Oct 2019 15:46:12 +0000</pubDate><guid>https://taetaetae.github.io/2019/10/13/batch-nondisruptive-deploy/</guid><description>&lt;p>&lt;a href="https://taetaetae.github.io/2019/09/29/woowabros-spring-batch/" target="_blank" rel="noopener noreffer ">지난 포스팅&lt;/a>, 그러니까 우아한 형제들에서 초대를 받아 Spring batch 에 대한 테크세미나에 다녀 왔다. 그 중 가장 인상깊었던 부분이 바로 &lt;code>무중단 배포&lt;/code>. 차일피일 미루다 필자가 속한 팀에서도 배포때마다 가장 불편을 느끼고 있었던 부분이었기도 했고&lt;!--more -->, &lt;code>그런가보다&lt;/code> 하며 개념만 알고 넘어가기엔 무언가 양심에 찔려 직접 무중단 배포를 할 수 있도록 구성을 해보고 테스트까지 해보고자 한다.&lt;/p>
&lt;h2 id="상황-및-문제점">상황 및 문제점&lt;/h2>
&lt;p>리눅스 서버에 Jenkins가 설치되어 있고, Spring batch 모듈을 실행시키고 있다. 수동으로 실행을 하거나, Jenkins RestApi를 이용해서 실행을 할 수 있지만 주로 정해진 시간 즉, 스케쥴링에 의해 실행되곤 한다. 스케쥴링의 가장 작은 단위는 1분단위 배치도 있기 때문에 24시간 멈추지 않고 실행되고 있다고 무방하다. 하지만 배치 모듈이 수정되고, 배포를 하기 위해서는 다음과 같은 시나리오로 진행이 된다.&lt;/p>
&lt;ol>
&lt;li>Jenkins 설정의 &lt;code>끄기전 준비&lt;/code> 를 실행하여 더이상 Jenkins에 의해 Spring batch 모듈(이하 Job)이 실행되지 않도록 한다.&lt;/li>
&lt;li>새로운 Job은 더이상 실행되지 않지만 이미 실행중이였던 Job 은 강제로 중단을 하거나 Job 이 끝날때까지 기다린다.&lt;/li>
&lt;li>실행중인 Job이 없을 경우 이제 배포를 진행한다.&lt;/li>
&lt;li>배포가 완료되면 Jenkins 설정의 &lt;code>끄기전 준비&lt;/code>를 해제한다.&lt;/li>
&lt;/ol>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg" title="/images/batch-nondisruptive-deploy/wait.jpg" data-thumbnail="/images/batch-nondisruptive-deploy/wait.jpg" data-sub-html="&lt;h2>실행중인 Job이 안끝나면 마냥 기다릴텐가? 출처 : https://m.post.naver.com/viewer/postView.nhn?volumeNo=14100660&amp;memberNo=2032633&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg"
 data-srcset="https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg, https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg 1.5x, https://taetaetae.github.io/images/batch-nondisruptive-deploy/wait.jpg 2x"
 data-sizes="auto"
 alt="/images/batch-nondisruptive-deploy/wait.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">실행중인 Job이 안끝나면 마냥 기다릴텐가? &lt;br>출처 : &lt;a href="https://m.post.naver.com/viewer/postView.nhn?volumeNo=14100660&amp;amp;memberNo=2032633" target="_blank" rel="noopener noreffer ">https://m.post.naver.com/viewer/postView.nhn?volumeNo=14100660&amp;memberNo=2032633&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>실행되는 Job을 중단하지 못하는 상황 즉, 실행중에 중단하면 트랜잭션이 깨져 무조건 기다려야만 하는 상황이라면 배포 또한 계속 지연될 수 밖에 없는 상황인 것이다. Spring boot에 java config 를 활용하고 딱 &lt;code>jar&lt;/code> 파일 하나를 실행하는 방식이라면 &lt;code>jar&lt;/code>파일을 바꿔치기 하는 식으로 고민을 해볼수도 있을것 같다. 하지만 Legacy 코드가 아직 존재하여 일반 Spring 에 xml 로 config 하는 방식으로 운영중이라 &lt;code>jar&lt;/code>파일 하나만 바꿔치기 하기엔 무리가 있는 상황.&lt;/p>
&lt;p>은총알처럼 어디에서나 사용이 가능한 만병통치약 같은 방법은 없다. 언제나 그랬듯 현재 시스템(xml config 방식)에 가장 최적화된 방법, 그리고 java config 방식에서도 사용이 가능할것 같은 방법을 생각해 보았다.&lt;/p>
&lt;h2 id="무중단-배포를-가능케-하는-3가지-핵심">무중단 배포를 가능케 하는 3가지 핵심&lt;/h2>
&lt;p>&lt;strong>1. 배포를 매번 새로운 경로에 배포한다.&lt;/strong>
각 회사마다, 그리고 서비스마다 정말 다양한 배포 시스템이 있다. 그들의 공통점은 원격서버의 &lt;code>특정 경로&lt;/code>에 빌드된 파일들을 밀어 넣어준다는 것. 시나리오는 다음과 같다.&lt;/p>
&lt;ol>
&lt;li>배포할때마다 별도의 디렉토리를 생성한뒤 심볼릭 링크를 연결해준다.&lt;/li>
&lt;li>배포는 &lt;code>1&lt;/code>에서 연결한 심볼릭 링크에 배포되도록 설정, 결국 매번 만들어지는 디렉토리에 배포가 되게 된다.&lt;/li>
&lt;/ol>
&lt;p>여기서 중요한점은 &amp;ldquo;배포할 때마다 새로운 디렉토리에 배포가 된다&amp;rdquo; 와 배포시에는 항상 심볼릭 링크에만 배포를 하면 되기 때문에 &amp;ldquo;배포시스템이 새로 만들어지는 디렉토리의 경로를 몰라도 무방하다&amp;quot;는 점이다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nb">cd&lt;/span> /~~~/deploy/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 임시 디렉토리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DIRECTORY_NAME&lt;/span>&lt;span class="o">=&lt;/span>batch_&lt;span class="k">$(&lt;/span>/bin/date +%Y%m%d%H%M%S&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir &lt;span class="nv">$DIRECTORY_NAME&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 쉘 스크립트를 실행하면 batch_20191012205218 와 같은 디렉토리가 생성이 된다. 심볼릭 링크 관련해서는 바로 아래 이어서 설명하겠다.&lt;/p>
&lt;p>&lt;strong>2. 심볼릭 링크의 원래 링크를 즉시 변경&lt;/strong>
보통 심볼릭 링크 (즉, 바로가기) 의 경로를 변경하기 위해서는 아래처럼 지웠다가 삭제하는 식으로 했었는데&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ mkdir directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ mkdir directory_b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ln -s directory_a asdf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ll
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">asdf -&amp;gt; directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># directory_a 에서 directory_b 로 바꾸는 경우 (심볼릭 링크 자체를 삭제하고 다시 심볼릭 링크 생성)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ rm asdf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ln -s directory_b asdf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ll
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">asdf -&amp;gt; directory_b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">directory_b
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 되면 삭제하고 ~ 다시 만들어지는 타이밍에 배포가 되거나 실행이 되는 즉, 해당 경로에 엑세스 하는 경우 이전의 경로를 바라본다거나 의도했던 방식으로 실행이 되지 않는 상황이 발생한다. (찰나의 타이밍 이지만 필자는 이러한 문제로 이전의 경로를 바라보는 문제가 발생했었다.) 그래서 ln 의 옵션중인 &lt;code>-Tfs&lt;/code>옵션으로 즉시 변경을 해주도록 하자. (&lt;a href="https://linux.die.net/man/1/ln" target="_blank" rel="noopener noreffer ">ln man 참고&lt;/a>)&lt;/p></description></item><item><title>네트워크 모니터링이 궁금할땐 ? Packetbeat !</title><link>https://taetaetae.github.io/2019/09/08/network-monitor-by-packetbeat/</link><pubDate>Sun, 08 Sep 2019 18:11:34 +0000</pubDate><guid>https://taetaetae.github.io/2019/09/08/network-monitor-by-packetbeat/</guid><description>&lt;p>모니터링은 서비스 로직 개발 만큼 한번씩 고민해보고 경험해 봤을 중요한 영역이라 할 수 있다. 그중 웹서버에서 제공해주는 엑세스 로그는 운영하고 있는 웹서비스에 대해 여러가지 측면에서 분석할 수 있는 가장 강력한 아이템 중에 하나라고 생각한다. &lt;!--more -->이를 통해 사용자들이 어떤 url을 많이 호출하고, 어떤 user-agent형태를 사용하는지 알게 되면 그에 따라 서비스 전략을 변경할수도 있고 악의적으로 공격적인 요청에 대해 웹서버단에서 차단을 할 수 있기 때문이다.
이렇게 &lt;code>inbound 트래픽(외부에서 들어오는 요청)&lt;/code>에 대해서는 엑세스 로그를 잘 분석하면 기존의 웹 어플리케이션과는 전혀 무관하게 모니터링이 가능하지만 반대로 &lt;code>outbund 트래픽(외부로 나가는 요청)&lt;/code>에 대해서는 어떤식으로 모니터링을 할 수 있을까?&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/network-monitor-by-packetbeat/passbook.jpg" title="/images/network-monitor-by-packetbeat/passbook.jpg" data-thumbnail="/images/network-monitor-by-packetbeat/passbook.jpg" data-sub-html="&lt;h2>월급통장의 inbound 트래픽보다 outbound 트래픽이 너무 많은 요즘&amp;hellip;이미지 출처 : https://www.app24moa.com/feedDetail/2/2002&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/network-monitor-by-packetbeat/passbook.jpg"
 data-srcset="https://taetaetae.github.io/images/network-monitor-by-packetbeat/passbook.jpg, https://taetaetae.github.io/images/network-monitor-by-packetbeat/passbook.jpg 1.5x, https://taetaetae.github.io/images/network-monitor-by-packetbeat/passbook.jpg 2x"
 data-sizes="auto"
 alt="/images/network-monitor-by-packetbeat/passbook.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">월급통장의 inbound 트래픽보다 outbound 트래픽이 너무 많은 요즘&amp;hellip;&lt;br>이미지 출처 : &lt;a href="https://www.app24moa.com/feedDetail/2/2002" target="_blank" rel="noopener noreffer ">https://www.app24moa.com/feedDetail/2/2002&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>예컨데, 날씨 서비스를 하기 위해 외부에서 &lt;code>서울날씨&lt;/code>라는 페이지를 조회했을 경우 기상청 API에서 넘겨받은 데이터를 가공하여 보여준다고 가정해보자. 이때 기상청에서 제공해주는 특정 API중에 어느 하나가 늦게 응답이 온다거나, 특정시간대에 에러응답을 받을경우 과연 이를 어떤식으로 모니터링 할수 있을까? 어플리케이션 코드에 &lt;code>모니터링을 위한 코드&lt;/code>를 추가할 것인가? 혹 하나의 서버에서 A모듈은 java로, B모듈은 python으로 개발되었을 경우 각각 모듈마다 모니터링을 위한 코드를 추가하는 식으로 하다보면 비지니스 로직을 방해하거나 오히려 추가한 코드 또한 관리해야 하는 배보다 배꼽이 더 커져버릴 상황도 생길수 있다.
어플리케이션의 비지니스 로직과는 무관하게 서버 자체에서 외부로 나가는 네트워크 트래픽에 대해 모니터링을 할 수 있는 &lt;code>가벼우면서도 심플한 모듈&lt;/code>을 찾고 싶었다. 어플리케이션의 개발언어가 무엇이든 상관없이 별도의 에이전트 형식으로 띄워두기만 하면 네트워크 트래픽을 수집 및 분석, 나아가서는 모니터링까지 할수있는&amp;hellip; 그래서 찾다보니 역시나 이러한 고민을 누군가는 하고 있었고 오픈소스까지 되어있는 Elastic Stack 의 Beat중 &lt;code>Packetbeat&lt;/code>라는 데이터 수집모듈을 알게 되었다.&lt;/p>
&lt;blockquote>
&lt;p>역시 내가 하고있는 고민은 이미 누군가 했던 고민들&amp;hellip; 이러한 고민에 대해 해결하는 방법을 보다 빨리 찾는게 경쟁력이 될텐데&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;p>이번 포스팅에서는 Packetbeat 에 대해 간단히 알아보고 이를 활용하여 outbound 트래픽에 대해 모니터링을 해보며 어떤식으로 활용할 수 있는지에 대해 알아보고자 한다.&lt;/p>
&lt;h2 id="packetbeat-">Packetbeat ?&lt;/h2>
&lt;p>ElasticStack 중에 데이터 수집기 플랫폼인 &lt;code>Beats&lt;/code>중 네트워크 트래픽 데이터에 대해 수집을 할 수 있는 데이터 수집기를 제공하고 있다. &lt;a href="https://ko.wikipedia.org/wiki/Pcap" target="_blank" rel="noopener noreffer ">pcap&lt;/a>라이브러리를 이용하여 서버의 네트워크 레벨에서 데이터를 수집 및 분석한 후 외부로(Elasticsearch, Logstash, Kafka 등) 전송해주는 &lt;code>경량 네트워크 패킷 분석기&lt;/code>라고 &lt;a href="https://www.elastic.co/kr/products/beats/packetbeat" target="_blank" rel="noopener noreffer ">공식 홈페이지&lt;/a>에 소개되고 있다.
몇번 사용해보면서 느낀 장점들은 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>설치 및 실행이 너무 간단하다.&lt;/li>
&lt;li>설정값 튜닝을 통해 간단하지만, 그러한 간단함에 비해서 너무 강력한 수집이 가능하다.&lt;/li>
&lt;li>앞서 이야기 했던 어플리케이션 코드와는 전혀 무관하게 작동한다.&lt;/li>
&lt;/ul>
&lt;h2 id="무엇을-해볼것인가-aka-목표">무엇을 해볼것인가?! (a.k.a. 목표)&lt;/h2>
&lt;p>필자가 운영하는 &lt;a href="http://daily-devblog.com" target="_blank" rel="noopener noreffer ">Daily-DevBlog&lt;/a> 라는 서비스가 있다. &lt;del>(갑분 서비스 홍보)&lt;/del> 여러 사람들의 rss를 조회하고 파싱해서 메일을 보내주는 서비스 인데, packetbeat 사용 예시를 들기위해 조금 변형하여 모든 rss를 접근하고 가장 최신글의 제목을 출력하는 아주 간단한 python 스크립트로 outbound 트래픽을 발생시켜 보고자 한다.
그리고 packetbeat 를 이용하여 외부로 호출되는 트래픽을 수집하고 Elasticsearch 로 인덱싱 하여 최종적으로는 어느 rss의 속도가 가장 느린지 실행되는 python코드와는 전혀 관련없이 모니터링 해보고자 한다.
python 코드는 다음과 같다.&lt;/p>
&lt;blockquote>
&lt;p>참고로 필자는 &lt;code>awesome-devblog&lt;/code>의 운영자분께 해당 데이터 사용에 대해 허락을 받은 상태이다.&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">yaml&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">feedparser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">blog_info_list_yml_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://raw.githubusercontent.com/sarojaba/awesome-devblog/master/db.yml&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">blog_info_list_yml&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">blog_info_list_yml_url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">blog_info_yaml_parse_list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">blog_info_list_yml&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">blog_info&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">blog_info_yaml_parse_list&lt;/span> &lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s1">&amp;#39;rss&amp;#39;&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">blog_info&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">blog_info&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;rss&amp;#39;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rss_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">blog_info&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;rss&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">parse_feed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">feedparser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rss_url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">parse_feed_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parse_feed&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">entries&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">blog_info&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;|&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">parse_feed_data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;|&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">parse_feed_data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;link&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 코드를 실행하면 아래처럼 아주 간단하게 &lt;code>블로그 주인의 이름&lt;/code>과 &lt;code>최신글 제목&lt;/code>, &lt;code>링크&lt;/code>가 출력이 된다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/network-monitor-by-packetbeat/rss_python_script.jpg" title="/images/network-monitor-by-packetbeat/rss_python_script.jpg" data-thumbnail="/images/network-monitor-by-packetbeat/rss_python_script.jpg" data-sub-html="&lt;h2>그러고 보니 너무 오랜만에 글쓰네&amp;hellip; (숙연)&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/network-monitor-by-packetbeat/rss_python_script.jpg"
 data-srcset="https://taetaetae.github.io/images/network-monitor-by-packetbeat/rss_python_script.jpg, https://taetaetae.github.io/images/network-monitor-by-packetbeat/rss_python_script.jpg 1.5x, https://taetaetae.github.io/images/network-monitor-by-packetbeat/rss_python_script.jpg 2x"
 data-sizes="auto"
 alt="/images/network-monitor-by-packetbeat/rss_python_script.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">그러고 보니 너무 오랜만에 글쓰네&amp;hellip; (숙연)&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="백문이-불여일견-백견이-불여일타">백문이 불여일견? 백견이 불여일타!&lt;/h2>
&lt;p>언제 어디서부터 유래된 이야기 인지는 모르지만 &amp;ldquo;백번 듣는것이 한번 보는것보다 못하고, 백번 보는것이 한번 타자 치는것보다 못하다&amp;rdquo; 라는 &lt;code>개발버전&lt;/code> 속담이 있다. 자, 위에서 정의한 목표를 이루기 위해 실제로 각종 모듈을 설치해 보도록 하자! ( 필자가 테스트 했던 서버의 환경은 CentOS 7.4 64Bit 이니 참고 )&lt;/p></description></item><item><title>아파치 로드밸런싱으로 여러 WAS 운영하기</title><link>https://taetaetae.github.io/2019/08/04/apache-load-balancing/</link><pubDate>Sun, 04 Aug 2019 19:50:43 +0000</pubDate><guid>https://taetaetae.github.io/2019/08/04/apache-load-balancing/</guid><description>&lt;p>웹서버 하나만 사용하거나 WAS 하나만을 사용하며 웹서비스를 운영하는 경우는 극히 드물다. 웹서버의 장점과 WAS의 장점 그 두마리의 토끼를 다 잡기 위해 보통 앞단에 웹서버를 두고 그 뒤에 WAS를 두며 서비스를 운영하곤 한다. 헌데 운영하는 서비스가 인기가 많아져(?) 사용량이 많아지다면 그만큼 응답이 느려 (TPS 등) 서버를 늘려야 하는 상황이 생긴다고 가정해보자.&lt;!--more --> (물론 서버를 늘리는 것보다 캐시를 적용하거나 로직을 바꿔보는 노력이 선행되야 하겠지만&amp;hellip;) 당연히 서버부터 구매하며 &amp;ldquo;Scale Out&amp;quot;을 하려고 할것이다. 만약 원래 운영하던 서버가 너무 좋아서 CPU나 메모리 사용률이 거의 바닥이여도 서버를 구매해야 할까?
서버를 구매하게되면 결국 두개 이상의 서버가 운영될텐데 그 서버들을 앞에서 묶어주며 트래픽을 분산시켜주는 무언가가 필요하다. 그러한 기술을 바로 &lt;code>로드밸런싱&lt;/code> 이라고 한다. 통상 L4 스위치를 활용하여 요청을 여러 서버들로 분산시키며 산술적으로는 서버 대수만큼 성능이 좋아지는 효과를 볼 수 있다.
하지만 앞서 말했듯 서버의 자원 사용률이 바닥일 정도로 거의 사용을 안할경우 서버를 구매하는건 너무나 비효율적이다. 이번 포스팅에서는 서버를 늘리지 않으면서 웹서버 중 아파치를 활용하여 여러 WAS를 운영하는 방법에 대해 알아보고자 한다. 서버 늘려야 하는 상황에서 사용해 볼 수 있는 나만의 좋은 무기(?)가 생긴게 아닐까 생각이 든다.&lt;/p>
&lt;p>아파치는 &lt;a href="https://httpd.apache.org/#apache-httpd-22-end-of-life-2018-01-01" target="_blank" rel="noopener noreffer ">EOL&lt;/a>이 되었기 때문에 2.4버전으로 설치하고, WAS는 편의상 톰켓 최신버전으로 설치해서 동일한 서버에 아파치 한대와 톰켓 3대를 연동하는것을 목적으로 한다. 로드밸런싱이 어떤식으로 이루어 지고 하위에 연결된 톰켓을 컨트롤 하는 방법 또한 알아볼 예정이다.&lt;/p>
&lt;blockquote>
&lt;p>서버 환경 및 설치하게 될 각 버전은 다음과 같다.
서버 : CentOS 7.4 64Bit
apache : httpd-2.4.39
tomcat : apache-tomcat-8.5.43
tomcat-connectors(mod_jk) : 1.2.46&lt;/p>&lt;/blockquote>
&lt;h2 id="apache-와-tomcat-설치">Apache 와 Tomcat 설치&lt;/h2>
&lt;p>필자의 포스팅에서 종종 나오는 부분이기도 하고, 구글링 해보면 바로 설치 방법을 쉽게 찾을 수 있겠지만 그렇다고 언급을 안하고 넘어가기엔 너무 불친절하니&amp;hellip; 치트키처럼(?) 빠르게 정리해보자.&lt;/p>
&lt;ul>
&lt;li>Apache&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">$ wget http://apache.tt.co.kr//httpd/httpd-2.4.39.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf httpd-2.4.39.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ./configure --prefix=/home/~~~/apache
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make &lt;span class="err">&amp;amp;&amp;amp;&lt;/span> make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd /home/~~~/apache/bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chown root:계정명 httpd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo chmod +s httpd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ vi /home/~~~/apache/conf/httpd.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">User 계정명
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Grop 계정명
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ /home/~~~/apache/bin/apachectl start ← 실행
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 설치를 한뒤 실행을 시키고 서버의 ip를 접속해보면 아래와 같은 화면을 볼 수 있다.&lt;/p>
&lt;ul>
&lt;li>Tomcat&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">$ wget http://mirror.apache-kr.org/tomcat/tomcat-8/v8.5.43/bin/apache-tomcat-8.5.43.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf apache-tomcat-8.5.43.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ /home/apache-tomcat-8.5.43/bin/start.sh ← 실행
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>톰켓의 기본 http 포트인 8080으로 접속을 해보면 귀여운 고양이가 있는 톰켓 기본화면을 볼 수 있다.&lt;/p>
&lt;h2 id="아파치와-톰켓-연동하기">아파치와 톰켓 연동하기&lt;/h2>
&lt;p>아파치와 톰켓의 연동은 &lt;code>mod_jk&lt;/code> 와 &lt;code>mod_proxy&lt;/code> 등 다양한 모듈로 연동을 할 수 있는데 이번 포스팅에서는 &lt;code>mod_jk&lt;/code> 를 활용하는 방법에 대해 알아보고자 한다. 우선 mod_jk 를 설치하자.&lt;/p>
&lt;blockquote>
&lt;p>간단히 mod_jk 는 컴파일, 설정 등 복잡하지만 톰켓 전용 바이너리 프로토콜인 AJP를 사용하기 때문에 높은 성능을 기대할수가 있다. mod_proxy 는 반면 기본으로 아파치에 탑재되어있는 모듈이기 때문에 별도의 모듈 설치가 필요 없고 설정도 간단하다는 장점이 있다. 각 연동방식의 장단점이 있기 때문에 본인이 운영하는 서버 상황에 맞추어 적용 할 필요가 있다.&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>mod_jk 설치&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">$ wget http://apache.tt.co.kr/tomcat/tomcat-connectors/jk/tomcat-connectors-1.2.46-src.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf tomcat-connectors-1.2.46-src.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd tomcat-connectors-1.2.46-src/native
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ./configure --with-apxs=/home/~~~/apache/bin/apxs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make &lt;span class="err">&amp;amp;&amp;amp;&lt;/span> make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ /home/~~~/apache/modules 하위에 mod_jk.so가 생김
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>mod_jk 를 활용하면 AJP라는 통신으로 아파치와 톰켓이 연동되는데 톰켓의 기본 AJP 포트는 8009번임을 알고 다음처럼 설정을 해주자.&lt;/p>
&lt;ul>
&lt;li>apache/conf/workers.properties&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">worker.list=tomcat1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">worker.tomcat1.port=8009
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">worker.tomcat1.host=localhost
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">worker.tomcat1.type=ajp13
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">worker.tomcat1.lbfactor=1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>apache/conf/httpd.conf&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">LoadModule jk_module modules/mod_jk.so
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">IfModule&lt;/span> &lt;span class="na">jk_module&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> JkWorkersFile conf/workers.properties
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> JkLogFile logs/mod_jk.log
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> JkLogLevel info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> JkMount /* 	tomcat1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">IfModule&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하고서 아파치와 톰켓을 재시작 후에 서버의 ip로 접속해보면 (별도의 port 없이) 톰켓 설정페이지로 랜딩이 되는것을 확인할 수 있다.&lt;/p>
&lt;h2 id="로드밸런싱을-위한-작업">로드밸런싱을 위한 작업&lt;/h2>
&lt;p>여기까지는 본 포스팅을 작성하기 위한 밑거름이라고 말할 수 있다. 이제 실제로 로드밸런싱을 해볼 차례.
앞서 톰켓 하나만 설치했는데 편의상 톰켓 3개를 설치해두자. (하나를 설치하고 cp -r 명령어를 활용하는게 빠르다.) 그 다음 각 톰켓의 모든 포트를 셋다 다르게 설정해야 하는데 겹치지 않도록 설정해 두고 (필자는 앞자리를 1,2,3 이런식으로 다르게 설정하였다.) 워커(workers.properties)를 아래처럼 설정해주자.&lt;/p></description></item><item><title>스프링을 활용한 대용량 파일 업로드 구현</title><link>https://taetaetae.github.io/2019/07/21/spring-file-upload/</link><pubDate>Sun, 21 Jul 2019 22:09:58 +0000</pubDate><guid>https://taetaetae.github.io/2019/07/21/spring-file-upload/</guid><description>&lt;p>개발을 하다보면 실제로 직접 구현을 해본적은 없지만 여기저기서 들어본 지식과 그 동안의 짬밥(?)으로 추측해볼수 있는 부분들이 있다. 물론 모든일에 정답은 없겠지만 요즘 느끼는건 책에서 공부만 해본것과 다른 블로그들에서 눈으로만 보고 넘어가는것들 그리고 직접 손가락을 움직여가며 왜 여기서는 이 방법을 사용하지 고민하면서 구현을 해본다는건 정말 엄청나게 큰 차이가 있는것 같다. &lt;!--more -->
웹 어플리케이션을 개발하다보면 한번 쯤 만나게 되는 &lt;code>파일 업로드&lt;/code> 기능. 필자도 몇번 구현은 해봤지만 그냥 단순히 &lt;code>구현&lt;/code>만 해본 상태였다가 최근에 그냥 파일 업로드가 아닌 &lt;code>대용량&lt;/code> 파일 업로드에서의 문제가 발생하여 여기저기 삽질을 하게 되었고 정리도 해볼겸 스프링에서의 대용량 파일 업로드시 한번쯤 고려해봐야 할 부분에 대해 정리를 해보려고 한다.&lt;/p>
&lt;blockquote>
&lt;p>물론 구글에서 검색을 해보면 아마 필자가 쓴것 보다 더 자세하고 좋은 글들이 있겠지만 필자는 보다 &lt;code>대용량&lt;/code>에 집중에서 작성해 보고자 한다. 명심하자. &amp;ldquo;아무리 흐린 잉크라도 좋은 기억력보다 낫다&amp;rdquo; 라는 말이 있듯이&lt;/p>&lt;/blockquote>
&lt;h2 id="스프링을-활용한-파일-업로드-구현">스프링을 활용한 파일 업로드 구현&lt;/h2>
&lt;p>우선 완전 초기상태에서 시작하기 위해 스프링 부트 프로젝트를 만들고 간단하게 파일 업로드를 할 수 있는 form 페이지와 업로드 버튼을 눌렀을때 작동하게 되는 컨트롤러를 만들어 보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">java.io.File&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">java.io.IOException&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">java.io.InputStream&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.apache.commons.io.FileUtils&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.springframework.stereotype.Controller&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.springframework.web.bind.annotation.RequestMapping&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.springframework.web.bind.annotation.RequestMethod&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.springframework.web.bind.annotation.RequestParam&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.springframework.web.multipart.MultipartFile&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">lombok.extern.slf4j.Slf4j&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Controller&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">FileUploadController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="c1">// 너무 간단 ...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/form&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">form&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;form&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/upload&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RequestMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">POST&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">upload&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nd">@RequestParam&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;file&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">MultipartFile&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">multipartFile&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;### upload&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">File&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">targetFile&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">File&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/home1/irteam/&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">multipartFile&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getOriginalFilename&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">InputStream&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">fileStream&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">multipartFile&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getInputStream&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">FileUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">copyInputStreamToFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fileStream&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">targetFile&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IOException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">FileUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">deleteQuietly&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">targetFile&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">printStackTrace&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;redirect:/form&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>upload&lt;/code> 요청이 들어오면 &lt;code>file&lt;/code>이라는 이름의 파라미터로 &lt;code>MultipartFile&lt;/code>을 받고 파일의 이름을 확인 후 스트림을 읽어 특정 경로에 파일로 저장하는 로직이다. 그다음 &lt;code>/form&lt;/code>을 접속하게 되면 나오는 폼 화면을 만들자. 이것도 아주 심플하게!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>파일 업로드&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">form&lt;/span> &lt;span class="na">action&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/upload&amp;#34;&lt;/span> &lt;span class="na">method&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;post&amp;#34;&lt;/span> &lt;span class="na">enctype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;multipart/form-data&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;file&amp;#34;&lt;/span> &lt;span class="na">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;파일 선택&amp;#34;&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;file&amp;#34;&lt;/span>&lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;submit&amp;#34;&lt;/span> &lt;span class="na">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;업로드&amp;#34;&lt;/span>&lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">form&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>multipart/form-data&lt;/code> 라는 Content-Type 을 명시해주고 파일을 선택하면 &lt;code>/upload&lt;/code>로 POST요청을 하도록 설정한다. 이렇게 되면 너무 간단하게 + 이상없이 파일이 업로드가 잘 되니 이게 이야기 할 꺼리인가(?) 싶을정도로 심플하다.&lt;/p>
&lt;h2 id="그런데-파일-크기가-크다면">그런데 파일 크기가 크다면?&lt;/h2>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-file-upload/omg.jpg" title="/images/spring-file-upload/omg.jpg" data-thumbnail="/images/spring-file-upload/omg.jpg" data-sub-html="&lt;h2>설마 파일 업로드 하는 용량이 크겠어?&amp;hellip; 왠지 파일의 용량이 크면 문제있을것 같은데&amp;hellip; 출처 : https://m.blog.naver.com/naibbo0407/30170815180&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-file-upload/omg.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-file-upload/omg.jpg, https://taetaetae.github.io/images/spring-file-upload/omg.jpg 1.5x, https://taetaetae.github.io/images/spring-file-upload/omg.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-file-upload/omg.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">설마 파일 업로드 하는 용량이 크겠어?&amp;hellip; 왠지 파일의 용량이 크면 문제있을것 같은데&amp;hellip; &lt;br>출처 : &lt;a href="https://m.blog.naver.com/naibbo0407/30170815180" target="_blank" rel="noopener noreffer ">https://m.blog.naver.com/naibbo0407/30170815180&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>개발을 하다보면 항상 생각해야 할 부분중에 하나가 바로 확장성인것 같다. 이 부분에서 역시 문제가 되었던 것. 평소보다 용량이 큰 파일이 업로드가 되면서 (평소 3&lt;del>400MB 였다가 3&lt;/del>4GB정도의 파일이 업로드가 되는 매직) 업로드가 안되는 상황이 발생하였다. 당연히 문제가 발생하면 누군가 말했듯 로그부터 살펴보았는데 Apache - (AJP) - Tomcat 으로 구성된 환경에서 tomcat 로그에 &lt;code>### upload&lt;/code>라는 로그가 없고 아파치 로그엔 &lt;code>502 에러&lt;/code>가 발생한 것이었다. 왜 톰켓 로그도 안남고 그전에 에러가 발생하였을까?
이때부터 (근거없는 추측을 하며&amp;hellip;) 고난과 역경의 삽질을 하기 시작하게 된다. 톰켓 버전이 문제일까? 로그가 안찍혔다면 다른 필터나 인터셉터에서 무언가를 먹고(?)있는건 아닐까? 잠깐, 근데 원래 대용량 업로드가 되긴 해? 파일 업로드/다운로드 하는 사이트 보면 별도 프로그램으로 하던데&amp;hellip; 꼬불꼬불 미로속을 헤메는것만 같았던 삽질의 문제는 결국 &lt;code>메모리&lt;/code>에 있었다.
파일을 업로드 하게 되면 해당 내용을 우선 메모리에 담게 되고 다 담은 후 메모리에 있는 내용을 was에 전달한 뒤 HttpServletRequest 로 넘어오게 된다.(Apache &amp;gt; Tomcat) 그런데 파일을 업로드 하면서 메모리에 파일이 써지다가 메모리 부족으로 OOM이 발생하게 되버린 것이었다. 또한 스프링 파일 최대크기를 별도로 지정하지 않고 있었기 때문에 메모리가 충분했다 하더라도 에러가 발생했을 상황이었다. ( &lt;a href="https://spring.io/guides/gs/uploading-files/" target="_blank" rel="noopener noreffer ">https://spring.io/guides/gs/uploading-files/&lt;/a> 참조 )&lt;/p></description></item><item><title>Spring에서 Request를 우아하게 로깅하기</title><link>https://taetaetae.github.io/2019/06/30/controller-common-logging/</link><pubDate>Sun, 30 Jun 2019 18:39:47 +0000</pubDate><guid>https://taetaetae.github.io/2019/06/30/controller-common-logging/</guid><description>&lt;p>스프링 기반의 웹 어플리케이션을 만들다 보면 요청을 처리하는데 맨 처음에 위치하고 있는 &lt;code>Controller&lt;/code>(이하 컨트롤러)라는 레이어를 만들게 된다. 그럴때면 사용자가 어떤 요청(Request)을 하였는지에 대해 확인이 필요할 수 있다. &lt;!--more --> 물론 확인을 안해도 무방하지만 가급적 로깅은 시스템 로직에 영향을 주지 않는 범위에서 최대한 다양하게 &lt;code>미리&lt;/code> 해두는게 나중에 유지보수시 편할 수 있다. (예전 조직장님께서 말씀하신게 아직도 머릿속에 꽉 자리잡고 있다&amp;hellip;)
아~주 일반적으로, 컨트롤러에서는 다음과 같이 메소드 단위로 파라미터를 직접 로깅하게 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">SampleController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/test1&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test1&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nd">@RequestParam&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;id : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;length : &amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 되면 사용자가 &lt;code>GET /test1&lt;/code> 이라는 요청을 보낼때 어떤 파라미터로 호출하였는지에 대해 로깅이 남게 되는데 항상 &lt;code>log.info(&amp;quot;id : {}&amp;quot;, id);&lt;/code> 과 같이 수동으로 로깅을 남겨야 하는 불편함이 생긴다. 물론 꼼꼼하게 메소드마다 로깅을 적어주면 전혀 문제될게 없지만 이러한 컨트롤러 ~ 메소드가 한두개가 아닌 수십 또는 수백개일 경우엔 그때마다 로깅을 적어줘야 하는 불편함이 있을 수 있다. 또한 자칫 깜박하고 로깅을 빼먹고 배포를 하게 된 경우 모니터링시 로깅을 하지 않아서 다시 로깅하고 배포를 하는, 별것도 아닌데(?) &amp;ldquo;정말 불편한&amp;rdquo; 상황이 있을 수 있다.
이번 포스팅에서는 사용자의 요청을 모니터링 하기 위해 컨트롤러마다 코드를 작성해가며 로깅을 하는것이 아니라 &lt;code>HttpServletRequestWrapper&lt;/code> 라는 것과 &lt;code>Filter&lt;/code>, &lt;code>AOP&lt;/code>를 이용하여 &lt;strong>Request의 정보를 한곳에서 우아하게 로깅하는 방법&lt;/strong>에 대해 알아보고자 한다.&lt;/p>
&lt;h2 id="요구사항">요구사항&lt;/h2>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/controller-common-logging/bull_fighting.gif" title="/images/controller-common-logging/bull_fighting.gif" data-thumbnail="/images/controller-common-logging/bull_fighting.gif" data-sub-html="&lt;h2>와 개발하자아!출처 : https://gfycat.com/ko/brightevilaoudad&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/controller-common-logging/bull_fighting.gif"
 data-srcset="https://taetaetae.github.io/images/controller-common-logging/bull_fighting.gif, https://taetaetae.github.io/images/controller-common-logging/bull_fighting.gif 1.5x, https://taetaetae.github.io/images/controller-common-logging/bull_fighting.gif 2x"
 data-sizes="auto"
 alt="/images/controller-common-logging/bull_fighting.gif" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">와 개발하자아!&lt;br>출처 : &lt;a href="https://gfycat.com/ko/brightevilaoudad" target="_blank" rel="noopener noreffer ">https://gfycat.com/ko/brightevilaoudad&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>투우사가 흔드는 빨간 천을 보며 돌진하는 황소처럼 (쓰고보니 너무 TMI 같다&amp;hellip;.) 당장 코딩을 시작하며 개발을 할 수도 있지만 정작 원하는 기능이 무엇인지 천천히 정리하고 넘어갈 필요가 있는 것 같다. (어쩔땐 오히려 후자가 더 빠른 개발을 하게 되는것 같다.)&lt;/p>
&lt;ol>
&lt;li>GET, POST 등 다양한 http method 로 구현된 모든 컨트롤러의 파라미터와 기타 Request 정보가 로깅이 되야 한다.&lt;/li>
&lt;li>컨트롤러, 메소드가 늘어날때마다 별도의 코드 추가 없이 한곳에서 공통적으로 로깅이 되야 한다.&lt;/li>
&lt;li>URL 중 특정 패턴으로 들어오는 요청은 다른 방식으로 로깅을 하거나, 로깅에서 제외할 수 있어야 한다.&lt;/li>
&lt;li>앞서 말했듯 다른 비지니스 로직에 영향을 주지 않아야 한다.&lt;/li>
&lt;/ol>
&lt;h2 id="구현하기---request-의-파라미터-정리">구현하기 - Request 의 파라미터 정리&lt;/h2>
&lt;p>Request 의 모든 로깅을 한곳에서 처리하기 위해서 filter(필터)를 활용하였다. 필터는 Dispatcher servlet의 앞단에 위치하고 있기 때문에 모든 정보를 확인할 수 있는데 용이하다. 물론 인터셉터를 활용해서도 방법이 있겠지만 본 포스팅 에서는 필터를 활용해서 구현하는것을 목적으로 한다. (사실 인터셉터로 몇번 시도해보다가 실패해서&amp;hellip;유유 )&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/controller-common-logging/spring-request-lifecycle.jpg" title="/images/controller-common-logging/spring-request-lifecycle.jpg" data-thumbnail="/images/controller-common-logging/spring-request-lifecycle.jpg" data-sub-html="&lt;h2>Spring MVC Request Life Cycle출처 : https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/controller-common-logging/spring-request-lifecycle.jpg"
 data-srcset="https://taetaetae.github.io/images/controller-common-logging/spring-request-lifecycle.jpg, https://taetaetae.github.io/images/controller-common-logging/spring-request-lifecycle.jpg 1.5x, https://taetaetae.github.io/images/controller-common-logging/spring-request-lifecycle.jpg 2x"
 data-sizes="auto"
 alt="/images/controller-common-logging/spring-request-lifecycle.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">Spring MVC Request Life Cycle&lt;br>출처 : &lt;a href="https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/" target="_blank" rel="noopener noreffer ">https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>Filter를 만들기 전에 Filter에서 사용할 주요 핵심(?) 클래스가 필요한데 &lt;code>HttpServletRequest&lt;/code> 를 Wrapping 해서 사용하기 위해 &lt;code>HttpServletRequestWrapper&lt;/code>를 상속받는 클래스를 만들자. Request 에 담겨있는 param 과 body로 요청이 들어올 경우 body에 있는 내용을 param 에 담는 로직이다. 주요 설명은 코드 안에서 주석으로 설명하겠다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">ReadableRequestWrapper&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">extends&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpServletRequestWrapper&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 상속&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Charset&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rawData&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HashMap&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">ReadableRequestWrapper&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HttpServletRequest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="kd">super&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">putAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getParameterMap&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 원래의 파라미터를 저장&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">charEncoding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getCharacterEncoding&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 인코딩 설정&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">encoding&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">isBlank&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">charEncoding&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">StandardCharsets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">UTF_8&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Charset&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">forName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">charEncoding&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">InputStream&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getInputStream&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">rawData&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IOUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">toByteArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">is&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// InputStream 을 별도로 저장한 다음 getReader() 에서 새 스트림으로 생성&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="c1">// body 파싱&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">collect&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getReader&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">lines&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">collect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Collectors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">joining&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">lineSeparator&lt;/span>&lt;span class="p">()));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collect&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// body 가 없을경우 로깅 제외&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getContentType&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getContentType&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">ContentType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">MULTIPART_FORM_DATA&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getMimeType&lt;/span>&lt;span class="p">()))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 파일 업로드시 로깅제외&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">JSONParser&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonParser&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">JSONParser&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">Object&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonParser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collect&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">instanceof&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">JSONArray&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">JSONArray&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonArray&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">JSONArray&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">jsonParser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collect&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">setParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;requestBody&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonArray&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">toJSONString&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">else&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">JSONObject&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonObject&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">JSONObject&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">jsonParser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collect&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="n">Iterator&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">iterator&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonObject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">keySet&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">iterator&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">while&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">iterator&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">hasNext&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">iterator&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">next&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="n">setParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonObject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="na">toString&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;\\\&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;ReadableRequestWrapper init error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">paramArray&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">getParameterValues&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">paramArray&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">paramArray&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">paramArray&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">else&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getParameterMap&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Collections&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">unmodifiableMap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Enumeration&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getParameterNames&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Collections&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">enumeration&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">keySet&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getParameterValues&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dummyParamValue&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dummyParamValue&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">dummyParamValue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">arraycopy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dummyParamValue&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dummyParamValue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">param&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">setParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">param&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">values&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">values&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletInputStream&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getInputStream&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ByteArrayInputStream&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">byteArrayInputStream&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ByteArrayInputStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">rawData&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ServletInputStream&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">boolean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">isFinished&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">boolean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">isReady&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setReadListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ReadListener&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">readListener&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="c1">// Do nothing&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">read&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">byteArrayInputStream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">read&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BufferedReader&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getReader&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BufferedReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">InputStreamReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getInputStream&lt;/span>&lt;span class="p">(),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">encoding&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>가장 중요한 부분은 &lt;code>IOUtils.toByteArray(is)&lt;/code> 요 부분인데, InputStream은 한번밖에 읽을 수 없기 때문에 이 필터에서 스트림을 읽는 대신, 래퍼 구현으로 새 스트림 생성하도록 작업을 하였다. 자칫 잘못하다간 body의 내용이 유실될 수도 있기 때문이다.
참조 :
&lt;a href="http://stackoverflow.com/questions/10210645/http-servlet-request-lose-params-from-post-body-after-read-it-once" target="_blank" rel="noopener noreffer ">http://stackoverflow.com/questions/10210645/http-servlet-request-lose-params-from-post-body-after-read-it-once&lt;/a>
&lt;a href="http://stackoverflow.com/questions/3769259/why-is-the-parameter-value-an-object-hash-code-for-request-getparametermap-ge" target="_blank" rel="noopener noreffer ">http://stackoverflow.com/questions/3769259/why-is-the-parameter-value-an-object-hash-code-for-request-getparametermap-ge&lt;/a>&lt;/p></description></item><item><title>spring-boot에서 mybatis로 mysql 연동하기</title><link>https://taetaetae.github.io/2019/04/21/spring-boot-mybatis-mysql-xml/</link><pubDate>Sun, 21 Apr 2019 22:47:04 +0000</pubDate><guid>https://taetaetae.github.io/2019/04/21/spring-boot-mybatis-mysql-xml/</guid><description>&lt;p>실무에서 개발을 하다보면 과거 누군가 잘 구성해 놓은 밥상(legacy)에 숟가락만 얹는 느낌으로 &lt;code>로직 구현&lt;/code>만 할때가 있다. 그러다보면 각종 레이어가 어떻게 구성(설정)되어있는지도 모르고 &lt;!-- more --> 간혹 설정에서 문제가 발생하면 &amp;ldquo;아 내가 이것도 모르고 이제까지 개발을 해왔나&amp;rdquo; 하는 자괴감이 들며 몇시간을 삽질하는 경우가 있다. 그게 지금의 필자인것 같다. (눙물&amp;hellip;)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/mung.jpg" title="/images/spring-boot-mybatis-mysql-xml/mung.jpg" data-thumbnail="/images/spring-boot-mybatis-mysql-xml/mung.jpg" data-sub-html="&lt;h2>출처 : http://blog.naver.com/PostView.nhn?blogId=ondo_h&amp;logNo=221437452142&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/mung.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/mung.jpg, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/mung.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/mung.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-mybatis-mysql-xml/mung.jpg" width="30%" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : &lt;a href="http://blog.naver.com/PostView.nhn?blogId=ondo_h&amp;amp;logNo=221437452142" target="_blank" rel="noopener noreffer ">http://blog.naver.com/PostView.nhn?blogId=ondo_h&amp;logNo=221437452142&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>사이드 프로젝트 초기셋팅을 하며 호기롭게 spring boot 최신버전에서 db를 연동하려 했는데 막상 완전 바닥부터 해본 경험이 적다보니 (spring boot 2 버전에서는 더욱더&amp;hellip;) 어디서부터 뭘 설정을 해야할지&amp;hellip; 그리고 &lt;code>이럴때 보는&lt;/code> 도큐먼트를 봐도 잘 이해가 안되어 삽질을 해가며 당황하기 일쑤였다.
이번 포스팅에서는 아래와 같은 구성을 하는데 목표를 두고자 한다.&lt;/p>
&lt;ul>
&lt;li>Spring Boot 2 프로젝트를 처음 만들고&lt;/li>
&lt;li>mybatis 를 사용해서&lt;/li>
&lt;li>mysql 을 연동하는것 (AWS 의 RDS를 사용, 추후 RDS사용법에 대해 블로깅 예정)&lt;/li>
&lt;/ul>
&lt;p>위와 같은 상황을 처음 접하는 분들께 도움이 되었으면 하는 바램으로 짧게나마 필자의 삽질기를 여행해보자.&lt;/p>
&lt;h2 id="spring-boot-2-프로젝트-만들기">Spring boot 2 프로젝트 만들기&lt;/h2>
&lt;p>필자는 IntelliJ를 사용하고 있어서 새로 프로젝트를 만들려고 할때 클릭 몇번만으로 dependency 설정까지 다 해주기 때문에 편하고 좋았다. 혹 이클립스나 다른 IDE를 사용하고 있다면 &lt;a href="https://start.spring.io/" target="_blank" rel="noopener noreffer ">https://start.spring.io/&lt;/a> 을 참고하면 도움이 될것같다. 여기서도 클릭 몇번으로 IntelliJ 에서 해주는 것처럼 내가 사용할 모듈을 선택하고 generate 를 누르면 프로젝트가 생성되어 다운로드 받아진다. (참 좋은 세상&amp;hellip;)
우선 File → New → Project 를 눌러서 아래 창을 열어보자. 그리고 뭔가 다 해줄것 같은 (개발도 해주면 안되나&amp;hellip;) &lt;code>Spring Initializr&lt;/code>을 선택후 아래와 같은 설정을 적어준 뒤 다음을 눌러준다.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/1.jpg" title="/images/spring-boot-mybatis-mysql-xml/1.jpg" data-thumbnail="/images/spring-boot-mybatis-mysql-xml/1.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/1.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/1.jpg, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/1.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/1.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-mybatis-mysql-xml/1.jpg" width="80%" />
 &lt;/a>
&lt;p>사용할 모듈을 선택해주자. 필자는 이것저것(?)을 도와주는 &lt;code>lombok&lt;/code>과 &lt;code>Mybatis&lt;/code>, &lt;code>MySQL&lt;/code>을 선택하고 프로젝트를 생성하였다. 그러면 이쁜(?) pom.xml 과 함께 당장 개발을 시작할 수 있는 환경이 제공된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.mybatis.spring.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>mybatis-spring-boot-starter&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>2.0.1&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>mysql&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>mysql-connector-java&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>	
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.projectlombok&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>lombok&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;optional&amp;gt;&lt;/span>true&lt;span class="nt">&amp;lt;/optional&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/2.jpg" title="/images/spring-boot-mybatis-mysql-xml/2.jpg" data-thumbnail="/images/spring-boot-mybatis-mysql-xml/2.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/2.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/2.jpg, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/2.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/2.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-mybatis-mysql-xml/2.jpg" width="80%" />
 &lt;/a>
&lt;p>우선 여기까지 잘 되었는제 확인해보기 위해 Controller 에 현재시간을 출력하는걸 만들어 보고&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RestController&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">ApiController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@GetMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/helloWorld&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">helloWorld&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">now&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTimeFormatter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ISO_LOCAL_DATE_TIME&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>톰켓을 실행해보면 정상적으로 접속과 출력이 되는것을 확인할 수 있다.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/3.jpg" title="/images/spring-boot-mybatis-mysql-xml/3.jpg" data-thumbnail="/images/spring-boot-mybatis-mysql-xml/3.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/3.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/3.jpg, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/3.jpg 1.5x, https://taetaetae.github.io/images/spring-boot-mybatis-mysql-xml/3.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-boot-mybatis-mysql-xml/3.jpg" width="80%" />
 &lt;/a>
&lt;h2 id="mysql-연동하기">MySQL 연동하기&lt;/h2>
&lt;p>필자가 허둥지둥 했던점 중 하나는 MyBatis와 MySQL을 동시에 연동하려고 하다보니 문제가 발생해도 어디서의 문제인지를 제대로 파악하지 못하고 삽질했다는 점이다. 여기서 정확히 짚고 넘어가면 우선 데이터를 연결해주는 ORM인 MyBatis를 셋팅해준 다음 MySQL을 연동해주는 식으로 분리해서 설정을 하면 햇갈리지 않고 (돌아가지 않고) 보다 빠르게 설정이 가능할것 같다. (여기서 순서는 중요하지 않고 별도로 설정해야 한다는 관점이 중요한것 같다.)
우선 &lt;code>src/main/resources&lt;/code>폴더에 있는 &lt;code>application.properties&lt;/code> 에 다음처럼 작성해주자.&lt;/p>
&lt;pre tabindex="0">&lt;code>spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.jdbc-url=jdbc:mysql://{url}:{port}/{db}
spring.datasource.hikari.username={id}
spring.datasource.hikari.password={password}
&lt;/code>&lt;/pre>&lt;p>위의 jdbc-url 항목에서 AWS에서 제공하는 RDS를 사용하는 경우 RDS에서 제공해주는 엔드포인트와 포트를 적어주면 된다. (추후 AWS - RDS에 대해 블로깅 예정이다.)
Spring Boot 2.0 이후부터 기본적으로 사용되는 커넥션 풀이 HikariCP로 변경되었다고 한다. (&lt;a href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes#hikaricp" target="_blank" rel="noopener noreffer ">링크&lt;/a>) 커넥션 풀 종류중 성능이 좋다고 하는데 &lt;a href="https://github.com/brettwooldridge/HikariCP" target="_blank" rel="noopener noreffer ">링크&lt;/a>를 가보면 다른 커넥션 풀 라이브러리와 성능을 비교한 벤치마크 결과를 확인할 수 있다.
위처럼 &lt;code>spring.datasource.hikari&lt;/code> 가 prefix로 붙고 각종 정보들을 적어주어 config 에서 인식될수 있도록 해주자. 그 다음 DataSource 설정을 해준다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@PropertySource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;classpath:/application.properties&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">DatabaseConfiguration&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@ConfigurationProperties&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">prefix&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;spring.datasource.hikari&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HikariConfig&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">hikariConfig&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HikariConfig&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DataSource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">dataSource&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">DataSource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dataSource&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HikariDataSource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hikariConfig&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;datasource : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dataSource&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dataSource&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위 내용은 DataSource 를 hikariConfig에서 설정한 정보로 만들어 준다는 의미이다. 이렇게만 하고 프로젝트를 다시 실행시켜보면 logger 에 의해 datasource 의 정보를 볼수가 있다.&lt;/p></description></item><item><title>AWS 프리티어 발급부터 EC2 접속까지</title><link>https://taetaetae.github.io/2019/04/14/aws-freetier-create-and-ssh-access/</link><pubDate>Sun, 14 Apr 2019 17:39:03 +0000</pubDate><guid>https://taetaetae.github.io/2019/04/14/aws-freetier-create-and-ssh-access/</guid><description>&lt;p>IT 쪽에 일을 하고 있거나 관심을 가지고 있는 사람이라면 한번쯤을 들어봤을 AWS(Amazon Web Services). 이름에서도 알수있는 것처럼 아마존에서 제공하는 각종 원격 컴퓨팅 웹서비스이다. &lt;!-- more --> 아마존은 이러한 서비스를 누구나 쉽게 접근해볼수 있도록 &lt;a href="https://aws.amazon.com/ko/free/" target="_blank" rel="noopener noreffer ">AWS 프리티어&lt;/a>를 제공해 주는데 이 프리티어 만으로도 과금없이 (또는 최소화 하여) 웹서비스를 구성할수 있다. 필자가 운영하고 있는 &lt;a href="http://daily-devblog.com" target="_blank" rel="noopener noreffer ">기술블로그 구독서비스&lt;/a>또한 AWS 프리티어로 운영되고 있다.
최근 GDG Seoul, P-typer, Sketch Seoul 에서 주최한 &lt;a href="https://www.meetup.com/ko-KR/GDG-Seoul/events/259463050/" target="_blank" rel="noopener noreffer ">D.light 345 투게더톤&lt;/a>에 참가하며 사이드 프로젝트를 하고 있는데 마침 AWS를 사용하게 되었다. 예전에 사용했을때는 장님 코끼리 만지듯이 설정을 했었는데 이번기회를 통해 다시한번 정리를 해본다.
본 포스팅에서는 AWS 계정을 발급받고 신용카드 확인까지 된 계정에서 EC2 서버를 발급받고 putty를 활용하여 서버에 접근을 해보는것을 목표로 둔다.&lt;/p>
&lt;blockquote>
&lt;p>(사이드 프로젝트를 하면서) 아마도 웹서비스를 개발하면서 AWS를 활용하는 부분에 대해 시리즈물로 포스팅을 하게 될것 같다.
사실 너무 간단해서 이런걸 글로 쓰나? 라고 할수도 있지만 눈으로만 보는것과 직접 해보는 것이 다르고, 이걸 다시 글로써 정리를 하는것 또한 완전 다른 부분이기 때문에 포스팅을 해본다.&lt;/p>&lt;/blockquote>
&lt;h2 id="ec2-생성하기">EC2 생성하기&lt;/h2>
&lt;p>EC2? Amazon Elastic Compute Cloud의 약자로 물리서버가 아닌 클라우드 서버를 제공하고 있다. EC2의 장점은 서버의 스펙을 쉽고 자유롭게 조정할 수 있는점이 가장 매력있게 생각한다. 우선 콘솔에 들어가 EC2를 검색후 접속을 하고 &lt;code>인스턴스 시작&lt;/code>을 눌러서 인스턴스 생성 화면으로 들어간다.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-1.jpg" title="/images/aws-freetier-create-and-ssh-access/ec2-1.jpg" data-thumbnail="/images/aws-freetier-create-and-ssh-access/ec2-1.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-1.jpg"
 data-srcset="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-1.jpg, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-1.jpg 1.5x, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-1.jpg 2x"
 data-sizes="auto"
 alt="/images/aws-freetier-create-and-ssh-access/ec2-1.jpg" width="80%" />
 &lt;/a>
&lt;p>AMI 즉 생성할 이미지를 선택하는 부분인데 여기서 주의할점은 잘못선택 했다간 계정 만들었을때의 카드로 생각지도 못할 금액이 결제가 되버릴수도 있다. (실제로 필자도 AWS를 처음 만져볼때 아무생각없이 좋아보이는걸로 했다가 한 30달러 정도를 지불했어야만 했다&amp;hellip;) 좌측에 보면 &lt;code>프리 티어만&lt;/code>이라는 체크박스를 체크하고 자신이 원하는 이미지를 선택하자. 일반적인 리눅스 서버를 발급받고 싶기 때문에 빨간 영역의 이미지를 선택하고 선택한 이미지의 스팩을 다시한번 확인하자. (cpu 1개에 메모리도 1기가&amp;hellip; 너무 짜지만 무료니까&amp;hellip;)&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-2.jpg" title="/images/aws-freetier-create-and-ssh-access/ec2-2.jpg" data-thumbnail="/images/aws-freetier-create-and-ssh-access/ec2-2.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-2.jpg"
 data-srcset="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-2.jpg, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-2.jpg 1.5x, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-2.jpg 2x"
 data-sizes="auto"
 alt="/images/aws-freetier-create-and-ssh-access/ec2-2.jpg" width="80%" />
 &lt;/a>
&lt;p>마지막으로 &lt;code>시작하기&lt;/code> 를 누르면 키 페어를 선택 또는 생성하도록 안내가 나오는데 당연히 아무것도 안한 상태라 &lt;code>새 키 페어 생성&lt;/code>을 선택해 주고 이름을 지정한뒤 키 파일을 받아준다. 이 부분에서도 조심해야할 점이 키 페어를 한번 다운 받으면 다시 동일한 키 페어를 다운받을수가 없게 된다. (나중에 다시 발급을 받아야 하는 번거로운 문제가&amp;hellip;) 다운을 받고 잊어버리지 않도록 잘 보관해두자.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-3.jpg" title="/images/aws-freetier-create-and-ssh-access/ec2-3.jpg" data-thumbnail="/images/aws-freetier-create-and-ssh-access/ec2-3.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-3.jpg"
 data-srcset="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-3.jpg, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-3.jpg 1.5x, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-3.jpg 2x"
 data-sizes="auto"
 alt="/images/aws-freetier-create-and-ssh-access/ec2-3.jpg" width="80%" />
 &lt;/a>
&lt;p>키 페어를 다운 받으면 생성중이라는 메세지와 함께 결과화면이 나온다. 여기서도 중요한 부분! &lt;code>프리티어&lt;/code>라는 달콤한 키워드 때문에 들뜬 마음으로 성급하게 빨리 서버를 받아보고 싶다고 &lt;code>다음다음 신공&lt;/code>을 하다보면 자칫 간과할수가 있는데 화면을 보면 &lt;code>결제 알림 생성&lt;/code>이라는 다행스러운 기능이 있다. 별 어려운 설정이 아니니 꼭 설정을 해서 필자같이 기부(?)를 하는 일이 발생하지 않았으면 한다&amp;hellip;&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-4.jpg" title="/images/aws-freetier-create-and-ssh-access/ec2-4.jpg" data-thumbnail="/images/aws-freetier-create-and-ssh-access/ec2-4.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-4.jpg"
 data-srcset="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-4.jpg, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-4.jpg 1.5x, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-4.jpg 2x"
 data-sizes="auto"
 alt="/images/aws-freetier-create-and-ssh-access/ec2-4.jpg" width="80%" />
 &lt;/a>
&lt;p>EC2 인스턴스가 생성이 되었다. 인스턴스의 각종 정보를 확인할수가 있는데 public IP, public DNS 까지 제공되는것을 확인할 수 있다. (추후 DNS를 구입하게 되다면 이 IP에 연결을 시켜 도메인으로 해당 서버에 접속을 할수가 있게 된다.)&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-5.jpg" title="/images/aws-freetier-create-and-ssh-access/ec2-5.jpg" data-thumbnail="/images/aws-freetier-create-and-ssh-access/ec2-5.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-5.jpg"
 data-srcset="https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-5.jpg, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-5.jpg 1.5x, https://taetaetae.github.io/images/aws-freetier-create-and-ssh-access/ec2-5.jpg 2x"
 data-sizes="auto"
 alt="/images/aws-freetier-create-and-ssh-access/ec2-5.jpg" width="80%" />
 &lt;/a>
&lt;h2 id="putty-로-발급받은-ec2-인스턴스에-접속을-해보자">putty 로 발급받은 EC2 인스턴스에 접속을 해보자.&lt;/h2>
&lt;p>이제 발급받은 EC2 인스턴스에 접속을 해볼 차례이다. 다양한 서버 접속툴이 있지만 필자는 putty를 가장 선호한다. 디자인은 구닥다리처럼 보일지 모르겠지만 개인적으로 직관적인 UI에 가벼운 프로그램이라 생각이 든다. 우선 putty를 &lt;a href="https://www.putty.org/" target="_blank" rel="noopener noreffer ">다운&lt;/a> 받고 &lt;code>putty.exe&lt;/code>를 실행시킨뒤에 바로 ssh 접속을 하면 너무 간단하게 서버 접속에 성공을 할수 있지만 위에서 받은 키 페어 파일을 다시 private key 로 전환해야 하는데 putty를 다운받으면 동일한 폴더에 &lt;code>puttygen.exe&lt;/code>라는 파일을 실행시켜주자.
그다음 &lt;code>pem&lt;/code>파일을 불러와서 마우스를 움직여서 게이지(?)를 다 채우고 &lt;code>save private key&lt;/code>를 줄러 저장을 하는데 여기서 주의할점은 &lt;code>ppk&lt;/code>파일명을 &lt;code>pem&lt;/code>파일명과 동일하게 저장해야 한다는 것이다. (안그러면 서버 접속시 실패가 남&amp;hellip; 삽질&amp;hellip;)&lt;/p></description></item><item><title>Jenkins 업그레이드 및 Master-Slave 구성</title><link>https://taetaetae.github.io/2019/03/17/jenkins-upgrade-master-slave/</link><pubDate>Sun, 17 Mar 2019 18:23:03 +0000</pubDate><guid>https://taetaetae.github.io/2019/03/17/jenkins-upgrade-master-slave/</guid><description>&lt;p>어떠한 작업(Job)이 있다고 가정해보자. 이를 &amp;ldquo;정해진 시간에 주기적&amp;rdquo; 이나 &amp;ldquo;필요할때&amp;rdquo; 작업을 수행하고 싶다면 어떤 툴(Tool)이 떠오르는가? &lt;!-- more -->그리고 이 작업(Job)들의 실행이력 등 전체적으로 관리하고 필요에 따라 다양한 플러그인을 활용하여 입맛에 맞는 작업(Job)으로 구성하고 싶을때 가장 첫번째로 떠오르는 툴은 바로 &amp;ldquo;Jenkins&amp;rdquo; 다. (극히 필자 개인적인 생각일수도 있지만&amp;hellip; ) 물론 리눅스 기반의 crontab 이나 다른 스케쥴러를 활용할수도 있다. 다만 필자 개인적인 느낌으로 나만의 Jarvis(?)처럼 내가 원하는데로 설정만 해두면 정해진 시간에 수행하고 그 결과를 로그로 남겨놓고 문제가 발생했을때 알림도 받을수 있으니 너무 좋은 툴이라 생각이 든다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" title="/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" data-thumbnail="/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" data-sub-html="&lt;h2>실제로 Jarvis가 있다면 얼마나 편할까출처 : https://gfycat.com/ko/colossalsociablebuffalo&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/ColossalSociableBuffalo-size_restricted.gif" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">실제로 Jarvis가 있다면 얼마나 편할까&lt;br>출처 : &lt;a href="https://gfycat.com/ko/colossalsociablebuffalo" target="_blank" rel="noopener noreffer ">https://gfycat.com/ko/colossalsociablebuffalo&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>지난 &lt;a href="https://taetaetae.github.io/2018/12/02/jenkins-install/" target="_blank" rel="noopener noreffer ">포스팅&lt;/a>에서는 Jenkins 를 설치하는 방법에 대해 알아보았다. (정확히 말하면 치트키 수준의&amp;hellip; ) 이번 포스팅에서는 Jenkins에 노드를 추가하여 master-slave 분산환경으로 구성하는 방법과 Jenkins 버전을 업그레이드 하는 방법에 대해 정리해보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>마침 필자의 팀에서 젠킨스를 분산환경으로 운영하고 있었는데 버전은 1.x &amp;hellip; 간헐적으로 Jenkins 버전 이슈로 에러가 발생해서 업그레이드를 해야하는 상황이 생긴것이다. 시키지도 않은 일을 하면서 팀에 도움도 될겸, 포스팅도 할겸, 1석 2조 효과. 서버 환경은 CentOS 7.4 64Bit 에서 테스트 하였다.&lt;/p>&lt;/blockquote>
&lt;h2 id="jenkins-버전-업그레이드-하기">Jenkins 버전 업그레이드 하기&lt;/h2>
&lt;p>Jenkins를 업그레이드 하게되면 기존에 있었던 Jenkins의 환경설정은 어떻게 마이그레이션 할까? Job 실행기록들은 그냥 날려버려야 하나? 걱정을 하며 구글링을 해본다. 그러면 &amp;ldquo;안해본것에 대한 두려움&amp;rdquo; 을 갖는 필자의 마음이 무색할 정도로 너무 간단하게도 그냥 기존에 있던 war 파일을 최신버전으로 교체하고 재시작 하라고 나온다. 읭? 뭐이리 간단해? 대부분의 문제들은 지레 겁부터 먹고 실행에 옮기지 &lt;del>못해서&lt;/del> 않아서 해결을 하지 못하는게 절반 이상같다. 자, 바로 실행에 옮겨보자.
우선 버전 업그레이드를 테스트 하기 위해 일부러 &lt;a href="http://mirrors.jenkins.io/war-stable/" target="_blank" rel="noopener noreffer ">낮은버전&lt;/a>으로 설치를 해둔다. (필자는 1.609.1로 설치해봤다.) 그리고 버전 업그레이드 후 설정이 그대로 옮겨지는지를 확인하기위해 Security 설정을 해서 Jenkins 접근시 로그인 여부를 물어보록 설정해둔다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg" title="/images/jenkins-upgrade-master-slave/old_jenkins.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/old_jenkins.jpg" data-sub-html="&lt;h2>우측 하단에 빨간영역으로 낮은버전이 설치된것을 확인할수 있다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/old_jenkins.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/old_jenkins.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">우측 하단에 빨간영역으로 낮은버전이 설치된것을 확인할수 있다.&lt;/figcaption>
 &lt;/figure>
&lt;p>설정이 완료되었으면 최신버전의 war를 다운받아 교체하고 재시작을 해준다. 그러면 너무나도 간단하게 버전이 업그레이드가 된것을 확인할수 있다. 그리고 처음에 설정한 Security 설정까지 그대로 유지되는것 또한 확인이 가능하다. 물론 구 버전에서 설치되었던 플러그인들이 버전업이 되며 그에 따라 지원하지 않는 문제들이 생길 수 있는데 이 부분은 플러그인을 업그레이드를 해준다거나 각 상황에 맞는 대응을 해줘야 한다. 이렇게 해서 생각보다(?) 너무 간단하게 버전업이 완료되었다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" title="/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" data-sub-html="&lt;h2>업그레이드 후 플러그인 업그레이드도 동일하게 맞춰주는게 중요하다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/upgrade_complete.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/upgrade_complete.jpg" width="80%" />
 &lt;/a>&lt;figcaption class="image-caption">업그레이드 후 플러그인 업그레이드도 동일하게 맞춰주는게 중요하다.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="jenkins-분산환경-구성하기-노드-추가하기">Jenkins 분산환경 구성하기 (노드 추가하기)&lt;/h2>
&lt;p>이번엔 Jenkins를 분산환경으로 구성해보고자 한다. 이렇게 노드를 추가하며 분산환경을 구성하는 이유는 마스터-슬레이브(Master-Slave) 패턴의 장점을 얻고자 함이다. 마스터는 작업을 쪼개고 슬레이브로 구성된 노드에게 분배를 하게되면 슬레이브 서버는 마스터의 요청을 처리하고 리턴하게 된다. 마치 스타크래프트에서 일꾼을 늘려서 미네랄과 가스를 더 빨리 얻는것처럼 말이다.&lt;/p>
&lt;p>여기서 필자가 가장 많이 삽질한 부분. 슬레이브 서버를 추가하는데 슬레이브 서버가 되는 서버에 동일하게 젠킨스를 설치하고 그들을 모두 연결하려 했던것&amp;hellip; 마치 클러스터링 하는것처럼&amp;hellip; 당연히 Jenkins 들의 묶음형태(?) 가 되야 할것같은 생각으로 시도하였지만 엄청난 삽질의 연속이 되어버렸다. 알고보니 마스터 Jenkins에서 슬레이브 서버에 작업을 전달할수 있도록 연동만 시켜주면 자동으로 Agent를 마스터 Jenkins가 슬레이브 서버에 설치/실행을 하고 작업을 분할하는것을 확인할 수 있었다. 자, 그럼 시작해보자.&lt;/p>
&lt;ol>
&lt;li>마스터 서버에서 공개키와 개인키 생성
먼저 마스터 서버와 슬레이브 서버를 SSH로 통신할수 있도록 SSH 키 설정을 해준다. 통상 홈 디렉토리 하위 .ssh 폴더에서 생성한다.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">ssh 키 생성
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ssh-keygen -t rsa
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Generating public/private rsa key pair.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter file in which to save the key &lt;span class="o">(&lt;/span>/~/.ssh/id_rsa&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter passphrase &lt;span class="o">(&lt;/span>empty &lt;span class="k">for&lt;/span> no passphrase&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter same passphrase again:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your identification has been saved in /~/.ssh/id_rsa.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Your public key has been saved in /~/.ssh/id_rsa.pub.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The key fingerprint is:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SHA256:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ user@hostname
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">The key&lt;span class="err">&amp;#39;&lt;/span>s randomart image is:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+---&lt;span class="o">[&lt;/span>RSA 2048&lt;span class="o">]&lt;/span>----+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>oo. . &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>o... o + &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>. .o o+.o &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>.++++. +o+o.. &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>o.+*&lt;span class="o">=&lt;/span>.o.SEoo&lt;span class="o">=&lt;/span> &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> . o+.*...+ + &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> .. + +. + &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> + . . &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> ... &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+----&lt;span class="o">[&lt;/span>SHA256&lt;span class="o">]&lt;/span>-----+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">공개키 확인
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cat id_rsa.pub
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ssh-rsa AAAAB3Nza~~~~~~~~eQKcx8B6uAflRm1J8In1 user@hostname
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>슬레이브 서버에서 마스터 서버에서 만든 공개키를 등록
슬레이브 서버에서는 마스터 서버에서 SSH 접속을 허용해야 하기때문에 마스터 서버에서 생성한 공개키를 등록해준다. 슬레이브 서버의 홈 디렉토리 하위 .ssh 폴더아래 파일을 만들고 위 공개키를 넣어주자.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ vi authorized_keys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ssh-rsa AAAAB3Nza~~~~~~~~eQKcx8B6uAflRm1J8In1 user@hostname
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>Jenkins 에서 Credentials 을 만들때 Private Key 설정을 &amp;ldquo;From the Jenkins master ~/.ssh&amp;quot;으로 설정한다. 나중에 이 정보로 인증을 처리한다.&lt;/li>
&lt;/ol>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg" title="/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/jenkins_upgrade_3.jpg" width="80%" />
 &lt;/a>
&lt;ol start="4">
&lt;li>노드를 추가하고 조금 있으면 마스터 노드가 슬레이브 서버에 에이전트를 설치/실행하고 연동이 된것을 확인할수 있다.&lt;/li>
&lt;/ol>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg" title="/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg" data-thumbnail="/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg"
 data-srcset="https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg 1.5x, https://taetaetae.github.io/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg 2x"
 data-sizes="auto"
 alt="/images/jenkins-upgrade-master-slave/jenkins_upgrade_4.jpg" width="80%" />
 &lt;/a>
&lt;p>실제로 슬레이브 서버에서 프로세스를 확인하면 아래처럼 에이전트( slave.jar )가 설치/실행되고 있는것을 확인할수 있다.&lt;/p></description></item><item><title>기술블로그 구독서비스 개발 후기 - 3부</title><link>https://taetaetae.github.io/2019/02/17/daily-dev-blog-3/</link><pubDate>Sun, 17 Feb 2019 01:17:27 +0000</pubDate><guid>https://taetaetae.github.io/2019/02/17/daily-dev-blog-3/</guid><description>&lt;p>작년 7월 12일부터 시작한 필자의 첫 토이프로젝트인 &lt;a href="http://daily-devblog.com" target="_blank" rel="noopener noreffer ">기술블로그 구독서비스&lt;/a>. 오픈할 때까지만 해도 &amp;ldquo;AWS 프리티어를 사용하고 있는 1년 안에 구독자가 설마 1,000명이 넘겠어?&amp;rdquo; 라고 생각을 했었는데 &lt;!-- more --> 오픈을 하고 220일째 되는 바로 어제 어느덧 벌써 구독자가 1,000명을 달성하게 되었다. 그 기념으로 그동안 미뤄두었던 &lt;code>기술블로그 구독서비스 개발 후기&lt;/code> 시리즈의 3부를 쓰고자 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/daily-dev-blog-3/nice_minion.gif" title="/images/daily-dev-blog-3/nice_minion.gif" data-thumbnail="/images/daily-dev-blog-3/nice_minion.gif" data-sub-html="&lt;h2>오예~ 1,000명이다! 땡큐! 출처 : https://gfycat.com/ko/leafytorngroundbeetle&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/daily-dev-blog-3/nice_minion.gif"
 data-srcset="https://taetaetae.github.io/images/daily-dev-blog-3/nice_minion.gif, https://taetaetae.github.io/images/daily-dev-blog-3/nice_minion.gif 1.5x, https://taetaetae.github.io/images/daily-dev-blog-3/nice_minion.gif 2x"
 data-sizes="auto"
 alt="/images/daily-dev-blog-3/nice_minion.gif" width="70%" />
 &lt;/a>&lt;figcaption class="image-caption">오예~ 1,000명이다! 땡큐! &lt;br>출처 : &lt;a href="https://gfycat.com/ko/leafytorngroundbeetle" target="_blank" rel="noopener noreffer ">https://gfycat.com/ko/leafytorngroundbeetle&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>혹시 전에 내용을 보고자 하면 아래 링크에서 확인할 수 있다.&lt;/p>
&lt;ul>
&lt;li>1부 : &lt;a href="https://taetaetae.github.io/2018/08/05/daily-dev-blog-1/" target="_blank" rel="noopener noreffer ">왜 만들게 되었는가 그리고 어떤 구조로 만들었는가&lt;/a>&lt;/li>
&lt;li>2부 : &lt;a href="https://taetaetae.github.io/2018/08/09/daily-dev-blog-2/" target="_blank" rel="noopener noreffer ">문제발생 및 Trouble Shooting&lt;/a>&lt;/li>
&lt;li>3부 : &lt;a href="https://taetaetae.github.io/2019/02/17/daily-dev-blog-3/" target="_blank" rel="noopener noreffer ">앞으로의 계획과 방향성&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="그간-어떤-식으로-서비스를-운영했는가">그간 어떤 식으로 서비스를 운영했는가?&lt;/h2>
&lt;p>(한마디로 정리할 순 없는 지난 220일이었지만…) 딱 한마디로 정리하자면 &lt;code>엄청나게 많은 것을 배우고 경험할 수 있었으나 그만큼 힘들었던 시간들&lt;/code>이라고 말할 수 있을 것 같다. 	2부에서 이야기한 &lt;code>문제 발생에 따른 Trouble Shooting&lt;/code>들도 있었지만 운영을 해오다 보니 사전에 생각하지도 못한 부분에서 문제가 생기는 정말 다양한 경험을 할 수 있었기 때문이다.&lt;/p>
&lt;ul>
&lt;li>블로그 포스팅을 수집하는 과정에서의 문제
일부 블로그 RSS url에 접근을 할 때 요청에 대한 응답이 무한대로 멈춰버리는 현상이 간헐적으로 있었다. 이는 별도의 타임아웃을 설정하지 않았기 때문이다. 그래서 어느 정도의 타임아웃을 두고 시간 내에 응답이 없을 경우 다음 포스팅으로 넘어가도록 하였다. (타임아웃은 아주 기본적인 부분인데&amp;hellip;)&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>requests.get(rss_url, timeout=10.0)
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>메일 발송하는 과정에서의 문제
가끔 메일이 오지 않는다고 친절하게 필자 개인 메일로 연락이 오는 경우가 있었다. 그때마다 서버의 상태를 보면 서버에 직접 접속조차 안 될 정도로 메모리 사용량이 너무 많아서 그때마다 AWS 웹 콘솔에서 강제로 서버를 재부팅을 하곤 했었다. 예전에도 이야기한 것처럼 AWS 프리티어를 사용하고 있다 보니 서버의 메모리가 1기가밖에 되지 않아서 &amp;hellip; 제한된 시스템에서 서비스 운영을 할 수밖에 없는 상황이었다.
그래서 수집/발송 상태를 로깅으로 쉽게 볼 수 있고 스케줄링을 하기 위해 띄워둔 Jenkins(tomcat)를 중단하고 crontab으로 스케줄링을 하도록 하였고, 로깅은 &lt;a href="https://stackoverflow.com/questions/4811738/how-to-log-cron-jobs" target="_blank" rel="noopener noreffer ">별도의 파일로 로깅&lt;/a>하도록 변경하였다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">/usr/bin/python3.6 /home/~~~/email_send.py &amp;gt; /home/~~~/logs/job/email_send_&lt;span class="sb">`date +\%Y\%m\%d\%H\%M\%S`&lt;/span>.log 2&amp;gt;&lt;span class="err">&amp;amp;&lt;/span>1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>또한 기존에는 빠르게 발송하기 위해 냅다 스레드로 돌렸는데 구독자 수가 많아지다 보니 &lt;code>RuntimeError: can't start new thread&lt;/code> 라고 스레드를 만들 수 없다는 에러가 발생하기도 했다. 그래서 Pool을 사용하는 방식의 &lt;a href="https://docs.python.org/3.4/library/multiprocessing.html" target="_blank" rel="noopener noreffer ">multiprocessing&lt;/a> 을 도입하여 스레드로 발송할 때보다는 엄청나게 빠른 속도는 아닐지라도 효율적인 메모리 사용으로 2분 안에 1,000명에게 안정된 메일을 보낼 수 있게 되었다. (여담이지만 메일이 안 온다고 알려주셨던 분들께 이 자리를 빌려 감사의 인사를 전하고 싶다.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">multiprocessing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Pool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Pool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pool&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sendMail&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">email_list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;a href="https://www.heroku.com/" target="_blank" rel="noopener noreffer ">Heroku&lt;/a> 나 &lt;a href="https://www.netlify.com/" target="_blank" rel="noopener noreffer ">Netlify&lt;/a> 같이 서버를 직접 들어가지 않고 앱 형태로 배포하는 식으로 할 수도 있다. 하지만 초기에 이 토이프로젝트를 시작할 때 실 서비스와 최대한 동일한 시스템으로 운영해보고 싶었기 때문에 라즈베리파이에 설치하는 것까지 알아보다 결국 AWS를 사용하기로 하게 되었다.
그렇다면 AWS 프리티어를 사용하지 않고 별도의 서버를 구매하면 안 될까? 하는 생각도 해봤지만 최소한의 인프라로 최대한의 성능을 내보고 싶은 욕심(?) 때문에 1년간은 프리티어로 운영하고 그다음엔 (혹은 소프트웨어적으로 한계까지 도달한다면) 서버를 구매해서 운영하게 될 것 같다. (적어도 이후에도 이 서비스를 유지한다는 가정하에&amp;hellip;)&lt;/p>&lt;/blockquote>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/daily-dev-blog-3/daily-ddb-check.jpg" title="/images/daily-dev-blog-3/daily-ddb-check.jpg" data-thumbnail="/images/daily-dev-blog-3/daily-ddb-check.jpg" data-sub-html="&lt;h2>농부의 마음으로&amp;hellip; 출처 : http://www.iwithjesus.com/news/articleView.html?idxno=2511&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/daily-dev-blog-3/daily-ddb-check.jpg"
 data-srcset="https://taetaetae.github.io/images/daily-dev-blog-3/daily-ddb-check.jpg, https://taetaetae.github.io/images/daily-dev-blog-3/daily-ddb-check.jpg 1.5x, https://taetaetae.github.io/images/daily-dev-blog-3/daily-ddb-check.jpg 2x"
 data-sizes="auto"
 alt="/images/daily-dev-blog-3/daily-ddb-check.jpg" width="70%" />
 &lt;/a>&lt;figcaption class="image-caption">농부의 마음으로&amp;hellip; &lt;br>출처 : &lt;a href="http://www.iwithjesus.com/news/articleView.html?idxno=2511" target="_blank" rel="noopener noreffer ">http://www.iwithjesus.com/news/articleView.html?idxno=2511&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>아침 10시가 되면 자동으로 메일이 잘 발송되었는지, 혹 어제 수집된 것이 아니라 예전에 수집된 내용이 중복 발송된 건 아닌지, 발송은 구독한 사람 전부에게 잘 보내졌는지&amp;hellip; 거의 매일같이 Daily-DevBlog 서비스를 살피며 지낸 것 같다. (하루라도 문제가 생기면 밤을 새워서라도 원인을 파악하고 다음 발송에는 정상적으로 발송되도록 수정하기도 했다.)&lt;/p></description></item><item><title>누구나 할 수 있는 엑세스 로그 분석 따라 해보기 (by Elastic Stack)</title><link>https://taetaetae.github.io/2019/02/10/access-log-to-elastic-stack/</link><pubDate>Sun, 10 Feb 2019 14:37:31 +0000</pubDate><guid>https://taetaetae.github.io/2019/02/10/access-log-to-elastic-stack/</guid><description>&lt;p>필자가 Elastic Stack을 알게된건 2017년 어느 여름 동기형이 공부하고 있는것을 보고 호기심에 따라하며 시작하게 되었다. 그때까지만 해도 버전이 2.x 였는데 지금 글을 쓰고있는 2019년 2월초 최신버전이 6.6이니 정말 빠르게 변화하는것 같다. &lt;!-- more -->빠르게 변화하는 버전만큼 사람들의 관심도 (드라마틱하게는 아니지만) 꾸준히 늘어나 개인적으로, 그리고 실무에서도 활용하는 범위가 많아지고 있는것 같다.&lt;/p>
&lt;script type="text/javascript" src="https://ssl.gstatic.com/trends_nrtr/1709_RC01/embed_loader.js">&lt;/script> &lt;script type="text/javascript"> trends.embed.renderExploreWidget("TIMESERIES", {"comparisonItem":[{"keyword":"elasticsearch","geo":"KR","time":"today 5-y"}],"category":0,"property":""}, {"exploreQuery":"date=today%205-y&amp;geo=KR&amp;q=elasticsearch","guestPath":"https://trends.google.co.kr:443/trends/embed/"}); &lt;/script>
&lt;p>그래서 그런지 최근들어 &lt;code>(아주 코딱지만큼 조금이라도 더 해본)&lt;/code> 필자에게 Elastic Stack 사용방법에 대해 물어보는 주변 지인들이 늘어나고 있다. 그리고 예전에 한창 공부했을때의 버전보다 많이 바꼈기에 이 기회에 &amp;ldquo;그대로 따라만 하면 Elastic Stack을 구성할 수 있을만한 글&amp;quot;을 써보고자 한다. 사실 필자가 예전에 &amp;ldquo;도큐먼트를 보기엔 너무 어려워 보이는 느낌적인 느낌&amp;rdquo; 때문에 삽질하며 구성한 힘들었던 기억을 되살려 최대한 심플하고 처음 해보는 사람도 따라하기만 하면 &amp;ldquo;아~ 이게 Elastic Stack 이구나!&amp;rdquo;, &amp;ldquo;이런식으로 돌아가는 거구나!&amp;rdquo; 하는 도움을 주고 싶다.&lt;/p>
&lt;blockquote>
&lt;p>+ 그러면서 최신버전도 살펴보고&amp;hellip; 1석2조, 이런게 바로 블로그를 하는 이유이지 않을까?
다시한번 말하지만 도큐먼트가 최고 지침서이긴 하다&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;p>&lt;a href="https://www.elastic.co/kr/products" target="_blank" rel="noopener noreffer ">Elastic 공식 홈페이지&lt;/a>에 가면 각 제품군들에 대해 그림으로 된 자세한 설명과 도큐먼트가 있지만 이들을 어떤식으로 조합하여 사용하는지에 대한 전체적인 흐름을 볼 수 있는 곳은 없어 보인다. (지금 보면 도큐먼트가 그 어디보다 설명이 잘되어 있다고 생각되지만 사전 지식이 전혀없는 상태에서는 봐도봐도 어려워 보였다.)
이번 포스팅에서는 &lt;strong>Apache access log를 Elasticsearch에 인덱싱 하는 방법&lt;/strong>에 대해 설명해보고자 한다.&lt;/p>
&lt;h2 id="전체적인-흐름">전체적인 흐름&lt;/h2>
&lt;p>필자는 글보다는 그림을 좋아하는 편이라 전체적인 흐름을 그림으로 먼저 보자.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/access-log-to-elastic-stack/concept.jpg" title="/images/access-log-to-elastic-stack/concept.jpg" data-thumbnail="/images/access-log-to-elastic-stack/concept.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/access-log-to-elastic-stack/concept.jpg"
 data-srcset="https://taetaetae.github.io/images/access-log-to-elastic-stack/concept.jpg, https://taetaetae.github.io/images/access-log-to-elastic-stack/concept.jpg 1.5x, https://taetaetae.github.io/images/access-log-to-elastic-stack/concept.jpg 2x"
 data-sizes="auto"
 alt="/images/access-log-to-elastic-stack/concept.jpg" width="100%" />
 &lt;/a>
&lt;ol>
&lt;li>외부에서의 접근이 발생하면 apache 웹서버에서 설정한 경로에 access log가 파일로 생성이 되거나 있는 파일에 추가가 된다. 해당 파일에는 한줄당 하나의 엑세스 정보가 남게 된다.&lt;/li>
&lt;li>fileBeat에서 해당 파일을 트래킹 하고 있다가 라인이 추가되면 이 정보를 logstash 에게 전달해준다.&lt;/li>
&lt;li>logastsh 는 filebeat에서 전달한 정보를 특정 port로 input 받는다.&lt;/li>
&lt;li>받은 정보를 filter 과정을 통해 각 정보를 분할 및 정제한다. (ip, uri, time 등)&lt;/li>
&lt;li>정리된 정보를 elasticsearch 에 ouput 으로 보낸다. (정확히 말하면 인덱싱을 한다.)&lt;/li>
&lt;li>elasticsearch 에 인덱싱 된 정보를 키바나를 통해 손쉽게 분석을 한다.&lt;/li>
&lt;/ol>
&lt;p>한번의 설치고 일련의 과정이 뚝딱 된다면 너무 편하겠지만, 각각의 레이어가 나뉘어져있는 이유는 하는 역활이 전문적으로(?) 나뉘어져 있고 각 레이어에서는 세부 설정을 통해 보다 효율적으로 데이터를 관리할 수 있기 때문이다.&lt;/p>
&lt;blockquote>
&lt;p>beats라는 레이어가 나오기 전에는 logstash에서 직접 file을 바라보곤 했었는데 beats가 logstash 보다 가벼운 shipper 목적으로 나온 agent 이다보니 통상 logstash 앞단에 filebeat를 위치시키곤 한다고 한다.&lt;/p>&lt;/blockquote>
&lt;p>전체적인 그림은 위와 같고, 이제 이 글을 보고있는 여러분들이 따라할 차례이다. 각 레이어별로 하나씩 설치를 해보며 구성을 해보자. 설치순서는 데이터 흐름의 순서에 맞춰 다음과 같은 순서로 설치를 해야 효율적으로 볼수가 있다. (아래순서대로 하지 않을경우 설치/시작/종료 를 각각의 타이밍에 맞추어 해줘야 할것 같아 복잡할것같다.)&lt;/p>
&lt;pre tabindex="0">&lt;code>elasticsearch → logstash → kibana → filebeat
&lt;/code>&lt;/pre>&lt;p>이 포스팅은 CentOS 7.4에서 Java 1.8, apache 2.2가 설치되어있다는 가정하에 보면 될듯하다. 또한 각 레이어별 설명은 구글링을 하거나 Elastic 공식 홈페이지에 가보면 자세히 나와있으니 기본 설명은 안하는것으로 하고, 각 레이어의 세부 설정은 하지 않는것으로 한다.&lt;/p>
&lt;h3 id="elasticsearch">Elasticsearch&lt;/h3>
&lt;p>&lt;a href="https://www.elastic.co/kr/products/elasticsearch" target="_blank" rel="noopener noreffer ">공식 홈페이지&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">다운받고 압축풀고 심볼릭 경로 만들고 (심볼릭 경로는 선택사항)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.6.0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar zxvf elasticsearch-6.6.0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ln -s elasticsearch-6.6.0 elasticsearch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">설정 파일을 열고 추가해준다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd elasticsearch/conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ vi elasticsearch.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">path.data: /~~~/data/elasticsearch (기본경로에서 변경할때추가)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">path.logs: /~~~/logs/elasticsearch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">network.host: 0.0.0.0 # 외부에서 접근이 가능하도록 (실제 ip를 적어줘도 됨)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">elasticsearch 의 시작과 종료를 조금이나마 편하게 하기위해 스크립트를 작성해줌 (이것또한 선택사항)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd ../bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ echo &amp;#39;./elasticsearch -d -p es.pid&amp;#39; &amp;gt; start.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ echo &amp;#39;kill &lt;span class="sb">`cat es.pid`&lt;/span>&amp;#39; &amp;gt; stop.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ chmod 755 start.sh stop.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>혹시 아래와 같은 에러가 발생할경우 &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-cli-run-prod-mode" target="_blank" rel="noopener noreffer ">공식문서&lt;/a> 대로 진행해준다.&lt;/p></description></item><item><title>Spring MVC Redirect 처리중에 발생한 Out Of Memory 원인 분석하기</title><link>https://taetaetae.github.io/2019/01/10/spring-redirect-oom/</link><pubDate>Thu, 10 Jan 2019 18:39:47 +0000</pubDate><guid>https://taetaetae.github.io/2019/01/10/spring-redirect-oom/</guid><description>&lt;p>초창기 신입시절에 배우거나 사용했던 기술적인 방법들이 있다. 시간이 지날수록 왠만해선 다른방법은 사용하지 않으려 하고 &lt;code>습관&lt;/code>처럼 기존에 사용했던 방법을 고수하는 버릇이 있다. 그 이유는 과거에 사용했을때 아무 탈 없이 잘 되었기 때문에, 그리고 빠른 구현 때문이라는 핑계일 것 같다. &lt;!-- more -->이러한 버릇은 비단 이 글을 적고있는 필자 뿐만이 아니라 대부분의 개발자들이 가지고 있을꺼라 조심스레 추측해본다. (아니라면&amp;hellip;더욱 분발 해야겠다&amp;hellip;ㅠ)
최근 운영하고 있는 서비스에서 장애 상황까지 갈수있는 위험한 상황이 있었는데 팀내 코드리뷰를 통해 문제점을 파악할 수 있었다. 그 원인은 Spring MVC Controller 레벨에서 redirect 처리를 할때 return값의 Cardinality가 높을경우 다음과 같이 사용하면 안된다고&amp;hellip;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;/test&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RequestMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">GET&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;어떠한 로직에 의해 생성되는 url&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;redirect:&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// &amp;lt;- 위험 포인트!&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 코드가 왜? 어디가 어때서?
이제까지 Controller 레벨에서 redirect 처리를 할때 아무생각없이 위에 있는 코드 형태로 구현을 했는데 저러한 코드 때문에 OOM이 발생하여 fullGC 가 여러번 발생하고, 일시적으로 서비스가 지연되는 현상이 발생했다고 한다. 자주 사용하던 방법이였는데 장애를 유발할수 있는 위험한 방법이였다니&amp;hellip;
이번 포스팅에서는 이러한 방법이 왜 잘못되었는지 실제로 테스트를 통해 몸소(?) 체감을 해보고, 그럼 어떤 방법으로 redirect 처리를 해야 하는가와 개선을 함으로써 기존방식에 비해 어떤점이 좋아졌는지에 대해서 정리해보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>뭔가 &lt;strong>내것으로 만들기&lt;/strong> 시리즈물이 나올것만 같은 느낌이다&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;h2 id="기존방식의-문제점-재현-및-다양한-원인분석">기존방식의 문제점 재현 및 다양한 원인분석&lt;/h2>
&lt;p>기존방식으로 했을때 왜 OOM이 발생했을까? 우리는 개발자이기 때문에 이런저런 글들만 보고 추측 할것이 아니라 직접 재현을 해보고 다양한 시각에서 원인분석을 해보자.
먼저 기본적인 Spring MVC 뼈대를 만들고 redirect 하는 return 값의 Cardinality가 높도록 random string 을 만들어 주도록 한다. 즉, &lt;code>/random&lt;/code>을 호출하면 &lt;code>/result/ETmHfowFkU&lt;/code>처럼 random string 이 만들어 지며 redirect 처리가 되는 매우 심플한 구조이다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Spring 버전은 4.0.6.RELEASE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Controller&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">TestController&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;random&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RequestMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">GET&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">random&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;redirect:result/&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">UUID&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">randomUUID&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;result/{message}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RequestMethod&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">GET&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">result&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ModelMap&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nd">@PathVariable&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">addAttribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;result&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>또한 해당 프로젝트에서는 AOP를 사용하고 있었기 때문에 그때와 동일한 상황으로 재현을 하기 위해 AOP관련 설정도 추가해준다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@EnableWebMvc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@EnableAspectJAutoProxy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@ComponentScan&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">HelloWorldConfiguration&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;HelloWorld&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ViewResolver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">viewResolver&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">InternalResourceViewResolver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">viewResolver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">InternalResourceViewResolver&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">viewResolver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setViewClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">JstlView&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">viewResolver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setPrefix&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/WEB-INF/views/&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">viewResolver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setSuffix&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;.jsp&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">viewResolver&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 한뒤 tomcat으로 최대/최소 메모리를 256m으로 설정후 해당 모듈을 띄워준다. 그다음 메모리 상태를 보기 위해 tomcat에 pinpoint를 연동하고 마지막으로 호출테스트를 위해 nGrinder을 설정해준다. 특별한 설정은 없고 위 컨트롤러의 url (/random) 을 여러번 호출하도록 하였다. nGrinder을 설정하는대에는 &lt;a href="https://black9p.github.io/2019/01/02/nGrinder-%EA%B0%84%ED%8E%B8-%EC%82%AC%EC%9A%A9%EA%B0%80%EC%9D%B4%EB%93%9C/" target="_blank" rel="noopener noreffer ">이 블로그 포스팅&lt;/a>을 참고해서 설정하였다.&lt;/p>
&lt;p>자, 이제 테스트를 시작해보자. (마치 수술 집도하는것 같은 기분으로&amp;hellip;간호사~ 칼!)&lt;/p>
&lt;ol>
&lt;li>nGrinder
nGrinder의 기본 스크립트에서 url만 해당 서버로 호출되도록 바꿔주고 총 가상 사용자는 2,000으로 시간은 5분으로 설정후에 테스트 시작을 하였더니 다음과 같은 그래프를 볼수 있었다.&lt;/li>
&lt;/ol>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-redirect-oom/test1-ngrinder.jpg" title="/images/spring-redirect-oom/test1-ngrinder.jpg" data-thumbnail="/images/spring-redirect-oom/test1-ngrinder.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-redirect-oom/test1-ngrinder.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-redirect-oom/test1-ngrinder.jpg, https://taetaetae.github.io/images/spring-redirect-oom/test1-ngrinder.jpg 1.5x, https://taetaetae.github.io/images/spring-redirect-oom/test1-ngrinder.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-redirect-oom/test1-ngrinder.jpg" width="100%" />
 &lt;/a>
&lt;p>TPS가 불안정해지다가 어느시점부터 낮아지는것을 확인할 수 있다. 이게 서비스 였다면 사용자가 접속하는데 불편을 느꼈을꺼라 추측을 해본다. 또한 아주 간단한 random string 을 리턴하는 페이지 임에도 불구하고 에러 응답이 적지 않은것을 확인할 수 있었다.&lt;/p>
&lt;ol start="2">
&lt;li>pinpoint
메모리 상태는 어떤지 확인하기 위해 pinpoint를 확인해보면 다음과 같은 그래프를 볼수 있었다.&lt;/li>
&lt;/ol>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-redirect-oom/test1-pinpoint.jpg" title="/images/spring-redirect-oom/test1-pinpoint.jpg" data-thumbnail="/images/spring-redirect-oom/test1-pinpoint.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-redirect-oom/test1-pinpoint.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-redirect-oom/test1-pinpoint.jpg, https://taetaetae.github.io/images/spring-redirect-oom/test1-pinpoint.jpg 1.5x, https://taetaetae.github.io/images/spring-redirect-oom/test1-pinpoint.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-redirect-oom/test1-pinpoint.jpg" width="100%" />
 &lt;/a>
&lt;p>보기만해도 심장이 벌렁벌렁(?) 뛸 정도로 무서운 그림이다. 실제로 서비스에 (이정도까진 아니였지만) 비슷한 상황이 발생했었다. 메모리가 테스트를 점점 하면 할수록 올라가다가 fullGC가 발생하더니 대나무 숲에 있는 대나무마냥 fullGC가 빼곡히 발생하였다. (이러니&amp;hellip; 페이지 접근에 지연이 생긴것 같다.)&lt;/p></description></item><item><title>천만 명의 사용자에게 1분 내로 알림 보내기 (병렬프로세스의 최적화)</title><link>https://taetaetae.github.io/2019/01/02/faster-parallel-processes/</link><pubDate>Wed, 02 Jan 2019 12:43:44 +0000</pubDate><guid>https://taetaetae.github.io/2019/01/02/faster-parallel-processes/</guid><description>&lt;p>만약 1번부터 10번까지 번호표가 있는 사람들 총 열명에게 혼자서 동일한 내용의 메일을 보낸다고 가정해보자. 그리고 메일 발송시 한번에 한명에게만 보내야 하는 제한사항이 있을때 과연 당신은 어떤식으로 보내겠는가? 이어서 읽지말고 한번 생각해보자.&lt;!-- more -->
아무것도 고려하지 않고 단순하게 생각한다면 1번 보내고 &amp;gt; 2번 보내고 &amp;hellip; 9번 보내고 &amp;gt; 10번 보내는 방법이 먼저 떠오르게 된다. (for loop 1 to 10 &amp;hellip; ) 하지만 보내야 할 사람들이 많아져서 백명, 천명 많게는 천만명에게 보내야 할 경우 방금과 같은 순차적인 방법을 사용하면 너무 늦게 발송된다는건 코드를 작성하지 않아도 알 수있는 문제&amp;hellip; 그렇다면 어떤 방법으로 보내야 보다 빨리 보낼수 있을까?
이번 포스팅에서는 필자가 운영하고 있는 서비스에서 기존에 있던 병렬프로세스를 어떤식으로 최적화 했는지, 그래서 결국 얼마나 빨라졌는지에 대한 과정을 정리해 보고자 한다. 비단 메일 발송이나 앱 푸시 등 특정 도메인에 국한되지는 않고 전반적인 프로세스에 대해 이해를 한다면 다른 곳에서도 비슷한 방법으로 활용할 수 있을꺼라 기대 해본다.&lt;/p>
&lt;hr>
&lt;h2 id="상황파악-및-목표">상황파악 및 목표&lt;/h2>
&lt;p>(원할한 이해를 돕기 위하여) 먼저 필자가 운영하고있는 서비스를 간략히 소개부터 해야겠다. (그렇다고 필자 혼자 다 하는건 아님^^;&amp;hellip;)
셀럽의 방송이 시작되면 구독한 사용자에게 각 모바일 기기에 설치되어있는 앱으로 알림을 보내어 예정에 없던 깜짝 라이브 방송이나 VOD 영상 오픈을 보다 빠르게 확인할 수 있도록 제공하고 있다.
여기서, 알림이 늦게 발송되면 셀럽은 방송을 시작하고 팬들이 들어오기까지 기다려야 한다거나 반대로 팬들은 방송 시작하고 뒤늦게 방송을 보게되는 불편함이 생기게 된다. 그리고 중복으로 알림이 발송되거나 특정 사용자들에게 발송이 누락되면 안 되는 등 &amp;ldquo;알림&amp;rdquo; 이란 기능은 서비스에 있어서 중요한 기능 중에 하나라고 할수 있다.&lt;/p>
&lt;blockquote>
&lt;p>여기서 &amp;ldquo;발송 시간&amp;quot;은 처음 발송작업 시작부터 마지막 사용자에 대해 사내 발송 플랫폼으로 발송 요청을 하기까지의 시간을 의미&lt;/p>&lt;/blockquote>
&lt;p>그리고 &amp;ldquo;채널&amp;rdquo; 이라는 샐럽단위의 그룹이 있는데 영상과 채널의 관계는 1:N이다. 즉, 하나의 영상을 여러 채널에 연결시킬수 있어서 하나의 영상에 대해 여러 채널들에게 연결을 시켜놓으면 채널을 구독하고있는 각각의 사용자에게 모두 알림을 발송 할수가 있게 된다.&lt;/p>
&lt;p>우선, 알람이 사용자에게 전달되기까지의 큰 흐름은 다음과 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/faster-parallel-processes/push_process.jpg" title="/images/faster-parallel-processes/push_process.jpg" data-thumbnail="/images/faster-parallel-processes/push_process.jpg" data-sub-html="&lt;h2>알림 프로세스&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/faster-parallel-processes/push_process.jpg"
 data-srcset="https://taetaetae.github.io/images/faster-parallel-processes/push_process.jpg, https://taetaetae.github.io/images/faster-parallel-processes/push_process.jpg 1.5x, https://taetaetae.github.io/images/faster-parallel-processes/push_process.jpg 2x"
 data-sizes="auto"
 alt="/images/faster-parallel-processes/push_process.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">알림 프로세스&lt;/figcaption>
 &lt;/figure>
&lt;ol>
&lt;li>서비스에서 보낼 대상과 보낼 정보를 조합하여&lt;/li>
&lt;li>사내 푸시 발송 플랫폼인 사내 발송 플랫폼에게 전달을 하면 플랫폼에 따라 발송이 되고&lt;/li>
&lt;li>최종적으로는 사용자의 모바일 기기에 노출이 됨&lt;/li>
&lt;/ol>
&lt;p>간단하게 &amp;ldquo;병렬로 발송하면 되지 않을까?&amp;ldquo;라는 필자의 생각이 부끄러워질 정도로 이미 redis, rabbitMQ 를 활용해서 아래 그림처럼 병렬 프로세스로 구성되어 있었다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/faster-parallel-processes/legacy_structure.jpg" title="/images/faster-parallel-processes/legacy_structure.jpg" data-thumbnail="/images/faster-parallel-processes/legacy_structure.jpg" data-sub-html="&lt;h2>기존 구조&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/faster-parallel-processes/legacy_structure.jpg"
 data-srcset="https://taetaetae.github.io/images/faster-parallel-processes/legacy_structure.jpg, https://taetaetae.github.io/images/faster-parallel-processes/legacy_structure.jpg 1.5x, https://taetaetae.github.io/images/faster-parallel-processes/legacy_structure.jpg 2x"
 data-sizes="auto"
 alt="/images/faster-parallel-processes/legacy_structure.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">기존 구조&lt;/figcaption>
 &lt;/figure>
&lt;ol>
&lt;li>라이브가 시작되거나 VOD가 오픈될 경우 api가 호출이 되고 다시 배치 서버에게 영상의 고유번호를 전달&lt;/li>
&lt;li>전달받은 영상의 고유번호를 rabbitMQ의 수신자 조회 Queue에 produce&lt;/li>
&lt;li>수신자 조회 Queue의 consumer인 수신자 조회 모듈에서 영상의 고유번호를 consume 후 아래 작업을 진행
3-1. 영상:채널 은 1:N 구조이기 때문에 여러 채널의 사용자들에게 알림을 발송할 수 있고, 영상에 연결된 채널들의 user를 db에서 가져온다.
3-2. 가져온 user를 (중복으로 알림이 발송되지 않기 위해) java set에 담고 모든 채널을 조회했다면 redis에 sorted set으로 담는다.
3-3. 적당한 크기로 분할하고 이 분할정보를 발송 Queue에 produce&lt;/li>
&lt;li>발송 모듈에서 분할 정보를 consume 하고 아래 작업을 진행 (병렬처리)
4-1. redis 에서 user 모음을 가져오고
4-2. 조회한 user에 해당하는 deviceId를 db에서 가져옴&lt;/li>
&lt;li>deviceId와 컨텐츠 정보를 활용하여 적절한 payload를 구성 후 사내 발송 플랫폼 에게 전달&lt;/li>
&lt;/ol>
&lt;p>기존 구조에서 발송 시간은 서비스에서 구독자 수가 가장 많은 채널 기준으로 약 1.1천만 명에게 최종 11분 정도 소요되고 있었다. (맨 처음에 이야기 한 순차적인 방법이였다면&amp;hellip; 훨씬더 오래 걸렸을꺼라 예상해본다&amp;hellip;)&lt;/p></description></item><item><title>Jenkins 설치 치트키</title><link>https://taetaetae.github.io/2018/12/02/jenkins-install/</link><pubDate>Sun, 02 Dec 2018 04:37:59 +0000</pubDate><guid>https://taetaetae.github.io/2018/12/02/jenkins-install/</guid><description>&lt;p>&amp;ldquo;show me the money&amp;rdquo;, &amp;ldquo;black sheep wall&amp;rdquo;.
어렸을적 스타크래프트라는 게임이 나오고서 입에 달고 살았던 &lt;code>치트키&lt;/code>. 게임이 시작되고 해당 치트키를 입력하면 돈이 들어오거나 맵이 훤하게 보여 컴퓨터를 이기는데 도움을 주곤 했었다. &lt;!-- more -->
개발을 하면서 Jenkins는 나 대신 어떤 업무를 수행하는데 강력한 툴 중에 하나이다. (물론 만능이라는 소리는 아니지만&amp;hellip;) 새로운 프로젝트가 시작되거나 개발도중 무언가 자동화를 하고 싶을 경우엔 Jenkins를 찾게 되는데 그럴때마다 설치를 하고 이런저런 설정이 필요하다.
눈치를 챘을수도 있지만 이 포스트는 오로지 &lt;code>젠킨스 설치하는 방법&lt;/code>을 아주 간단하고 핵심만 정리하고자 한다. 마치 &lt;code>치트키&lt;/code>처럼.
나중에 다시 보기위해 + 누군가 해당 포스트를 보고 도움이 되었으면 하는 바람으로.&lt;/p>
&lt;p>(물론 이 방법밖에 있는건 아니지만 필자는 아래와 방법을 사용하고 있다.)&lt;/p>
&lt;hr>
&lt;p>우선 CentOS 환경에 Java가 설치되어 있는 상황이라 가정한다.&lt;/p>
&lt;ul>
&lt;li>적당한 위치에 tomcat 다운 ( &lt;a href="https://tomcat.apache.org/download-80.cgi" target="_blank" rel="noopener noreffer ">https://tomcat.apache.org/download-80.cgi&lt;/a> )
&lt;pre tabindex="0">&lt;code>wget {압축파일 다운경로, 필자는 apache-tomcat-8.5.35 }
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>압축 해제후 하위 폴더중 webapps로 이동
&lt;pre tabindex="0">&lt;code>tar -zxvf apache-tomcat-8.5.35.tar.gz
cd apache-tomcat-8.5.35/webapps
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>Jenkins 다운 ( &lt;a href="https://jenkins.io/download/" target="_blank" rel="noopener noreffer ">https://jenkins.io/download/&lt;/a> )
&lt;pre tabindex="0">&lt;code>wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>tomcat 하위폴더중 conf 폴더로 이동
&lt;pre tabindex="0">&lt;code>cd ../conf
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>server.xml 수정 및 http port 확인
&lt;pre tabindex="0">&lt;code>vi server.xml

&amp;lt;Host&amp;gt; 하위에 추가
&amp;lt;Context path=&amp;#34;/jenkins&amp;#34; debug=&amp;#34;0&amp;#34; privileged=&amp;#34;true&amp;#34; docBase=&amp;#34;jenkins.war&amp;#34; /&amp;gt;

port 확인
&amp;lt;Connector port=&amp;#34;8080&amp;#34; protocol=&amp;#34;HTTP/1.1&amp;#34;/&amp;gt;
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>해당 서버의 ip와 위 port에 맞춰 url 입력후 jenkins 설치
&lt;pre tabindex="0">&lt;code>http://ip:8080/jenkins
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ul></description></item><item><title>Jenkins에서 파이썬 출력을 실시간으로 보고싶다면?</title><link>https://taetaetae.github.io/2018/12/02/python-buffer/</link><pubDate>Sun, 02 Dec 2018 01:40:11 +0000</pubDate><guid>https://taetaetae.github.io/2018/12/02/python-buffer/</guid><description>&lt;p>필자가 운영하고 있는 &lt;a href="http://daily-devblog.com" target="_blank" rel="noopener noreffer ">Daily Dev Blog&lt;/a> 라는 서비스는 매일 동일한 시간에 주기적으로 데이터를 크롤링 하고 사용자에게 메일을 발송하는 일련의 작업을 수행하고 있다. 헌데 예상하지 못한 부분에서 예외가 발생하게 되면 어떤경우는 메일 발송을 못한다거나 기존에 발송했던 데이터를 다시 보내는 등 정상적이지 못한 상황을 맞이하게 된다.&lt;!-- more -->&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/python-buffer/ddb_duplication.png" title="/images/python-buffer/ddb_duplication.png" data-thumbnail="/images/python-buffer/ddb_duplication.png" data-sub-html="&lt;h2>메일이 하루라도 잘못오면 여기저기서 연락이 온다. 감사한 분들&amp;hellip;&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/python-buffer/ddb_duplication.png"
 data-srcset="https://taetaetae.github.io/images/python-buffer/ddb_duplication.png, https://taetaetae.github.io/images/python-buffer/ddb_duplication.png 1.5x, https://taetaetae.github.io/images/python-buffer/ddb_duplication.png 2x"
 data-sizes="auto"
 alt="/images/python-buffer/ddb_duplication.png" />
 &lt;/a>&lt;figcaption class="image-caption">메일이 하루라도 잘못오면 여기저기서 연락이 온다. 감사한 분들&amp;hellip;&lt;/figcaption>
 &lt;/figure>
&lt;p>이런저런 바쁜일들로 차일피일 미루다 마침 여유가 생겨 기존에는 Crontab 스케쥴로 파이썬 스크립트를 실행하던 것에서 Jenkins로 옮기는 작업을 했다. 젠킨스가 스케쥴링을 해주고 실행이력을 보여주며, 실시간으로 스크립트가 돌아가는걸 볼수 있을것 같다는 기대감에서이다. 위에서 이야기 했던 예외상황을 보다 빠르고 편하게 실시간으로 디버깅을 하기 위해서가 가장 컸다.&lt;/p>
&lt;h2 id="당연히-될거라고-생각했으나">당연히 될거라고 생각했으나&amp;hellip;&lt;/h2>
&lt;p>작업은 간단할꺼라 생각했다.&lt;/p>
&lt;ol>
&lt;li>우선 &lt;a href="https://taetaetae.github.io/2018/12/02/jenkins-install/" target="_blank" rel="noopener noreffer ">Jenkins를 설치&lt;/a>하고&lt;/li>
&lt;li>기존에 스크립트 파일을 Jenkins Job으로 옮긴후에&lt;/li>
&lt;li>적당한 코드 중간중간에 디버깅이 용이하도록 로그를 출력하게 해둔다음&lt;/li>
&lt;li>스케쥴링만 걸어두면 끝이라고 생각했다.&lt;/li>
&lt;/ol>
&lt;p>하지만, 이렇게 간단하게 끝날것만 같았던 작업이 은근 귀찮은 작업이 될줄이야. 디버깅을 위해 로그를 출력하도록 해놨는데 모든 스크립트가 끝이 나서야 해당 로그가 출력되는 것이였다. 로그를 실시간으로 볼수 없다면 Crontab에서 Jenkins로 옮기는 이유가 크게 없게 된다. 실제로 아래처럼 코드를 작성하고 Jenkins Job을 실행시켜보면 다 끝나고서야 출력이 되는걸 볼수 있었다.&lt;/p>
&lt;p>(1초에 한번씩 5초동안 로그를 찍는 간단한 코드다.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;start&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">second&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;end&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/python-buffer/before.gif" title="/images/python-buffer/before.gif" data-thumbnail="/images/python-buffer/before.gif" data-sub-html="&lt;h2>스크립트가 다 끝나서야 출력을 볼수 있다ㅠ 실시간으로 디버깅이 어렵다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/python-buffer/before.gif"
 data-srcset="https://taetaetae.github.io/images/python-buffer/before.gif, https://taetaetae.github.io/images/python-buffer/before.gif 1.5x, https://taetaetae.github.io/images/python-buffer/before.gif 2x"
 data-sizes="auto"
 alt="/images/python-buffer/before.gif" />
 &lt;/a>&lt;figcaption class="image-caption">스크립트가 다 끝나서야 출력을 볼수 있다ㅠ 실시간으로 디버깅이 어렵다.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="그럼-어떻게-해야할까">그럼 어떻게 해야할까?&lt;/h2>
&lt;p>개발을 하면서 만나는 대부분의 문제들은 누군가 과거에 경험했던 문제였고, 이미 해결된 문제일 확률이 상당히 높은것들이 많다. 이번에도 역시, 갓 스택 오버플로우 : &lt;a href="https://stackoverflow.com/questions/107705/disable-output-buffering" target="_blank" rel="noopener noreffer ">https://stackoverflow.com/questions/107705/disable-output-buffering&lt;/a>&lt;/p>
&lt;p>위 링크에서 알려준것처럼 해보면 다음과 같이 로그가 출력되는대로 젠킨스에서 볼수 있게 된다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/python-buffer/after.gif" title="/images/python-buffer/after.gif" data-thumbnail="/images/python-buffer/after.gif" data-sub-html="&lt;h2>콘솔환경에서의 디버깅은 로깅이 최고!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/python-buffer/after.gif"
 data-srcset="https://taetaetae.github.io/images/python-buffer/after.gif, https://taetaetae.github.io/images/python-buffer/after.gif 1.5x, https://taetaetae.github.io/images/python-buffer/after.gif 2x"
 data-sizes="auto"
 alt="/images/python-buffer/after.gif" />
 &lt;/a>&lt;figcaption class="image-caption">콘솔환경에서의 디버깅은 로깅이 최고!&lt;/figcaption>
 &lt;/figure>
&lt;p>정리해보면 다음과 같은 방법이 있겠다.&lt;/p>
&lt;ol>
&lt;li>&lt;code>Execute Python script&lt;/code> 을 활용하여 Jenkins 에 직접 코드를 작성하는 경우&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>print의 flush옵션을 활용 ( &lt;a href="https://docs.python.org/3/library/functions.html?highlight=print#print" target="_blank" rel="noopener noreffer ">https://docs.python.org/3/library/functions.html?highlight=print#print&lt;/a> )&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hello&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flush&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>매번 print 가 될때마다 flush가 되도록 재정의&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Unbuffered&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">object&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">stream&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">writelines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">datas&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">writelines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">datas&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__getattr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attr&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attr&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">Unbuffered&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>&lt;code>Execute shell&lt;/code>을 활용하여 특정경로의 Python 파일을 실행할 경우&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;code>-u&lt;/code> 옵션을 줘서 실행시킨다. ( python -u python_module.py )&lt;/li>
&lt;/ul>
&lt;p>이렇게 두고보면 너무 간단한 작업인데 이런 방법을 모르는 상황에서는 작성된 Python Script를 Shell Script로 다시 감싸보거나 Python 코드를 쓰지 말까 까지 생각했었다&amp;hellip; 삽질의 연속들&amp;hellip; (Shell Script로 작성하면 바로바로 보였기 때문&amp;hellip;)&lt;/p>
&lt;p>다시한번 모르면 몸이 고생한다(?)라는걸 몸소 체험한 좋은&amp;hellip;시간이였다.&lt;/p></description></item><item><title>2018 Pycon. 그리고 첫 발표를 하다.</title><link>https://taetaetae.github.io/2018/08/28/pycon-2018/</link><pubDate>Tue, 28 Aug 2018 23:43:16 +0000</pubDate><guid>https://taetaetae.github.io/2018/08/28/pycon-2018/</guid><description>&lt;p>IT관련 행사에 참여하면 여러가지 정보를 얻을수 있다. 개인적으로는 사실 정보를 얻기 위함보다 그곳의 분위기를 현장에서 몸소 느끼고 참여한 사람들의 눈빛을 보며 해이해진 마음가짐을 다시 다잡을수 있음이 가장 큰 목적이다. 그에 올해 Pycon도 하나의 전환점이 되길 바라는 마음으로 신청을 하게 되었다.&lt;/p>
&lt;!-- more -->
&lt;h2 id="등록">등록&lt;/h2>
&lt;p>&lt;a href="https://www.pycon.kr" target="_blank" rel="noopener noreffer ">https://www.pycon.kr&lt;/a>
얼리버드 등록을 한다고 Facebook에서 홍보를 하길래 그런가보다 했는데 잠깐 회사일에 집중하고 다시 보니 이미 매진이 되어있었다. 사실 Pycon 은 올해가 처음 가보는거라 인기를 실감할수 없었는데 이정도일줄은 상상도 못했다. (나중에 알게 된 사실이지만 올해가 가장 인원이 많았다고&amp;hellip;) 그래서 나중에 진행되었던 일반표 등록은 휴대폰 알람까지 걸어두며 늦지않게 등록할수 있었다. 세부 일정들이 업데이트가 되고 어떤 세션을 들을까 고민하면서 간략 소개를 하나둘씩 보게 되었는데 Python을 만지며 평소에 궁금했던거나 재밌어 보이는 세션들이 너무많아 고민을 많이 했다. 한가지 아쉬운건 로그인 기반이 아니다 보니 (임시 로그인기반?) 내 시간표 설정하는게 없었다. 나는 별도로 적어서 갔지만 나중엔 그런 기능이 생겼으면 좋겠다.&lt;/p>
&lt;blockquote>
&lt;p>2019년 Pycon엔 크롬 익스텐션으로 기능을 만들어 로그인 여부와 상관없이 몇시에 내가 어떤 세션을 들을건지에 대한 설정을 하고 이를 이미지로 캡쳐해서 출력/다운 받을수 있는 걸 만들어 보고 싶다. (그전에 미뤄뒀던 크롬 익스텐션 개발하는 방법부터 공부하자&amp;hellip;)&lt;/p>&lt;/blockquote>
&lt;h2 id="첫째날">첫째날&lt;/h2>
&lt;p>개인적으로 아침잠이 너무 많은데 알림이 울리기도 전에 눈이 떠졌고 행사장에 도착해보니 후원사 부스는 아직 텅텅 비어있었고, 밤새가면서 준비를 하셨는지 자원봉사자 분들은 여기저기 빈백에 누워(쓰러져) 자고 있었다. 그만큼 Pycon에 대한 기대가 컸나보다. 시간이 지나니 하나둘씩 사람들이 등록을 하며 오기 시작하였고 역시나 행사에 꽃중에 꽃인 후원사 부스에서 나눠주는 이벤트 상품들을 받기 바빴다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/pycon-2018/day1.png" title="/images/pycon-2018/day1.png" data-thumbnail="/images/pycon-2018/day1.png" data-sub-html="&lt;h2>DIVE INTO DIVERSITY !!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/pycon-2018/day1.png"
 data-srcset="https://taetaetae.github.io/images/pycon-2018/day1.png, https://taetaetae.github.io/images/pycon-2018/day1.png 1.5x, https://taetaetae.github.io/images/pycon-2018/day1.png 2x"
 data-sizes="auto"
 alt="/images/pycon-2018/day1.png" />
 &lt;/a>&lt;figcaption class="image-caption">DIVE INTO DIVERSITY !!&lt;/figcaption>
 &lt;/figure>
&lt;p>키노트를 시작으로 사람들은 각자 듣고싶은 세션에 참가하며 행사는 시작이 되었다. 전체적으로 기술의 난이도는 초급 수준의 발표였던걸로 느껴졌다. (물론 나는 초초초급도 안되는 꼬꼬마 수준이지만&amp;hellip;) 대부분 Python으로 어떤걸 해봤고, 어떤 어려움이 있었고, 이러저러한 상황들을 만났으며, 요런 경우에서는 어떻게 하며 해결을 하였다는 등 기술을 활용한 &amp;ldquo;경험기&amp;quot;에 대한 내용들을 들을수 있었다.
Pycon의 슬로건인 &lt;code>DIVE INTO DIVERSITY&lt;/code>에 걸맞게 아주 &lt;code>다양한 주제&lt;/code>로 흥미있는 발표내용들이였다. 기억나는 것들중에 인상깊었던 부분들을 정리해본다.&lt;/p>
&lt;ul>
&lt;li>파이썬 문화(?)중의 하나는 몰라서 물어보는 사람에게 구글링을 하라기보다 직접 알려주라는 것이다.&lt;/li>
&lt;li>배우고 싶다면 다른사람들을 가르치는것부터(알려주는것부터) 시작하라.&lt;/li>
&lt;li>여성 개발자, 여성 발표자들도 점점 늘어나고 있다.&lt;/li>
&lt;li>파이썬을 개발 현장(?)이 아닌 다른곳에서 사용한다면 작업 속도도 빠르고 얻어내는 가치또한 훨씬 더 방대하다.&lt;/li>
&lt;li>엑셀로 할수 있는 작업을 파이썬으로 할수 있다.&lt;/li>
&lt;li>파이썬의 다양한 라이브러리는 일상의 도움을 준다.&lt;/li>
&lt;/ul>
&lt;p>행사를 들으며 꼭 질문을 해야지 하는 마음을 갖고 있었는데 (그래야 오래 기억에 남으니) 마침 어떤 세션에서 궁금한게 있어 질문을 할수 있었다. (질문을 하니 파이썬 관련 책 선물도 받았다.^^)
그리고 마지막 &lt;code>라이트닝 톡&lt;/code>이라는 세션이 있었는데 여러 발표자들이 짤막하게 5분동안 하고싶은 이야기를 하는 세션이였다. 5분이라는 제한이 있기에 다들 쉽고 편하게 발표하는듯 보였으나 발표 자료나 발표내용을 보면 꼭 그렇게 간단하게 발표하는건 아니였다. 본 세션에서 말하기엔 다소 분량이 작은 알차고 깨알같은 발표도 있었고, 매년 Pycon 라이트닝톡에 발표하는게 목표이신 분도 있었다.
발표를 들으면서 난 언제 저런자리에 가서 발표를 할수 있을까 하는 마음이 스쳐 지나갈때 쯤. &amp;ldquo;왜못하지? 나 파이썬으로 만든거 있잖아?&amp;rdquo; 라고 혼잣말로 궁시렁거리며 둘째날에 있는 라이트닝톡에서 발표하기로 마음을 먹고 서둘러서 참가 신청을 보냈다. 그러고서는 저녁을 먹고 집에 늦게 돌아와 새벽 3시넘어서야 발표자료를 완성하였지만 &amp;ldquo;발표&amp;rdquo; 라는 부담감때문에 어렵게 잠에 들었다.&lt;/p>
&lt;h2 id="둘째날">둘째날&lt;/h2>
&lt;p>어제와는 달리 오늘은 잠을 많이 못자서 인지 늦게 일어나 첫 세션이 시작하고서 거의 끝날 즈음에 행사장에 도착하게 되었다. &amp;ldquo;괜히 발표 한다고 한걸까&amp;rdquo; 라는 생각이 들며 진행위 본부에 가서 발표 순서를 확인해보니 첫번째&amp;hellip; 슬슬 머리가 아파오기 시작했다. 그래도 듣기로한 세션은 들어보고 싶어서 집중해서 세션들을 돌아가며 들었지만 머릿속에는 온통 &amp;ldquo;발표 발표 발표&amp;quot;라는 생각때문에 오히려 다른분께서 하시는 발표를 집중해서 듣지 못하였다.&lt;/p></description></item><item><title>자바 객체 복사하기 ( feat. how to use CloneUtils? )</title><link>https://taetaetae.github.io/2018/08/21/how-to-use-cloneutils/</link><pubDate>Tue, 21 Aug 2018 18:02:47 +0000</pubDate><guid>https://taetaetae.github.io/2018/08/21/how-to-use-cloneutils/</guid><description>&lt;p>자바(Java)로 개발을 하다보면 한번쯤 객체를 복사하는 로직을 작성할때가 있다. 그때마다 나오는 이야기인 &lt;code>Shalldow Copy&lt;/code> 와 &lt;code>Deep Copy&lt;/code>. 한국어로 표현하면 얕은 복사와 깊은 복사라고 이야기를 하는데 이 두 개념의 차이는 아주 간단하다. 객체의 주소값을 복사하는지, 아니면 객체의 실제 값(value)를 복사하는지. &lt;!-- more --> 이 둘의 차이점을 소개하는 글들은 워낙 많으니 패스하도록 하고 이번 포스팅에서는 &lt;code>Deep Copy&lt;/code>를 할때 &lt;code>org.apache.http.client.utils&lt;/code> 하위에 있는 &lt;code>CloneUtils&lt;/code> 사용법에 대해 정리 하고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>그냥 쓰면 되는거 아닌가? 라고 생각했지만 (별거 아니라고 생각했지만) 해보고 안해보고의 차이는 엄청컸고 사용할때 주의점이 몇가지 있어 정리 하려고 한다.&lt;/p>&lt;/blockquote>
&lt;p>예제에 앞서 본 포스팅에서 사용할 객체를 간단히 정리하면 다음과 같다. (학교에서 학생 신상정보를 관리한다고 가정해보자.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 이름&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">age&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 나이&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">Family&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">family&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 가족&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Family&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 이름&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">age&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 나이&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">boolean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">isOfficeWorkers&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 직장인 여부&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">PhysicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">height&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 키&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">weight&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 몸무게&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="객체는-cloneable-interface-를-implement-해야하고-clone-메소드를-public-으로-override-해야한다">객체는 Cloneable interface 를 implement 해야하고 clone 메소드를 public 으로 override 해야한다.&lt;/h3>
&lt;p>당연한 이야기가 될수도 있으나 &lt;code>CloneUtils&lt;/code>를 사용하기 위해서는 해당 객체는 Cloneable interface 를 implement 해야한다. 그리고 나서 clone 메소드를 override 해야되는데 여기서 가장 중요한점은 외부에서도 호출이 가능해야하기 때문에 &lt;code>public&lt;/code> 으로 override를 해야한다. (기본은 protected 로 되어있다.) 우선 간단히 객체를 생성하고 출력부터 해보자. (출력을 이쁘게 하기 위해 &lt;code>ToStringBuilder.reflectionToString&lt;/code>을 사용하였다.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="n">PhysicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PhysicalInformation&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">height&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">180&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">weight&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">70&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToStringBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">reflectionToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ToStringStyle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">DEFAULT_STYLE&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>결과는 당연히&lt;/p>
&lt;pre tabindex="0">&lt;code>PhysicalInformation@5d6f64b1[height=180,weight=70]
&lt;/code>&lt;/pre>&lt;p>이제 Cloneable interface 를 implement 하고 clone 메소드를 public 으로 override 한뒤, CloneUtils를 사용해서 객체를 복사해보자. 테스트를 하면서 &lt;code>Shalldow Copy&lt;/code>도 해보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// class setting&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">PhysicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">implements&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Cloneable&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">height&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">weight&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Object&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">clone&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CloneNotSupportedException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// public 으로 바꿔주자.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">clone&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// test code&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PhysicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PhysicalInformation&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">height&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">180&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">weight&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">70&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PhysicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">physicalInformationShalldowCopy&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PhysicalInformation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">physicalInformationDeepCopy&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">physicalInformationDeepCopy&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PhysicalInformation&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">CloneUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">clone&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CloneNotSupportedException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">printStackTrace&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 원본&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToStringBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">reflectionToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ToStringStyle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">DEFAULT_STYLE&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 얕은 복사&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToStringBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">reflectionToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformationShalldowCopy&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ToStringStyle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">DEFAULT_STYLE&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 깊은 복사&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToStringBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">reflectionToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformationDeepCopy&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ToStringStyle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">DEFAULT_STYLE&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 값 변경&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">weight&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">80&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">height&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">170&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 원본&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToStringBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">reflectionToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformation&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ToStringStyle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">DEFAULT_STYLE&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 얕은 복사&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToStringBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">reflectionToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformationShalldowCopy&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ToStringStyle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">DEFAULT_STYLE&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 깊은 복사&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToStringBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">reflectionToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">physicalInformationDeepCopy&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ToStringStyle&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">DEFAULT_STYLE&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>결과는 원본과 얕은 복사를 한것은 메모리 주소(?)가 같으나 깊은 복사를 한것은 데이터는 같지만 주소가 다르고 값을 변경해도 영향을 주지 않는다. (완전히 서로다른 객체인것을 증명)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">PhysicalInformation&lt;span class="ni">@1376c05c&lt;/span>[height=180,weight=70]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PhysicalInformation&lt;span class="ni">@1376c05c&lt;/span>[height=180,weight=70]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PhysicalInformation&lt;span class="ni">@1b4fb997&lt;/span>[height=180,weight=70]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PhysicalInformation&lt;span class="ni">@1376c05c&lt;/span>[height=170,weight=80]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PhysicalInformation&lt;span class="ni">@1376c05c&lt;/span>[height=170,weight=80]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PhysicalInformation&lt;span class="ni">@1b4fb997&lt;/span>[height=180,weight=70]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>만약 위에서 clone을 기본값인 protected로 override를 하게 되면 어떤 결과를 가져올까?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">Exception in thread &amp;#34;main&amp;#34; java.lang.NoSuchMethodError: com.PhysicalInformation.clone()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	at org.apache.http.client.utils.CloneUtils.cloneObject(CloneUtils.java:55)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	at org.apache.http.client.utils.CloneUtils.clone(CloneUtils.java:77)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	at com.Test.main(Test.java:16)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>접근제한자에서 눈치를 챌수도 있었겠지만 접근을 할수없어 CloneUtils 이 리플렉션을 하는 과정에서 Exception을 발생한다. 꼭! public 으로 override를 해주자.&lt;/p>
&lt;h3 id="객체-내에-clone이-안되는-변수는-별도-처리가-필요하다">객체 내에 clone이 안되는 변수는 별도 처리가 필요하다.&lt;/h3>
&lt;p>객체 내에 있는 멤버 변수는 원시 변수(int, char, float 등) , Immutable Class (String, Boolean, Integer 등) 또는 Enum 형식일 때는 원본의 값을 바로 대입해도 되지만, 그렇지 않을 때는 멤버변수의 clone을 호출하여 복사해야 한다. 말로만 보면 무슨이야기 인지 모르니 예제를 보자.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">implements&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Cloneable&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">age&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">Family&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">family&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Object&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">clone&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CloneNotSupportedException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">clone&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Student 클래스에서 Cloneable 를 implements 하고 clone 메소드를 override 하였다. (여기서 구멍이 있다!!) 그다음 Family 클래스는 초기 그대로 두고 CloneUtils을 사용해서 객체를 복사하는 코드를 작성해보자.&lt;/p></description></item><item><title>기술블로그 구독서비스 개발 후기 - 2부</title><link>https://taetaetae.github.io/2018/08/09/daily-dev-blog-2/</link><pubDate>Thu, 09 Aug 2018 20:37:26 +0000</pubDate><guid>https://taetaetae.github.io/2018/08/09/daily-dev-blog-2/</guid><description>&lt;p>1부에서는 기술블로그 구독서비스(이하 서비스)를 왜 만들게 되었고 어떤구조로 만들가에 대해 이야기를 해보았다면, 이번 포스팅에서는 만들면서 만나게 된 각종 &lt;code>트러블슈팅 종합세트(?)&lt;/code>를 하나씩 풀어보고자 한다. &lt;!-- more -->물론 개발을 하면서 아무 문제 없이 잘 되면 당연히 좋겠으나 잘되도 이상한게 개발이라는 세계가 아니던가.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/daily-dev-blog-2/why_normal.png" title="/images/daily-dev-blog-2/why_normal.png" data-thumbnail="/images/daily-dev-blog-2/why_normal.png" data-sub-html="&lt;h2>잘 안되면 문제, 잘 되도 문제 ㅠㅠ 출처 : https://www.clien.net/service/board/park/9111495&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/daily-dev-blog-2/why_normal.png"
 data-srcset="https://taetaetae.github.io/images/daily-dev-blog-2/why_normal.png, https://taetaetae.github.io/images/daily-dev-blog-2/why_normal.png 1.5x, https://taetaetae.github.io/images/daily-dev-blog-2/why_normal.png 2x"
 data-sizes="auto"
 alt="/images/daily-dev-blog-2/why_normal.png" />
 &lt;/a>&lt;figcaption class="image-caption">잘 안되면 문제, 잘 되도 문제 ㅠㅠ 출처 : &lt;a href="https://www.clien.net/service/board/park/9111495" target="_blank" rel="noopener noreffer ">https://www.clien.net/service/board/park/9111495&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;ul>
&lt;li>1부 : &lt;a href="https://taetaetae.github.io/2018/08/05/daily-dev-blog-1/" target="_blank" rel="noopener noreffer ">왜 만들게 되었는가 그리고 어떤 구조로 만들었는가&lt;/a>&lt;/li>
&lt;li>2부 : &lt;a href="https://taetaetae.github.io/2018/08/09/daily-dev-blog-2/" target="_blank" rel="noopener noreffer ">문제발생 및 Trouble Shooting&lt;/a>&lt;/li>
&lt;li>3부 : &lt;a href="https://taetaetae.github.io/2019/02/17/daily-dev-blog-3/" target="_blank" rel="noopener noreffer ">앞으로의 계획과 방향성&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>지난 1부에서 이야기 했던것처럼 문제 - 해결, 문제 - 해결 식으로 나열해보고자 한다. 다소 글의 전개가 뒤죽박죽일수도 있겠지만 말 그대로 &lt;code>트러블슈팅 종합세트&lt;/code>이니 독자들의 양해를 미리 구한다.&lt;/p>
&lt;h2 id="트러블-슈팅-리스트">트러블 슈팅 리스트&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="#10%ec%8b%9c%ec%97%90-%eb%a1%9c%ec%a7%81%ec%9d%b4-%ec%8b%a4%ed%96%89%eb%90%98%ec%97%88%ec%a7%80%eb%a7%8c-%eb%a9%94%ec%9d%bc%ec%9d%84-11%ec%8b%9c-%eb%84%98%ec%96%b4%ec%84%9c-%eb%b0%9b%ea%b2%8c-%eb%90%9c%eb%8b%a4" rel="">10시에 로직이 실행되었지만 메일을 11시 넘어서 받게 된다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="#%ec%a0%9c%eb%aa%a9%ec%97%86%eb%8a%94-%ea%b8%80-%eb%b8%94%eb%a1%9c%ea%b7%b8-RSS%ed%8c%8c%ec%8b%b1-%ec%98%a4%eb%a5%98-%ea%b0%84%ed%97%90%ec%a0%81%ec%9c%bc%eb%a1%9c-%ec%98%a4%eb%a5%98%ea%b0%80-%ec%83%9d%ea%b8%b4%eb%8b%a4" rel="">제목없는 글? 블로그 RSS파싱 오류? 간헐적으로 오류가 생긴다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="#%eb%a9%94%ec%9d%bc-%eb%82%b4%ec%97%90-class%eb%a5%bc-%ec%a0%81%ec%9a%a9%ed%95%98%ec%97%ac-CSS-%ec%b2%98%eb%a6%ac%ea%b0%80-%eb%b6%88%ea%b0%80%eb%8a%a5%ed%95%98%eb%8b%a4" rel="">메일 내에 class를 적용하여 CSS 처리가 불가능하다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="#%eb%a9%94%ec%9d%bc%ec%9d%84-%eb%b3%b4%eb%83%88%ec%9c%bc%eb%82%98-%ec%8a%a4%ed%8c%b8%ec%9c%bc%eb%a1%9c-%ec%b2%98%eb%a6%ac%eb%90%9c%eb%8b%a4" rel="">메일을 보냈으나 스팸으로 처리된다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="#Elastic-Stack%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%a0%ec%88%98-%ec%97%86%eb%8b%a4" rel="">Elastic Stack을 사용할수 없다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="#%eb%a9%94%ec%9d%bc-%eb%b3%b4%eb%82%b4%eb%8a%94-%eb%b0%9c%ec%86%a1%ec%86%8d%eb%8f%84%ea%b0%80-%eb%84%88%eb%ac%b4-%eb%8a%90%eb%a6%ac%eb%8b%a4" rel="">메일 보내는 발송속도가 너무 느리다.&lt;/a>&lt;/li>
&lt;li>&lt;a href="#%ea%b5%ac%eb%8f%85%ed%95%b4%ec%a0%9c%ea%b0%80-%ec%95%84%eb%8b%8c-%ec%9e%90%ec%b2%b4-%ec%88%98%ec%8b%a0%ea%b1%b0%eb%b6%80%eb%8a%94-%ec%96%b4%ec%b0%8c-%ec%b2%98%eb%a6%ac%ed%95%a0%ea%b9%8c" rel="">구독해제가 아닌 자체 수신거부는 어찌 처리할까?&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="10시에-로직이-실행되었지만-메일을-11시-넘어서-받게-된다">10시에 로직이 실행되었지만 메일을 11시 넘어서 받게 된다.&lt;/h2>
&lt;p>👉 해결방안 : Divide and Conquer&lt;/p>
&lt;p>본 서비스의 요구사항중 하나는 매일 오전 10시, 구독자들에게 어제 등록된 글을 수집하여 메일로 보내주는게 있다. 우선 로직은 다음과 같은 순서로 진행되게 개발하였고, jenkins 등 별도의 스케쥴러 관리 어플리케이션에 의해 할수도 있었으나 이 또한 심플하게 crontab 에 등록하여 매일 오전 10시에 실행되도록 하였다.&lt;/p>
&lt;pre tabindex="0">&lt;code>1. awesome-devblog 에서 블로거들의 RSS 피드를 조회한다.
2. 어제 등록된 글이 있다면 리스트에 담는다.
3. 조회가 끝나면 메일형식에 맞추어 html 문자열을 만든다.
4. 만들어진 문자열을 가지고 등록된 구독자들에게 메일을 보낸다.
&lt;/code>&lt;/pre>&lt;p>로직은 아주 간단했다. 데이터를 파싱하는 방법이나 메일형식에 맞추어 html문자열을 만드는 등 별도의 라이브러리를 사용하는 다소 복잡한 부분만 빼면 단순히 for문과 if문을 조합해서 로직을 구성할수 있었다. 헌데, 10시에 해당로직이 실행되었지만 최대 1시간이 지나고서야 메일을 받는 경우도 있었다. 이게 무슨일일까!?
눈치를 챘을수도 있지만 RSS 피드를 조회하는 곳에서 오래걸린 것이다. 티스토리나 네이버등 다른 블로그들은 RSS를 읽고 파싱하는 속도가 그렇게 오래 걸리지 않았는데 (1초 이내) 유독 이글루스 블로그의 RSS파싱이 오래걸리는건 1분까지도 걸리던 것이였다. ( 참고로 &lt;a href="https://github.com/kurtmckee/feedparser" target="_blank" rel="noopener noreffer ">RSS 파싱모듈&lt;/a>, &lt;a href="https://github.com/yaml/pyyaml" target="_blank" rel="noopener noreffer ">yaml 파싱모듈&lt;/a> 을 사용했다. )
아마 RSS의 형식이 약간 달라서 그런것 같긴 한데 그렇다고 이글루스 일 경우에 파싱을 다르게 하는건 좀 그렇고&amp;hellip; 추후 이글루스가 아닌 또다른 파싱속도가 느린 블로그의 RSS를 만날수도 있기에 RSS 타입별로 예외처리를 하는건 좀 아닌것 같았다.
이런저런 고민끝에 아주 간단하게도 임무(?)를 나누는식으로 해결 하였다. 즉, RSS를 읽고 메일에 보낼 데이터를 만드는 job 하나와 만들어진 데이터를 가지고 이메일을 보내는 job 으로 나눈뒤 RSS를 분석하는 job은 9시에, 메일보내는 job은 10시에 보내도록 해서 생각보다 아주 심플하게 문제를 해결할수 있었다.
복잡하고 어려운 문제를 꼭 복잡하고 어렵게만 해결해야하는 법은 없는것 같다. &lt;code>모로 가도 서울만 가면 된다&lt;/code>라는 속담이 있지 않는가.&lt;/p>
&lt;h2 id="제목없는-글-블로그-rss파싱-오류-간헐적으로-오류가-생긴다">제목없는 글? 블로그 RSS파싱 오류? 간헐적으로 오류가 생긴다.&lt;/h2>
&lt;p>👉 해결방안 : 언제나 신경써야 하는 예외처리(try-catch)&lt;/p>
&lt;p>내가 만든 코드는 언제나 내 생각대로&lt;code>만&lt;/code> 돌아갔으면 하는건 모든 개발자의 마음과 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/daily-dev-blog-2/deploy_pray.jpg" title="/images/daily-dev-blog-2/deploy_pray.jpg" data-thumbnail="/images/daily-dev-blog-2/deploy_pray.jpg" data-sub-html="&lt;h2>흔한 IT 종사자들.deploy 출처 : https://9gag.com/gag/a0Yxw4B/operations-team-before-leaving-for-holidays&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/daily-dev-blog-2/deploy_pray.jpg"
 data-srcset="https://taetaetae.github.io/images/daily-dev-blog-2/deploy_pray.jpg, https://taetaetae.github.io/images/daily-dev-blog-2/deploy_pray.jpg 1.5x, https://taetaetae.github.io/images/daily-dev-blog-2/deploy_pray.jpg 2x"
 data-sizes="auto"
 alt="/images/daily-dev-blog-2/deploy_pray.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">흔한 IT 종사자들.deploy 출처 : &lt;a href="https://9gag.com/gag/a0Yxw4B/operations-team-before-leaving-for-holidays" target="_blank" rel="noopener noreffer ">https://9gag.com/gag/a0Yxw4B/operations-team-before-leaving-for-holidays&lt;/a>&lt;/figcaption>
 &lt;/figure>
&lt;p>하지만 그생각도 잠시 언제나 예외는 발생하기 마련. ( 물론 전혀 예외가 발생 안할수도 있으나 만약 발생하지 않았다 할지라도 발생할수 있는 가능성은 염두해둬야 한다. ) 파싱하는 과정에서 제목이 없는글로 온다거나, 가끔 RSS url 응답이 404 또는 503 인 경우가 있었다.&lt;/p></description></item><item><title>기술블로그 구독서비스 개발 후기 - 1부</title><link>https://taetaetae.github.io/2018/08/05/daily-dev-blog-1/</link><pubDate>Sun, 05 Aug 2018 10:27:21 +0000</pubDate><guid>https://taetaetae.github.io/2018/08/05/daily-dev-blog-1/</guid><description>&lt;p>이번 포스팅은 약간의 자투리 시간을 활용하여 이것저것 만져보다 만들게 된 &lt;code>Daily DevBlog&lt;/code>(기술블로그 구독서비스)에 대해 이야기 하려고 한다. &lt;!-- more -->
하나의 글에 관련 내용을 모두 담기에는 양이 많아서 읽는사람도 지루하고, 글을 쓰는 필자 또한 &lt;code>어불성설&lt;/code> 할것같아 크게 3개의 시리즈로 나눠서 최대한 자세하고 현장감(?)있게 글을 써보려고 노력했다.&lt;/p>
&lt;ul>
&lt;li>1부 : &lt;a href="https://taetaetae.github.io/2018/08/05/daily-dev-blog-1/" target="_blank" rel="noopener noreffer ">왜 만들게 되었는가 그리고 어떤 구조로 만들었는가&lt;/a>&lt;/li>
&lt;li>2부 : &lt;a href="https://taetaetae.github.io/2018/08/09/daily-dev-blog-2/" target="_blank" rel="noopener noreffer ">문제발생 및 Trouble Shooting&lt;/a>&lt;/li>
&lt;li>3부 : &lt;a href="https://taetaetae.github.io/2019/02/17/daily-dev-blog-3/" target="_blank" rel="noopener noreffer ">앞으로의 계획과 방향성&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>글에 들어가기 앞서 최종 결과는 &lt;a href="http://daily-devblog.com" target="_blank" rel="noopener noreffer ">http://daily-devblog.com&lt;/a> 에서 확인할수 있다.&lt;/p>
&lt;hr>
&lt;h2 id="무엇이-나를-움직이게-했는가">무엇이 나를 움직이게 했는가&lt;/h2>
&lt;p>얼마전까지 오픈소스는 정말 실력있는 개발자나 유명한 사람들 말고는 금기의 영역(?)이라고 생각했었지만 최근 &lt;a href="https://taetaetae.github.io/2018/07/01/open-source-software-develpoer-story-review/" target="_blank" rel="noopener noreffer ">오픈소스 개발자 이야기 세미나&lt;/a>를 다녀온뒤 마음속에 있었던 벽이 사라지는듯 했다. 세미나를 들으면서 &amp;lsquo;나도 무언가를 만들어 볼수는 없을까?&amp;rsquo;, &amp;lsquo;회사라는 명찰을 떼면 난 어느 수준에서 개발을 하고 있는 것일까?&amp;rsquo; 등 여러 생각들이 머리를 멤돌다 &lt;a href="https://www.slideshare.net/zzsza/intro-102870757" target="_blank" rel="noopener noreffer ">개발자를 위한 글쓰기&lt;/a>라는 글에서 기술블로그들을 모아놓은 &lt;a href="https://awesome-devblog.herokuapp.com" target="_blank" rel="noopener noreffer ">awesome-devblog&lt;/a>를 소개하는 글을 보게 되었고 내 머릿속에 정리안되던 그 생각들은 &amp;ldquo;이 데이터를 활용해서 무언가를 만들어보자!&amp;ldquo;로 귀결되었다.&lt;/p>
&lt;blockquote>
&lt;p>다른 이야기 이지만, awesome-devblog 을 보고 당장 내 블로그도 등록해야지 했었는데 이미 등록이 되어 있었다;; 등록해주신 분께 감사하다는 생각이 들기전에 내 블로그가 누군가에게 보여지고 있구나 하며 새삼 놀라움이 더 컸다.&lt;/p>&lt;/blockquote>
&lt;h2 id="요구사항과-도구-그리고-설계">요구사항과 도구 그리고 설계&lt;/h2>
&lt;p>만들려고 생각해봤던 요구사항은 다음과 같다. 마치 회사에서 개발전 스펙을 정리하듯&amp;hellip;&lt;/p>
&lt;ol>
&lt;li>웹페이지를 활용해서 구독하고자 하는 사람들의 이메일을 수집할수 있어야 한다.&lt;/li>
&lt;li>매일 전날 작성된 글을 수집하고 조합하여 구독하고자 하는 사람들에게 메일을 보낼수 있어야 한다.&lt;/li>
&lt;/ol>
&lt;p>위 두가지만 보면 너무 간단했다. 또한 기존에 사용하지 않았던 기술들을 사용해보면서 &lt;code>최대한 심플하게&lt;/code> 개발하는것을 첫 개인 프로젝트의 목표로 하고 싶었다. 하여 생각한 아키텍처는 다음과 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/daily-dev-blog-1/architecture.png" title="/images/daily-dev-blog-1/architecture.png" data-thumbnail="/images/daily-dev-blog-1/architecture.png" data-sub-html="&lt;h2>최대한 심플하게 설계해보자.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/daily-dev-blog-1/architecture.png"
 data-srcset="https://taetaetae.github.io/images/daily-dev-blog-1/architecture.png, https://taetaetae.github.io/images/daily-dev-blog-1/architecture.png 1.5x, https://taetaetae.github.io/images/daily-dev-blog-1/architecture.png 2x"
 data-sizes="auto"
 alt="/images/daily-dev-blog-1/architecture.png" />
 &lt;/a>&lt;figcaption class="image-caption">최대한 심플하게 설계해보자.&lt;/figcaption>
 &lt;/figure>
&lt;p>데이터는 해당 github에 있길래 그냥 가져다 쓰려고 했으나 그래도 데이터를 관리하시는 분께 허락을 받고 사용하는게 상도덕(?)인것 같아 수소문끝에 연락을 해서 허락받는데 성공하였다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/daily-dev-blog-1/message.png" title="/images/daily-dev-blog-1/message.png" data-thumbnail="/images/daily-dev-blog-1/message.png" data-sub-html="&lt;h2>데이터 사용을 허락해주신 천사같으신분&amp;hellip;&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/daily-dev-blog-1/message.png"
 data-srcset="https://taetaetae.github.io/images/daily-dev-blog-1/message.png, https://taetaetae.github.io/images/daily-dev-blog-1/message.png 1.5x, https://taetaetae.github.io/images/daily-dev-blog-1/message.png 2x"
 data-sizes="auto"
 alt="/images/daily-dev-blog-1/message.png" />
 &lt;/a>&lt;figcaption class="image-caption">데이터 사용을 허락해주신 천사같으신분&amp;hellip;&lt;/figcaption>
 &lt;/figure>
&lt;blockquote>
&lt;p>이 자리를 빌어 데이터를 사용할수 있도록 &lt;a href="https://www.facebook.com/sarojaba" target="_blank" rel="noopener noreffer ">허락해주신분&lt;/a> 께 감사인사를 표합니다.&lt;/p>&lt;/blockquote>
&lt;p>홈페이지를 만들기 위해서는 이제껏 삼겹살에 소주처럼(응?) Java에 Spring을 사용해 왔었지만 이번엔 좀 다른 방식을 사용하고 싶었다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/daily-dev-blog-1/language_framework.png" title="/images/daily-dev-blog-1/language_framework.png" data-thumbnail="/images/daily-dev-blog-1/language_framework.png" data-sub-html="&lt;h2>물론 삼겹살에 맥주, 치킨에 소주를 먹어도 되긴 하지만&amp;hellip;&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/daily-dev-blog-1/language_framework.png"
 data-srcset="https://taetaetae.github.io/images/daily-dev-blog-1/language_framework.png, https://taetaetae.github.io/images/daily-dev-blog-1/language_framework.png 1.5x, https://taetaetae.github.io/images/daily-dev-blog-1/language_framework.png 2x"
 data-sizes="auto"
 alt="/images/daily-dev-blog-1/language_framework.png" />
 &lt;/a>&lt;figcaption class="image-caption">물론 삼겹살에 맥주, 치킨에 소주를 먹어도 되긴 하지만&amp;hellip;&lt;/figcaption>
 &lt;/figure>
&lt;p>최근에 Flask라는 python기반 웹 프레임워크를 만져본 &lt;a href="https://taetaetae.github.io/2018/06/29/simple-web-server-flask-apache/" target="_blank" rel="noopener noreffer ">경험&lt;/a>이 있어서 이렇다할 고민없이 빠른 결정을 할수 있었다. 또한 DB는 mysql 이나 기타 memory DB를 사용할까 했지만 이또한 심플하게 파일을 활용하는 sqlite3 을 사용하고자 하였다.&lt;/p>
&lt;h2 id="웹서버_최종_수정_파이널_진짜_확정">웹서버_최종_수정_파이널_진짜_확정&lt;/h2>
&lt;p>Flask를 활용하기 위해서는 당연히 웹서버가 필요했다. 처음엔 &lt;code>awesome-devblog&lt;/code>에서도 사용하고 있던 &lt;a href="https://www.heroku.com/" target="_blank" rel="noopener noreffer ">https://www.heroku.com/&lt;/a> 를 이용해서 해보려 했으나 매일 구독자들에게 메일을 보내는 등 스케쥴러 기능같은건 구현하기 힘들었고 인스턴트 어플리케이션을 등록하는 형태라 사용자의 메일을 입력받고 저장하는 로직을 만들기는 어려워 보였다. (필자가 heroku를 너무 수박 겉핥기식으로 봐서 일수도 있다&amp;hellip;)
좀더 찾아보니 &lt;a href="https://www.pythonanywhere.com/" target="_blank" rel="noopener noreffer ">https://www.pythonanywhere.com/&lt;/a> 라는 제한적이지만 무료 서비스가 있었는데 웹콘솔도 지원하고 상당히 매력있어 보여서 &lt;code>이거다!&lt;/code> 하며 개발을 시작을 했으나 (나름 도메인까지 그럴싸하게 만들었지만&amp;hellip; &lt;a href="http://dailydevblog.pythonanywhere.com/" target="_blank" rel="noopener noreffer ">http://dailydevblog.pythonanywhere.com/&lt;/a> ) 세상에 공짜는 없다는 말을 실감하며 앞서 말했던 요구사항을 완벽하게 구현할 수 없는 상황이였다.(request 제한, 스케쥴러 등록 개수 제한 등 보다 여러기능을 사용하기 위해서는 돈을 내고 써야&amp;hellip;)
마지막 희망으로 언제샀는지 서랍속 깊이 자고있던 라즈베리 파이를 꺼내서 공유기 DDNS설정을 하고 라즈베리안을 설치하며 웹서버를 위한 셋팅을 시도해보았으나 언제나 그렇듯 (시험공부 하기전에 책상 정리하고 괜히 방청소까지 하다가 피곤해서 자버리는듯한 느낌) 배보다 배꼽이 클것같아 이또한 진행하다가 중단하게 된다.
결국 AWS에서 1년동안은 무료로 사용할수 있는 &lt;a href="https://aws.amazon.com/ko/free/" target="_blank" rel="noopener noreffer ">Free Tier&lt;/a> 라는걸 발견하고 이참에 나도한번 사용해보자라는 마음을 가지고 과금되지 않게 조심조심 셋팅을 할수 있었다. 물론 뒤에서 이야기 하겠지만 약간의 과금은 필요했다ㅠ (나름 심도깊었던 고민을 한방에 해결해버리는 AWS 짱;; 이래서 AWS~ AWS~ 하는가 싶었다.)&lt;/p></description></item><item><title>초간단 API서버 만들기 - 2부 (Python + Flask + Nginx)</title><link>https://taetaetae.github.io/2018/07/01/simple-web-server-flask-nginx/</link><pubDate>Sun, 01 Jul 2018 02:00:00 +0000</pubDate><guid>https://taetaetae.github.io/2018/07/01/simple-web-server-flask-nginx/</guid><description>&lt;p>&lt;a href="https://taetaetae.github.io/2018/07/01/simple-web-server-flask-apache/" target="_blank" rel="noopener noreffer ">지난포스팅&lt;/a>에 이어 이번엔 Flask와 Nginx를 연동하는 방법을 정리해보고자 한다. Apache로 연동했는데 왜 또 Nginx로 연동하는걸 정리하지(?)하며 의문이 들수 있는데 다른 포스팅을 봐도 &lt;!-- more --> Apache + Flask 조합보다 Nginx + Flask 조합이 더 많고 지난 포스팅에서도 알수있었듯이 (&lt;a href="https://taetaetae.github.io/2018/06/27/apache-vs-nginx/" target="_blank" rel="noopener noreffer ">Apache VS Nginx&lt;/a>) 둘중 어느것이 좋다고 할수도 없고 각 상황에서 연동하는 방법을 알고 있다면 이 또한 나만의 무기가 될것같아 Nginx를 연동하는 방법을 정리해보려 한다.&lt;/p>
&lt;p>1부에서 왜 Flask인가, Flask의 장점에 대해 정리를 했으니 이번 포스팅에서는 별도로 작성하진 않는다.&lt;/p>
&lt;h2 id="nginx-설치---">Nginx 설치 ( &lt;a href="https://nginx.org/en/" target="_blank" rel="noopener noreffer ">https://nginx.org/en/&lt;/a> )&lt;/h2>
&lt;p>역시 소스설치를 한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 다운을 받고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ https://nginx.org/download/nginx-1.14.0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 압축을 푼 다음
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf nginx-1.14.0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 폴더로 이동해서 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd nginx-1.14.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 설치할 디렉토리를 설정하고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ./configure --prefix=/~~~/apps/nginx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> make 파일을 만들고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 설치를 진행한다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하면 일단 Nginx는 설치가 되었다.&lt;/p>
&lt;h2 id="uwsgi-설치---">uWSGI 설치 ( &lt;a href="https://uwsgi-docs.readthedocs.io/" target="_blank" rel="noopener noreffer ">https://uwsgi-docs.readthedocs.io/&lt;/a> )&lt;/h2>
&lt;p>앞서 Apache와 연동할때는 별도의 모듈을 Apache에게 등록하는 형태였다면 Nginx는 WSGI프로토콜을 활용하는 WSGI 어플리케이션을 실행하는 어플리케이션 서버를 활용해야 한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 다운을 받고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ wget https://projects.unbit.it/downloads/uwsgi-latest.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 압축을 풀고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar zxvf uwsgi-latest.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 폴더로 이동하여
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd uwsgi-2.0.17
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> make 명령어를 호출하면 &amp;#39;uwsgi&amp;#39;이라는 실행파일이 생성된다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="nginx-설정">Nginx 설정&lt;/h2>
&lt;p>Apache와 비슷하게 uWSGI 관련 설정을 해준다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">server {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> listen 80;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> server_name localhost;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location / { # ( / ) 경로로 들어올 경우
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> include uwsgi_params; # GET/POST 등 기본적으로 필요한 환경변수를 include 해준다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uwsgi_pass 127.0.0.1:3031; # 요청을 IP:PORT로 전달한다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>별도의 모듈을 사용하지 않기때문에 전달해주는 (proxy느낌) 설정을 해준다.&lt;/p>
&lt;h2 id="uwsgi-실행-및-nginx-재시작">uWSGI 실행 및 Nginx 재시작&lt;/h2>
&lt;p>앞서 설치한 &lt;code>uwsgi&lt;/code>를 아래처럼 IP:port 를 명시적으로 적어주고 (위에서 전달받은 IP:PORT와 동일하게) Apache 연동시 활용했던 wsgi파일을 이번에도 동일하게 사용하도록 해서 실행한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">$ ./uwsgi -s 127.0.0.1:3031 --wsgi-file /~~~/python_app/hello_world.wsgi
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하면 background로 실행되는게 아닌 foreground로 실행되기 때문에 &lt;code>&amp;amp;&lt;/code>을 사용한다던지 해서 background로 실행되도록 해준다. 그후 Nginx를 재시작 해주면 원하는 그토록 원했던 &lt;code>Hello World!&lt;/code>를 만날수가 있게 된다.&lt;/p>
&lt;p>Apache연동과 조금 다른점은 모듈을 사용하지않고 별도의 전달 어플리케이션(?)이 필요하다는점이다. 간단히 Apache처럼 모듈만 넣으면 되는게 아니라서 불편할수도 있을것 같지만 한편으로는 관리할수있는 포인트가 더 늘어난 셈이라 어떤 측면에서는 활용할수 있는 방법이 하나 늘어난것으로 볼수도 있다.&lt;/p>
&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>막상 정리하고 나면 아무것도 아닌데 알기 위해서 몸부림을 쳐가며 책이며 구글링을 하는 과정을 통해 점점 성장을 하는것 같다. (성장통이라고나 할까) 이렇게 단순히 Flask를 할수있다 가 아닌 웹서버를 연동할수있다. 그것도 Apache와 Nginx 두개나. 이것도 언젠간 나만의 무기가 되지 않을까?&lt;/p></description></item><item><title>초간단 API서버 만들기 - 1부 (Python + Flask + Apache)</title><link>https://taetaetae.github.io/2018/06/29/simple-web-server-flask-apache/</link><pubDate>Fri, 29 Jun 2018 23:00:00 +0000</pubDate><guid>https://taetaetae.github.io/2018/06/29/simple-web-server-flask-apache/</guid><description>&lt;p>Static한 HTML이 아닌 로직이 필요한 API서버를 구성한다고 가정해보자. (이제까지 지식으로)처음 머릿속에 떠오르는건 Java를 사용하고 스프링으로 어플리케이션을 만들고 apache에 tomcat을 연동한 다음 &amp;hellip;&lt;!-- more --> 이러한 방법으로 API서버를 구성할수 있겠지만 프로토타이핑 또는 테스트 목적으로 만들기 위해서는 설정하는 시간이 은근 많이 소요된다. (물론 Java Config, Spring Boot 등 간소해졌지만&amp;hellip;)
얼마전부터 Python에 대한 매력을 뼈저리게 느끼고 있다보니 Python으로 API서버를 구성할순 없을까 알아봤고 (모바일 게임 듀랑고 서버가 python이라고 하기도 하고&amp;hellip;) &lt;code>Flask&lt;/code>와 &lt;code>Django&lt;/code>가 있어서 둘다 써본 결과 필자는 &lt;code>Flask&lt;/code>가 맞겠다고 생각해서 정리를 해볼까 한다.&lt;/p>
&lt;blockquote>
&lt;p>&amp;lsquo;장고&amp;rsquo;라고도 불리는 Django에는 모든것들이 다 들어가 있어서 사용하기 너무 편리하다. (DB, 어드민 등 ) 하지만 Flask는 내가 사용할 것들만 import해서 사용하는 방식이라 어떤 측면에서는 아무것도 없다 할수 있겠으나 커스터마이징에 용이하다고 볼수 있었기에 Flask를 선택하게 되었다. (Django가 Flask보다 안좋다는 말은 아니니 오해는 하지 마시길&amp;hellip;)&lt;/p>&lt;/blockquote>
&lt;p>글쓰기에 앞서 본 포스팅은 2개의 포스팅에 걸쳐 시리즈(?)형식으로 작성할 예정이다. 1부에서는 Flask가 무엇이고 이를 어떻게 사용하며 Apache와 연동하는 방법을 소개하고, 2부에서는 Nginx와 연동하는 방법을 소개한다.
환경은 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>CentOS 7.4&lt;/li>
&lt;li>Python 3.6 (기본은 2.7이였으나 추가로 설치)&lt;/li>
&lt;/ul>
&lt;h2 id="flask---">Flask ( &lt;a href="http://flask.pocoo.org/" target="_blank" rel="noopener noreffer ">http://flask.pocoo.org/&lt;/a> )&lt;/h2>
&lt;p>공식 홈페이지에서도 보면 알수 있듯이 너~무 간단하다. 단지 아래 코드 몇줄만 작성하면 우리가 모든 프로그램 초기 작성시 항상 만나는 &amp;ldquo;Hello World&amp;quot;를 볼수 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#hello_world.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">flask&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Flask&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Flask&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hello&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;Hello World!&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위와같이 작성하고 &lt;code>python hello.py&lt;/code>로 실행해두고 브라우저에서 &lt;code>http://127.0.0.1:5000&lt;/code> 을 요청하면 반가운 Hello World를 만날수 있다. (너무 간단;;) 자세한 문법은 &lt;a href="http://flask.pocoo.org/docs" target="_blank" rel="noopener noreffer ">도큐먼트&lt;/a>를 참조하면 될듯하고 이 Flask를 잘만 활용한다면 보다 빠르고 간단하게 API서버를 구성할수 있을거라 생각한다.&lt;/p>
&lt;p>Hello World를 찍었으면 된거 아닌가 라고 질문할수도 있겠으나 실제 서비스에서 사용하기 위해서는 앞단에 웹서버를 두는게 여러 측면에서 효율적이다. 주로 사용하는 웹서버는 Apache 와 Nginx가 있는데 여기서는 Apache와 연동하는 방법을 정리 해보고자 한다.&lt;/p>
&lt;h2 id="apache-설치---">Apache 설치 ( &lt;a href="http://archive.apache.org/" target="_blank" rel="noopener noreffer ">http://archive.apache.org/&lt;/a> )&lt;/h2>
&lt;p>우선 필자는 &lt;code>yum&lt;/code> 이나 &lt;code>apt-get&lt;/code>처럼 패키지 관리자로 설치하는것을 그렇게 좋아하지 않는다. 이유는 커스터마이징을 할 경우 시스템 어느곳에 설치되어있는지를 한눈에 파악하기 어렵고 윈도우경우 &lt;code>Program Files&lt;/code>처럼 내가 추가로 설치하고 관리하는 프로그램들을 한곳에서 관리하고 싶기에 왠만하면 소스를 직접 컴파일하여 설치하곤 한다. 이번 역시 아파치도 소스로 설치하려고 한다.
현재 아파치는 2.4버전이 Stable버전으로 되어있지만 보다 레퍼런스가 많은 2.2버전으로 설치하기 위해 어렵게 아카이빙된 경로를 통해 다운을 받고 설치를 한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 다운을 받고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ wget http://archive.apache.org/dist/httpd/httpd-2.2.29.tar.gz 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 압축을 푼 다음
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar xvzf httpd-2.2.29.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 해당 폴더로 들어가
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd httpd-2.2.29
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 컴파일 후 설치 경로를 정해주고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ./configure --prefix=/~~~/apps/apache
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> make 파일을 만든다음
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 설치를 해준다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 되면 /~~~/apps/apache/ 하위에 필요한 파일들이 설치가 되는데 root계정이 아닌 일반계정으로 실행하기 위해서는 /bin하위에 있는 httpd에 대한 실행/소유권한을 변경해줘야 한다. (아니면 그냥 root권한으로 시작/종료. 왜? Apache는 80port를 사용하는데 일반적으로 리눅스에서는 1024 아래 port를 컨트롤 하기 위해서는 root권한이 있어야 사용이 가능, 그게 아니라면 이처럼 별도의 설정이 필요하다.)&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo chown root:계정명 httpd
$ sudo chmod +s httpd
&lt;/code>&lt;/pre>&lt;h2 id="mod_wsgi-설치---">mod_wsgi 설치 ( &lt;a href="https://code.google.com/archive/p/modwsgi/" target="_blank" rel="noopener noreffer ">https://code.google.com/archive/p/modwsgi/&lt;/a> )&lt;/h2>
&lt;p>웹 서버 게이트웨이 인터페이스(WSGI, Web Server Gateway Interface)는 웹서버와 웹 애플리케이션의 인터페이스를 위한 파이선 프레임워크다. 라고 정의되어있다. 즉, 웹서버(Apache)와 위에서 만든 Flask 어플리케이션을 연동해주기 위한 프레임워크이다. 이또한 소스로 설치해보자. (위와 같은 이유로~)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 다운을 받고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ wget https://github.com/GrahamDumpleton/mod_wsgi/archive/3.5.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 압축을 푼 다음
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar -zxvf 3.5.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 폴더에 들어가서
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ cd mod_wsgi-3.5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 아파치의 빌드툴인 apxs의 경로를 설정해주고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 필자와 같이 기본 python 버전을 사용하지 않을꺼라면 꼭 python경로를 설정해줘야 한다! (중요)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ./configure --with-apxs=/~~~/apps/apache/bin/apxs --with-python=/usr/bin/python3.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> make 파일을 만들고
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">-&lt;/span> 설치~
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ make install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 설치를 하면 자동으로 아파치 하위 /modules 폴더안에 mod_wsgi.so 파일이 생긴다. (필자는 이것도 모르고 mod_wsgi.so파일을 다운 받으려고 구글링을 몇일째 했던 기억이 ㅠ)&lt;/p></description></item><item><title>Apache냐 Nginx냐, 그것이 알고싶다.</title><link>https://taetaetae.github.io/2018/06/27/apache-vs-nginx/</link><pubDate>Wed, 27 Jun 2018 17:17:33 +0000</pubDate><guid>https://taetaetae.github.io/2018/06/27/apache-vs-nginx/</guid><description>&lt;p>웹서버는 HTTP 프로토콜을 통해 읽힐수 있는 문서를 처리를 하며 일반적으로 웹 어플리케이션의 앞단에 배치되곤 한다. 동적인 리소스는 WAS에게 처리하도록 하고 정적인 리소스를 보다 효율적으로 처리하기 위한 방법일수도 있다. 크게 Apache와 Nginx가 사용되곤 하는데 이 둘의 차이는 무엇일까?&lt;!-- more --> 사실 필자는 사내에서 주로 Apache만 사용하다보니 Nginx는 그저 &lt;code>Apache와는 다른 방식의 웹서버다&lt;/code> 또는 &lt;code>보다 경량화 되었다&lt;/code> 정도로만 알고있었는데 이번기회를 통해 제대로 알고 비교를 해보면서 결국 어떤게 좋은지 알아보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>구글링을 조금만 해보면 Apache와 Nginx를 비교하는 포스팅이 많이 나온다. 이번 포스팅의 목적 이러한 정보들을 단순히 요약/종합 하려는게 아니고, 최대한 실무 서비스를 운영하는 시각으로 정리하고자 함을 밝힌다.&lt;/p>&lt;/blockquote>
&lt;h2 id="apache---">Apache ( &lt;a href="https://httpd.apache.org/" target="_blank" rel="noopener noreffer ">https://httpd.apache.org/&lt;/a> )&lt;/h2>
&lt;p>우리나라에서 웹어플리케이션을 개발하는 사람들은 한번쯤은 들어봤을 &lt;code>Apache&lt;/code>. 국내 일반적인 기업에서 웹서버의 표준으로 자리잡았다고 해도 과언이 아닐것 같다. Client에서 요청을 받으면 MPM (Multi Processing Module : 다중처리모듈) 이라는 방식으로 처리를 하는데 대표적으로는 Prefork와 Worker방식이 있다. 간단하게 어떤식으로 처리하는지 알고 넘어가자.&lt;/p>
&lt;ul>
&lt;li>Prefork MPM
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-vs-nginx/prefork.gif" title="/images/apache-vs-nginx/prefork.gif" data-thumbnail="/images/apache-vs-nginx/prefork.gif" data-sub-html="&lt;h2>Prefork MPM, http://old.zope.org/Members/ike/Apache2/osx/configure_html&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-vs-nginx/prefork.gif"
 data-srcset="https://taetaetae.github.io/images/apache-vs-nginx/prefork.gif, https://taetaetae.github.io/images/apache-vs-nginx/prefork.gif 1.5x, https://taetaetae.github.io/images/apache-vs-nginx/prefork.gif 2x"
 data-sizes="auto"
 alt="/images/apache-vs-nginx/prefork.gif" />
 &lt;/a>&lt;figcaption class="image-caption">Prefork MPM, &lt;a href="http://old.zope.org/Members/ike/Apache2/osx/configure_html" target="_blank" rel="noopener noreffer ">http://old.zope.org/Members/ike/Apache2/osx/configure_html&lt;/a>&lt;/figcaption>
 &lt;/figure>&lt;/li>
&lt;/ul>
&lt;p>실행중인 프로세스를 복제되어 처리가 된다. 각 프로세스는 한번에 한 연결만 처리하고 요청량이 많아질수록 프로세스는 증가하지만 복제시 메모리영역까지 복제되어 동작하므로 프로세스간 메모리 공유가 없어 안정적이라 볼수 있다.&lt;/p>
&lt;ul>
&lt;li>Worker MPM
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-vs-nginx/worker.gif" title="/images/apache-vs-nginx/worker.gif" data-thumbnail="/images/apache-vs-nginx/worker.gif" data-sub-html="&lt;h2>Worker MPM, http://old.zope.org/Members/ike/Apache2/osx/configure_html&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-vs-nginx/worker.gif"
 data-srcset="https://taetaetae.github.io/images/apache-vs-nginx/worker.gif, https://taetaetae.github.io/images/apache-vs-nginx/worker.gif 1.5x, https://taetaetae.github.io/images/apache-vs-nginx/worker.gif 2x"
 data-sizes="auto"
 alt="/images/apache-vs-nginx/worker.gif" />
 &lt;/a>&lt;figcaption class="image-caption">Worker MPM, &lt;a href="http://old.zope.org/Members/ike/Apache2/osx/configure_html" target="_blank" rel="noopener noreffer ">http://old.zope.org/Members/ike/Apache2/osx/configure_html&lt;/a>&lt;/figcaption>
 &lt;/figure>&lt;/li>
&lt;/ul>
&lt;p>Prefork 동작방식이 1개의 프로세스가 1개의 스레드로 처리가 되었다면 Worker 동작방식은 1개의 프로세스가 각각 여러 쓰레드를 사용하게 된다. 쓰레드간의 메모리를 공유하며 PreFork방식보다 메모리를 덜 사용하는 장점이 있다.&lt;/p>
&lt;p>참고로 WAS로 tomcat을 연동하는 경우라면 &lt;a href="https://tomcat.apache.org/download-connectors.cgi" target="_blank" rel="noopener noreffer ">mod_jk&lt;/a>, &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_proxy.html" target="_blank" rel="noopener noreffer ">mod_proxy&lt;/a>, &lt;a href="https://httpd.apache.org/docs/2.4/mod/mod_proxy_ajp.html" target="_blank" rel="noopener noreffer ">mod_proxy_ajp&lt;/a> 방식을 Apache 자체적으로 지원해주기 때문에 다양하고 효율적으로 tomcat을 연동할수 있다. &lt;a href="https://www.lesstif.com/pages/viewpage.action?pageId=12943367" target="_blank" rel="noopener noreffer ">참고링크&lt;/a>&lt;/p>
&lt;h2 id="nginx---">Nginx ( &lt;a href="https://nginx.org/en/" target="_blank" rel="noopener noreffer ">https://nginx.org/en/&lt;/a> )&lt;/h2>
&lt;p>Nginx에 대해 살펴보기 전에 &lt;a href="https://trends.google.co.kr" target="_blank" rel="noopener noreffer ">구글 트랜드&lt;/a>를 활용하여 Nginx에 대한 관심이 어느정도인지를 보고 넘어가자.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-vs-nginx/google-trand.png" title="/images/apache-vs-nginx/google-trand.png" data-thumbnail="/images/apache-vs-nginx/google-trand.png" data-sub-html="&lt;h2>최근 5년간 구글트랜드, 파란색이 Apache이고 빨간색이 Nginx&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-vs-nginx/google-trand.png"
 data-srcset="https://taetaetae.github.io/images/apache-vs-nginx/google-trand.png, https://taetaetae.github.io/images/apache-vs-nginx/google-trand.png 1.5x, https://taetaetae.github.io/images/apache-vs-nginx/google-trand.png 2x"
 data-sizes="auto"
 alt="/images/apache-vs-nginx/google-trand.png" />
 &lt;/a>&lt;figcaption class="image-caption">최근 5년간 구글트랜드, 파란색이 Apache이고 빨간색이 Nginx&lt;/figcaption>
 &lt;/figure>
&lt;p>전세계는 Nginx보다는 Apache에 대한 관심이 많은것으로 보이는데 국내는 아주 조금씩 Nginx에 대한 관심이 오르는것을 볼수있었다. (그래도 아직은 Apache가 월등히 우세한 편이다.)
그럼 Nginx는 어떤식으로 돌아가는 것일까? 가장 유명한(?) 특징이라면 &lt;code>Event Driven 방식&lt;/code>을 꼽을수 있을것 같다. Event Driven 방식에 대해 잠깐 언급을 하고 넘어가면 요청이 들어오면 어떤 동작을 해야하는지만 알려주고 다른요청을 처리하는 방식이다. (&lt;a href="https://dzone.com/articles/producer-consumer-pattern" target="_blank" rel="noopener noreffer ">Producer Consumer Pattern&lt;/a>과 유사하다.) 그러다보니 프로세스를 fork하거나 쓰레드를 사용하는 아파치와는 달리 CPU와 관계없이 모든 IO들을 전부 Event Listener로 미루기 때문에 흐름이 끊기지 않고 응답이 빠르게 진행이 되어 1개의 프로세스로 더 빠른 작업이 가능하게 될수 있다. 이때문에 메모리적인 측면에서 Nginx가 System Resource를 적게 처리한다는 장점이 있다고 한다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-vs-nginx/nginx_process_model.png" title="/images/apache-vs-nginx/nginx_process_model.png" data-thumbnail="/images/apache-vs-nginx/nginx_process_model.png" data-sub-html="&lt;h2>Nginx Process Model (https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale)&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-vs-nginx/nginx_process_model.png"
 data-srcset="https://taetaetae.github.io/images/apache-vs-nginx/nginx_process_model.png, https://taetaetae.github.io/images/apache-vs-nginx/nginx_process_model.png 1.5x, https://taetaetae.github.io/images/apache-vs-nginx/nginx_process_model.png 2x"
 data-sizes="auto"
 alt="/images/apache-vs-nginx/nginx_process_model.png" />
 &lt;/a>&lt;figcaption class="image-caption">Nginx Process Model (&lt;a href="https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale" target="_blank" rel="noopener noreffer ">https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale&lt;/a>)&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="그래서-뭐가-좋은가">그래서 뭐가 좋은가?&lt;/h2>
&lt;p>이 포스팅을 적으면서 마지막엔 &lt;code>Apache가 더좋다&lt;/code> 또는 &lt;code>Nginx가 더좋다&lt;/code>로 마무리를 짓고 싶었는데 어느 시사/교양 프로그램처럼 어쩔수 없는 &lt;code>열린결말&lt;/code>로 마무리를 지을수밖에 없을것 같다. (어찌보면 이게 정답일수도?)&lt;/p>
&lt;p>기술의 선택에 있어서 정답은 없는것 같다.(물론 Spring 을 사용하느냐 서블릿을 직접 구현하는냐 와는 좀 다른 성격의 이야기;;) 운영하고 있는 서비스의 상황을 잘 알고 튜닝을 해가면서 가장 효율적인것을 선택하는게 정답이라고 말할수 밖에&amp;hellip; 커뮤니티 파워를 무시 못하기 때문에 Apache를 선택할수도 있을테고, 점점 관심도가 올라간다는건 그만큼의 장점이 있고 또한 메모리 측면에서 동접자 처리시 효율적인 Nginx를 사용할수 있을것 같다.&lt;/p></description></item><item><title>시계열 데이터를 분석하여 미래 예측 하기(Anomaly Detection)</title><link>https://taetaetae.github.io/2018/05/31/anomaly-detection/</link><pubDate>Thu, 31 May 2018 17:03:41 +0000</pubDate><guid>https://taetaetae.github.io/2018/05/31/anomaly-detection/</guid><description>&lt;p>급변하는 날씨를 예측하려면 어떠한 정보가 있어야 할까?
또는 마트를 운영하는 담당자인 경우 매장 운영시간을 정해야 한다면 어떠한 기준으로?
뜨거운 감자인 비트코인 시장에서 수익을 얻으려면 어떤 정보들이 있어야 물리지(?) 않을수 있을까?&lt;/p>
 &lt;!--more --> 
&lt;p>위 질문에 공통된 정답은 &lt;code>예전 기록들&lt;/code>인것 같다. 날씨예측은 기상청에서 과거 기록들을 보고 비가 올지 말지를 결정하고 ( 과거 날씨 서비스를 담당해봤지만 단순히 과거 기록들로 예측한다는건 불가능에 가깝긴 하다. ) 매장 운영시간은 예전에 손님들이 언제왔는지에 대한 데이터를 보고. 비트코인이나 주식은 차트를 보고 어느정도는 상승장일지 하락장일지 추측이 가능하다고 한다. ( 물론 호재/악재에 따라 흔들리지만..ㅠㅠ..?? )
이처럼 시간의 흐름에 따라 만들어진 데이터를 분석하는것을 &lt;code>시계열 데이터 분석&lt;/code>이라 부르고 있다. 필자가 운영하는 서비스에서 시계열 데이터 분석을 통해 장애를 사전에 방지하는 사례를 공유 해보고자 한다.&lt;/p>
&lt;h2 id="상황파악부터">상황파악부터&lt;/h2>
&lt;p>손자병법에는 지피지기 백전불태 라는 말이 있다. 그만큼 현 상황을 잘 알아야 대응을 잘할수 있다는것. 필자가 운영하는 서비스는 PG(Payment Gateway) 서비스로 쇼핑몰같은 온/오프라인 사업자와 실제 카드사와의 중간 역활을 해주고 있다. 이를테면 사용자가 &lt;code>생수를 10,000원에 XX카드로 구매해줘&lt;/code> 라고 요청이 오면 그 정보를 다시 형식에 맞춰 카드사로 전달하여 사용자가 물건을 구매할수 있도록 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/anomaly-detection/pg.png" title="/images/anomaly-detection/pg.png" data-thumbnail="/images/anomaly-detection/pg.png" data-sub-html="&lt;h2>PG서비스 : 쇼핑몰과 카드사의 중간에서 릴레이 해주는 역활이라 보면된다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/anomaly-detection/pg.png"
 data-srcset="https://taetaetae.github.io/images/anomaly-detection/pg.png, https://taetaetae.github.io/images/anomaly-detection/pg.png 1.5x, https://taetaetae.github.io/images/anomaly-detection/pg.png 2x"
 data-sizes="auto"
 alt="/images/anomaly-detection/pg.png" />
 &lt;/a>&lt;figcaption class="image-caption">PG서비스 : 쇼핑몰과 카드사의 중간에서 릴레이 해주는 역활이라 보면된다.&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="요구사항-및-과거-데이터-분석">요구사항 및 과거 데이터 분석&lt;/h2>
&lt;p>서비스를 운영해보니 감지하기 어려운 상황들이 있었다.&lt;/p>
&lt;ul>
&lt;li>연동하는 쇼핑몰에서 문제가 발생하거나 네트워크 문제가 발생할경우 즉, 트래픽이 평소보다 적게 들어올 경우&lt;/li>
&lt;li>정상적인 에러(e.g. 잔액부족) 가 갑자기 많이 발생할 경우&lt;/li>
&lt;/ul>
&lt;p>이를 분석하기위해 기존의 트래픽/데이터를 분석해봐야 했다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/anomaly-detection/trade_count.png" title="/images/anomaly-detection/trade_count.png" data-thumbnail="/images/anomaly-detection/trade_count.png" data-sub-html="&lt;h2>결제건수 Kibana Visualize, 기영이 패턴&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/anomaly-detection/trade_count.png"
 data-srcset="https://taetaetae.github.io/images/anomaly-detection/trade_count.png, https://taetaetae.github.io/images/anomaly-detection/trade_count.png 1.5x, https://taetaetae.github.io/images/anomaly-detection/trade_count.png 2x"
 data-sizes="auto"
 alt="/images/anomaly-detection/trade_count.png" />
 &lt;/a>&lt;figcaption class="image-caption">결제건수 Kibana Visualize, 기영이 패턴&lt;/figcaption>
 &lt;/figure>
&lt;p>위 그래프는 결제데이터 카운트 인데 어느정도 패턴을 찾을수 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/anomaly-detection/error_count.png" title="/images/anomaly-detection/error_count.png" data-thumbnail="/images/anomaly-detection/error_count.png" data-sub-html="&lt;h2>에러건수 Kibana Visualize, 악어 패턴..(무리수..)&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/anomaly-detection/error_count.png"
 data-srcset="https://taetaetae.github.io/images/anomaly-detection/error_count.png, https://taetaetae.github.io/images/anomaly-detection/error_count.png 1.5x, https://taetaetae.github.io/images/anomaly-detection/error_count.png 2x"
 data-sizes="auto"
 alt="/images/anomaly-detection/error_count.png" />
 &lt;/a>&lt;figcaption class="image-caption">에러건수 Kibana Visualize, 악어 패턴..(무리수..)&lt;/figcaption>
 &lt;/figure>
&lt;p>위 그래프는 에러카운트 인데 일정한 패턴 속에서 어느 지점에서는 튀는것을 확인할수 있다. (빨간색 영역) 그렇다면 어떤 방법으로 장애상황보다 앞서서 감지를 할수 있을까? ( 장애 : 어떠한 내/외부 요인으로 인해 정상적인 서비스가 되지 않는 상태 )&lt;/p>
&lt;h2 id="장애발생-전에-먼저-찾아보자">장애발생 전에 먼저 찾아보자!&lt;/h2>
&lt;p>가장 간단하게는 기존 데이터를 보고 수동으로 설정하는 방법이 있을수 있다. 예로들어 자정 즈음에는 결제량이 가장 많기때문에 약 xx건으로 설정해두고, 새벽에는 결제량이 가장 적기 때문에 약 yy건으로 설정해둔 후 에러 건수나 결제건수에 대해 실시간으로 검사를 해가면서 설정한 값보다 벗어날 경우 알림을 주는 방법이다.
하지만 아무리 과거 데이터를 완벽하게 분석했다 할지라도 24시간 모든 시점에서 예측은 벗어날 수밖에 없다. (예로들어 쇼핑 이벤트를 갑작스럽게 하게되면 결제량은 예측하지 못할정도로 늘어날테고&amp;hellip;) 또한 설정한 예측값을 벗어날 경우 수동으로 다시 예측값을 조정해줘야 하는데, 이럴꺼면 24시간 종합 상황실에서 사람이 직접 눈으로 보는것 보다 못할것 같다. (인력 리소스가 충분하다면 뭐&amp;hellip; 그렇게 해도 된다.)&lt;/p>
&lt;h2 id="지난-데이터와-비교하기">지난 데이터와 비교하기&lt;/h2>
&lt;p>일주일 기준으로 지난 일주일과의 데이터를 비교해보는 방법또한 있다. 간단하게 설명하면 이번주 월요일 10시의 데이터와 지난주 월요일 10시의 데이터의 차이를 비교해보는 방법이다. 키바나에서 클릭 몇번만으로 시각화를 도와주는 Visualize 기능을 통해 지난 일주일과 이번주를 비교해보면 아래 그래프처럼 표현이 가능하다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/anomaly-detection/visualize.png" title="/images/anomaly-detection/visualize.png" data-thumbnail="/images/anomaly-detection/visualize.png" data-sub-html="&lt;h2>일주일 전 데이터와 단순 비교&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/anomaly-detection/visualize.png"
 data-srcset="https://taetaetae.github.io/images/anomaly-detection/visualize.png, https://taetaetae.github.io/images/anomaly-detection/visualize.png 1.5x, https://taetaetae.github.io/images/anomaly-detection/visualize.png 2x"
 data-sizes="auto"
 alt="/images/anomaly-detection/visualize.png" />
 &lt;/a>&lt;figcaption class="image-caption">일주일 전 데이터와 단순 비교&lt;/figcaption>
 &lt;/figure>
&lt;p>이 경우도 지난주 상황과 이번주 상황이 다른 경우에는 원하는 비교 항목 외에 다른 요인이 추가되기 때문에 원하는 비교를 할수가 없고 위에서 수동으로 설정하는 방법과 별반 다를바 없을것으로 생각된다.&lt;/p>
&lt;h2 id="조금더-우아하게-언제부턴가-우아하단-말을-좋아하는것-같다">조금더 우아하게! &lt;code>(언제부턴가 우아하단 말을 좋아하는것 같다..)&lt;/code>&lt;/h2>
&lt;p>개발자는 문제에 대해서 언제나 분석을 토대로 접근을 하는것을 목표로 해야한다. 언제부턴가 Hot한 머신러닝을 도입해 보고 싶었으나 아직 그런 실력이 되질 못하고&amp;hellip; 폭풍 구글링을 통해 알게된 Facebook에서 만든 &lt;a href="https://facebook.github.io/prophet/" target="_blank" rel="noopener noreffer ">Prophet&lt;/a>이라는 모듈을 활용해보고자 한다.
&lt;a href="https://opensource.fb.com/#artificial" target="_blank" rel="noopener noreffer ">https://opensource.fb.com/#artificial&lt;/a> 이곳에 가보면 여러 Artificial Intelligence 관련된 오픈소스들중에 Prophet 모듈을 찾을수 있다. 다행히도 &lt;a href="https://namu.wiki/w/BSD%20%EB%9D%BC%EC%9D%B4%EC%84%A0%EC%8A%A4" target="_blank" rel="noopener noreffer ">BSD License&lt;/a>라서 실무에서도 다양하게 활용할수 있을것으로 보인다. 친절하게도 &lt;code>Quick Start&lt;/code>을 통해 어떤식으로 예측을 하는지 보여준다. 참고로 Python 과 R 을 지원한다. (python 의 대단함을 다시금 느끼며&amp;hellip;)
구성은 CentOS 7 + python3.6 + jenkins 를 활용한다. (python 경험이 부족하므로 코드가 허접할수도 있으니 양해바란다.)
데이터 분석시 가장 많이 사용된다는 &lt;code>Pandas&lt;/code>와 대규모 다차원 배열을 쉽게 처리 할 수 있게 해주는 &lt;code>numpy&lt;/code>, 그리고 &lt;code>bprophet&lt;/code>를 비롯한 필요한 모듈들을 pip로 설치해준다.&lt;/p></description></item><item><title>아파치 엑세스 로그에 408코드가?</title><link>https://taetaetae.github.io/2018/04/29/apache-408-response-code/</link><pubDate>Sun, 29 Apr 2018 17:39:36 +0000</pubDate><guid>https://taetaetae.github.io/2018/04/29/apache-408-response-code/</guid><description>&lt;p>예전에 아파치 로그를 엘라스틱 스택을 활용하여 &lt;a href="https://taetaetae.github.io/2018/04/10/apache-access-log-user-agent" target="_blank" rel="noopener noreffer ">내 서버에 누가 들어오는지를 확인할수 있도록 구성&lt;/a>을 해두고 몇일간 지켜보니 다음과 같은 엑세스 로그가 발생하고 있었다.&lt;!-- more -->&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:27:33 +0900] &amp;#34;GET /aaa/ HTTP/1.1&amp;#34; 200 6001 30788 &amp;#34;http://www.naver.com&amp;#34; &amp;#34;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:28:08 +0900] &amp;#34;-&amp;#34; 408 - 30 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:28:08 +0900] &amp;#34;-&amp;#34; 408 - 28 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:28:08 +0900] &amp;#34;-&amp;#34; 408 - 12 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:28:08 +0900] &amp;#34;-&amp;#34; 408 - 30 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:28:50 +0900] &amp;#34;GET /aaa/ HTTP/1.1&amp;#34; 200 5999 13521 &amp;#34;http://www.naver.com/&amp;#34; &amp;#34;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:14 +0900] &amp;#34;GET /aaa/ HTTP/1.1&amp;#34; 200 5996 19437 &amp;#34;http://www.naver.com&amp;#34; &amp;#34;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:15 +0900] &amp;#34;GET /aaa/ HTTP/1.1&amp;#34; 200 5997 17553 &amp;#34;http://www.naver.com&amp;#34; &amp;#34;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:15 +0900] &amp;#34;GET /aaa/ HTTP/1.1&amp;#34; 200 5998 17429 &amp;#34;http://www.naver.com/&amp;#34; &amp;#34;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:53 +0900] &amp;#34;-&amp;#34; 408 - 30 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:53 +0900] &amp;#34;-&amp;#34; 408 - 30 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:53 +0900] &amp;#34;-&amp;#34; 408 - 32 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:53 +0900] &amp;#34;-&amp;#34; 408 - 38 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:29:53 +0900] &amp;#34;-&amp;#34; 408 - 29 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:30:54 +0900] &amp;#34;GET /aaa/ HTTP/1.1&amp;#34; 200 6000 17881 &amp;#34;http://www.naver.com&amp;#34; &amp;#34;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:31:34 +0900] &amp;#34;-&amp;#34; 408 - 30 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:31:34 +0900] &amp;#34;-&amp;#34; 408 - 30 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1.2.3.4 - - [26/Apr/2018:01:31:34 +0900] &amp;#34;-&amp;#34; 408 - 25 &amp;#34;-&amp;#34; &amp;#34;-&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>한시간에 만건이상 &lt;code>응답코드는 408&lt;/code>, &lt;code>referrer도 없고&lt;/code>, &lt;code>useragent도 없는&lt;/code>, &lt;code>ip들도 매우 다양한&lt;/code> 이상한 녀석들이 요청되고 있었다.&lt;/p>
&lt;blockquote>
&lt;p>이렇게 엑세스 로그를 분석할수 있는 구성을 해두고 나니 보였지 안그랬음 그냥 지나갔을 터..&lt;/p>&lt;/blockquote>
&lt;p>이러한 데이터를 키바나에서 보면 아래처럼 볼수있는데 한눈에 봐도 과연 의미있는 요청들일까? 하는 의구심이 들정도이다. (1시간 아파치 엑세스 로그)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-408-response-code/200vs400.png" title="/images/apache-408-response-code/200vs400.png" data-thumbnail="/images/apache-408-response-code/200vs400.png" data-sub-html="&lt;h2>주황색이 408응답&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-408-response-code/200vs400.png"
 data-srcset="https://taetaetae.github.io/images/apache-408-response-code/200vs400.png, https://taetaetae.github.io/images/apache-408-response-code/200vs400.png 1.5x, https://taetaetae.github.io/images/apache-408-response-code/200vs400.png 2x"
 data-sizes="auto"
 alt="/images/apache-408-response-code/200vs400.png" />
 &lt;/a>&lt;figcaption class="image-caption">주황색이 408응답&lt;/figcaption>
 &lt;/figure>
&lt;p>그럼 이런 호출들은 도대체 뭘까? 천천히 생각좀 해보자.&lt;/p>
&lt;ol>
&lt;li>정상적이지 않는 호출로 우리 서버의 취약점을 파악하려 하는것들일까?&lt;/li>
&lt;li>응답코드 408은 요청시간초과 응답코드인데&amp;hellip; 오히려 클라이언트 입장에서 문제가 있는건 아닐까?&lt;/li>
&lt;li>어플리케이션 로직이 잘못되어 무한루프에 빠졌나;&lt;/li>
&lt;/ol>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes" target="_blank" rel="noopener noreffer ">위키백과&lt;/a>에서는 아파치 응답코드 중 &lt;code>408&lt;/code>에 대한 응답을 다음과 같이 알려주고 있다.&lt;/p>
&lt;blockquote>
&lt;p>The server timed out waiting for the request. According to HTTP specifications: &amp;ldquo;The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time&lt;/p>&lt;/blockquote>
&lt;p>즉, 아파치 단에서 타임아웃을 내버리는 상황. 여러 다양한 키워드들로 구글링을 해봐도 이렇다할 검색결과를 찾지 못하고 네트워크 관련상황인지 싶어 크롬 개발자도구를 열어 네트워크 지연 테스트를 해보았으나 별 효과가 없었다. 그렇게 범인찾는 형사의 심정으로 이것저것 알아보다 우연히 집에서 원격으로 회사 VPN 붙어서 테스트 하던도중 관련 증상을 재현 할수 있게 되었다.&lt;/p>
&lt;h3 id="-재현상황"># 재현상황&lt;/h3>
&lt;p>우선 아파치버전은 2.2이고 &lt;code>KeepAlive Off&lt;/code>가 되어있는 상황. 아래그림처럼 집PC - 공유기 - VPN - Apache - tomcat jenkins 상황이였는데 젠킨스에 한번 접속후에는 항상 408 응답이 주루룩(?) 발생하는것을 알수 있었다. (사실 맨위에 엑세스 로그가 재현한 엑세스 로그이다.)&lt;/p></description></item><item><title>내 서버에는 누가 들어오는걸까? (실시간 user-agent 분석기)</title><link>https://taetaetae.github.io/2018/04/10/apache-access-log-user-agent/</link><pubDate>Tue, 10 Apr 2018 23:20:19 +0000</pubDate><guid>https://taetaetae.github.io/2018/04/10/apache-access-log-user-agent/</guid><description>&lt;p>Desktop 및 스마트폰의 대중화로 다양한 OS와 브라우저들을 사용하게 되었다. 이때, 내가 운영하는 웹서버에 들어오는 사람들은 무슨 기기로 접속을 하는 것일까? 혹여 특정 OS의 특정 브라우저에서만 안되는 버그를 잡기 위해 몇일밤을 고생하며 겨우 수정했는데&amp;hellip; 과연 그 OS의 브라우저에서는 접속은 하기나 하는걸까? (ㅠㅠ) &lt;!-- more -->
만약, 접속 사용자의 Device 정보를 알고있다면 고생하며 버그를 잡기 전에 먼저 해당 Device 사용율을 체크해 볼수도 있고(수정이 아닌 간단한 얼럿으로 해결한다거나?) 비지니스 모델까지 생각해야하는 서비스라면 타겟팅을 정하는 등 다양한 활용도가 높은 것이 바로 &lt;code>User-Agent&lt;/code>라고 한다(이하 UA). 일반 Apache 를 웹서버로 운영하고 있다고 가정을 하고 어떻게 분석을 할수 있었는지, 그리고 분석을 하며 좀더 우아한(?) 방법은 없는지 알아 보고자 한다.&lt;/p>
&lt;h2 id="user-agent가-뭐야">User-Agent가 뭐야?&lt;/h2>
&lt;p>백문이 불여일타(?)라 했던가, 우선 &lt;a href="http://www.useragentstring.com" target="_blank" rel="noopener noreffer ">http://www.useragentstring.com&lt;/a> 를 들어가보자. 그러면 자신의 OS 및 브라우저 등 정보를 파싱해서 보여주는데 &lt;a href="https://en.wikipedia.org/wiki/User_agent" target="_blank" rel="noopener noreffer ">위키백과&lt;/a>에 따르면 &amp;lsquo;사용자를 대신하여 일을 수행하는 소프트웨어 에이전트&amp;rsquo;라고 한다. 즉, UA만 알아도 어떤 기기/브라우저를 사용하는지 알 수 있다는것.
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent" target="_blank" rel="noopener noreffer ">mozilla&lt;/a>에 가보면 스팩 등 다양한 UA를 볼수가 있는데 특히 맨 아래보면 기기/브라우저별로 지원정보가 나와있다. 여기서도 보면 모든 모바일 삼성 브라우저를 제외하고는 전부 지원이 되는걸 확인할수 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-access-log-user-agent/browser_compatibility.png" title="/images/apache-access-log-user-agent/browser_compatibility.png" data-thumbnail="/images/apache-access-log-user-agent/browser_compatibility.png" data-sub-html="&lt;h2>출처 : developer.mozilla.org&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-access-log-user-agent/browser_compatibility.png"
 data-srcset="https://taetaetae.github.io/images/apache-access-log-user-agent/browser_compatibility.png, https://taetaetae.github.io/images/apache-access-log-user-agent/browser_compatibility.png 1.5x, https://taetaetae.github.io/images/apache-access-log-user-agent/browser_compatibility.png 2x"
 data-sizes="auto"
 alt="/images/apache-access-log-user-agent/browser_compatibility.png" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : developer.mozilla.org&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="기존의-방법">기존의 방법&lt;/h2>
&lt;p>그럼 어떻게 내 서버에 들어온 사용자들의 UA를 확인할 수 있을까? (앞서 Apache를 웹서버로 운영한다고 했으니) Apache access log 에는 Apache에서 제공해주는 모듈을 이용해 접속한 클라이언트의 정보가 남겨지곤 한다. 그렇다면 이 access log를 리눅스 명령어든 엑셀로 뽑아서든지 활용해서 정규식으로 포맷팅 하고 그 결과를 다시 그룹화 시키면 얼추 원하는 데이터를 추출해 낼수 있다. ( 버거형들이 만들어둔 정규식을 가져다 사용할수도 있겠다. &lt;a href="https://regexr.com/?37l4e" target="_blank" rel="noopener noreffer ">https://regexr.com/?37l4e&lt;/a> )
하지만, 우선 자동화가 안되어있어 데이터를 구하고 싶을때마다 귀차니즘에 걸릴수 있고 슈퍼 개발자 파워를 기반으로(?) 데이터 추출을 자동화 한들 &lt;code>실시간&lt;/code>으로 보고싶을땐 제한사항이 많다.&lt;/p>
&lt;h2 id="좀더-나은-방법">좀더 나은 방법(?)&lt;/h2>
&lt;p>실시간 데이터를 모니터링 하는데에는 다양한 오픈소스와 다양한 툴이 있겠지만 경험이 부족한건지 아직까진 ElasticStack 만한걸 못본것 같다. 간단하게 설명을 하면 access log 를 사용하지 않고 front단에서 javascript 로 UA를 구한다음 이러한 정보를 받을수 있는 API를 만들어 그쪽으로 보내면 서버에서 해당 UA를 분석해서 카프카로 보내고 ..!@#$%^blabla&amp;hellip;
^^; 그림으로 보자.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-access-log-user-agent/user_agent_method_1.png" title="/images/apache-access-log-user-agent/user_agent_method_1.png" data-thumbnail="/images/apache-access-log-user-agent/user_agent_method_1.png" data-sub-html="&lt;h2>좀더 나은 방법&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-access-log-user-agent/user_agent_method_1.png"
 data-srcset="https://taetaetae.github.io/images/apache-access-log-user-agent/user_agent_method_1.png, https://taetaetae.github.io/images/apache-access-log-user-agent/user_agent_method_1.png 1.5x, https://taetaetae.github.io/images/apache-access-log-user-agent/user_agent_method_1.png 2x"
 data-sizes="auto"
 alt="/images/apache-access-log-user-agent/user_agent_method_1.png" />
 &lt;/a>&lt;figcaption class="image-caption">좀더 나은 방법&lt;/figcaption>
 &lt;/figure>
&lt;p>front단에서는 &lt;code>navigator.userAgent&lt;/code>를 활용하여 UA를 구할수 있었고, API에서는 UA를 받고 파싱을 하는데 관련 코드는 다음과 같이 작성하였다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">VERSION_SEPARATOR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;.&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">userAgentParsingToMap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">userAgent&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Object&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">HashMap&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">browser&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Browser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">lookup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">userAgent&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">HashMap&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OS&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">lookup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">userAgent&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">HashMap&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Device&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">lookup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">userAgent&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;browserName&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">browser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;family&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;browserVersion&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">getVersion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">browser&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;osName&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;family&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;osVersion&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">getVersion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;deviceModel&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;deviceBrand&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;brand&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getVersion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HashMap&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">majorVersion&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;major&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">majorVersion&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">EMPTY&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">minorVersion&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;minor&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pathVersion&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">dataMap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">StringBuffer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">StringBuffer&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">majorVersion&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">minorVersion&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">VERSION_SEPARATOR&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">minorVersion&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">StringUtils&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pathVersion&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">VERSION_SEPARATOR&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pathVersion&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">toString&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>참고로 Java단에서 UA를 파싱하는 parser가 여러가지가 있는데 그중 &lt;code>uap_clj&lt;/code>라는 모듈이 그나마 잘 파싱이 되어서 사용하게 되었다.&lt;/p>
&lt;ul>
&lt;li>모듈별 비교&lt;/li>
&lt;/ul>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>모듈&lt;/th>
 &lt;th>Browser&lt;/th>
 &lt;th>OS&lt;/th>
 &lt;th>Device&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>eu.bitwalker.useragentutils.UserAgent&lt;/td>
 &lt;td>O&lt;/td>
 &lt;td>불명확함 (Android 5.x)&lt;/td>
 &lt;td>X&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>net.sf.uadetector.UserAgentStringParser&lt;/td>
 &lt;td>O&lt;/td>
 &lt;td>O&lt;/td>
 &lt;td>불명확함 (Smartphone)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>uap_clj.java.api.*&lt;/td>
 &lt;td>O&lt;/td>
 &lt;td>O&lt;/td>
 &lt;td>O&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>Parsing 비교
&lt;ul>
&lt;li>UA&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">&amp;#34;Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>결과&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code> - Browser : {patch=3239, family=Chrome Mobile, major=63, minor=0}
 - OS : {patch=1, patch_minor=, family=Android, major=5, minor=1}
 - Device : {model=Nexus 6, family=Nexus 6, brand=Generic_Android}
&lt;/code>&lt;/pre>&lt;p>위와 같이 구성을 하면 Elasticsearch에 인덱싱된 데이터를 Kibana에서 입맛에 맞게 실시간으로 볼수있게 되었다!
하지만 API를 만드는 부분 + front에서 별도의 javascript가 들어간다는 부분 + 운영하는 모든 페이지에 해당 javascript를 넣어야지만 볼수있는 부분 등 약간 아쉬운 감이 있다. 뭔가 더 좋은 방법이 없을까? 깔끔하면서도 &lt;code>우아한&lt;/code> 방법. 짱구를 열심히 더 굴려보자.&lt;/p></description></item><item><title>gzip 설정으로 속도를 더 빠르게!</title><link>https://taetaetae.github.io/2018/04/01/apache-gzip/</link><pubDate>Sun, 01 Apr 2018 17:58:11 +0000</pubDate><guid>https://taetaetae.github.io/2018/04/01/apache-gzip/</guid><description>&lt;p>내가 운영중인 웹서비스의 응답속도를 보다 더 빠르게 하기 위해서는 어떤 방법이 있을까?
웹 서비스를 위해 서버를 구성할 경우 일반적으로 앞단에 &lt;code>웹서버&lt;/code>를 두고 그뒤에 &lt;code>WAS&lt;/code>를 두는 설계를 하곤 한다. &lt;!-- more -->여기서 웹서버는 대표적으로 Apache나 Nginx가 있고 WAS는 tomcat이나 기타 다른 모듈을 사용하는데 이렇게 두단계로 나누는 이유는 여러가지가 있겠지만 여기서는 앞단의 웹서버(Apache)의 설정으로 응답속도를 줄일수 있는 방법을 알아 보고자 한다.&lt;/p>
&lt;h2 id="웹페이지의-응답속도를-줄일수-있는-일반적인방법들">웹페이지의 응답속도를 줄일수 있는 &amp;lsquo;일반적인&amp;rsquo;방법들&lt;/h2>
&lt;p>꼭 서버의 설정들을 건드리지 않고도 웹페이지의 응답속도를 줄일수 있는 방법은 다양하다. 가장 간단하게 코드 레벨에서 설정할수 있는 방법으로는 스타일시트를 위에 선언하거나 java script는 코드 아래부분에 넣는것만으로도 어느정도 응답속도를 줄일수 있다고 한다.&lt;/p>
&lt;blockquote>
&lt;p>(사족) 신입시절 회사 대표님이 필수로 읽어보라고 전 직원들에게 선물해주셨던 &lt;a href="http://book.naver.com/bookdb/book_detail.nhn?bid=4587095" target="_blank" rel="noopener noreffer ">웹사이트 최적화기법 (스티브 사우더스 저)&lt;/a>이 생각이 난다. &lt;del>모두 사주려면 돈이 얼마야&amp;hellip;&lt;/del> 그만큼 웹개발자들에게 중요하면서도 한편으로는 기본이 되는 부분들이니 한번쯤 목차라도 읽어보는게 좋을듯 하다.&lt;/p>&lt;/blockquote>
&lt;p>사실 이 포스팅을 작성하게된 가장 큰 계기는 얼마전 사내 해커톤을 하면서 경험한 부분 때문이다. ( + 들어만 봤지 실제로 해보지는 않아서&amp;hellip; ) 서버에서 node(React)를 띄우고 그 앞단에 Apache로 단순 Port Redirect ( 80 → 3000 ) 시켜주고 있었는데 react 에서 사용하는 &lt;code>bundle.js&lt;/code>의 용량이 크다보니 최초 페이지 접근시 로딩시간이 5초 이상되어버린 것이다. bundle.js를 줄여보는등 다양한 방법을 사용했다가 결국 Apache 설정을 통해 1초 이내로 줄일수 있었다.&lt;/p>
&lt;h2 id="gzip">gzip&lt;/h2>
&lt;p>우선 &lt;code>gzip&lt;/code>이란 파일 압축에 쓰이는 응용 소프트웨어로 GNU zip의 준말이라고 한다. (참고 : &lt;a href="https://ko.wikipedia.org/wiki/Gzip" target="_blank" rel="noopener noreffer ">위키백과 Gzip&lt;/a>) 이를 사용하기 위해서는 브라우저가 지원을 해야하는데 &lt;a href="https://caniuse.com/#search=gzip" target="_blank" rel="noopener noreffer ">https://caniuse.com/#search=gzip&lt;/a> 을 보면 대부분의 브라우저에서 지원하는것을 볼수 있다.&lt;/p>
&lt;h2 id="데이터-흐름">데이터 흐름&lt;/h2>
&lt;p>그럼 gzip 을 사용했을때와 사용하지 않았을때의 차이는 어떻게 다를까? 우선 Request/Response Flow 를 잠깐 살펴보면 다음과 같다.&lt;/p>
&lt;ul>
&lt;li>gzip 사용 전&lt;/li>
&lt;/ul>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-gzip/before_gzip.png" title="/images/apache-gzip/before_gzip.png" data-thumbnail="/images/apache-gzip/before_gzip.png" data-sub-html="&lt;h2>출처 : betterexplained.com&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-gzip/before_gzip.png"
 data-srcset="https://taetaetae.github.io/images/apache-gzip/before_gzip.png, https://taetaetae.github.io/images/apache-gzip/before_gzip.png 1.5x, https://taetaetae.github.io/images/apache-gzip/before_gzip.png 2x"
 data-sizes="auto"
 alt="/images/apache-gzip/before_gzip.png" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : betterexplained.com&lt;/figcaption>
 &lt;/figure>
&lt;ol>
&lt;li>브라우저가 서버측에 &lt;code>/index.html&lt;/code>을 요청한다.&lt;/li>
&lt;li>서버는 Request를 해석한다.&lt;/li>
&lt;li>Response에 요청한 내용을 담아 보낸다.&lt;/li>
&lt;li>Response를 기다렸다가 브라우저에 보여준다. (100kb)&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>gzip 사용 후&lt;/li>
&lt;/ul>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-gzip/after_gzip.png" title="/images/apache-gzip/after_gzip.png" data-thumbnail="/images/apache-gzip/after_gzip.png" data-sub-html="&lt;h2>출처 : betterexplained.com&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-gzip/after_gzip.png"
 data-srcset="https://taetaetae.github.io/images/apache-gzip/after_gzip.png, https://taetaetae.github.io/images/apache-gzip/after_gzip.png 1.5x, https://taetaetae.github.io/images/apache-gzip/after_gzip.png 2x"
 data-sizes="auto"
 alt="/images/apache-gzip/after_gzip.png" />
 &lt;/a>&lt;figcaption class="image-caption">출처 : betterexplained.com&lt;/figcaption>
 &lt;/figure>
&lt;ol>
&lt;li>브라우저가 서버측에 &lt;code>/index.html&lt;/code>을 요청한다.&lt;/li>
&lt;li>서버는 Request를 해석한다.&lt;/li>
&lt;li>Response에 요청한 내용을 담아 보낸다. 여기서 해당 내용을 압축하는 과정이 추가가 된다.&lt;/li>
&lt;li>Response header에 압축이 되어있다는 정보를 확인후 브라우저는 해당 내용을 받고(10kb), 압축을 해제한 후 사용자에게 보여준다.&lt;/li>
&lt;/ol>
&lt;p>정리하면, gzip을 사용하면 서버는 Client에게 보낼 Response를 압축하기 때문에 네트워크 비용을 줄일수 있어 응답속도가 빠른 장점이 있다.&lt;/p>
&lt;h2 id="무조건-사용해야-하는가">무조건 사용해야 하는가?&lt;/h2>
&lt;p>물론 무조건 좋은 &lt;code>(마치 show me the money 같은)&lt;/code>정답은 없다. 서버에서 압축을 하여 Client에게 보내면 그대로 사용자에게 보여주는것이 아니라 &lt;code>압축을 해제하는 과정&lt;/code>이 추가적으로 필요하다. 이러한 과정에서 브라우저는 cpu를 사용하게 되어 오히려 랜더링 하는 과정이 느려질수 있어 자칫 응답속도는 빨라졌다 하더라도 사용자 체감상 더 느려진것처럼 보여질수 있다. 따라서 상황에 맞춰 gzip을 사용해야 할것인지 말것인지에 대해 테스트가 필요하다.&lt;/p>
&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>학부시절 또는 신입시절, 아파치는 정적인 리소스를 담당하고 톰켓은 서블릿 처럼 데이터 가공이 필요한 페이지를 담당한다고 주문을 외우듯 하였지만, 웹서버에서 압축을 하면 어떤 효과가 있는지 실제로 경험해보는게 가장 중요한것 같다.
적용 방법은 복붙하는 느낌이라 아파치 공식 문서를 링크 하는것으로 해당 포스팅을 마무리 하겠다.&lt;/p>
&lt;ul>
&lt;li>Apache Document (사용법) : &lt;a href="https://httpd.apache.org/docs/2.2/ko/mod/mod_deflate.html" target="_blank" rel="noopener noreffer ">https://httpd.apache.org/docs/2.2/ko/mod/mod_deflate.html&lt;/a>&lt;/li>
&lt;li>적용 테스트 사이트
&lt;ul>
&lt;li>&lt;a href="https://developers.google.com/speed/pagespeed/insights" target="_blank" rel="noopener noreffer ">https://developers.google.com/speed/pagespeed/insights&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.whatsmyip.org/http-compression-test" target="_blank" rel="noopener noreffer ">http://www.whatsmyip.org/http-compression-test&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>RestClientException 처리</title><link>https://taetaetae.github.io/2018/03/17/rest-client-exception/</link><pubDate>Sat, 17 Mar 2018 19:19:39 +0000</pubDate><guid>https://taetaetae.github.io/2018/03/17/rest-client-exception/</guid><description>&lt;p>Spring 환경에서 (Spring5는 달라졌지만&amp;hellip;) 외부 API로의 호출을 할때 자주 쓰이는 RestTemplate. 이때 request에 대해 정상적인 응답이 아닌 경우에 대한 처리는 어떻게 할까? 막연하게 생각을 해보면 Http Status Code를 받아서 판별을 하면 되지만 Http Status Code만 봐도 엄청&lt;del>많다. &lt;!-- more --> ( 위키피디아 참고 : &lt;a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes" target="_blank" rel="noopener noreffer ">https://en.wikipedia.org/wiki/List_of_HTTP_status_codes&lt;/a> )
if&lt;/del>else 로 다 나눌수도 없고&amp;hellip; 우선 성공/실패에 대한 판별은 어떻게 할까 하고 코드를 파고파고 들어가다가 알게된 부분에 대해 정리를 해보겠다.&lt;/p>
&lt;h2 id="http-status-code-그룹정의">Http Status Code 그룹정의&lt;/h2>
&lt;p>Spring-project github에 가보면 HttpStatus 하위에 내부 Enum으로 아래처럼 정의되어 있다. ( &lt;a href="https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-web/src/main/java/org/springframework/http/HttpStatus.java#L505" target="_blank" rel="noopener noreffer ">링크&lt;/a> )&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">enum&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpStatus&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Series&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">series&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Series&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">valueOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">enum&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Series&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">INFORMATIONAL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">SUCCESSFUL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">2&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">REDIRECTION&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">3&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">CLIENT_ERROR&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">SERVER_ERROR&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">5&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Series&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">valueOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">status&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">seriesCode&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">status&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">100&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Series&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">series&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">values&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">series&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">value&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">seriesCode&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">					&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">series&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">				&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">throw&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IllegalArgumentException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;No matching constant for [&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">status&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;]&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">	
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>약 80여개 응답코드들을 크게 5개 묶음으로 정의해 놓은것을 볼수있다. 즉, 201 / 201 / 202 같은 녀석들은 전부 &lt;code>SUCCESSFUL&lt;/code> 성공 으로 그룹핑이 되는것을 확인할수 있다.&lt;/p>
&lt;h2 id="그럼-resttemplate-에서는-어떻게-처리하고-있나">그럼 RestTemplate 에서는 어떻게 처리하고 있나?&lt;/h2>
&lt;p>코드를 쭉쭉 따라가다 보면 아래처럼 4xx, 5xx 는 에러라고 판단하고 그에 따라 RestClientException 을 반환하는것을 확인할수 있다.&lt;/p>
&lt;ul>
&lt;li>RestTemplate 호출 &lt;a href="https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java#L649" target="_blank" rel="noopener noreffer ">링크&lt;/a>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">ClientHttpRequest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">createRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">requestCallback&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">requestCallback&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">doWithRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">execute&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">handleResponse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">responseExtractor&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">responseExtractor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">extractData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">else&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IOException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ex&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>에러 판별 &lt;a href="https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java" target="_blank" rel="noopener noreffer ">링크&lt;/a>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">protected&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">boolean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">hasError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HttpStatus&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">statusCode&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">statusCode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">series&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpStatus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">Series&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">CLIENT_ERROR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">statusCode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">series&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HttpStatus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">Series&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">SERVER_ERROR&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="성공실패-처리는-어떻게-할것인가">성공/실패 처리는 어떻게 할것인가&lt;/h2>
&lt;p>각자 정의하기 나름일것 같다. 우선 아주 간단하게 RestTemplate 를 사용할때 예외처리를 하여 정의된 대로 4xx, 5xx가 에러라고 판단할 수 있을것 같고&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">responseBody&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">restTemplate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">postForObject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">httpEntity&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">RestClientException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 에러인 경우 RestClientException 을 내뱉는다.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;##### restTemplate error, url = {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>정의된 에러(?)와는 조금 다르게 처리하고 싶다면 &lt;code>DefaultResponseErrorHandler&lt;/code>을 상속받고 &lt;code>hasError&lt;/code>메소드를 무조건 패스하도록 Override 하고난 다음 응답 코드를 받아서 처리하는 방법이 있을수 있겠다.
(물론 이러한 방법 말고 다양한 방법이 있을것 같다.)&lt;/p></description></item><item><title>소나큐브 이용 코드 정적분석 자동화</title><link>https://taetaetae.github.io/2018/02/08/jenkins-sonar-github-integration/</link><pubDate>Thu, 08 Feb 2018 20:10:54 +0000</pubDate><guid>https://taetaetae.github.io/2018/02/08/jenkins-sonar-github-integration/</guid><description>&lt;p>&lt;code>코드 정적분석&lt;/code>이라 함은 실제 프로그램을 실행하지 않고 코드만의 형태에 대한 분석을 말한다. 이를테면 냄새나는 코드(?)라던지, 위험성이 있는 코드, 미리 정의된 규칙이나 코딩 표준을 준수하는지에 대한 분석을 말하는데 java 기준으로는 아래 다양한 (잘 알려진) 정적분석 도구들이 있다.&lt;!-- more -->&lt;/p>
&lt;ul>
&lt;li>PMD
&lt;ul>
&lt;li>미사용 변수, 비어있는 코드 블락, 불필요한 오브젝트 생성과 같은 Defect을 유발할 수 있는 코드를 검사&lt;/li>
&lt;li>&lt;a href="https://pmd.github.io" target="_blank" rel="noopener noreffer ">https://pmd.github.io&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>FindBugs
&lt;ul>
&lt;li>정해진 규칙에 의해 잠재적인 에러 타입을 찾아줌&lt;/li>
&lt;li>&lt;a href="http://findbugs.sourceforge.net" target="_blank" rel="noopener noreffer ">http://findbugs.sourceforge.net&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>CheckStyle
&lt;ul>
&lt;li>정해진 코딩 룰을 잘 따르고 있는지에 대한 분석&lt;/li>
&lt;li>&lt;a href="http://checkstyle.sourceforge.net" target="_blank" rel="noopener noreffer ">http://checkstyle.sourceforge.net&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>이외에 &lt;code>SonarQube&lt;/code> 라는 툴이 있는데 개인적으로 위 알려진 다른 툴들의 종합판(?)이라고 생각이 들었고, 그중 가장 인상깊었던 기능이 github과 연동이 되고 적절한 구성을 하게 되면 코드를 수정하는과 동시에 자동으로 분석을 하고 리포팅까지 해준다는 부분이였다. ( &lt;del>더 좋은 방법이 있는지는 모르겠으나&lt;/del> 다른 도구들은 수동으로 돌려줘야 하고 리포팅 또한 Active하지 못한(?) 아쉬운 점이 있었다. )&lt;/p>
&lt;p>지금부터 Jenkins + github web-hook + SonarQube 를 구성하여 코드를 수정하고 PullRequest를 올리게 되면 수정한 파일에 대해 자동으로 정적분석이 이뤄지고, 그에대한 리포팅이 해당 PullRequest에 댓글로 달리도록 설정을 해보겠다. (코드리뷰를 봇(?)이 자동으로 해주는게 얼마나 편한 일인가&amp;hellip;)&lt;/p>
&lt;h2 id="기본-컨셉">기본 컨셉&lt;/h2>
&lt;p>전체적인 컨셉은 다음 그림과 같다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png" title="/images/jenkins-sonar-github-integration/concept.png" data-thumbnail="/images/jenkins-sonar-github-integration/concept.png" data-sub-html="&lt;h2>전체 컨셉&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png"
 data-srcset="https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png, https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png 1.5x, https://taetaetae.github.io/images/jenkins-sonar-github-integration/concept.png 2x"
 data-sizes="auto"
 alt="/images/jenkins-sonar-github-integration/concept.png" />
 &lt;/a>&lt;figcaption class="image-caption">전체 컨셉&lt;/figcaption>
 &lt;/figure>
&lt;ol>
&lt;li>IDE에서 코드수정을 하고 remote 저장소에 commit &amp;amp; push를 한다.
그 다음 github에서 master(혹은 stable한 branch)에 대해 작업 branch를 PullRequest 올린다.&lt;/li>
&lt;li>미리 등록한 github의 web-hook에 의해 PullRequest 정보들을 jenkins에 전송한다.&lt;/li>
&lt;li>전달받은 정보를 재 가공하여 SonarQube로 정적분석을 요청한다.&lt;/li>
&lt;li>SonarQube에서 분석한 정보를 다시 jenkins로 return 해준다.&lt;/li>
&lt;li>SonarQube으로부터 return 받은 정보를 해당 PullRequest의 댓글에 리포팅을 해준다.&lt;/li>
&lt;/ol>
&lt;p>간단히 보면 (뭐 간단하니 쉽네~) 라고 볼수도 있겠지만 나는 이런 전체 흐름을 설정하는데 있어 어려웠다.&lt;/p>
&lt;blockquote>
&lt;p>사실 셋팅하는 과정에서 적지않은 삽질을 했었기에, 이 포스팅을 적는 이유일수도 있겠다.
더불어 검색을 해봐도 이렇게 전체흐름이 정리된 글이 잘 안보여서 + 내가 한 삽질을 다른 누군가도 할것같아서(?)&lt;/p>&lt;/blockquote>
&lt;h2 id="maven-설치">Maven 설치&lt;/h2>
&lt;p>기본적으로 Maven의 H2DB를 사용하므로 SonarQube를 설치하기전에 Maven부터 설치해줘야 한다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget http://apache.mirror.cdnetworks.com/maven/maven-3/3.5.2/binaries/apache-maven-3.5.2-bin.tar.gz
$ tar -zxvf apache-maven-3.5.2-bin.tar.gz
(환경변수 셋팅후 )
$ mvn -version
Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T16:58:13+09:00)
...
&lt;/code>&lt;/pre>&lt;h2 id="sonarqube-설치">SonarQube 설치&lt;/h2>
&lt;p>정적분석을 도와주는 SonarQube를 설치해보자.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget https://sonarsource.bintray.com/Distribution/sonarqube/sonarqube-6.7.1.zip
$ unzip sonarqube-6.7.1.zip
$ cd sonarqube-6.7.1/bin/linux-x86-64
$ ./sonar.sh start
Starting SonarQube...
Started SonarQube.
&lt;/code>&lt;/pre>&lt;p>기본적으로 9000포트를 사용하고 있으니 다른포트를 사용하고자 한다면 /sonarqube-6.7.1/conf/sonar.properties 내 &lt;code>sonar.web.port=9000&lt;/code> 을 수정해주면 된다. (SonarQube도 Elasticsearch를 사용하구나&amp;hellip;)
설치후 실행을 한뒤 &lt;code>서버IP:9000&lt;/code>을 접속해보면 아래 화면처럼 나온다. (혹시 접속이 안된다거나 서버가 실행이 안된다면 &lt;code>./sonar.sh console&lt;/code>로 로그를 보면 문제해결에 도움이 될수도 있다. )&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png" title="/images/jenkins-sonar-github-integration/sonar_main.png" data-thumbnail="/images/jenkins-sonar-github-integration/sonar_main.png" data-sub-html="&lt;h2>SonarQube 메인화면&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png"
 data-srcset="https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png, https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png 1.5x, https://taetaetae.github.io/images/jenkins-sonar-github-integration/sonar_main.png 2x"
 data-sizes="auto"
 alt="/images/jenkins-sonar-github-integration/sonar_main.png" />
 &lt;/a>&lt;figcaption class="image-caption">SonarQube 메인화면&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="sonarqube-scanner-설치">SonarQube Scanner 설치&lt;/h2>
&lt;p>소스를 연동시켜 정적분석을 하기 위해서는 SonarQube Scanner 라는게 필요하다고 한다. 아래 url에서 다운받아 적절한 곳에 압축을 풀어두자.
&lt;a href="https://docs.sonarqube.org/display/SCAN/Analyzing&amp;#43;with&amp;#43;SonarQube&amp;#43;Scanner" target="_blank" rel="noopener noreffer ">https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner&lt;/a>&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.0.3.778-linux.zip
$ unzip sonar-scanner-cli-3.0.3.778-linux.zip
&lt;/code>&lt;/pre>&lt;h3 id="-jenkins-설치-및-sonarqube-연동"># jenkins 설치 및 SonarQube 연동&lt;/h3>
&lt;p>jenkins 설치는 간단하니 별도 언급은 안하고 넘어가&amp;hellip;려고 했으나, 하나부터 열까지 정리한다는 마음으로~
&lt;a href="https://jenkins.io/download/" target="_blank" rel="noopener noreffer ">https://jenkins.io/download/&lt;/a> 에서 최신버전을 tomcat/webapps/ 아래에 다운받고 server.xml 을 적절하게 수정해준다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
$ vi tomcat/conf/server.xml
&amp;lt;Connector port=&amp;#34;19001&amp;#34; protocol=&amp;#34;HTTP/1.1&amp;#34; # 포트 변경
&amp;lt;Context path=&amp;#34;/jenkins&amp;#34; debug=&amp;#34;0&amp;#34; privileged=&amp;#34;true&amp;#34; docBase=&amp;#34;jenkins.war&amp;#34; /&amp;gt; #추가
# tomcat/bin/startup.sh
&lt;/code>&lt;/pre>&lt;p>jenkins 설치를 완료 한 후 필요한 플러그인을 추가로 설치해준다.&lt;/p>
&lt;ul>
&lt;li>Python Plugin&lt;/li>
&lt;li>GitHub Pull Request Builder&lt;/li>
&lt;li>GitHub plugin&lt;/li>
&lt;/ul>
&lt;p>접속 : &lt;code>서버IP:19001&lt;/code> (참고로 한 서버에서 다 설치하다보니 port 충돌을 신경쓰게되었다. )
처음 jenkins를 실행하면 이런저런 설정을 하는데 특별한 설정 변경없이 next버튼을 연신 눌러면 설치가 완료 되고, SonarQube를 사용하기 위해 &lt;code>SonarQube Scanner for Jenkins&lt;/code>라는 플러그인을 설치해주자. (이건 각 버전마다 궁합(?)이 안맞을수도 있으니 확인이 필요할수도 있다. 내가 설치한 버전은 jenkins 2.89, SonarQube Plugin 2.6.1이다.)
설치를 하면 jenkins &amp;gt; configure 에서 &lt;code>SonarQube servers&lt;/code>정보를 등록해준다.&lt;/p></description></item><item><title>Github의 WebHook을 이용하여 자동 Jenkins Job 실행</title><link>https://taetaetae.github.io/2018/02/08/github-web-hook-jenkins-job-excute/</link><pubDate>Thu, 08 Feb 2018 17:10:54 +0000</pubDate><guid>https://taetaetae.github.io/2018/02/08/github-web-hook-jenkins-job-excute/</guid><description>&lt;p>PullRequest가 발생하면 알림을 받고싶다거나, 내가 관리하는 레파지토리에 댓글이 달릴때마다 또는 이슈가 생성될때마다 정보를 저장하고 싶다거나. 종합해보면 &lt;code>Github에서 이벤트가 발생할때 어떤 동작을 해야 할 경우&lt;/code> Github에서 제공하는 Webhook 을 사용하여 목적을 달성할 수 있다.
아 당연한 이야기이지만 언급하고 넘어갈께 있다면, Github에서 Jenkins Job을 호출하기 위해서는 Jenkins가 외부에 공개되어 있어야 한다. (내부사설망이나 private 한 설정이 되어있다면 호출이 안되어 Webhook기능을 사용할 수 없다.)&lt;/p>
&lt;h2 id="jenkins-security-설정">Jenkins Security 설정&lt;/h2>
&lt;p>Jenkins Job을 외부에서 URL로 실행을 하기 위해서는 아래 설정이 꼭 필요하다. (이 설정을 몰라서 수많은 삽질을 했다.)
CSRF Protection 설정 체크를 풀어줘야 한다. 이렇게 되면 외부에서 Job에 대한 트리거링이 가능해 진다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png" title="/images/github-web-hook-jenkins-job-excute/jenkins_config.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/jenkins_config.png" data-sub-html="&lt;h2>Jenkins &amp;gt; Configure Global Security&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_config.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/jenkins_config.png" />
 &lt;/a>&lt;figcaption class="image-caption">Jenkins &amp;gt; Configure Global Security&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="jenkins-job-설정">Jenkins Job 설정&lt;/h2>
&lt;p>Github 에서 Webhook에 의해 Jenkins Job을 실행하게 될텐데, 그때 정보들이 &lt;code>payload&lt;/code>라는 파라미터와 함께 &lt;code>POST&lt;/code> 형식으로 호출이 되기 때문에 미리 Job에서 받는 준비(?)를 해둬야 한다.
설정은 간단하게 다음과 같이 Job 파라미터 설정을 해주면 된다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" title="/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" data-sub-html="&lt;h2>Jenkins &amp;gt; 해당 Job &amp;gt; configure&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/jenkins_job_param.png" />
 &lt;/a>&lt;figcaption class="image-caption">Jenkins &amp;gt; 해당 Job &amp;gt; configure&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="github-webhook-설정">Github Webhook 설정&lt;/h2>
&lt;p>이제 Github Repository 의 Hook 설정만 하면 끝이난다. 해당 Repository &amp;gt; Settings &amp;gt; Hooks 설정에 들어가서 &lt;code>Add webhook&lt;/code>을 선택하여 Webhook을 등록해준다.
URL은 &lt;code>{jenkins URL}/jenkins/job/{job name}/buildWithParameters&lt;/code>식으로 설정해주고 Content Type 은 &lt;code>application/x-www-form-urlencoded&lt;/code>으로 선택한다. 언제 Webhook을 트리거링 시킬꺼냐는 옵션에서는 원하는 설정에 맞추면 되겠지만 나는 &lt;code>pullRequest&lt;/code>가 등록 될때만 미리 만들어 놓은 Jenkins Job을 실행시킬 계획이였으니 &lt;code>Let me select individual events.&lt;/code>을 설정하고 &lt;code>Pull Request&lt;/code>에 체크를 해준다. 아래 그림처럼 말이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png" title="/images/github-web-hook-jenkins-job-excute/oss_webhook.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/oss_webhook.png" data-sub-html="&lt;h2>해당 Repositroy &amp;gt; Settings &amp;gt; Hooks&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/oss_webhook.png" />
 &lt;/a>&lt;figcaption class="image-caption">해당 Repositroy &amp;gt; Settings &amp;gt; Hooks&lt;/figcaption>
 &lt;/figure>
&lt;p>이렇게 등록하고 다시 들어가서 맨 아랫 부분&lt;code>Recent Deliveries&lt;/code>을 보면 ping test 가 이루어져 정상적으로 응답을 받은것을 확인할수가 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" title="/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" data-sub-html="&lt;h2>Webhook 등록 결과&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/oss_webhook_result.png" />
 &lt;/a>&lt;figcaption class="image-caption">Webhook 등록 결과&lt;/figcaption>
 &lt;/figure>
&lt;p>이렇게 설정을 다 한 뒤 PullRequest를 발생시키면 Jenkins 해당 Job에서는 파라미터를 받으며 실행이 된것을 확인할수가 있다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" title="/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" data-thumbnail="/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" data-sub-html="&lt;h2>Jenkins Job 실행 결과&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png"
 data-srcset="https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png 1.5x, https://taetaetae.github.io/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png 2x"
 data-sizes="auto"
 alt="/images/github-web-hook-jenkins-job-excute/jenkins_webhook_result.png" />
 &lt;/a>&lt;figcaption class="image-caption">Jenkins Job 실행 결과&lt;/figcaption>
 &lt;/figure>
&lt;p>끝~&lt;/p></description></item><item><title>Github과 Jenkins 연동하기</title><link>https://taetaetae.github.io/2018/02/08/github-with-jenkins/</link><pubDate>Thu, 08 Feb 2018 17:10:21 +0000</pubDate><guid>https://taetaetae.github.io/2018/02/08/github-with-jenkins/</guid><description>&lt;p>Jenkins에서 Github의 소스를 가져와서 빌드를 하는 등 Github과 Jenkins와 연동을 시켜줘야하는 상황에서, 별도의 선행 작업이 필요하다. 다른 여러 방법이 있을수 있는데 여기서는 SSH로 연동하는 방법을 알아보고자 한다.&lt;!-- more -->&lt;/p>
&lt;p>우선 Jenkins가 설치되어있는 서버에서 인증키를 생성하자.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ ssh-keygen -t rsa -f id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa.
Your public key has been saved in id_rsa.pub.
The key fingerprint is:
SHA256:~~~~~ ~~~@~~~~~
The key&amp;#39;s randomart image is:
+---[RSA 2048]----+
| o*+**=*=**+ |
| o B=o+o++o |
| E+.o+ + oo .|
| oo. * o ...|
| .+ S = o |
| . + o . |
| . . . |
| . |
| |
+----[SHA256]-----+

$ ls
id_rsa id_rsa.pub
&lt;/code>&lt;/pre>&lt;p>개인키(id_rsa)는 젠킨스에 설정해준다. (처음부터 끝까지 복사, 첫줄 마지막줄 빼면 안된다&amp;hellip; )&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png" title="/images/github-with-jenkins/ssh_jenkins.png" data-thumbnail="/images/github-with-jenkins/ssh_jenkins.png" data-sub-html="&lt;h2>젠킨스에 SSH 개인키 설정&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png"
 data-srcset="https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png, https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png 1.5x, https://taetaetae.github.io/images/github-with-jenkins/ssh_jenkins.png 2x"
 data-sizes="auto"
 alt="/images/github-with-jenkins/ssh_jenkins.png" />
 &lt;/a>&lt;figcaption class="image-caption">젠킨스에 SSH 개인키 설정&lt;/figcaption>
 &lt;/figure>
&lt;p>그 다음 공개키(id_rsa.pub)는 Github에 설정을 해준다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png" title="/images/github-with-jenkins/ssh_github.png" data-thumbnail="/images/github-with-jenkins/ssh_github.png" data-sub-html="&lt;h2>Github에 SSH 공개키 설정&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png"
 data-srcset="https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png, https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png 1.5x, https://taetaetae.github.io/images/github-with-jenkins/ssh_github.png 2x"
 data-sizes="auto"
 alt="/images/github-with-jenkins/ssh_github.png" />
 &lt;/a>&lt;figcaption class="image-caption">Github에 SSH 공개키 설정&lt;/figcaption>
 &lt;/figure>
&lt;p>이렇게 한뒤 Jenkins 에서 임의로 job을 생성하고 job 설정 &amp;gt; 소스코드 관리 에서 git 부분에 아래처럼 테스트를 해서 정상적으로 연동이 된것을 확인한다. &lt;code>Credentials&lt;/code> 값을 위에서 설정한 개인키로 설정하고, repo 주소를 SSH용으로 적었을때 에러가 안나오면 성공한것이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png" title="/images/github-with-jenkins/ssh_complete.png" data-thumbnail="/images/github-with-jenkins/ssh_complete.png" data-sub-html="&lt;h2>정상 연결되면 Jenkins 오류도 없고, github SSH 키에 녹색불이 들어온다.&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png"
 data-srcset="https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png, https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png 1.5x, https://taetaetae.github.io/images/github-with-jenkins/ssh_complete.png 2x"
 data-sizes="auto"
 alt="/images/github-with-jenkins/ssh_complete.png" />
 &lt;/a>&lt;figcaption class="image-caption">정상 연결되면 Jenkins 오류도 없고, github SSH 키에 녹색불이 들어온다.&lt;/figcaption>
 &lt;/figure>
&lt;p>끝~&lt;/p></description></item><item><title>linux(centOS)에서 selenium 설정하기 (feat. python)</title><link>https://taetaetae.github.io/2018/02/01/linux-selenium/</link><pubDate>Thu, 01 Feb 2018 14:52:10 +0000</pubDate><guid>https://taetaetae.github.io/2018/02/01/linux-selenium/</guid><description>&lt;p>테스트 코드로 안되는 실제 브라우저단 사용성 테스트를 하고싶은 경우가 있다. 이를테면 화면이 뜨고, 어떤 버튼을 누르면, 어떤 결과가 나와야 하는 일련의 &lt;code>Regression Test&lt;/code>. 이때 활용할수 있는게 다양한 도구가 있지만 이번엔 &lt;code>selenium&lt;/code> 에 대해서 알아보고자 한다.&lt;!-- more -->&lt;/p>
&lt;p>처음부터 사실 web application 테스트를 하려고 selenium 를 알아보게 된건 아니고, 내가 참여하고 있는 특정 밴드(네이버 BAND)에서 일주일에 한번씩 동일한 형태의 글을 올리고 있는데 (일종의 한주 출석체크 같은&amp;hellip;) 이를 자동화 해볼순 없을까 하며 밴드 API를 찾아보다 selenium 라는것을 알게되었고, 매크로처럼 어떤버튼 누르고 그다음 어떤버튼 누르고 하는 일련의 과정을 코드로 구성할수 있다는 점에 감동을 받아(?) + 별도의 API를 발급받지 않아도 되어 사용하게 되었다. (물론 UI가 바뀌면 골치아프겠지만&amp;hellip;)&lt;/p>
&lt;blockquote>
&lt;p>여기서는 selenium 이 무엇인지에 대한 설명은 하지 않는다. (인터넷에 나보다 정리 잘된글이 많으니&amp;hellip;) 단, linux 환경에서 셋팅하는 정보가 너무 없고 몇일동안 삽질을 한게 아쉬워서 그 과정을 포스팅 해본다. (나같은 분이 이 글을 보고 도움이 되실꺼라는 기대를 갖으며&amp;hellip;)&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>※ 주의 : 본 포스팅은 밴드 서비스에 글을 올릴수 있는 비 정상적인 방법의 공유가 아닌, selenium에 대한 사용 후기(?)에 대한 글입니다. (참고로 막혔어요 -ㅁ-)&lt;/p>&lt;/blockquote>
&lt;h2 id="설정하기">설정하기&lt;/h2>
&lt;p>서버 환경은 CentOS 7.4 64Bit + Python 3.6.3 + jdk 8 이다. 우선 selenium 을 설치해준다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo python3.6 -m pip install selenium
&lt;/code>&lt;/pre>&lt;p>그 다음 CentOS에서 크롬브라우저를 설치하기 위하여 yum 저장소를 추가한다. (꼭 크롬이 아니더라도 파이어폭스나 지금은 지원이 끊긴 팬텀JS 같은것으로 활용할수도 있으나 다른것들도 해봤는데 자꾸 설정에서 걸려서 크롬에 대한 내용을 포스팅 한다.)&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo vi /etc/yum.repos.d/google-chrome.repo
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/x86_64
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
&lt;/code>&lt;/pre>&lt;p>그리고는 yum 으로 크롬 브라우저를 설치한다. (내가 설치했을때의 버전은 &lt;code>google-chrome-stable.x86_64 0:64.0.3282.119-1&lt;/code>)&lt;/p>
&lt;pre tabindex="0">&lt;code>$ yum install google-chrome-stable
&lt;/code>&lt;/pre>&lt;p>크롬드라이버를 설치해야한다. 다음 url에서 받을수 있는데 &lt;a href="https://sites.google.com/a/chromium.org/chromedriver/downloads" target="_blank" rel="noopener noreffer ">https://sites.google.com/a/chromium.org/chromedriver/downloads&lt;/a> 나는 2.35 linux64 버전을 받았다. 다운받고 unzip 하면 딱하나 파일이 있는데 나중에 selenium 을 사용할때 이용되니 path를 알아두자.&lt;/p>
&lt;p>그다음 파이썬 코드를 작성한다. 내가 짠 파이썬 코드는 다음과 같은 순서로 실행이 된다.&lt;/p>
&lt;ul>
&lt;li>밴드 접속 ( &lt;a href="https://band.us/home" target="_blank" rel="noopener noreffer ">https://band.us/home&lt;/a> )&lt;/li>
&lt;li>로그인&lt;/li>
&lt;li>특정 밴드 선택&lt;/li>
&lt;li>글쓰기 버튼 누르고 양식에 맞춰 글 작성&lt;/li>
&lt;li>글 등록&lt;/li>
&lt;/ul>
&lt;p>파이썬 코드는 아래처럼 작성하였다. (중요부분만.. 그 아래는 자유)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">selenium&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">webdriver&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">selenium.webdriver.chrome.options&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Options&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 초기화 --------------------------------------------&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chrome_options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Options&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chrome_options&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_argument&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--headless&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">webdriver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Chrome&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">executable_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;home/~~~/chromedriver&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chrome_options&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">chrome_options&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">implicitly_wait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;https://band.us/home&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 로그인 --------------------------------------------&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_element_by_class_name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;_loginLink&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">click&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">...&lt;/span>&lt;span class="n">생략&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 실행을 해보면 작동이 잘~ 된다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/linux-selenium/result.png" title="/images/linux-selenium/result.png" data-thumbnail="/images/linux-selenium/result.png" data-sub-html="&lt;h2>selenium &amp;#43; python 으로 자동작성된 밴드 글&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/linux-selenium/result.png"
 data-srcset="https://taetaetae.github.io/images/linux-selenium/result.png, https://taetaetae.github.io/images/linux-selenium/result.png 1.5x, https://taetaetae.github.io/images/linux-selenium/result.png 2x"
 data-sizes="auto"
 alt="/images/linux-selenium/result.png" />
 &lt;/a>&lt;figcaption class="image-caption">selenium + python 으로 자동작성된 밴드 글&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>&lt;code>selenium&lt;/code> 에 대해 찾아보면 거의 윈도우 환경에서 돌아가는것들에 대한 포스팅이 많았다. 난 리눅스 환경에서 스케쥴러(젠킨스 같은)를 통해 자동으로 화면없이 작동시키고 싶었는데 아무리 찾아봐도 + 삽질해도 잘 안되었다. 결국 사내에도 나같은 삽질을 하신 분을 찾고 묻고 물어 &lt;code>크롬드라이버&lt;/code>만 있어야 하는것이 아니라 &lt;code>크롬앱&lt;/code>또한 있어야 동작을 한다는것을 알게 되었다.
&lt;strong>역시, 내가 한 삽질은 누군가 이미 한 삽질이라는걸 다시한번 깨닳은 좋은(?) 시간이였다.&lt;/strong>&lt;/p>
&lt;p>이걸로 나중에 내가 맡고있는 서비스에 대한 웹 자동 테스트 툴도 만들어 볼 생각이다.&lt;/p></description></item><item><title>아파치 엑세스 로그를 엘라스틱서치에 인덱싱 해보자.</title><link>https://taetaetae.github.io/2018/01/25/apache-access-log-to-es/</link><pubDate>Thu, 25 Jan 2018 21:18:35 +0000</pubDate><guid>https://taetaetae.github.io/2018/01/25/apache-access-log-to-es/</guid><description>&lt;p>apache access log 를 분석하고 싶은 상황이 생겼다. 아니 그보다 apache access에 대해서 실시간으로 보고싶었고, log를 검색 &amp;amp; 데이터를 가공하여 유의미한 분석결과를 만들어 보고 싶었다. 그에 생각한것이 (역시) &lt;code>ElasticStack&lt;/code>.&lt;!-- more -->&lt;/p>
&lt;p>처음에 생각한 방안은 아래 그림처럼 단순했다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-access-log-to-es/model_1.png" title="/images/apache-access-log-to-es/model_1.png" data-thumbnail="/images/apache-access-log-to-es/model_1.png" data-sub-html="&lt;h2>처음 생각한 단순한 구조&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-access-log-to-es/model_1.png"
 data-srcset="https://taetaetae.github.io/images/apache-access-log-to-es/model_1.png, https://taetaetae.github.io/images/apache-access-log-to-es/model_1.png 1.5x, https://taetaetae.github.io/images/apache-access-log-to-es/model_1.png 2x"
 data-sizes="auto"
 alt="/images/apache-access-log-to-es/model_1.png" />
 &lt;/a>&lt;figcaption class="image-caption">처음 생각한 단순한 구조&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;p>하지만, 내 단순한(?) 예상은 역시 빗나갔고 logstash에서는 다음과 같은 에러를 내뱉었다.&lt;/p>
&lt;blockquote>
&lt;p>retrying individual bulk actions that failed or were rejected by the previous bulk request&lt;/p>&lt;/blockquote>
&lt;p>request가 많아짐에 따라 elasticsearch가 버벅거리더니 logstash에서 대량작업은 거부하겠다며 인덱싱을 멈췄다. 고민고민하다 elasticsearch에 인덱싱할때 부하가 많이 걸리는 상황에서 중간에 버퍼를 둔 경험이 있어서 facebook그룹에 문의를 해봤다.
&lt;a href="https://www.facebook.com/groups/elasticsearch.kr/?multi_permalinks=1566735266745641" target="_blank" rel="noopener noreffer ">https://www.facebook.com/groups/elasticsearch.kr/?multi_permalinks=1566735266745641&lt;/a>
역시 나보다 한참을 앞서가시는 분들은 이미 에러가 뭔지 알고 있으셨고, 중간에 버퍼를 두고 하니 잘된다는 의견이 있어 나도 따라해봤다. 물론 답변중에 나온 redis가 아닌 기존에도 비슷한 구조에서 사용하고 있던 kafka를 적용.
아, 그전에 현재구성은 Elasticsearch 노드가 총 3대로 클러스터 구조로 되어있는데 노드를 추가로 늘리며 스케일 아웃을 해보기전에 할수있는 마지막 방법이다 생각하고 중간에 kafka를 둬서 부하를 줄여보고 싶었다. (언제부턴가 마치 여러개의 톱니바퀴가 맞물려 돌아가는듯한 시스템 설계를 하는게 재밌었다.) 아래 그림처럼 말이다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-access-log-to-es/model_2.png" title="/images/apache-access-log-to-es/model_2.png" data-thumbnail="/images/apache-access-log-to-es/model_2.png" data-sub-html="&lt;h2>그나마 좀더 생각한 구조&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-access-log-to-es/model_2.png"
 data-srcset="https://taetaetae.github.io/images/apache-access-log-to-es/model_2.png, https://taetaetae.github.io/images/apache-access-log-to-es/model_2.png 1.5x, https://taetaetae.github.io/images/apache-access-log-to-es/model_2.png 2x"
 data-sizes="auto"
 alt="/images/apache-access-log-to-es/model_2.png" />
 &lt;/a>&lt;figcaption class="image-caption">그나마 좀더 생각한 구조&lt;/figcaption>
 &lt;/figure>
&lt;p>그랬더니 거짓말 처럼 에러하나 없이 잘 인덱싱이 될수 있었다. logstash가 양쪽에 있는게 약간 걸리긴 하지만, 처음에 생각한 구조보다는 에러가 안나니 다행이라 생각한다.&lt;/p>
&lt;p>이 구조를 적용하면서 얻은 Insight가 있기에, 각 항목별로 적어 보고자 한다. ( &lt;del>이것만 적어놓기엔 너무 없어보여서..&lt;/del> )&lt;/p>
&lt;h2 id="access-log-를-어떻게-분석하여-인덱싱-할것인가">access log 를 어떻게 분석하여 인덱싱 할것인가?&lt;/h2>
&lt;p>apache 2.x를 사용하고 별도의 로그 포맷을 정하지 않으면 아래와 같은 access log가 찍힌다.
&lt;code>123.1.1.1 - - [25/Jan/2018:21:55:35 +0900] &amp;quot;GET /api/test?param=12341234 HTTP/1.1&amp;quot; 200 48 1144 &amp;quot;http://www.naver.com/&amp;quot; &amp;quot;Mozilla/5.0 (iPhone; CPU iPhone OS 11_1_2 like Mac OS X) AppleWebKit/604.3.5 (KHTML, like Gecko) Mobile/15B202 NAVER(inapp; blog; 100; 4.0.44)&amp;quot;&lt;/code>
그럼 이 로그를 아무 포맷팅 없이 로깅을 하면 그냥 한줄의 텍스트가 인덱싱이 된다. 하지만 이렇게 되면 elasticsearch 데이터를 다시 재가공하거나 별도의 작업이 필요할수도 있으니 중간에 있는 logstash에게 일을 시켜 좀더 nice 한 방법으로 인덱싱을 해보자. 바로 logstash 의 filter 기능이다. 그중 Grok filter 라는게 있는데 패턴을 적용하여 row data 를 필터링하는 기능이다. 조금 찾아보니 너무 고맙게도 아파치 필터 예제가 있어 수정하여 적용할수 있었다. &lt;a href="http://grokconstructor.appspot.com/do/match?example=2" target="_blank" rel="noopener noreffer ">http://grokconstructor.appspot.com/do/match?example=2&lt;/a>
그래서 적용한 필터설정은 다음과 같다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">filter &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> grok &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">match&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">message&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s2">&amp;#34;%{IP:clientIp} (?:-|) (?:-|) \[%{HTTPDATE:timestamp}\] \&amp;#34;(?:%{WORD:httpMethod} %{URIPATH:uri}%{GREEDYDATA}(?: HTTP/%{NUMBER})?|-)\&amp;#34; %{NUMBER:responseCode} (?:-|%{NUMBER})&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하고 elasticsearch 에 인덱싱을 하면 키바나에서 다음과 같이 볼수 있다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/apache-access-log-to-es/access_log_kibana.png" title="/images/apache-access-log-to-es/access_log_kibana.png" data-thumbnail="/images/apache-access-log-to-es/access_log_kibana.png" data-sub-html="&lt;h2>키바나에 내가 원하는 구조대로 이쁘게 들어가 있는 access log&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/apache-access-log-to-es/access_log_kibana.png"
 data-srcset="https://taetaetae.github.io/images/apache-access-log-to-es/access_log_kibana.png, https://taetaetae.github.io/images/apache-access-log-to-es/access_log_kibana.png 1.5x, https://taetaetae.github.io/images/apache-access-log-to-es/access_log_kibana.png 2x"
 data-sizes="auto"
 alt="/images/apache-access-log-to-es/access_log_kibana.png" />
 &lt;/a>&lt;figcaption class="image-caption">키바나에 내가 원하는 구조대로 이쁘게 들어가 있는 access log&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;h2 id="각-필드가-아닌-한줄로-인덱싱이-되어버린다">각 필드가 아닌 한줄로 인덱싱이 되어버린다.&lt;/h2>
&lt;p>Elasticsearch 에 인덱싱이 되긴 하는데 로그 한줄이 통째로 들어가 버린다. &lt;code>message&lt;/code>라는 이름으로&amp;hellip; 알고보니 현재 구조는 logstash가 kafka 앞 뒤에 있다보니 producer logstash 와 consumer logstash 의 &lt;code>codec&lt;/code>이 맞아야 제대로 인덱싱이 될수 있었다.
먼저 access log에서 kafka 로 produce 하는 logstash 에서는 output 할때 codec 을 맞춰주고&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">output &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kafka &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">bootstrap_servers&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s2">&amp;#34;123.1.2.3:9092,123.1.2.4:9092&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">topic_id&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s2">&amp;#34;apache-log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">codec&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; json&lt;span class="o">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>kafka 에서 consume 하는 logstash 에서는 input 에서 codec 을 맞춰준다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">input &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kafka &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">bootstrap_servers&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s2">&amp;#34;123.1.2.3:9092,123.1.2.4:9092&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">topic_id&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="s2">&amp;#34;apache-log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">codec&lt;/span> &lt;span class="o">=&lt;/span>&amp;gt; json&lt;span class="o">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그렇게 되면 codec이 맞아 각 필드로 &lt;code>이쁘게&lt;/code> 인덱싱을 할수 있게 되었다.&lt;/p></description></item><item><title>파이썬 버전 업그레이드 (2.6 > 3.6)</title><link>https://taetaetae.github.io/2018/01/08/python-2-to-3/</link><pubDate>Mon, 08 Jan 2018 13:44:50 +0000</pubDate><guid>https://taetaetae.github.io/2018/01/08/python-2-to-3/</guid><description>&lt;p>파이썬 2.x 에서는 depreate 된 모듈도 많고 3.x에서만 지원되는 버전들이 많아지면서 실컷 개발을 해도 파이썬 버전때문에 다시 짜야하는 상황이 생긴다. 파이썬 버전업을 하고싶어 구글링을 해보면 이렇다할 정리된 문서가 잘 안나온다. (영어로된 포스트는 많이 있긴 하나, 필자의 환경과는 맞지 않는 &amp;hellip;)&lt;!-- more --> 그래서 이것저것 삽질을 한 결과 파이썬 버전을 올릴수 있었고, 이를 포스팅 해보고자 한다.&lt;/p>
&lt;blockquote>
&lt;p>그러보고니 2018년 첫 포스팅이네&amp;hellip; 올해는 정말 적어도 한달에 1~2개는 올릴수 있는 내가 되기를&amp;hellip;&lt;/p>&lt;/blockquote>
&lt;h2 id="환경">환경&lt;/h2>
&lt;ul>
&lt;li>CentOS 6.9&lt;/li>
&lt;li>기본으로 python 2.6 이 설치되어 있는것을 확인할수 있다. (환경마다 다를수 있음.)&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ python -V
Python 2.6
&lt;/code>&lt;/pre>&lt;h2 id="설치순서">설치순서&lt;/h2>
&lt;ul>
&lt;li>필요한 유틸리티를 설치한다.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ sudo yum update
$ sudo yum install yum-utils
$ sudo yum groupinstall development
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>yum 저장소에서는 최신 파이썬 릴리즈를 제공하지 않으므로 RPM 패키를 제공하는 IUM 이라는 추가 저장소를 설치&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ sudo yum install -y https://repo.ius.io/ius-release-el7.rpm
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>파이썬 3.6 버전을 설치&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ sudo yum install python36u
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>pip 등 패키지 관련 모듈도 함께 설치&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>sudo yum install python36u-pip
sudo yum install python36u-devel
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>여기까지 하면 기존 파이썬 &lt;code>2.6&lt;/code>과 새로 설치된 파이썬 &lt;code>3.6&lt;/code> 이 설치되어있다.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ ll /usr/bin/python*
-rwxr-xr-x 1 root root 9997450 Jan 2 16:02 python
lrwxrwxrwx 1 root root 6 Jan 1 06:02 python2 -&amp;gt; python
-rwxr-xr-x 1 root root 9032 Aug 19 2016 python2.6
-rwxr-xr-x 1 root root 1418 Aug 19 2016 python2.6-config
-rwxr-xr-x 2 root root 6808 Oct 12 08:19 python3.6
lrwxrwxrwx 1 root root 26 Jan 2 20:48 python3.6-config -&amp;gt; /usr/bin/python3.6m-config
-rwxr-xr-x 2 root root 6808 Oct 12 08:19 python3.6m
-rwxr-xr-x 1 root root 173 Oct 12 08:19 python3.6m-config
-rwxr-xr-x 1 root root 3339 Oct 12 08:16 python3.6m-x86_64-config
lrwxrwxrwx 1 root root 16 Apr 25 2017 python-config -&amp;gt; python2.6-config
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>환경변수를 설정해준다.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ sudo mv python python_backup
$ sudo ln -s python3.6 python
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>확인&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ python -V
Python 3.6.3
&lt;/code>&lt;/pre>&lt;h2 id="pip-를-이용한-모듈-설치">pip 를 이용한 모듈 설치&lt;/h2>
&lt;ul>
&lt;li>pip란 Python Package Index 의 약자로 공식홈페이지는 다음과 같다. ( &lt;a href="https://pypi.python.org/pypi/pip" target="_blank" rel="noopener noreffer ">https://pypi.python.org/pypi/pip&lt;/a> )&lt;/li>
&lt;li>설치할 모듈을 다음과 같이 설치해주면 된다. ex : requests 모듈인 경우&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ sudo python3.6 -m pip install requests
&lt;/code>&lt;/pre></description></item><item><title>What is Kafka?</title><link>https://taetaetae.github.io/2017/11/02/what-is-kafka/</link><pubDate>Thu, 02 Nov 2017 21:30:13 +0000</pubDate><guid>https://taetaetae.github.io/2017/11/02/what-is-kafka/</guid><description>&lt;p>필자가 맡고있는 서비스에 Elastic Stack 을 도입하면서 중간에 버퍼가 필요하여 Message-Queue 시스템들을 알아보던 중 Kafka 에 대해 알아보고, 정리를 해보게 된다.&lt;/p>
&lt;h2 id="기본설명-및-기존-메세징-시스템과-다른점">기본설명 및 기존 메세징 시스템과 다른점&lt;/h2>
&lt;ul>
&lt;li>메세징 큐의 일종&lt;/li>
&lt;li>말 그대로 &lt;code>분산형 스트리밍 플랫폼&lt;/code>, LinkedIn에서 여러 구직 + 채용 정보들을 한곳에서 처리(발행/구독)할수 있는 플랫폼으로 개발이 시작&lt;/li>
&lt;li>대용량의 실시간 로그 처리에 특화되어 설계된 메시징 시스템, 기존 범용 메시징 시스템대비 TPS가 매우 우수&lt;/li>
&lt;li>메시지를 기본적으로 메모리에 저장하는 기존 메시징 시스템과는 달리 메시지를 파일 시스템에 저장 → 카프카 재시작으로 인한 메세지 유실 우려 감소&lt;/li>
&lt;li>기존의 메시징 시스템에서는 broker가 consumer에게 메시지를 push해 주는 방식인데 반해, Kafka는 consumer가 broker로부터 직접 메시지를 가지고 가는 pull 방식으로 동작하기 때문에 consumer는 자신의 처리능력만큼의 메시지만 broker로부터 가져오기 때문에 최적의 성능을 낼 수 있다.&lt;/li>
&lt;/ul>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/what-is-kafka/kafka2.png" title="/images/what-is-kafka/kafka2.png" data-thumbnail="/images/what-is-kafka/kafka2.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/what-is-kafka/kafka2.png"
 data-srcset="https://taetaetae.github.io/images/what-is-kafka/kafka2.png, https://taetaetae.github.io/images/what-is-kafka/kafka2.png 1.5x, https://taetaetae.github.io/images/what-is-kafka/kafka2.png 2x"
 data-sizes="auto"
 alt="/images/what-is-kafka/kafka2.png" />
 &lt;/a>
&lt;h2 id="카프카-주요-개념">카프카 주요 개념&lt;/h2>
&lt;ul>
&lt;li>producer : 메세지 생산(발행)자.&lt;/li>
&lt;li>consumer : 메세지 소비자
&lt;ul>
&lt;li>consumer group : consumer 들끼리 메세지를 나눠서 가져간다.offset 을 공유하여 중복으로 가져가지 않는다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>broker : 카프카 서버를 가리킴&lt;/li>
&lt;li>zookeeper : 카프카 서버 (+클러스터) 상태를 관리하고&lt;/li>
&lt;li>cluster : 브로커들의 묶음&lt;/li>
&lt;li>topic : 메세지 종류&lt;/li>
&lt;li>partitions : topic 이 나눠지는 단위&lt;/li>
&lt;li>Log : 1개의 메세지&lt;/li>
&lt;li>offset : 파티션 내에서 각 메시지가 가지는 unique id&lt;/li>
&lt;/ul>
&lt;h2 id="카프카는-어떤식으로-돌아가는가">카프카는 어떤식으로 돌아가는가&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>zookeeper 가 kafka 의 상태와 클러스터 관리를 해준다.
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/what-is-kafka/kafka#.png" title="/images/what-is-kafka/kafka3.png" data-thumbnail="/images/what-is-kafka/kafka3.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/what-is-kafka/kafka3.png"
 data-srcset="https://taetaetae.github.io/images/what-is-kafka/kafka3.png, https://taetaetae.github.io/images/what-is-kafka/kafka3.png 1.5x, https://taetaetae.github.io/images/what-is-kafka/kafka#.png 2x"
 data-sizes="auto"
 alt="/images/what-is-kafka/kafka3.png" />
 &lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>정해진 topic 에 producer 가 메세지를 발행해놓으면 consumer 가 필요할때 해당 메세지를 가져간다. (여기서 카프카로 발행된 메세지들은 consumer가 메세지를 소비한다고 해서 없어지는게 아니라 카프카 설정&lt;code> log.retention.hours(default : 168[7일])&lt;/code>에 의해 삭제된다.)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/what-is-kafka/kafka4.png" title="/images/what-is-kafka/kafka4.png" data-thumbnail="/images/what-is-kafka/kafka4.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/what-is-kafka/kafka4.png"
 data-srcset="https://taetaetae.github.io/images/what-is-kafka/kafka4.png, https://taetaetae.github.io/images/what-is-kafka/kafka4.png 1.5x, https://taetaetae.github.io/images/what-is-kafka/kafka4.png 2x"
 data-sizes="auto"
 alt="/images/what-is-kafka/kafka4.png" />
 &lt;/a>
&lt;ul>
&lt;li>partition 개수와 consumer group 개념&lt;/li>
&lt;/ul>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/what-is-kafka/kafka5.png" title="/images/what-is-kafka/kafka5.png" data-thumbnail="/images/what-is-kafka/kafka5.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/what-is-kafka/kafka5.png"
 data-srcset="https://taetaetae.github.io/images/what-is-kafka/kafka5.png, https://taetaetae.github.io/images/what-is-kafka/kafka5.png 1.5x, https://taetaetae.github.io/images/what-is-kafka/kafka5.png 2x"
 data-sizes="auto"
 alt="/images/what-is-kafka/kafka5.png" />
 &lt;/a>
&lt;ul>
&lt;li>하얀색(consumer-01) : 파티션 개수가 4개인데 비해 컨슈머가 3개, 이렇게 되면 어느 컨슈머가 두개의 파티션을 담당해야하는 상황이 생긴다.&lt;/li>
&lt;li>주황색(consumer-02) : 파티션 개수가 4개인데 비해 컨슈머가 5개, 이렇게 되면 하나의 노는(?) 컨슈머가 생기는 상황이 생긴다.&lt;/li>
&lt;li>가장 적절한 개수는 정해지지 않았지만 통상 컨슈머그룹의 컨슈머 개수와 파티션 개수를 동일하게 가져가곤 한다.&lt;/li>
&lt;/ul>
&lt;h2 id="참고-url">참고 url&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://kafka.apache.org/" target="_blank" rel="noopener noreffer ">http://kafka.apache.org/&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.popit.kr/author/peter5236/" target="_blank" rel="noopener noreffer ">http://www.popit.kr/author/peter5236/&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://jinhokwon.tistory.com/168" target="_blank" rel="noopener noreffer ">http://jinhokwon.tistory.com/168&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://programist.tistory.com/entry/Apache-Kafka-" target="_blank" rel="noopener noreffer ">http://programist.tistory.com/entry/Apache-Kafka-&lt;/a>클러스터링-구축-및-테스트&lt;/li>
&lt;li>&lt;a href="https://www.elastic.co/kr/blog/just-enough-kafka-for-the-elastic-stack-part1" target="_blank" rel="noopener noreffer ">https://www.elastic.co/kr/blog/just-enough-kafka-for-the-elastic-stack-part1&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.slideshare.net/springloops/apache-kafka-intro20150313springloops-46067669" target="_blank" rel="noopener noreffer ">https://www.slideshare.net/springloops/apache-kafka-intro20150313springloops-46067669&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Apache keepAlive</title><link>https://taetaetae.github.io/2017/08/28/apache-keep-alive/</link><pubDate>Mon, 28 Aug 2017 19:56:40 +0000</pubDate><guid>https://taetaetae.github.io/2017/08/28/apache-keep-alive/</guid><description>&lt;p>서버를 운영하다보면 간혹 문제가 발생하곤 한다. 이를테면 메모리가 다른이유없이 올라간다거나, 사용자 입장에서 응답속도가 간헐적으로 느린다거나. 그럴때마다 선배개발자분들께서 가장먼저 입에 오르내리는 단어. &lt;code>keepAlive&lt;/code>.&lt;/p></description></item><item><title>hexo 블로그에 tranquilpeak 테마 적용하기</title><link>https://taetaetae.github.io/2017/08/27/hexo-themes-tranquilpeak/</link><pubDate>Sun, 27 Aug 2017 17:52:56 +0000</pubDate><guid>https://taetaetae.github.io/2017/08/27/hexo-themes-tranquilpeak/</guid><description>&lt;p>여러가지 hexo 테마중에 그나마(?) 영어로 된 문서가 있어서 적용해보게 된 tranquilpeak 라는 테마. 오늘은 해당 테마를 적용하면서 겪은 문제, 그리고 적용 방법에 대해서 간략하게나마 정리해보고자 한다. (다른 테마들은 거의다 중국쪽이나 일본&amp;hellip;)&lt;!-- more -->
먼저 hexo 공식사이트에서 알려주는 테마들은 다음 사이트에서 확인해 볼수 있다.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://hexo.io/themes/index.html" target="_blank" rel="noopener noreffer ">https://hexo.io/themes/index.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>기존에는 &lt;code>hueman&lt;/code>이라는 테마를 사용하고 있었는데 (&lt;a href="https://github.com/ppoffice/hexo-theme-hueman" target="_blank" rel="noopener noreffer ">링크&lt;/a>), 오랜만에 블로그를 다시(?) 시작하는 느낌을 내보고 싶었고 보다 더 심플하고 유행에 안탈것 같은(순전히 필자 생각) 테마를 찾아보다 &lt;code>tranquilpeak&lt;/code>이라는 테마를 선택하게 되었다.&lt;/p>
&lt;ul>
&lt;li>공식홈페이지 : &lt;a href="https://github.com/LouisBarranqueiro/hexo-theme-tranquilpeak" target="_blank" rel="noopener noreffer ">https://github.com/LouisBarranqueiro/hexo-theme-tranquilpeak&lt;/a>&lt;/li>
&lt;li>샘플사이트 : &lt;a href="http://louisbarranqueiro.github.io/hexo-theme-tranquilpeak/" target="_blank" rel="noopener noreffer ">http://louisbarranqueiro.github.io/hexo-theme-tranquilpeak/&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>우선 간략하게 설치과정을 나열해보면 다음과 같다.&lt;/p>
&lt;ol>
&lt;li>themes 폴더내에 테마파일을 받은후 압축 해제&lt;/li>
&lt;li>테마 폴더 이름을 변경&lt;/li>
&lt;li>_config.yml 파일 내에 테마 설정 부분 변경 ( theme: tranquilpeak )&lt;/li>
&lt;li>hexo clean → hexo generate → hexo server(or hexo deploy)&lt;/li>
&lt;/ol>
&lt;p>이렇게 하면 아주 간단하게 테마가 변경이 된다. 혹여나(필자처럼) 기존 테마를 커스터마이징 하고 싶을 경우는 별도의 과정이 추가로 필요하다. 기존에는 css나 js만 변경하면 간단히 수정되었는데 이 테마는 약간의 빌드(?)를 필요로 한다. 따라서 css나 js등 html 요소들을 수정하였다면 다음과 같은 과정이 필요하다.(테마폴더 최상위에서)&lt;/p>
&lt;ol>
&lt;li>npm install&lt;/li>
&lt;li>bower install&lt;/li>
&lt;li>css 나 js 변경&lt;/li>
&lt;li>grunt build&lt;/li>
&lt;li>hexo clean → hexo generate → hexo server(or hexo deploy)&lt;/li>
&lt;/ol>
&lt;p>나같은 경우는 테마에 적용된 폰트를 바꾸기 위해 &lt;a href="http://blog.lattecom.xyz/2016/05/08/tranquilpeak-theme-web-font" target="_blank" rel="noopener noreffer ">블로그&lt;/a> 를 참조하였다. (해당 아티클에다 댓글폭탄을 ㅎㅎ;;)&lt;/p></description></item><item><title>mybatis insert/update 쿼리실행후 결과 가져오기</title><link>https://taetaetae.github.io/2017/04/04/mybatis-use-generated-keys/</link><pubDate>Tue, 04 Apr 2017 11:41:28 +0000</pubDate><guid>https://taetaetae.github.io/2017/04/04/mybatis-use-generated-keys/</guid><description>&lt;p>&lt;code>Select&lt;/code>문이 아닌 다른 &lt;code>SQL Query&lt;/code>(insert, update 등) 를 실행하고서 결과를 봐야하는 상황이 생긴다. 정확히 잘 수행되었나에 대한 확인. 어떻게 쿼리가 잘 수행되었나를 확인하는 방법은 다음과 같다.
※ 참고 url : &lt;a href="http://www.mybatis.org/mybatis-3/ko/sqlmap-xml.html" target="_blank" rel="noopener noreffer ">http://www.mybatis.org/mybatis-3/ko/sqlmap-xml.html&lt;/a>&lt;/p>
&lt;h2 id="usegeneratedkeys-keyproperty-옵션">useGeneratedKeys, keyProperty 옵션&lt;/h2>
&lt;p>사용하는 데이터베이스가 자동생성키를 지원한다면(mySql 같은) 해당옵션을 이용해 결과를 리턴 받을수 있다.
예로들어 파라미터로 아래 모델객체를 넘긴다고 가정하고&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">Date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">regist_date&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>아래 mybatis 구문으로 insert를 시도하게되면, 파라미터로 넘긴 Student 객체의 id값에 insert 했을때의 key값(id)이 들어오게 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="n">Student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Student&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">student&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="n">bla&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">student&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="n">bla&lt;/span>&lt;span class="nd">@naver.com&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">mapper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">insertStudents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">student&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 쿼리실행&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">student&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getId&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 추출 가능&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;insert&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;insertStudents&amp;#34;&lt;/span> &lt;span class="na">useGeneratedKeys=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span> &lt;span class="na">keyProperty=&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span> &lt;span class="na">parameterType=&lt;/span>&lt;span class="s">&amp;#34;Student&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> insert into Students ( name, email )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> values ( #{name}, #{email} )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/insert&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="selectkey-옵션">selectKey 옵션&lt;/h2>
&lt;p>Oracle 같은 경우는 Auto Increment 가 없고 Sequence를 사용해야만 하기 때문에 위 옵션을 사용할수가 없다. 하지만 다른 우회적인(?) 방법으로 위와같은 효과를 볼수가 있다.
파라미터의 모델이나 java구문은 위와 동일하고 xml 쿼리 부분만 아래와 같이 설정해주면 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;insert&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;insertStudents&amp;#34;&lt;/span> &lt;span class="na">parameterType=&lt;/span>&lt;span class="s">&amp;#34;Student&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;selectKey&lt;/span> &lt;span class="na">keyProperty=&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span> &lt;span class="na">resultType=&lt;/span>&lt;span class="s">&amp;#34;int&amp;#34;&lt;/span> &lt;span class="na">order=&lt;/span>&lt;span class="s">&amp;#34;BEFORE&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> select SEQ_ID.nexyval FROM DUAL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/selectKey&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> insert into Students
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (id, name , email)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> values
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (#{id}, #{name}, #{email})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/insert&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위와같은 코드에서 쿼리가 실행되기 전에 id값에 Sequence에 의해 값을 셋팅하게 되고, 자동적으로 해당 값을 Student의 id에 set하게 되서 동일한 결과를 볼수가 있다.&lt;/p>
&lt;p>항상 테이블의 key값에만 해당하는것이 아니다. key값과는 전혀 상관없는 값도 &lt;code>selectKey&lt;/code> 구문으로 리턴할수가 있는데 &lt;code>order&lt;/code>옵션을 &lt;code>AFTER&lt;/code>로 주고 리턴하고자 하는 값을 명시해주면 된다.
아래 코드에서는 입력할시 id값을 Sequence에서 가져오는게 아니라 수동으로 넣어주고, 입력했던 id에 맞는 regist_date 값을 리턴받아 위에서처럼 동일하게 값를 가져올수 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;insert&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;insertStudents&amp;#34;&lt;/span> &lt;span class="na">parameterType=&lt;/span>&lt;span class="s">&amp;#34;Student&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;selectKey&lt;/span> &lt;span class="na">keyProperty=&lt;/span>&lt;span class="s">&amp;#34;regist_date&amp;#34;&lt;/span> &lt;span class="na">resultType=&lt;/span>&lt;span class="s">&amp;#34;java.util.Date&amp;#34;&lt;/span> &lt;span class="na">order=&lt;/span>&lt;span class="s">&amp;#34;AFTER&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> select regist_date FROM students WHERE id = #{id}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/selectKey&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> insert into Students
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (id, name , email, regist_date)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> values
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (#{id}, #{name}, #{email}, syadate)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/insert&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Oracle + Mybatis 환경에서의 Date 다루기</title><link>https://taetaetae.github.io/2017/03/23/oracle-mybatis-date/</link><pubDate>Thu, 23 Mar 2017 11:16:05 +0000</pubDate><guid>https://taetaetae.github.io/2017/03/23/oracle-mybatis-date/</guid><description>&lt;h2 id="상황">상황&lt;/h2>
&lt;ul>
&lt;li>Oracle, Java 8, mybatis3 환경&lt;/li>
&lt;li>Date컬럼에 데이터가 있는데 이를 select query로 조회하여 Model에 바인딩 시키고자 함.&lt;/li>
&lt;/ul>
&lt;!-- more -->
&lt;ul>
&lt;li>쿼리에 아무 기능을 추가하지 않고 Date 형태로 Model에 바인딩을 하면 시분초가 없어진 &lt;code>2017-01-01 00:00:00&lt;/code> 형태로 남게됨&lt;/li>
&lt;li>그래서 아래처럼 쿼리 작성할 때마다 TO_CHAR를 사용해서 포맷에 맞추어 형변환을 시키고 Date 또는 String으로 Model에 바인딩 하곤 했음.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">TO_CHAR&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">reg_ymdt&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;YYYY-MM-DD HH24:MI:SS&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">registDate&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>이렇게 하다보니 query 만들때마다 형변환하는 쿼리를 만들어줘야하고, 자칫 포맷형식을 다르게 적으면 엉뚱한 결과를 초래하거나, Date형을 그대로 받아 사용해야하는 상황에서는 다시 형변환하는 과정(&lt;code>String to Date&lt;/code>)을 해줘야만 함. .. &lt;code>귀차니즘의 시작 : 삽질&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="1-삽질의-시작">1. 삽질의 시작&lt;/h2>
&lt;h3 id="1-1-오라클의-date형--javasqldate-의-경우">1-1. &lt;code>오라클의 DATE형&lt;/code> → &lt;code>java.sql.Date&lt;/code> 의 경우&lt;/h3>
&lt;ul>
&lt;li>mybatis에서는 자동적으로 &lt;code>org.apache.ibatis.type.SqlDateTypeHandler&lt;/code>를 호출하게됨 &lt;a href="http://www.mybatis.org/mybatis-3/ko/configuration.html#typeHandlers" target="_blank" rel="noopener noreffer ">mybatis 3 문서 참고&lt;/a>&lt;/li>
&lt;li>해당 핸들러의 내부 데이터 변환 코드는 다음과 같음&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getNullableResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ResultSet&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rs&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">columnName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SQLException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getDate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">columnName&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>java.sql.ResultSet.getDate()&lt;/code>메소드를 호출하면 실제 &amp;lsquo;yyyy-mm-dd&amp;rsquo; 만 가져와 리턴하게됨 (여기서 디버깅 해보면 rs.getTimestamp(columnName)값은 시분초까지 다 들어가 있음)&lt;/li>
&lt;li>따라서 시간값이 없는 &lt;code>yyyy-mm-dd&lt;/code> 형태로 리턴이 됨&lt;/li>
&lt;/ul>
&lt;h3 id="1-2-오라클의-date형--javautildate-의-경우">1-2. &lt;code>오라클의 DATE형&lt;/code> → &lt;code>java.util.Date&lt;/code> 의 경우&lt;/h3>
&lt;ul>
&lt;li>mybatis에서는 자동적으로 &lt;code>org.apache.ibatis.type.DateOnlyTypeHandler&lt;/code>를 호출하게됨 &lt;a href="http://www.mybatis.org/mybatis-3/ko/configuration.html#typeHandlers" target="_blank" rel="noopener noreffer ">mybatis 3 문서 참고&lt;/a>&lt;/li>
&lt;li>해당 핸들러의 내부 데이터 변환 코드는 다음과 같음&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getNullableResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ResultSet&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rs&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">columnName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SQLException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="n">java&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">Date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sqlDate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getDate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">columnName&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sqlDate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">java&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sqlDate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getTime&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>위의 &lt;code>org.apache.ibatis.type.SqlDateTypeHandler&lt;/code> 변환코드에서 발생한 문제점과 같이 &lt;code>yyyy-mm-dd&lt;/code> 만 가져와서 java.sql.Date 객체를 만들고, 이 정보를 토대로 java.util.Date 객체를 만들게 되는데 앞서 시간값을 뺀 정보로 만들어졌기 때문에 결국 동일하게 &lt;code>yyyy-mm-dd&lt;/code> 형태로 리턴이 됨&lt;/li>
&lt;/ul>
&lt;h2 id="2-삽질완료-해결의-시작">2. 삽질완료, 해결의 시작&lt;/h2>
&lt;ul>
&lt;li>오라클 + mybatis 환경에서 Date타입을 다루기 위해서는 타입핸들러를 명시적으로 만들어줘야 한다는걸 알게됨.&lt;/li>
&lt;/ul>
&lt;h4 id="2-1-오라클의-date형--javasqldate-의-경우">2-1. &lt;code>오라클의 DATE형&lt;/code> → &lt;code>java.sql.Date&lt;/code> 의 경우&lt;/h4>
&lt;ul>
&lt;li>아래처럼 코드를 작성하여 커스텀 핸들러를 만들어 등록을 시켜준다.&lt;/li>
&lt;li>mybatis-config.xml&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;typeHandlers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;typeHandler&lt;/span> &lt;span class="na">handler=&lt;/span>&lt;span class="s">&amp;#34;com.naver.dbill.admin.common.handler.CustomDateHandler&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/typeHandlers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>CustomDateHandler.java&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">java.sql.Date&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">CustomDateHandler&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">extends&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">BaseTypeHandler&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Date&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getNullableResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ResultSet&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rs&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">columnName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SQLException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="n">Timestamp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sqlTimestamp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getTimestamp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">columnName&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sqlTimestamp&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Date&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sqlTimestamp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getTime&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>위 코드를 작성하고 실행해보면 정상적으로 시분초 값이 있는 완전한 Date 형태를 볼수 있다.&lt;/li>
&lt;/ul>
&lt;h4 id="2-2-오라클의-date형--javautildate-의-경우">2-2. &lt;code>오라클의 DATE형&lt;/code> → &lt;code>java.util.Date&lt;/code> 의 경우&lt;/h4>
&lt;ul>
&lt;li>아래처럼 코드를 작성하여 커스텀 핸들러를 만들어 등록을 시켜준다.&lt;/li>
&lt;li>단, &lt;a href="http://www.mybatis.org/mybatis-3/ko/configuration.html#typeHandlers" target="_blank" rel="noopener noreffer ">mybatis 3 문서&lt;/a>를 보면 &lt;code>java.sql.Date&lt;/code> 와는 다르게 기본으로 설정된 typeHandler가 JDBC에 따라 3가지가 있다.&lt;/li>
&lt;li>따라서 작성한 커스텀 핸들러를 적용하기 위해서는 명시적으로 &lt;code>자바타입&lt;/code> 과 &lt;code>JDBC타입&lt;/code> 을 적어줘야 정상적으로 오버라이딩이 되어 해당 핸들러를 사용하게 된다.&lt;/li>
&lt;li>mybatis-config.xml&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;typeHandlers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nt">&amp;lt;typeHandler&lt;/span> &lt;span class="na">handler=&lt;/span>&lt;span class="s">&amp;#34;com.naver.dbill.admin.common.handler.CustomDateHandler&amp;#34;&lt;/span> &lt;span class="na">javaType=&lt;/span>&lt;span class="s">&amp;#34;java.util.Date&amp;#34;&lt;/span> &lt;span class="na">jdbcType=&lt;/span>&lt;span class="s">&amp;#34;DATE&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/typeHandlers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>CustomDateHandler.java 는 위와 동일하다. ( import java.util.Date; 사용으로 변경 )&lt;/li>
&lt;/ul>
&lt;h3 id="삽질하며-알게된-보너스-지식">삽질하며 알게된 보너스 지식&lt;/h3>
&lt;ul>
&lt;li>&lt;code>java.sql.Date&lt;/code> 는 &lt;code>java.util.Date&lt;/code> 을 상속받았다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">extends&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">java&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">Date&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>검색을 하다보면 알수있겠지만 &lt;code>java.sql.Date&lt;/code> 는 JDBC등을 이용해서 데이터베이스의 데이터를 사용하는데 적합하고, &lt;code>java.util.Date&lt;/code> 은 보다 범용적인 날짜나 시각정보를 다룰때 적합하다고 한다.&lt;/li>
&lt;li>toString 메소드의 리턴 Format 형태
&lt;ul>
&lt;li>&lt;code>java.sql.Date&lt;/code> : yyyy-mm-dd&lt;/li>
&lt;li>&lt;code>java.util.Date&lt;/code> : EEE MMM dd HH:mm:ss zzz yyyy&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>mybatis 에서 형변환은 &lt;a href="http://www.mybatis.org/mybatis-3/ko/configuration.html#typeHandlers" target="_blank" rel="noopener noreffer ">mybatis 3 문서&lt;/a>에 나와있는 자바타입과 JDBC타입이 일치할 경우에 해당 타입 핸들러를 기본으로 사용하게 된다.&lt;/li>
&lt;/ul>
&lt;h3 id="정상혁-님-조언---작성하신분-">정상혁 님 조언 ( &lt;a href="http://d2.naver.com/helloworld/645609" target="_blank" rel="noopener noreffer ">http://d2.naver.com/helloworld/645609&lt;/a> 작성하신분 )&lt;/h3>
&lt;ul>
&lt;li>Oracle의 JDBC 드라이버가 예상 밖으로 동작하네요. Oracle의 DATE 타입도 문서를 보니 시분초까지 저장하게 되어 있는데, Oracle JDBC 구현체가 DATE 타입의 철학을 오해한게 아닌가하는 생각도 듭니다.&lt;/li>
&lt;li>참고로 java.sql.Date, java.sql.TimeStamp는 잘못된 설계라는 비판이 많습니다.&lt;/li>
&lt;li>저도 Java의 날짜와 시간 API 라는 글에서 아래와 같이 적은 적이 있습니다.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>java.sql.Date 클래스는 상위 클래스인 java.util.Date 클래스와 이름이 같다. 이 클래스를 두고 Java 플랫폼 설계자는 클래스 이름을 지으면서 깜빡 존 듯하다는 조롱까지 나왔다.[24]&lt;/p></description></item><item><title>스프링환경에서의 파라미터 관련 정리</title><link>https://taetaetae.github.io/2017/03/12/spring-parameter/</link><pubDate>Sun, 12 Mar 2017 18:03:01 +0000</pubDate><guid>https://taetaetae.github.io/2017/03/12/spring-parameter/</guid><description>&lt;p>일반적인 웹 프로젝트 구성에서는 &lt;code>Controller&lt;/code>레벨에서 응답을 받고 비지니스 로직을 처리 후에 다시 &lt;code>View&lt;/code>레벨로 넘어가는게 통상적인 흐름이다. 이 부분에서 파라미터 관련한 여러가지 부분에 대해 정리해보고자 한다.&lt;/p>
&lt;!-- more -->
&lt;h2 id="httpservletrequestgetparameter">httpServletRequest.getParameter()&lt;/h2>
&lt;p>아래소스처럼 &lt;code>HttpServletRequest&lt;/code>의 getParameter() 메서드를 이용하여 파라미터값을 가져올 수 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">home&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HttpServletRequest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">httpServletRequest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">httpServletRequest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;home&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="requestparam">@RequestParam&lt;/h2>
&lt;p>또다른 방법으로는 &lt;code>@RequestParam&lt;/code> 어노테이션을 이용하면 간단하게 파라미터값을 가져올수 있다. 우선, 해당 어노테이션의 옵션값들에 대해 간략하게 확인하고 넘어가는게 좋을듯 싶다. &lt;a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html" target="_blank" rel="noopener noreffer ">API문서 4.3.6 기준&lt;/a>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>이름&lt;/th>
 &lt;th>타입&lt;/th>
 &lt;th>설명&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>name, value (Alias for name)&lt;/td>
 &lt;td>String&lt;/td>
 &lt;td>파라미터 이름&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>required&lt;/td>
 &lt;td>boolean&lt;/td>
 &lt;td>해당 파라미터가 반드시 필수인지 여부, 기본값은 true&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>defaultValue&lt;/td>
 &lt;td>String&lt;/td>
 &lt;td>해당 파라미터의 기본값&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>위 옵션값들을 조합하여 컨트롤러 메소드에 적용해보면 아래 소스와 같이 만들어지고, 이렇게 reqeust에서 파라미터값을 가져올수 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">home&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nd">@RequestParam&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">defaultValue&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;false&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;home&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이 어노테이션을 이용하게되면 자칫 잘못하다간 에러를 만날수가 있는데 &lt;code>required&lt;/code>값을 true로 해놓고 (필수 파라미터 설정) 해당 파라미터를 사용하지 않고 요청을 보내게 되면 HTTP 400 에러를 받게 되니 각 옵션들을 정확히 확인하고 사용해야 할 것 같다.
물론 컨트롤러의 메소드에서 해당 어노테이션을 사용하지 않고도 아래 코드처럼 바로 받을수 있다. 이렇게 바로 받을 경우는 필수 파라미터값이 false로 설정이 되고 변수명과 동일한 파라미터만 받을수 있게 되며 기본값 설정을 할수는 없다. 방법의 차이라서 상황에 따라 맞춰 사용하면 될듯 하다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">home&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;home&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="requestbody">@RequestBody&lt;/h2>
&lt;p>&lt;code>@RequestBody&lt;/code>어노테이션을 사용할 경우 반드시 POST형식으로 응답을 받는 구조여야만 한다. 이를테면 JSON 이나 XML같은 데이터를 적절한 messageConverter로 읽을때 사용하거나, POJO형태의 데이터 전체로 받을경우에 사용된다. 단, 이 어노테이션을 사용하여 파라미터를 받을 경우 별도의 추가 설정(POJO 의 get/set 이나 json/xml 등의 messageConverter 등)을 해줘야 적절하게 데이터를 받을수가 있다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@PostMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">home&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nd">@ReqeustBody&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">student&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;home&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="modelattribute">@ModelAttribute&lt;/h2>
&lt;p>&lt;code>@RequestParam&lt;/code>과 비슷한데 1:1로 파라미터를 받을경우는 &lt;code>@RequestParam&lt;/code>를 사용하고, 도메인이나 오브젝트로 파라미터를 받을 경우는 &lt;code>@ModelAttribute&lt;/code>으로 받을수 있다. 또한 이 어노테이션을 사용하면 검증(Validation)작업을 추가로 할수 있는데 예로들어 null이라던지, 각 멤버변수마다 valid옵션을 줄수가 있고 여기서 에러가 날 경우 BindException 이 발생한다.&lt;/p>
&lt;h2 id="spring-command-객체">Spring command 객체&lt;/h2>
&lt;p>컨트롤러에서 파라미터로 받은 정보에 대해서는 view 에서 바로 사용이 가능하다. 예로 들어 아래그림처럼 이렇게 컨트롤러가 구성되어있고&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-parameter/spring-parameter-1.jpg" title="/images/spring-parameter/spring-parameter-1.jpg" data-thumbnail="/images/spring-parameter/spring-parameter-1.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-parameter/spring-parameter-1.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-parameter/spring-parameter-1.jpg, https://taetaetae.github.io/images/spring-parameter/spring-parameter-1.jpg 1.5x, https://taetaetae.github.io/images/spring-parameter/spring-parameter-1.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-parameter/spring-parameter-1.jpg" />
 &lt;/a>
&lt;p>이렇게 모델이 구성되어있을때&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-parameter/spring-parameter-2.jpg" title="/images/spring-parameter/spring-parameter-2.jpg" data-thumbnail="/images/spring-parameter/spring-parameter-2.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-parameter/spring-parameter-2.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-parameter/spring-parameter-2.jpg, https://taetaetae.github.io/images/spring-parameter/spring-parameter-2.jpg 1.5x, https://taetaetae.github.io/images/spring-parameter/spring-parameter-2.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-parameter/spring-parameter-2.jpg" />
 &lt;/a>
&lt;p>view 에서 이런식으로 구성되어있다고 가정해보자.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-parameter/spring-parameter-3.jpg" title="/images/spring-parameter/spring-parameter-3.jpg" data-thumbnail="/images/spring-parameter/spring-parameter-3.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-parameter/spring-parameter-3.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-parameter/spring-parameter-3.jpg, https://taetaetae.github.io/images/spring-parameter/spring-parameter-3.jpg 1.5x, https://taetaetae.github.io/images/spring-parameter/spring-parameter-3.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-parameter/spring-parameter-3.jpg" />
 &lt;/a>
&lt;p>이때 &lt;code>/student?name=taetaetae&amp;amp;age=32&amp;amp;address=green-factory&lt;/code>로 호출을 해보면 구지 Model에 값을 셋팅해주지 않아도 다음과 같이 정보를 읽을수 있게 된다.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/spring-parameter/spring-parameter-4.jpg" title="/images/spring-parameter/spring-parameter-4.jpg" data-thumbnail="/images/spring-parameter/spring-parameter-4.jpg">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/spring-parameter/spring-parameter-4.jpg"
 data-srcset="https://taetaetae.github.io/images/spring-parameter/spring-parameter-4.jpg, https://taetaetae.github.io/images/spring-parameter/spring-parameter-4.jpg 1.5x, https://taetaetae.github.io/images/spring-parameter/spring-parameter-4.jpg 2x"
 data-sizes="auto"
 alt="/images/spring-parameter/spring-parameter-4.jpg" />
 &lt;/a>
&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>스프링에서 파라미터를 받는 방법은 상당히 다양하다. 이게 정답이다 정의할수 없을정도로. 상황에 따라 맞는 방법으로 파라미터를 받아야 하겠고, 각 방법에 장/단점을 최대한 살려서 좀더 깔끔한 코드를 작성할수 있어야 하겠다.&lt;/p></description></item><item><title>github api 사용방법</title><link>https://taetaetae.github.io/2017/03/02/github-api/</link><pubDate>Thu, 02 Mar 2017 11:18:05 +0000</pubDate><guid>https://taetaetae.github.io/2017/03/02/github-api/</guid><description>&lt;p>github 에서는 레파지토리의 전반적인 상황에 대해 다양한 API를 제공해주고 있다. 이번에는 그 API를 사용하는 방법에 대해 알아보고자 한다.&lt;/p>
&lt;h2 id="personal-access-tokens-발급">Personal access tokens 발급&lt;/h2>
&lt;p>우선 정상적인 API를 사용하기 위해 &lt;code>Personal access tokens&lt;/code>를 발급받아야 한다. github 초기화면 &amp;gt; 우측상단 프로필사진 클릭 &amp;gt; setting &amp;gt; Personal access tokens 에 들어가 토큰을 생성을 한다.
해당 토큰의 허용범위를 설정한뒤 생성을 하면 만들어 지는데 여기서 발급되는 문자열은 따로 보관하는게 좋다. (나중에 다시 확인하려면 새로 재 생성하는 방법말고는 없기 때문에 한번 만들때 메모해 두는게 좋다.)
아래와 같이 생성완료.
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-api/github_access_tokens.png" title="/images/github-api/github_access_tokens.png" data-thumbnail="/images/github-api/github_access_tokens.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-api/github_access_tokens.png"
 data-srcset="https://taetaetae.github.io/images/github-api/github_access_tokens.png, https://taetaetae.github.io/images/github-api/github_access_tokens.png 1.5x, https://taetaetae.github.io/images/github-api/github_access_tokens.png 2x"
 data-sizes="auto"
 alt="/images/github-api/github_access_tokens.png" width="80%" />
 &lt;/a>&lt;/p>
&lt;h2 id="api-사용방법">API 사용방법&lt;/h2>
&lt;p>권한이 없는 Repository 의 내용을 확인할수 없듯이 github에서 제공하는 API또한 권한이 있는 Repository에 대해서만 API를 제공한다. 위에서 발급한 token 을 권한 체크할때 사용하는데 다양한 방법이 있을수 있겠으나 나는 간단하게 헤더에 포함시켜서 일반 GET 호출을 하는 방식으로 하였다. 윈도우 환경에서는 헤더 셋팅하고 호출하는게 조금 어려울수 있으니 이러한 부분을 설정할수 있는 &lt;code>Postman&lt;/code>이라는 프로그램으로 호출을 해본다.
아래처럼 url은 &lt;code>https://api.github.com/&lt;/code>으로 설정하고 &lt;code>Headers&lt;/code>파라미터에 &lt;code>Authorization&lt;/code>라는 key에 value를 위에서 발급받았던 token을 이용하여 &lt;code>token abcd~~&lt;/code>식으로 입력해준다음 &lt;code>send&lt;/code>버튼을 눌러주면 응답을 받을수가 있는데, 아래 그림은 제공하는 api의 모든 url을 확인하는 방법이다.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-api/github_api_call_1.png" title="/images/github-api/github_api_call_1.png" data-thumbnail="/images/github-api/github_api_call_1.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-api/github_api_call_1.png"
 data-srcset="https://taetaetae.github.io/images/github-api/github_api_call_1.png, https://taetaetae.github.io/images/github-api/github_api_call_1.png 1.5x, https://taetaetae.github.io/images/github-api/github_api_call_1.png 2x"
 data-sizes="auto"
 alt="/images/github-api/github_api_call_1.png" />
 &lt;/a>
&lt;p>아래애서는 위에서 확인된 api url을 활용하여 내가 권한이 있는 레파지토리 내에서 확인할수있는 정보에 대한 API를 호출해보았다.&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/github-api/github_api_call_2.png" title="/images/github-api/github_api_call_2.png" data-thumbnail="/images/github-api/github_api_call_2.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/github-api/github_api_call_2.png"
 data-srcset="https://taetaetae.github.io/images/github-api/github_api_call_2.png, https://taetaetae.github.io/images/github-api/github_api_call_2.png 1.5x, https://taetaetae.github.io/images/github-api/github_api_call_2.png 2x"
 data-sizes="auto"
 alt="/images/github-api/github_api_call_2.png" />
 &lt;/a>
&lt;p>API 호출시 가장 보편화되어있는(?) 스팩인 &lt;code>JSON&lt;/code>으로 응답이 내려오기때문에 어떠한 환경에서도 충분히 활용할수 있을것이라 생각한다.
나는 개인적으로 팀 내에서 하나의 &lt;code>Organizations&lt;/code>내에 여러 &lt;code>Repository&lt;/code>가 있는데 각각의 &lt;code>PullRequest&lt;/code>에 대해 코드리뷰를 해야하는 상황에서 일일히 다 찾아보기 귀찮아 github-api를 활용해 open된 &lt;code>PullRequest&lt;/code>가 있으면 알림을 주는 걸 만들어 보았다.&lt;/p></description></item><item><title>리눅스상에서 json 파싱</title><link>https://taetaetae.github.io/2017/02/28/shell-script-json/</link><pubDate>Tue, 28 Feb 2017 17:50:44 +0000</pubDate><guid>https://taetaetae.github.io/2017/02/28/shell-script-json/</guid><description>&lt;p>리눅스 상에서 json형태의 String 을 파싱해야하는 상황이라면 아래 라이브러리를 사용해보는것을 추천해본다.&lt;/p>
&lt;!-- more -->
&lt;h2 id="jq">jq&lt;/h2>
&lt;p>사용방법은 너무너무 간단하다.&lt;/p>
&lt;ul>
&lt;li>자신의 시스템에 맞는 라이브러리를 다운받고&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>(32-bit system)
$ wget http://stedolan.github.io/jq/download/linux32/jq
(64-bit system)
$ wget http://stedolan.github.io/jq/download/linux64/jq
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>실행 권한을 설정해 준 뒤&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>chmod +x ./jq
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>root 권한으로 해당 파일을 이동시킨다.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>sudo cp jq /usr/bin
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>실행은 다음과 같이 한다.
Json String 이 아래와 같이 있다고 가정했을때&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Google&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;location&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;street&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1600 Amphitheatre Parkway&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;city&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Mountain View&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;state&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;California&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;country&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;US&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;employees&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Michael&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;division&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Engineering&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Laura&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;division&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HR&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Elise&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;division&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Marketing&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>실제 사용과 결과는 다음과 같이 이루어 진다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ cat json.txt | jq &amp;#39;.name&amp;#39;
&amp;#34;Google&amp;#34;

$ cat json.txt | jq &amp;#39;.location.city&amp;#39;
&amp;#34;Mountain View&amp;#34;

$ cat json.txt | jq &amp;#39;.employees[0].name&amp;#39;
&amp;#34;Michael&amp;#34;

$ cat json.txt | jq &amp;#39;.location | {street, city}&amp;#39;
{
 &amp;#34;city&amp;#34;: &amp;#34;Mountain View&amp;#34;,
 &amp;#34;street&amp;#34;: &amp;#34;1600 Amphitheatre Parkway&amp;#34;
}
&lt;/code>&lt;/pre>&lt;p>보다 자세한 사용방법은 공식홈페이지( &lt;a href="https://stedolan.github.io/jq/" target="_blank" rel="noopener noreffer ">https://stedolan.github.io/jq/&lt;/a> )를 참조하면 좋을듯 하다.&lt;/p></description></item><item><title>eclipse에서 spring-boot로 web 만들기</title><link>https://taetaetae.github.io/2017/02/27/spring-boot-eclipse/</link><pubDate>Mon, 27 Feb 2017 14:37:27 +0000</pubDate><guid>https://taetaetae.github.io/2017/02/27/spring-boot-eclipse/</guid><description>&lt;p>Spring 환경에서 웹 어플리케이션을 만들어야 한다면 pom.xml 에 이런저런 설정들을 적어줘야 했다. 하지만 이런 수고(?)를 덜어줄수 있는 방법중에 한가지가 바로 Spring Boot로 만드는 방법인데, 이클립스 환경에서 만드는 법을 정리하고자 한다.&lt;/p>
&lt;!-- more -->
&lt;h2 id="new--maven-project">new &amp;gt; Maven Project&lt;/h2>
&lt;p>빈 Maven Project 를 만드는 방법은 아주 간단하니 생략하고&amp;hellip; 만들게 되면 pom.xml 은 아래처럼 아주 깔끔한(?)상태로 만들어지게 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;modelVersion&amp;gt;&lt;/span>4.0.0&lt;span class="nt">&amp;lt;/modelVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.example&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>boot&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>0.0.1-SNAPSHOT&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그러면 이 비어있는 pom.xml 에 Spring-Boot 에 필요한 설정들을 추가해주기로 한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;parent&amp;gt;&lt;/span> &lt;span class="c">&amp;lt;!--boot의 스타터를 사용하겠다고 명시적으로 설정--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-parent&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>1.5.1.RELEASE&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;relativePath&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/parent&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span> &lt;span class="c">&amp;lt;!--boot에서 스타터패키지로 제공해주는 것들중에 web 설정 부분 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-web&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-maven-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/plugins&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/build&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그다음 임의의 java 클래스를 하나 만들고 거기에 아래처럼 설정하면 끝&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@SpringBootApplication&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// @Configuration + @EnableAutoConfiguration + @ComponentScan 들의 종합 어노테이션&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">TestApplication&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SpringApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TestApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Spring Boot 에서는 내장WAS를 가지고 있기 때문에 main 메소드에서 우클릭후 &lt;code>run AS → Spring Boot App&lt;/code> 을 선택해주면 8080포트로 띄워지게 된다.&lt;/p>
&lt;h2 id="new--spring-starter-project">new &amp;gt; Spring Starter project&lt;/h2>
&lt;p>(STS가 설치되어있다는 가정하에)이 메뉴를 사용하면 위에서 했던 일련의 설정들을 자동으로 해주게 된다. 간단한 내용이니 next를 해주다 마지막에 &lt;code>Dependencies&lt;/code> 설정하는 부분에서 Web 을 체크해주고 &lt;code>Finish 버튼&lt;/code>을 누르면 끝&lt;/p>
&lt;h2 id="spring-initializr-startspringio">Spring Initializr (start.spring.io)&lt;/h2>
&lt;p>&lt;a href="http://start.spring.io/" target="_blank" rel="noopener noreffer ">http://start.spring.io/&lt;/a> 에 들어가보면 구지 설명하지 않아도 친절하게 Generate 해주는 페이지가 보인다. 여기서 web 을 &lt;code>Dependencies&lt;/code>에 추가하고 Generate를 하면 해당 프로젝트가 압축된 상태로 다운이 받아지게 되고 이를 IDE 에서 열어보면 위에서 했던 일련의 과정들이 설정되어 있는것을 확인해볼수가 있다.&lt;/p>
&lt;h2 id="내장톰켓을-사용안하고-별도-톰켓을-사용해야-하는-경우">내장톰켓을 사용안하고 별도 톰켓을 사용해야 하는 경우&lt;/h2>
&lt;p>Spring boot는 자체적으로 내장 WAS를 가지고 있다. 하지만 관리포인트나 이런저런 이유로 내장톰켓을 사용하지 못하는 환경이라면 다음과 같은 설정을 해주면 된다.&lt;/p>
&lt;ul>
&lt;li>일반적으로 빌드가 되면 &lt;code>jar&lt;/code>로 만들어 질텐데 &lt;code>war&lt;/code>로 빌드 되도록 수정을 해야한다. (was가 WAR를 물고 떠야하기 때문..)&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;packaging&amp;gt;&lt;/span>war&lt;span class="nt">&amp;lt;/packaging&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>dependency 에 tomcat을 추가해준다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-starter-tomcat&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>provided&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>아래처럼 main 메소드가 있는 클래스에 &lt;code>SpringBootServletInitializer&lt;/code>를 상속받게 한 후 &lt;code>configure&lt;/code>메소드를 오버라이딩 해준다.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@SpringBootApplication&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">TestApplication&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">extends&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SpringBootServletInitializer&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">protected&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">SpringApplicationBuilder&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">configure&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">SpringApplicationBuilder&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">builder&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">sources&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TestApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">SpringApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TestApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>톰켓에 띄우기 위하여 프로젝트 설정(Project Facets)에서 &lt;code>Dynamic Web Module&lt;/code>을 체크해준다.&lt;/li>
&lt;/ul>
&lt;p>참고 URL&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://www.donnert.net/86" target="_blank" rel="noopener noreffer ">http://www.donnert.net/86&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://opennote46.tistory.com/124" target="_blank" rel="noopener noreffer ">http://opennote46.tistory.com/124&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>lombok(롬복)소개 및 설치</title><link>https://taetaetae.github.io/2017/02/22/lombok/</link><pubDate>Wed, 22 Feb 2017 17:48:42 +0000</pubDate><guid>https://taetaetae.github.io/2017/02/22/lombok/</guid><description>&lt;p>일반적으로 자바개발을 하다보면 &lt;code>Model&lt;/code> 을 만들고 각 멤버변수를 접근할수 있는 (각 요소들이 private 접근권한을 가지고 있을때) method 를 만들게 된다. IDE에서 제공하는 아래처럼&amp;hellip; (윈도우/이클립스 기준)&lt;!-- more -->&lt;/p>
&lt;ul>
&lt;li>get/set 메소드 : &lt;code>Alt&lt;/code> + &lt;code>Shift&lt;/code> + &lt;code>S&lt;/code> + &lt;code>R&lt;/code>&lt;/li>
&lt;li>toString 메소드 : &lt;code>Alt&lt;/code> + &lt;code>Shift&lt;/code> + &lt;code>S&lt;/code> + &lt;code>S&lt;/code>&lt;/li>
&lt;li>기타 등등&amp;hellip;&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">grade&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">department&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getId&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getName&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getGrade&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">grade&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setGrade&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">grade&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">grade&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">grade&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getDepartment&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">department&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">setDepartment&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">department&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">department&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">department&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Override&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">toString&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;Student [id=&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;, name=&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;, grade=&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">grade&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;, department=&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">department&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;]&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> 
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하는 방법도 있지만 어노테이션 설정으로 적용할수 있는 간단한 라이브러리를 소개하고자 한다.
바로 &lt;code>lombok&lt;/code>, 공식 홈페이지 : &lt;a href="https://projectlombok.org" target="_blank" rel="noopener noreffer ">https://projectlombok.org&lt;/a>
설치 및 사용방법은 아주 간단하다. 공식 홈페이지에서 jar를 다운받고 실행, 아래처럼 이클립스 실행파일 경로를 설정해준다음에 인스톨을 누르면 된다.
&lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/lombok/lombok.png"
 data-srcset="https://taetaetae.github.io/images/lombok/lombok.png, https://taetaetae.github.io/images/lombok/lombok.png 1.5x, https://taetaetae.github.io/images/lombok/lombok.png 2x"
 data-sizes="auto"
 alt="/images/lombok/lombok.png"
 title="/images/lombok/lombok.png" />
maven 환경에서 dependency를 가져오기 위해서는 당연히 추가설정을 해줘야 한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.projectlombok&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>lombok&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>1.16.10&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span> &lt;span class="c">&amp;lt;!--버전은 그때 맞춰서--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>실제로 코드상에서 사용방법은 다음과 같다. 정말 간단히, 어노테이션만 적용해주면 끝!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">lombok.Data&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Student&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">grade&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">department&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그럼 이렇게 기본적인 method들이 생성된다.
&lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/lombok/lombok-annotation.png"
 data-srcset="https://taetaetae.github.io/images/lombok/lombok-annotation.png, https://taetaetae.github.io/images/lombok/lombok-annotation.png 1.5x, https://taetaetae.github.io/images/lombok/lombok-annotation.png 2x"
 data-sizes="auto"
 alt="/images/lombok/lombok-annotation.png"
 title="/images/lombok/lombok-annotation.png" />
일반적으로 &lt;code>@Data&lt;/code>를 사용하고 상황에 따라 필요한 어노테이션만 지정도 가능하다고 한다.&lt;/p>
&lt;ul>
&lt;li>@Getter and @Setter&lt;/li>
&lt;li>@NonNull&lt;/li>
&lt;li>@ToString&lt;/li>
&lt;li>@EqualsAndHashCode&lt;/li>
&lt;li>@Data&lt;/li>
&lt;li>@Cleanup&lt;/li>
&lt;li>@Synchronized&lt;/li>
&lt;li>@SneakyThrows
참고 URL : &lt;a href="http://jnb.ociweb.com/jnb/jnbJan2010.html" target="_blank" rel="noopener noreffer ">http://jnb.ociweb.com/jnb/jnbJan2010.html&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>logback 설정하기</title><link>https://taetaetae.github.io/2017/02/19/logback/</link><pubDate>Sun, 19 Feb 2017 15:10:45 +0000</pubDate><guid>https://taetaetae.github.io/2017/02/19/logback/</guid><description>&lt;p>자바 개발자라면 한번쯤은 들어봤고, 한번쯤은 사용했을법한 logger 로 &lt;code>log4j&lt;/code>가 있을것이다. 하지만 최근들어 &lt;code>logback&lt;/code>이라는것을 알게되었고, 왜 &lt;code>logback&lt;/code>을 사용해야 하는 이유라는 글이 있을정도로 여러 측면에서 개선이 된듯 하다. &lt;!-- more -->(&lt;a href="https://beyondj2ee.wordpress.com/2012/11/09/logback-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-reasons-to-prefer-logback-over-log4j" target="_blank" rel="noopener noreffer ">링크&lt;/a>)
이번에 작성할 글의 목적은 &lt;code>logback&lt;/code>을 설정하고 어떻게 사용하는지에 대해 작성해 보고자 한다.
※ 공식사이트 : &lt;a href="https://logback.qos.ch/" target="_blank" rel="noopener noreffer ">https://logback.qos.ch/&lt;/a>&lt;/p>
&lt;h2 id="pomxml">pom.xml&lt;/h2>
&lt;p>maven구조라고 가정했을때 &lt;code>logback Dependency&lt;/code>를 가져오기 위해서는 아래와 같이 pom.xml 에 설정해 주면 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>ch.qos.logback&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>logback-classic&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>1.1.7&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span> &lt;span class="c">&amp;lt;!--버전은 상황에 따라 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="로그레벨">로그레벨&lt;/h2>
&lt;p>&lt;code>ERROR&lt;/code>, &lt;code>WARN&lt;/code>, &lt;code>INFO&lt;/code>, &lt;code>DEBUG&lt;/code> or &lt;code>TRACE&lt;/code>&lt;/p>
&lt;h4 id="-logback-설정파일"># logback 설정파일&lt;/h4>
&lt;p>일반적으로 &lt;code>logback.xml&lt;/code> 이라는 이름으로 만들어 &lt;code>src/main/resources/&lt;/code>아래에 위치하게 된다. Spring-Boot 환경에서는 &lt;code>logback-spring.xml&lt;/code> 이라는 이름으로 설정해야 하는데 &lt;code>logback.xml&lt;/code>로 설정하면 스프링부트가 설정하기 전에 로그백 관련한 설정을 하기 때문에 제어할 수가 없게 된다.
( 공식사이트 메뉴얼 : &lt;a href="https://logback.qos.ch/documentation.html" target="_blank" rel="noopener noreffer ">https://logback.qos.ch/documentation.html&lt;/a> )&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;include&lt;/span> &lt;span class="na">resource=&lt;/span>&lt;span class="s">&amp;#34;org/springframework/boot/logging/logback/defaults.xml&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;include&lt;/span> &lt;span class="na">resource=&lt;/span>&lt;span class="s">&amp;#34;org/springframework/boot/logging/logback/console-appender.xml&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 변수 지정 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;property&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;LOG_DIR&amp;#34;&lt;/span> &lt;span class="na">value=&lt;/span>&lt;span class="s">&amp;#34;/logs&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;property&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;LOG_PATH_NAME&amp;#34;&lt;/span> &lt;span class="na">value=&lt;/span>&lt;span class="s">&amp;#34;${LOG_DIR}/data.log&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- FILE Appender --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;appender&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;FILE&amp;#34;&lt;/span> &lt;span class="na">class=&lt;/span>&lt;span class="s">&amp;#34;ch.qos.logback.core.rolling.RollingFileAppender&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;file&amp;gt;&lt;/span>${LOG_PATH_NAME}&lt;span class="nt">&amp;lt;/file&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 일자별로 로그파일 적용하기 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;rollingPolicy&lt;/span> &lt;span class="na">class=&lt;/span>&lt;span class="s">&amp;#34;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;fileNamePattern&amp;gt;&lt;/span>${LOG_PATH_NAME}.%d{yyyyMMdd}&lt;span class="nt">&amp;lt;/fileNamePattern&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;maxHistory&amp;gt;&lt;/span>60&lt;span class="nt">&amp;lt;/maxHistory&amp;gt;&lt;/span> &lt;span class="c">&amp;lt;!-- 일자별 백업파일의 보관기간 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/rollingPolicy&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;encoder&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;pattern&amp;gt;&lt;/span>%d{yyyy-MM-dd HH:mm:ss} [%-5p] [%F]%M\(%L\) : %m%n&lt;span class="nt">&amp;lt;/pattern&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/encoder&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/appender&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;appender&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;STDOUT&amp;#34;&lt;/span> &lt;span class="na">class=&lt;/span>&lt;span class="s">&amp;#34;ch.qos.logback.core.ConsoleAppender&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;layout&lt;/span> &lt;span class="na">class=&lt;/span>&lt;span class="s">&amp;#34;ch.qos.logback.classic.PatternLayout&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;pattern&amp;gt;&lt;/span>%d{yyyy-MM-dd HH:mm:ss} [%-5p] [%F]%M\(%L\) : %m%n&lt;span class="nt">&amp;lt;/pattern&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/layout&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/appender&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- TRACE &amp;gt; DEBUG &amp;gt; INFO &amp;gt; WARN &amp;gt; ERROR, 대소문자 구분 안함 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- profile 을 읽어서 appender 을 설정할수 있다.(phase별 파일을 안만들어도 되는 좋은 기능) --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;springProfile&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;local&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;root&lt;/span> &lt;span class="na">level=&lt;/span>&lt;span class="s">&amp;#34;DEBUG&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;appender-ref&lt;/span> &lt;span class="na">ref=&lt;/span>&lt;span class="s">&amp;#34;FILE&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;appender-ref&lt;/span> &lt;span class="na">ref=&lt;/span>&lt;span class="s">&amp;#34;STDOUT&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/root&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/springProfile&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;springProfile&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;real&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;root&lt;/span> &lt;span class="na">level=&lt;/span>&lt;span class="s">&amp;#34;INFO&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;appender-ref&lt;/span> &lt;span class="na">ref=&lt;/span>&lt;span class="s">&amp;#34;FILE&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;appender-ref&lt;/span> &lt;span class="na">ref=&lt;/span>&lt;span class="s">&amp;#34;STDOUT&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/root&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/springProfile&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="java-코딩에서의-로깅">java 코딩에서의 로깅&lt;/h2>
&lt;p>실제 사용은 다음과 같이 &lt;code>LoggerFactory&lt;/code>를 이용해서 사용하거나 &lt;code>Lombok&lt;/code>어노테이션을 활용하면 심플하게 사용이 가능하다.&lt;/p>
&lt;ul>
&lt;li>LoggerFactory 사용&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.slf4j.Logger&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">org.slf4j.LoggerFactory&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Foo&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Logger&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">logger&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LoggerFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getLogger&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Foo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">logger&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">debug&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;ID : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;foo&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>Lombok 어노테이션 사용&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nn">lombok.extern.slf4j.Slf4j&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@Slf4j&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Foo&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">debug&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;ID : {}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;foo&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>일반적인 웹 어플리케이션에서는 WAS에서 로깅을 따로 관리하고 있기 때문에 file 로 로깅을 할 필요는 없을것 같다.(일반 jar 형태에서는 파일 로깅이 필요 할수도&amp;hellip;)&lt;/p>
&lt;h2 id="참고사이트">참고사이트&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="http://yookeun.github.io/java/2015/11/10/log4jtologback/" target="_blank" rel="noopener noreffer ">http://yookeun.github.io/java/2015/11/10/log4jtologback/&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://java.ihoney.pe.kr/397" target="_blank" rel="noopener noreffer ">http://java.ihoney.pe.kr/397&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://logback.qos.ch/" target="_blank" rel="noopener noreffer ">https://logback.qos.ch/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>자바 8 Date</title><link>https://taetaetae.github.io/2017/01/10/java8-date/</link><pubDate>Tue, 10 Jan 2017 20:55:33 +0000</pubDate><guid>https://taetaetae.github.io/2017/01/10/java8-date/</guid><description>&lt;p>이제까지 내 기억으로는 Date 관련 클래스를 아래처럼 점차 바꿔써온걸로 기억이 난다.
&lt;code>java.util.Date&lt;/code> &amp;gt; &lt;code>java.util.Calendar&lt;/code> &amp;gt; &lt;code>org.joda.time&lt;/code>
그런데 java 8 버전에서 기존에 있었던 문제들을 개선해서 나왔다고 한다. (&lt;a href="http://d2.naver.com/helloworld/645609" target="_blank" rel="noopener noreffer ">네이버 HellowWorld 포스팅 참고&lt;/a>) &lt;code>JSR-310&lt;/code> 이라는 표준명세로.&lt;/p>
&lt;!-- more -->
&lt;p>지금부터는 JAVA 8 에서 제공하는 API로 날짜 연산을 어떻게 하는지에 대해 알아보고자 한다. (물론 수많은 날짜 연산 방법을이 있지만 자주 쓰이는 부분들 위주로 정리해보자.)&lt;/p>
&lt;ul>
&lt;li>Date &amp;gt; String (format)&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="n">LocalDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">now&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTimeFormatter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ofPattern&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;yyyy-MM-dd&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>String &amp;gt; Date (format)&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="n">LocalDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;2017-01-01 12:30:00&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DateTimeFormatter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ofPattern&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;yyyy-MM-dd HH:mm:ss&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>날짜/시간 증감&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="n">LocalDateTime&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">localDateTime&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">of&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">2017&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">10&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">localDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">plusDays&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 일&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">localDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">plusMonths&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 월&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">localDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">plusHours&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 시간&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">localDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">plusWeeks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 주&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">localDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">minusYears&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 년&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">localDateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">minusMinutes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 분&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>더 다양한 내용들은 아래 URL 에서 확인이 가능하다.
&lt;a href="https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html" target="_blank" rel="noopener noreffer ">https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html&lt;/a>&lt;/p></description></item><item><title>Spring Transactional 설정 및 주요속성</title><link>https://taetaetae.github.io/2017/01/08/transactional-setting-and-property/</link><pubDate>Sun, 08 Jan 2017 17:19:30 +0000</pubDate><guid>https://taetaetae.github.io/2017/01/08/transactional-setting-and-property/</guid><description>&lt;p>지난번에는 트랜잭션의 설정값에 대해 알아본 바 있다. [ &lt;a href="https://taetaetae.github.io/2016/10/08/20161008" rel="">Spring Transaction 옵션&lt;/a> ]
이번 포스팅에서는 실제로 스프링 환경에서 어떤식으로 설정해야 &lt;code>@Transactional&lt;/code> 어노테이션을 사용할수 있는지, 그리고 어떤 속성들이 있는지에 대해 알아보고자 한다.&lt;!-- more -->&lt;/p>
&lt;h2 id="설정">설정&lt;/h2>
&lt;p>기존 xml방식에서는 다음과 같이 설정을 한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;bean&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;transactionManager&amp;#34;&lt;/span> &lt;span class="na">class=&lt;/span>&lt;span class="s">&amp;#34;org.springframework.jdbc.datasource.DataSourceTransactionManager&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;property&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;dataSource&amp;#34;&lt;/span> &lt;span class="na">ref=&lt;/span>&lt;span class="s">&amp;#34;dataSource&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/bean&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;tx:annotation-driven&lt;/span> &lt;span class="na">transaction-manager=&lt;/span>&lt;span class="s">&amp;#34;transactionManager&amp;#34;&lt;/span> &lt;span class="na">proxy-target-class=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>혹, JavaConfig 방식으로 설정하기 위해서는 다음과 같이 설정한다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@EnableTransactionManagement&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">AppConfig&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Bean&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PlatformTransactionManager&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">transactionManager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">URISyntaxException&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">GeneralSecurityException&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ParseException&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">IOException&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">DataSourceTransactionManager&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dataSource&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위와같이 설정을 해주면 트랜잭션을 설정하고자 하는 곳 어디서든 &lt;code>@Transactional&lt;/code> 어노테이션을 지정해서 적용이 가능하다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">UserService&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@Transactional&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">boolean&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">insertUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="주요속성">주요속성&lt;/h2>
&lt;p>&lt;code>@Transactional&lt;/code> 어노테이션의 주요속성은 다음과 같다.&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>속성&lt;/th>
 &lt;th>설 명&lt;/th>
 &lt;th>사용 예&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>isolation&lt;/td>
 &lt;td>Transaction의 isolation Level. 별도로 정의하지 않으면 DB의 Isolation Level을 따름.&lt;/td>
 &lt;td>@Transactional(isolation=Isolation.DEFAULT)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>propagation&lt;/td>
 &lt;td>트랜잭션 전파규칙을 정의 , Default=REQURIED&lt;/td>
 &lt;td>@Transactional(propagation=Propagation.REQUIRED)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>readOnly&lt;/td>
 &lt;td>해당 Transaction을 읽기 전용 모드로 처리 (Default = false)&lt;/td>
 &lt;td>@Transactional(readOnly = true)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rollbackFor&lt;/td>
 &lt;td>정의된 Exception에 대해서는 rollback을 수행&lt;/td>
 &lt;td>@Transactional(rollbackFor=Exception.class)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>noRollbackFor&lt;/td>
 &lt;td>정의된 Exception에 대해서는 rollback을 수행하지 않음.&lt;/td>
 &lt;td>@Transactional(noRollbackFor=Exception.class)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>timeout&lt;/td>
 &lt;td>지정한 시간 내에 해당 메소드 수행이 완료되지 않은 경우 rollback 수행. -1일 경우 no timeout (Default = -1)&lt;/td>
 &lt;td>@Transactional(timeout=10)&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>자칫 잘못했다가는 원치않는 트랜잭션으로 잘못된 결과를 초래할수 있기때문에 기본값은 숙지하는게 좋을것 같다.&lt;/p></description></item><item><title>spring4에서 json view 활용하기(with @ResponseBody)</title><link>https://taetaetae.github.io/2017/01/07/spring4-json/</link><pubDate>Sat, 07 Jan 2017 15:47:59 +0000</pubDate><guid>https://taetaetae.github.io/2017/01/07/spring4-json/</guid><description>&lt;p>수많은 블로거분들의 도움을 받고자 구글링을 해서 적용을 해봤지만 너무많은 삽질을 했다.(해봤던 방식은 &lt;code>jsonViewResolver&lt;/code> 를 따로 설정해보거나, &lt;code>@RequestMapping&lt;/code> 옵션을 바꿔보는 수준..) 특히나 Spring설정방식이 예전 방식이였던 &lt;code>xml&lt;/code>이 아닌 &lt;code>javaconfig&lt;/code>였기 때문에 더욱더 자료가 없었고.. &lt;!-- more --> 한참을 삽질하다 해결을 하여 포스팅하게 된다. 우선 환경은 &lt;code>spring 4.3.4.RELEASE&lt;/code>, &lt;code>Maven&lt;/code>, &lt;code>jdk8&lt;/code>임을 밝힌다.&lt;/p>
&lt;h2 id="pomxml">pom.xml&lt;/h2>
&lt;p>&lt;code>jackson-mapper-asl&lt;/code>을 이용해서 하라는 블로거들도 있었지만, 아무리해도(뭔가 Spring버전과 맞지 않는듯 했다.) 잘 안되어 아래와 같은 dependency를 주었다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.fasterxml.jackson.core&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>jackson-databind&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>2.5.1&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="controller">Controller&lt;/h2>
&lt;p>아래와같이 &lt;code>@ResponseBody&lt;/code> 어노테이션을 설정해주고 리턴은 해당 모델을 넘기면 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@RequestMapping&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/test&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nd">@ResponseBody&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Object&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">test&lt;/span>&lt;span class="p">(){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> 	&lt;/span>&lt;span class="n">Map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Object&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HashMap&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Object&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> 	&lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;111&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> 	&lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">222&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> 	&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>그리고 호출을 해보면 기대했던것처럼 이쁘게 json형태로 나온다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;111&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">222&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>물론, list 나 array, 일반 객체도 가능하다.&lt;/p>
&lt;h2 id="정리">정리&lt;/h2>
&lt;p>삽질을 끝에 알게된 사실(?)을 정리해보자.
다른측면에서 분석을 해보면. &lt;code>@ResponseBody&lt;/code>을 이용하여 view 에 json 형태로 나타내고자 할 경우 가능한 상황은 &lt;code>toString&lt;/code>으로 했을때 json형태로 나올수 있으면 가능하다. 예로들어 아래처럼 클래스에 Lombok 어노테이션인 &lt;code>@Data&lt;/code>가 붙으면 자동으로 &lt;code>toString&lt;/code>을 오버라이딩 해주기 때문에 해당 클래스를 리턴하게되면 자동으로 json 처리가 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Student&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>@ResponseBody&lt;/code>을 붙이고 &lt;code>List&amp;lt;Student&amp;gt;&lt;/code>를 리턴하게 되면 에러가 나는데, 이럴경우 별도 라이브러리를 추가해줘야 자동으로 변환되어 json 형태로 나올수 있게 된다. (list.toString을 하면 json형태가 아닌 이상한 문자형태로 나오기 때문&amp;hellip; Map같은것도 마찬가지 이유로 별도 라이브러리를 추가해줘야 정상적으로 나온다.)&lt;/p>
&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>단순히 &lt;strong>@ResponseBody를 사용해서 json으로 리턴하려면 어떤 라이브러리를 추가해야한다&lt;/strong> 로 생각했던것에서, 이것저것 테스트 한 결과 &lt;strong>toString을 할수 있어야 하고 그 값이 json형태이면 가능하다&lt;/strong> 로 결론이 지어졌다. 확실히 장님 코끼리 만지듯이 &amp;lsquo;그런가보다&amp;rsquo;하고 넘어가면 삽질이 진짜 불필요한 삽질이 되는것 같다. 구글링을 해보고, 테스트를 해봐서, 결론적으로 &lt;code>내것&lt;/code>으로 만드는 습관을 가져야 겠다.&lt;/p></description></item><item><title>jsp include</title><link>https://taetaetae.github.io/2017/01/04/20170104/</link><pubDate>Wed, 04 Jan 2017 18:36:17 +0000</pubDate><guid>https://taetaetae.github.io/2017/01/04/20170104/</guid><description>&lt;h2 id="1-디렉티브방식">1. 디렉티브방식&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-jsp" data-lang="jsp">&amp;lt;%@ include file=&amp;#34;~~&amp;#34;%&amp;gt;
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>정적 include 방식, 인클루드 되는 내용이 단순하게 텍스트로 포함되어 컴파일이 된다. (복사된다는 느낌)&lt;/li>
&lt;/ul>
&lt;!-- more -->
&lt;ul>
&lt;li>주의할점은 비록 포함되는 페이지라 하더라도 한글을 제대로 처리하기 위해서는 포함되어지는 jsp파일 상단에 인코딩 명시를 해줘야 한다.&lt;/li>
&lt;li>포함되어지는 jsp 내용이 변경이 될 경우 해당 jsp를 사용하는 jsp를 강제로 변경(touch) 해줘서 다시 컴파일이 되게 해야하는 불편함이 있다.&lt;/li>
&lt;li>정적 방식이기 때문에 예로들어 전역변수를 인클루드 되는 jsp에서 지정하게 되면 상위jsp에서 사용이 가능하게 된다.&lt;/li>
&lt;/ul>
&lt;h2 id="2-액션태그-방식">2. 액션태그 방식&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-jsp" data-lang="jsp">&amp;lt;jsp:include page=&amp;#34;~~&amp;#34;/&amp;gt;
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>동적 include 방식, 포함하는 문서와 상관없이 동적으로 컴파일 된다. (완전히 별도로 동작하기 때문에 변수를 동시에 사용하려면 따로 파라미터로 넘겨줘야 한다.)&lt;/li>
&lt;li>&lt;code>flush&lt;/code> 옵션은 요청흐름이 넘어가면서 현재까지 페이지의 결과를 출력할 것인지 말것인지를 결정하는것이다. 일반적으로 &lt;code>false&lt;/code>로 설정한다.&lt;/li>
&lt;li>&lt;code>&amp;lt;jsp:param&amp;gt;&lt;/code>를 이용하여 파라미터를 전송할수 있다.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-jsp" data-lang="jsp">&amp;lt;jsp:include page=&amp;#34;...&amp;#34; flush=&amp;#34;false&amp;#34;&amp;gt;
 &amp;lt;jsp:param name=&amp;#34;name&amp;#34; value=&amp;#34;이름&amp;#34; /&amp;gt;
 &amp;lt;jsp:param name=&amp;#34;pageName&amp;#34; value=&amp;#34;페이지이름&amp;#34;/&amp;gt;
&amp;lt;/jsp:include&amp;gt;
&lt;/code>&lt;/pre>&lt;h2 id="3-jstl-방식">3. JSTL 방식&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-jsp" data-lang="jsp">&amp;lt;c:import url=&amp;#34;~~&amp;#34; /&amp;gt;
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>JSTL(JSP Standard Tag Library) 태그중의 하나&lt;/li>
&lt;li>컴파일 되고 동작하는 방식은 액션태그&lt;code>&amp;lt;jsp:include page=&amp;quot;~~&amp;quot;/&amp;gt;&lt;/code>와 같음&lt;/li>
&lt;li>현재 컨테이너 안에 있는 자원외에 다른 외부 자원도 포함이 가능하다.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-jsp" data-lang="jsp">&amp;lt;c:import url=&amp;#34;http://www.google.com/&amp;#34;/&amp;gt;
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>아래와 같이 보다 더 다양한 옵션이 제공된다&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>&amp;lt;c:import! url=&amp;#34;읽어올 URL&amp;#34;
 var=&amp;#34;읽어올 데이터를 저장할 변수명&amp;#34;
 scope=&amp;#34;변수의 공유 범위&amp;#34;
 varReader=&amp;#34;리소스의 내용을 Reader 객체로 읽어올 때 사용&amp;#34;
 charEncoding=&amp;#34;읽어온 데이터의 캐릭터셋 지정&amp;#34; /&amp;gt;
&lt;/code>&lt;/pre></description></item><item><title>Spring Transaction 옵션</title><link>https://taetaetae.github.io/2016/10/08/20161008/</link><pubDate>Sat, 08 Oct 2016 18:04:19 +0000</pubDate><guid>https://taetaetae.github.io/2016/10/08/20161008/</guid><description>&lt;h3 id="상황">상황&lt;/h3>
&lt;p>스프링 환경에서 일반적으로 DAO 나 BO 레벨에서 다음과 같이 코딩을 하게 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@Transactional&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">isolation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Isolation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">READ_COMMITTED&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">propagation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Propagation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">REQUIRED&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rollbackFor&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">throws&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sqlMapClient&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;~~~~&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>무분별하게 Ctrl+C,V 신공으로 트랜잭션 어노테이션을 가져다가 사용할수도 있겠으나, 각 값들이 어떤 역활을 아는지에 대해 알고 넘어갈 필요성이 있다.&lt;/p>
&lt;h3 id="transactional">@Transactional&lt;/h3>
&lt;p>우선 해당 어노테이션을 적용하면 적용된 클래스 또는 메소드에 트랜잭션이 적용된다. 따라서 로직 흐름에 맞추어 전체적으로 트랜잭션을 적용할것인지, 아니면 특정 메소드에 적용할것인지 전략을 잘 세워야 한다.&lt;/p>
&lt;h3 id="isolation">isolation&lt;/h3>
&lt;p>격리수준이라는 옵션이다. 트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준을 말하는데 옵션은 다음과 같다.&lt;/p>
&lt;ol>
&lt;li>READ_UNCOMMITTED (level 0)&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>트랜잭션에 처리중인 혹은 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 것을 허용&lt;/li>
&lt;li>어떤 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 B라는 아직 완료되지 않은(Uncommitted 혹은 Dirty) 데이터 B를 읽을 수 있다.
&lt;blockquote>
&lt;p>Dirty read : 위와 같이 다른 트랜잭션에서 처리하는 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상을 dirty read 라고 하며, READ UNCOMMITTED 격리수준에서만 일어나는 현상&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>READ_COMMITTED (level 1)&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>dirty read 방지 : 트랜잭션이 커밋되어 확정된 데이터만을 읽는 것을 허용&lt;/li>
&lt;li>어떠한 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 해당 데이터에 접근할 수 없다.&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>REPEATABLE_READ (level 2)&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능하다.&lt;/li>
&lt;li>선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신하거나 삭제하는 것을 불허함으로써 같은 데이터를 두 번 쿼리했을 때 일관성 있는 결과를 리턴함&lt;/li>
&lt;/ul>
&lt;ol start="4">
&lt;li>SERIALIZABLE (level 3)&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>완벽한 읽기 일관성 모드를 제공&lt;/li>
&lt;li>데이터의 일관성 및 동시성을 위해 MVCC(Multi Version Concurrency Control)을 사용하지 않음(MVCC는 다중 사용자 데이터베이스 성능을 위한 기술로 데이터 조회 시 LOCK을 사용하지 않고 데이터의 버전을 관리해 데이터의 일관성 및 동시성을 높이는 기술)&lt;/li>
&lt;li>트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다.&lt;/li>
&lt;/ul>
&lt;h3 id="propagation--전파옵션">propagation ( 전파옵션)&lt;/h3>
&lt;ul>
&lt;li>REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성&lt;/li>
&lt;li>REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성&lt;/li>
&lt;li>SUPPORT : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 nontransactionally로 실행&lt;/li>
&lt;li>MANDATORY : 부모 트랜잭션 내에서 실행되며 부모 트랜잭션이 없을 경우 예외가 발생&lt;/li>
&lt;li>NOT_SUPPORT : nontransactionally로 실행하며 부모 트랜잭션 내에서 실행될 경우 일시 정지&lt;/li>
&lt;li>NEVER : nontransactionally로 실행되며 부모 트랜잭션이 존재한다면 예외가 발생&lt;/li>
&lt;li>NESTED : 해당 메서드가 부모 트랜잭션에서 진행될 경우 별개로 커밋되거나 롤백될 수 있음. 둘러싼 트랜잭션이 없을 경우 REQUIRED와 동일하게 작동&lt;/li>
&lt;/ul>
&lt;h3 id="no-rollback-for---예외처리-기본값--없음">no-rollback-for - 예외처리 (기본값 : 없음)&lt;/h3>
&lt;p>특정 예외가 발생하더라도 롤백되지 않도록 설정&lt;/p>
&lt;h3 id="스프링-배치에서의-트랜잭션-내가-당했던-문제">스프링 배치에서의 트랜잭션 (내가 당했던(?) 문제)&lt;/h3>
&lt;p>스프링 배치에서는 Tasklet 에서 기본적으로 step 단위 트랜잭션을 지원하고 있다고 한다.
기본적으로 job이 하나의 tasklet 의 step 으로 실행하다보니 명시적이진 않지만 내부적으로 전체 트랜잭션으로 걸려있게 된다. 나같은 job 내 DAO delete 메소드에서 @Transactional 설정을 하고 그 DAO 메소드를 반복문에 의해 delete 하는 로직을 수행하는 부분이 있었는데 부모의 트랜잭션(tasklet에서 설정된 트랜잭션)으로 인해 dao 를 몇번 호출하던 job단위로 트렌젝션이 걸리게 되었다. (결국 트랜잭션은 반복문이 다 끝나야 적용이 된다는점.)
그러다보니 가끔 DB Query Lock이 걸렸는데 DB레벨에서 undolog를 남기는게 너무 무거워져 lock이 발생&lt;/p>
&lt;p>따라서 전파옵션을 수정해서 해당 문제를 해결하였다.&lt;/p>
&lt;pre tabindex="0">&lt;code># 기존
begin 
delete &amp;lt; for 반복문
commit

# 전파옵션 수정 (기존 REQUIRES 에서 REQUIRES_NEW 으로 수정)
for
 begin
 delete
 commit
for end 
&lt;/code>&lt;/pre></description></item><item><title>디자인패턴-싱글톤</title><link>https://taetaetae.github.io/2016/10/06/20161006/</link><pubDate>Thu, 06 Oct 2016 17:03:48 +0000</pubDate><guid>https://taetaetae.github.io/2016/10/06/20161006/</guid><description>&lt;p>디자인 패턴중에 가장 잘 알려진 싱글톤 에 대해서 알아보고자 한다. 멀티 스레드 환경에서 자주 이용되는 패턴이라고만 들었는데 이번 기회를 통해 제대로 정리해보자&lt;/p>
&lt;h2 id="싱글톤이-무엇인가">싱글톤이 무엇인가&lt;/h2>
&lt;blockquote>
&lt;p>싱글톤(Singleton)은 정확히 하나의 인스턴스만 생성되는 클래스이다.&lt;/p>&lt;/blockquote>
&lt;p>라고 이펙티브 자바에서 정의되어있다. 즉, 딱 하나만 생성하고 이를 여기저기서 사용하는 패턴이라 생각하면 될듯 하다. 싱글 스레드 환경에서는 당연히 인스턴스를 공유할 상황이 없겠지만 대부분 멀티 스레드 환경이기 때문에 싱글톤 패턴은 아주 중요한 부분이다.&lt;/p>
&lt;h3 id="아주-고전적인-방법-위험한-방법">아주 고전적인 방법 (위험한 방법)&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Singleton&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Singleton&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">Singleton&lt;/span>&lt;span class="p">(){}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Singleton&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getInstance&lt;/span>&lt;span class="p">(){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">			&lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Singleton&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">		&lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">	&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>위와 같은 상황에서 if절을 도달하는 시점이 각 스레드마다 다를경우 문제가 발생할 수 있다.(교묘한 시점에 객체가 1개 이상 반환될 여지가 있음) 이를 해결하기 위해서는 다음과 같이 getInstance()를 동기화 해주면 된다. 하지만 불필요하게 동기화 하는 오버헤드만 증가하게 된다.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">Singleton&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Singleton&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">Singleton&lt;/span>&lt;span class="p">(){}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">synchronized&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Singleton&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getInstance&lt;/span>&lt;span class="p">(){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">){&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Singleton&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">uniqueInstance&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="public-static-인스턴스로-생성">public static 인스턴스로 생성&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalCache&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sharedObject&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalCache&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">LocalCache&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이코드는 간단하다는 장점이 있는 반면에 유연하지 못한 부분이 있다. (아래 이어서 설명)&lt;/p>
&lt;h3 id="private-static-final-인스턴스로-생성">private static final 인스턴스로 생성&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalCache&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sharedObject&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalCache&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">LocalCache&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalCache&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">getInstance&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sharedObject&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>이렇게 하면 factory 메소드를 통해 객체를 반환받고, 반환 받는 시점에 다양한 작업들을 할수 있다.&lt;/p>
&lt;h3 id="enum-으로-생성">enum 으로 생성&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">enum&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LocalCacheEnum&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">LocalCache&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">//etc another functions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>잘 사용하지는 않지만 가장 좋은 세번째 방법인 enum으로 클래스를 만드는 방법이라고 한다. 복잡한 직렬화나 리플렉션(reflection) 상황에서도 직렬화가 자동으로 지원되고, 인스턴스가 여러개 생기지 않도록 확실하게 보장해준단다. (by effective java)&lt;/p>
&lt;h2 id="그럼-어디서-사용될까">그럼 어디서 사용될까&lt;/h2>
&lt;ol>
&lt;li>static 으로 선언해서 공통적으로 사용되는 부분이나 환경설정&lt;/li>
&lt;li>내용이 변경되면 다른 클래스에서도 그 부분이 똑같이 적용되어 실행되어야 할때&lt;/li>
&lt;li>자주 사용되는 부분을 싱글톤으로 만들어 생성되는 시간을 줄일때&lt;/li>
&lt;li>스프링에서의 DB커넥션 로직&lt;/li>
&lt;/ol></description></item><item><title>hexo 환경 구축하기</title><link>https://taetaetae.github.io/2016/09/23/20160923/</link><pubDate>Fri, 23 Sep 2016 10:26:53 +0000</pubDate><guid>https://taetaetae.github.io/2016/09/23/20160923/</guid><description>&lt;h3 id="개요">개요&lt;/h3>
&lt;p>&lt;a href="https://taetaetae.github.io/2016/09/18/hexo_github_blog" rel="">이전포스팅&lt;/a> 에서 이야기 한것과 같이 어느곳에서든지&lt;code>(집 또는 회사 등)&lt;/code> 블로그 포스팅을 할수 있는 환경을 만들고 싶었다. (git을 이용해서.) 그래서 git repository 를 두개를 만들었고, 하나는 실제 블로그서버로 이용하고 하나는 블로그를 포스팅하는 hexo 환경을 저장하게 된다. 지금부터 이야기 할 내용은 hexo환경을 git repository 에서 pull 받아서 환경구성하는 부분을 이야기 하려고 한다.&lt;/p>
&lt;h3 id="환경구성">환경구성&lt;/h3>
&lt;p>&lt;code>hexo설치와 git설치는 되어있다고 가정.&lt;/code>
먼저 구성할 폴더를 생성하고 이 폴더에 hexo 환경을 구성하겠다고 초기 셋팅을 한다&lt;/p>
&lt;pre tabindex="0">&lt;code>mkdir blog
hexo init blog
&lt;/code>&lt;/pre>&lt;p>그리고 hexo환경을 저장해둔 repository를 가져와야 하므로 git설정을 한다&lt;/p>
&lt;pre tabindex="0">&lt;code>cd blog/
git init
git remote add origin https://github.com/taetaetae/hexo.git
git fetch
&lt;/code>&lt;/pre>&lt;p>필요없는&lt;code>초기셋팅이 되는 파일&lt;/code>은 지우고&lt;/p>
&lt;pre tabindex="0">&lt;code>rm source/_posts/hello-world.md
rm -r themes/landscape/ #해당 테마를 사용하고 있다면 지울필요가 없다.
&lt;/code>&lt;/pre>&lt;p>hexo환경 repository 를 pull받는다&lt;/p>
&lt;pre tabindex="0">&lt;code>git reset --hard origin/master
git pull origin master
&lt;/code>&lt;/pre>&lt;p>hueman테마의 검색 기능을 사용한다는 가정하에 필요한 플러그인과, 나중에 deploy 할때 필요한 플러그인을 설치해준다&lt;/p>
&lt;pre tabindex="0">&lt;code>npm install hexo-deployer-git --save
npm install -S hexo-generator-json-content
&lt;/code>&lt;/pre>&lt;p>이렇게 되면 기존처럼 환경설정이 마무리 되고, 포스팅을 할수 있게 된다.&lt;/p>
&lt;h3 id="추가">추가&lt;/h3>
&lt;ul>
&lt;li>canonical 속성
npm install &amp;ndash;save hexo-auto-canonical&lt;/li>
&lt;li>사이트맵 속성
npm install hexo-generator-seo-friendly-sitemap &amp;ndash;save&lt;/li>
&lt;li>feed 속성
npm install hexo-generator-feed &amp;ndash;save&lt;/li>
&lt;/ul></description></item><item><title>hexo + github + blog 연동하기</title><link>https://taetaetae.github.io/2016/09/18/hexo_github_blog/</link><pubDate>Sun, 18 Sep 2016 15:38:34 +0000</pubDate><guid>https://taetaetae.github.io/2016/09/18/hexo_github_blog/</guid><description>&lt;h2 id="들어가기에-앞서">들어가기에 앞서&lt;/h2>
&lt;p>예전부터 블로그를 운영해야지 하구서 tistory, naver blog 등 다양한 플랫폼으로 시작을 했었지만 이렇다할 운영이 안되었고 &lt;del>사실 열정이 부족했었다.&lt;/del> 직접 홈페이지를 만들기에는 너무 많은 허들이 있다보니 (서버구축, 호스팅, 도메인 등 &amp;hellip;) 계속 차일피일 미루고 있었다.
그러다 github에서 제공하는 pages라는 걸 이용해서 무료로 도메인과 웹호스팅을 할수 있다는 부분을 알게되었고, 거기에다 jekyll을 이용하면 설치형 블로그를 운영할수 있다는것에 놀라웠다. 하지만 jekyll을 적용해보려고 이것저것 하다보니 ruby라는 언어로 만들어져있고 커스터마이징이 어렵다는 부분을 확인, 좀더 알아보다 hexo 라는 걸로 해당 블로그를 만들게 되었다.
필자처럼 남들과는 다른 블로그를 만들고 싶거나, git command 공부도 하면서 블로그를 운영해볼 사람들은 해당 글을 천천히 따라오면 좋을것 같다.&lt;/p>
&lt;h3 id="hexo-시작하기">hexo 시작하기&lt;/h3>
&lt;p>hexo 라는걸 시작하기 위해 몇가지 준비물이 있다.&lt;/p>
&lt;ol>
&lt;li>&lt;a href="http://nodejs.org" target="_blank" rel="noopener noreffer ">node 설치&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://git-scm.com" target="_blank" rel="noopener noreffer ">git 설치&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://github.com" target="_blank" rel="noopener noreffer ">github&lt;/a>에 블로그로 사용할 빈 repository 생성&lt;/li>
&lt;li>&lt;a href="http://github.com" target="_blank" rel="noopener noreffer ">github&lt;/a>에 hexo 설정을 저장할 빈 repository 생성&lt;/li>
&lt;/ol>
&lt;p>위 4가지(?!)가 전부 설치 되었다고 가정을 하고 시작을 해보겠다.&lt;/p>
&lt;h3 id="hexo-설치">hexo 설치&lt;/h3>
&lt;p>간단하다. &lt;a href="http://hexo.io" target="_blank" rel="noopener noreffer ">hexo&lt;/a> 페이지에도 나와있는것처럼 아래 명령어를 실행해주면 된다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ npm install -g hexo-cli
&lt;/code>&lt;/pre>&lt;h3 id="블로그로-운영할-폴더-hexo-초기화">블로그로 운영할 폴더 hexo 초기화&lt;/h3>
&lt;p>폴더 구조로 구성이 되기 때문에 임의의 폴더를 하나 만들고 해당 폴더를 hexo 명령어로 초기화 시켜준다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ mkdir &amp;lt;디렉토리명&amp;gt;
$ hexo init &amp;lt;디렉토리명&amp;gt;
&lt;/code>&lt;/pre>&lt;h3 id="로컬서버-띄워보기">로컬서버 띄워보기&lt;/h3>
&lt;p>이제 로컬에서 서버를 띄워서 블로그가 어떻게 나오는지 확인을 해보면 된다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ hexo s (or server)
&lt;/code>&lt;/pre>&lt;p>간혹 서버가 실행이 안되거나 오류가 발생, 수정한 부분이 반영이 안된다면 clean 명령어를 한번 해준 다음에 다시 서버를 실행해주면 되는 경우도 있다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ hexo clean
$ hexo s (or server)
&lt;/code>&lt;/pre>&lt;p>http://localhost:4000 을 접속해서 정상적으로 페이지가 나오는지 확인을 해보자.
페이지가 정상적으로 나온다면 성공!&lt;/p>
&lt;h3 id="글-작성">글 작성&lt;/h3>
&lt;p>아래 명령어를 실행하면 /source/_post/ 아래에 .md 파일이 생성이 된다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ hexo new &amp;lt;글 제목&amp;gt;
&lt;/code>&lt;/pre>&lt;p>해당 파일을 사용하기 편한 에디터로 열어서 마크다운 문법에 맞추어 수정을 하면 끝!&lt;/p>
&lt;h3 id="왜-두개의-repository가-필요한가">왜 두개의 repository가 필요한가&lt;/h3>
&lt;p>아래에서 이야기 하겠지만, 하나는 실제 블로그 내용이 올라갈 저장소이고 다른 하나는 블로그를 운영하고 있는 hexo 자체를 저장할 저장소이다. hexo 정보를 가지고 있지 않을꺼라면&lt;code>(필자처럼 다양한 PC에서 업로드 환경을 구축하지 않을꺼라면)&lt;/code> 하나의 레파지토리만 필요할수도 있다.&lt;/p>
&lt;h3 id="github-셋팅">github 셋팅&lt;/h3>
&lt;blockquote>
&lt;p>지금부터가 알짜배기다. 즉 이글을 포스팅 하는 의미. 다른 글들에서도 hexo 사용법을 친절하게 알려 주셨으나 github와의 연동, 그리고 어떤식으로 운영해야할지는 찾기 힘들었다. 필자는 감으로 그런가보다(?)하고서 터득한 바를 공유하려한다. (이게 정답은 아니지만, 나는 이렇게 사용하는게 맞겠다 싶어..)&lt;/p>&lt;/blockquote>
&lt;p>일반적으로 github에서 블로그로 사용할 repository를 만들게 되면 http://(github아이디).github.io/(repository이름) 으로 블로그가 만들어 지는데 뭔가 조금 이상해서 &lt;del>간지가 안나서&lt;/del> 찾고 찾아서 아래와 같은 방식으로 하게 되었다.
필자의 github 아이디는 taetaetae 이고 도메인은 아이디 그대로를 사용하여 &lt;a href="http://taetaetae.github.io" target="_blank" rel="noopener noreffer ">http://taetaetae.github.io&lt;/a> 으로 사용하고 싶었다. 따라서 github에 repository이름을 taetaetae.github.io로 만들어야 한다. 여기까지만 하면 일단 github에 배포할수 있는 준비가 되어있는 상태&lt;/p>
&lt;h3 id="hexo로-배포하기">hexo로 배포하기&lt;/h3>
&lt;p>포스팅한 글이 정상적으로 등록이 된 것을 로컬서버에서 확인이 되었으면 이 상태를 조금전 만든 github repository으로 배포(정확히 말하면 git push)해줘야 한다. 그전에 최상위 폴더에 있는 _config.yml 파일을 열어서 github 정보를 입력해 줘야 한다.
하단 영역 Deployment 부분에 다음과 같이 작성하고 저장한다.&lt;/p>
&lt;pre tabindex="0">&lt;code># Deployment
deploy:
 type: git
 repo: https://github.com/taetaetae/taetaetae.github.io
 branch: master
&lt;/code>&lt;/pre>&lt;p>그 다음 hexo 에서 github로 배포할수있는 플러그인을 설치해준다.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ npm install hexo-deployer-git --save
&lt;/code>&lt;/pre>&lt;p>이제 설정한 github에 배포를 하면 끝!&lt;/p>
&lt;pre tabindex="0">&lt;code>$ hexo deploy
&lt;/code>&lt;/pre>&lt;p>1분~3분 뒤에 도메인을 접속하여 정상적으로 페이지가 나오는지 확인하고, github에 파일들이 push가 잘 되었는지를 확인한다.&lt;/p>
&lt;h3 id="향후-관리-hexo-정보-저장">향후 관리 hexo 정보 저장&lt;/h3>
&lt;p>나중에 다른 PC에서도 블로그를 포스팅 할 경우가 있으니 hexo를 이용하여 포스팅 한 환경 자체를 저장 해야할 필요가 생겼다. 따라서 만들었던 폴더 또한 github에 업로드를 해놓는게 좋을것 같다 (지극히 개인적인 생각)
&lt;code>git command 설명은 따로 정리하지 않겠다. &lt;/code>&lt;/p></description></item></channel></rss>