<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Archives-2018 on</title><link>https://taetaetae.github.io/tags/archives-2018/</link><description>Recent content in Archives-2018 on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Mon, 31 Dec 2018 21:33:29 +0000</lastBuildDate><atom:link href="https://taetaetae.github.io/tags/archives-2018/index.xml" rel="self" type="application/rss+xml"/><item><title>2018 회고 - Coder가 아닌 Programmer로</title><link>https://taetaetae.github.io/2018/12/31/review-2018/</link><pubDate>Mon, 31 Dec 2018 21:33:29 +0000</pubDate><guid>https://taetaetae.github.io/2018/12/31/review-2018/</guid><description>&lt;p>매사에 행동하는 모든것들의 끝자락에서는 그동안 잘한것과 못한것을 다시 생각하며 잘한것은 보다 더 잘할수 있도록 하고 못한것은 왜 못했는지 그리고 어떻게 하면 못한 부분을 고칠수 있을지에 대한 시간을 갖으려고 노력해왔다. 그게 개발이 되었든 게임이 되었든 연인과의 데이트가 되었든 뭐든지. &lt;!-- more -->이러한 시간들은 필자에게 큰 인사이트를 얻을 수 있게 되었고 지난 한해를 돌이켜 보자면 개인적으로 계획한 전부를 다 이뤄내지는 못했지만 나름의 많은 경험과 성과를 달성했다고 생각해본다.
이제 몇시간 뒤면 올해가 끝나고 새로운 한 해가 시작되는 이 시점에 &lt;code>개발자로써의 회고&lt;/code>를 해보며 2018년 정리 및 2019년 목표를 다짐해보자.&lt;/p>
&lt;h2 id="글쓰는-개발자가-되자-개인-블로그-운영">글쓰는 개발자가 되자. 개인 블로그 운영&lt;/h2>
&lt;p>아주 오래전, 동기 형을 통해 &lt;code>개발자가 글을 써야하는 중요성&lt;/code>에 대해 절실하게 배우게 되었고 그때부터 블로그를 운영하기 시작하였다. 그 동기형의 말에 조금 더 내 생각을 첨가하자면 글을 쓰다보면 누군가 내 글을 본다는 마음에 내가 알고있는 지식을 보다 더 깊게 공부하게 되고 그것들이 모여 내 개발 히스토리가 만들어 지며 포트폴리오 등 다양하게 활용할 수 있기에 블로그를 운영하는건 정말 좋은 선택지 였던것 같다. 실제로 그냥 구글링 해서 알게된 것과는 또 다른 배움이 있었기 때문이다.
회사 일 그리고 개인 공부를 하면서 적어도 한달에 한가지 이상은 배우게 되기 때문에 올해 초 한달에 한개 이상의 글을 쓰기로 결심하였다.(그 달의 글이 없다면 뭔가 놀았거나(?) 미친듯이 바빴거나 아니면 게을렀거나&amp;hellip;) 블로그에 글을 쓴 내역을 그래프로 시각화 해보면 아래처럼 총 23개의 글을 작성하였고 월 평균 1.9개의 글을 작성하게 된것을 볼수 있다.&lt;/p>
&lt;blockquote>
&lt;p>9월달엔 팀 옮기자마자 엄청 바빴고, 11월엔 그 바쁜게 결실을 맺는 시간&amp;hellip; 이라 핑계를&amp;hellip; (나중에 블로깅 예정, 병렬 프로그래밍 관련)&lt;/p>&lt;/blockquote>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/review-2018/post_count.jpg" title="/images/review-2018/post_count.jpg" data-thumbnail="/images/review-2018/post_count.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/review-2018/post_count.jpg"
 data-srcset="https://taetaetae.github.io/images/review-2018/post_count.jpg, https://taetaetae.github.io/images/review-2018/post_count.jpg 1.5x, https://taetaetae.github.io/images/review-2018/post_count.jpg 2x"
 data-sizes="auto"
 alt="/images/review-2018/post_count.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">월별 글 작성수&lt;/figcaption>
 &lt;/figure>
&lt;p>위 결과만을 두고 봤을땐 많으면 많고 적으면 적다고 할 수 있는 결과지만 개인적으로는 자투리 시간을 활용해서 그간 배웠던것, 그리고 경험했지만 내것으로 만들지 못하고 보기만 하며 넘어간것들에 대해 귀찮지만 시간을 투자하고 정리했더라면 더 많은 글을 썼을것 같다는 조금 아쉬운 결과라고 생각이 든다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/review-2018/blog_ga.jpg" title="/images/review-2018/blog_ga.jpg" data-thumbnail="/images/review-2018/blog_ga.jpg" data-sub-html="&lt;h2>주 단위 PV, 누군가 내 글을 보고 있다는것에 뿌듯함&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/review-2018/blog_ga.jpg"
 data-srcset="https://taetaetae.github.io/images/review-2018/blog_ga.jpg, https://taetaetae.github.io/images/review-2018/blog_ga.jpg 1.5x, https://taetaetae.github.io/images/review-2018/blog_ga.jpg 2x"
 data-sizes="auto"
 alt="/images/review-2018/blog_ga.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">주 단위 PV, 누군가 내 글을 보고 있다는것에 뿌듯함&lt;/figcaption>
 &lt;/figure>
&lt;p>나름 열심히 글을 쓴 결과일까, GA를 통해 본 필자의 블로그에 유입량이 점점 늘어나는것을 보며 하나를 쓰더라도 좀더 자세히 독자의 입장에서 써야겠다고 다시한번 다짐하게 된다. 다만 글을 &amp;ldquo;많이&amp;rdquo; 쓰는것보다 하나를 작성하더라도 원인과 근거를 들어가며 문제를 정확히 파악하는데 집중을 해야하고, 단순 사용법 나열이 아닌 실제로 경험을 해가면서 &amp;ldquo;내것&amp;quot;으로 만드는 과정이 필요하겠다.&lt;/p>
&lt;h2 id="회사-팀-변경-그리고-토이-프로젝트">회사 팀 변경 그리고 토이 프로젝트&lt;/h2>
&lt;p>기존에 아무것도 없던 환경에서 서버 발급부터 이런 저런 서비스에 도움이 되는 다양한 모니터링 툴을 개발하며 무사히 서비스를 오픈을 하였고, 약간의 매너리즘이 생겨날 즈음 좋은 기회가 생겨 성격이 전혀 다른 서비스를 하는 팀을 옮기게 되었다. 약간 이직과도 비슷한 느낌으로 팀을 옮기게 되었는데 처음엔 새로운 지식을 습득해야 하는 두려움도 있었고 기존 서비스에 애정이 많아서 고민이 많았지만 벌써 옮긴지 5개월이 지나고 돌이켜보면 올해 가장 잘한 일 중 하나가 아닐까하는 생각이 든다. 전 팀에선 서비스를 운영하는데 그쳤지만 지금 내가 있는 곳은 대용량 서비스를 성능측면에서, 그리고 아키텍쳐 측면에서 보다 효율적으로 개발하는데 집중을 하려는 모습들이 보이기 때문이다. 더불어 팀에 투입되자마자 필자 홀로 기존에 있던 병렬 프로세스를 개선하여 서비스적으로 약 90%의 개선효과를 볼수있었는데 이 부분은 추후 포스팅 할 예정이다.
그리고 팀을 옮기기 한두달 전 개인적인 여유시간이 많이 있었고, 다른사람들의 블로그를 보며 챙겨보고 싶은 마음에 &lt;a href="http://daily-devblog.com" target="_blank" rel="noopener noreffer ">토이 프로젝트&lt;/a>를 만들게 되었다. 7월 중순부터 시작했으니 이것도 어느덧 반년이 지나고 있는데 운영을 해가면서 기능을 추가하기 위해 종종 밤을 새는 등 올 한해있어 꽤 많은것을 얻을수 있었던 시간이였다. 간혹 버그가 생겨 메일이 발송 안되면 지인 또는 모르는 분들이 메일로 제보도 해주시고 &amp;hellip; 색다른 경험이였다. 자세한 내용 및 후기는 &lt;a href="https://taetaetae.github.io/2018/08/05/daily-dev-blog-1/" target="_blank" rel="noopener noreffer ">개발후기-1&lt;/a> 과 &lt;a href="https://taetaetae.github.io/2018/08/09/daily-dev-blog-2/" target="_blank" rel="noopener noreffer ">개발후기-2&lt;/a>에서 확인 가능하다. (어서 3편을 쓰고 마무리를 지어야 할텐데&amp;hellip;) 그리고 최근에는 &lt;a href="http://daily-devblog.com/archive" target="_blank" rel="noopener noreffer ">아카이빙&lt;/a> 기능을 만들어 과거 글을 조회할수 있도록 만들었는데 2% 부족한 느낌이다&amp;hellip; (맘같아서는 형태소 분석을 해서 자동 필터링도 해보고 싶은데&amp;hellip;)&lt;/p></description></item><item><title>엘라스틱서치 12월 서울 밋업 후기</title><link>https://taetaetae.github.io/2018/12/13/elastic-meetup-201812/</link><pubDate>Thu, 13 Dec 2018 22:27:02 +0000</pubDate><guid>https://taetaetae.github.io/2018/12/13/elastic-meetup-201812/</guid><description>&lt;p>엘라스틱을 처음 접하게 된 건 2017년 여름 facebook 피드에 &amp;ldquo;Elastic Stack을 이용한 서울시 지하철 대시보드&amp;rdquo; 라는 &lt;a href="https://www.elastic.co/kr/blog/seoul-metro-2014" target="_blank" rel="noopener noreffer ">링크&lt;/a>를 보게 된 것부터인 것 같다. 그 당시 데이터 분석 및 자동화에 관심이 커지고 있던 찰나였는데 &lt;!-- more -->키바나로 간단하면서도 아주 멋진 대시보드를 그릴 수 있다는 게 너무 흥미롭게 다가왔고 거기다 실시간으로 볼수 있다는 점에 공부를 시작하지 않을 수 없었다. 그렇게 이것저것 만들어 보기도 하고 한국 엘라스틱서치 커뮤니티 활동을 해오던 찰나 (최근들어 눈팅만 하고 있지만&amp;hellip;) 올해 마지막 밋업을 한다고 하여 참여하게 되었다.&lt;/p>
&lt;h2 id="여기어때-본사-방문">여기어때 본사 방문&lt;/h2>
&lt;p>강남에 위치한 여기어때 본사에서 밋업을 하게 되어 덕분에 다른 회사 구경을 할 수 있게 되었다. 예전 다른 IT 스타트업 밋업 행사에서도 느꼈던 부분인데 엄청나게 큰 시설은 아니지만 아기자기하게 회사의 색깔과 특징을 잘 살려놓은 인테리어가 인상적이었다. 그런데 생각보다 사람이 너무~ 많이 와서 약간 집중이 안 될것 같았지만 다행히도 자리를 잘 잡아서 세션을 듣는 데는 무리가 없었다. (정확하진 않지만 참석하신 분들 중의 절반 정도만 강의장에 들어오고 나머지는 밖에서 듣는 걸 보고 이런 IT 행사의 인기를 다시 한번 실감할 수 있었다.)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_1.jpg" title="/images/elastic-meetup-201812/elastic_1.jpg" data-thumbnail="/images/elastic-meetup-201812/elastic_1.jpg" data-sub-html="&lt;h2>&amp;lsquo;여기어때&amp;rsquo; 본사건물에서 엘라스틱 밋업을!&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_1.jpg"
 data-srcset="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_1.jpg, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_1.jpg 1.5x, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_1.jpg 2x"
 data-sizes="auto"
 alt="/images/elastic-meetup-201812/elastic_1.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">&amp;lsquo;여기어때&amp;rsquo; 본사건물에서 엘라스틱 밋업을!&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="엘라스틱서치-65-최신버전-소개-및-커뮤니티-회고">엘라스틱서치 6.5 최신버전 소개 및 커뮤니티 회고&lt;/h2>
&lt;p>행사 처음 세션으로 김종민 커뮤니티 엔지니어 분께서 엘라스틱의 최근 업데이트 정보와 커뮤니티 활동에 대해서 회고해주셨다. 내가 처음 엘라스틱서치를 접한 버전이 2.4였는데 벌써 6.5라니&amp;hellip; 빨라도 너무 빠르다. 이번 버전에서는 한 클러스터에서 다른 클러스터로의 인덱스를 복제하는 방법인 Cross-cluster replication (클러스터 복제) 기능이 추가되었고 ODBC Client 추가, 자바 11지원 등 여러 가지 기능이 추가되었다고 한다.
특히 키바나에서는 파일을 업로드하면 자동으로 분석해서 인덱싱을 해주는 기능도 생겼고 (파일 크기가 100메가 제한이라는게 살짝 아쉽긴 했다.) 캔버스, 스페이스 등 역시 키바나 라는 생각이 들 정도로 비주얼라이징을 한번더 업그레이드 한듯 하다. (다 사용할 수 있을까 하는 정도로&amp;hellip; 엘라스틱 스택을 들어보기만 하던 함께 참석한 동기 녀석도 당장 해보겠다고 할 정도로&amp;hellip;)
다른 자세한 내용은 &lt;a href="https://www.elastic.co/kr/blog/elastic-stack-6-5-0-released" target="_blank" rel="noopener noreffer ">여기&lt;/a>서 확인이 가능하다.&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_1.jpg" title="/images/elastic-meetup-201812/elastic_2_1.jpg" data-thumbnail="/images/elastic-meetup-201812/elastic_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/elastic-meetup-201812/elastic_2_1.jpg"
 data-srcset="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_1.jpg, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_1.jpg 1.5x, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_1.jpg 2x"
 data-sizes="auto"
 alt="/images/elastic-meetup-201812/elastic_2_1.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">너무나 빠른 버전업과 너무나 발빠르게 움직이는 사람들&lt;/figcaption>
 &lt;/figure>
&lt;h2 id="엘라스틱서치-활용사례">엘라스틱서치 활용사례&lt;/h2>
&lt;p>스마일게이트 및 여기어때 에서 엘라스틱 서치를 활용한 사례를 발표해 주셨다. 하지만 아쉽게도 필자는 5.6 버전까지밖에 사용한 게 전부여서인지(그것도 일부 기능만) 전체 발표 내용을 다 이해를 하진 못했지만 구축하면서 생긴 문제나 삽질 경험담을 공유해주셔서 간접적으로라도 그때의 현장감(?)을 느낄 수 있어 좋았고, 한편으로 여태까지 나름 엘라스틱서치를 만져봤다고 약간의 자신감 반 자만심 반으로 생각했었는데 역시 세상엔 고수가 많구나 하며 다시 분발해야겠다고 다짐했다.
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_2.jpg" title="/images/elastic-meetup-201812/elastic_2_2.jpg" data-thumbnail="/images/elastic-meetup-201812/elastic_2_2.jpg" data-sub-html="&lt;h2>스마일게이트 &amp;#43; 여기어때&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_2.jpg"
 data-srcset="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_2.jpg, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_2.jpg 1.5x, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_2_2.jpg 2x"
 data-sizes="auto"
 alt="/images/elastic-meetup-201812/elastic_2_2.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">스마일게이트 + 여기어때&lt;/figcaption>
 &lt;/figure>&lt;/p>
&lt;h2 id="마치며">마치며&lt;/h2>
&lt;p>커뮤니티 활동 회고 시간에 누가 페이스북 커뮤니티에서 &amp;ldquo;공유&amp;quot;라는 단어를 사용해서 게시글을 작성했는지 키바나로 보여주고 밋업에 온 사람이 있다면 5만원 여기어때 쿠폰을 준다고 했었다. 마침 키바나 대시보드 한쪽 구석에 필자의 이름이 보였지만 (예전에 나름 활발하게 질문도 하고 공유도 했던 적이 있어서&amp;hellip;) 쿠폰을 받는구나 하며 기대를 하고 있었지만 아쉽게도 최근에 작성한 몇 분에게만 선물이 돌아갔다&amp;hellip; 하지만 그 아쉬움도 잠시, 무작위로 추첨하여 또 쿠폰을 준다고 했는데 당첨이 되어서ㅎㅎ 감사하게도 쿠폰을 받는 기쁨을 누릴 수 있었다!!&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_3.jpg" title="/images/elastic-meetup-201812/elastic_3.jpg" data-thumbnail="/images/elastic-meetup-201812/elastic_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/elastic-meetup-201812/elastic_3.jpg"
 data-srcset="https://taetaetae.github.io/images/elastic-meetup-201812/elastic_3.jpg, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_3.jpg 1.5x, https://taetaetae.github.io/images/elastic-meetup-201812/elastic_3.jpg 2x"
 data-sizes="auto"
 alt="/images/elastic-meetup-201812/elastic_3.jpg" />
 &lt;/a>&lt;figcaption class="image-caption">역시 밋업의 마무리는 굿즈모음이지(?)&lt;/figcaption>
 &lt;/figure>
&lt;p>매번 이런 IT밋업에 참가 신청을 하고 참석하기 전에는 &amp;ldquo;아 귀찮다. 취소할까. 날도 추운데. 피곤한데&amp;rdquo; 하며 가기 싫었지만 막상 와보면 생각보다 많은 것을 배워가고 얻어 간다. (쿠폰을 받아서가 아니라&amp;hellip;) 세션에 발표하시는 분들, 그리고 그 발표를 듣는 참석하신 분들의 눈동자에서 배움에는 끝이 없고 배워야 살아남는다는 걸 (특히 IT직군은 더&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>Deview 2018 리뷰 (Day 1, Day2)</title><link>https://taetaetae.github.io/2018/10/14/deview-2018/</link><pubDate>Sun, 14 Oct 2018 18:26:26 +0000</pubDate><guid>https://taetaetae.github.io/2018/10/14/deview-2018/</guid><description>&lt;p>회사 내에서도 대학시절 수강신청마냥 1분도 안되서 마감될 정도로 관심이 많았던 &lt;code>DEVIEW 2018&lt;/code>. 다행히 클릭신공으로 운좋게 신청에 성공하였고 팀에서도 바쁜 시기였지만 감사하게도 보내주셔서 올해는 이틀 모두 다녀올수 있게 되었다.&lt;!-- more --> 예전에는 &lt;code>연차가 올라가면 DEVIEW행사는 참여 안하겠지~&lt;/code>라는 생각이 있었는데 그때는 단순 호기심에 참석을 하고 싶었다면 이번에는 &lt;code>뭐라도 배워오자&lt;/code>라는 마음으로 신입 시절보다 조금더 성숙한 마음가짐과 자세를 가지고 참석을 하게 되었다.&lt;/p>
&lt;blockquote>
&lt;p>다시 생각해보면 호기심만으로 세션들을 듣고 부스에서 나눠주는 굿즈를 조금이라도 더 받아와야지 하고 생각했던 신입시절의 생각이 틀린건 아니였지만, 말 그대로 &lt;code>기술행사&lt;/code>이니만큼 가급적이면 세션에서 발표하는 내용을 내것으로 만들고 실무에서 또는 다른곳에서 활용할수는 없을까 하는 생각을 갖는게 보다 성장하려는 개발자로서의 자세가 아닐까 생각이 든다. (라고 멋드러지게 말하지만 세션내용의 절반이라도 이해하면 다행이겠지&amp;hellip;)&lt;/p>&lt;/blockquote>
&lt;h2 id="행사-시작-그리고-키노트">행사 시작 그리고 키노트&lt;/h2>
&lt;p>10초만에 마감되었다는 소리가 있을정도로 올해도 여전히 관심이 많았던 &lt;code>DEVIEW 2018&lt;/code>. 코엑스에 도착하고 등록을 한뒤 이곳저곳 부스들을 구경하기 바빴다. 이번에는 지난번과 달리 거의 네이버 서비스가 60&lt;del>70%를 자리잡고 있었고(파파고, 지도, 클로바, 글로벌 광고 등등) 일반 기업에서는 얼마 오지 않았다.(내 기억으로 5&lt;/del>6개?) 개인적으로 여러 다양한 회사들이 함께하는 기술행사가 되었으면 하는 바램이 있었지만 회사를 선정하는데, 그리고 기타 사정들이 있을꺼라는 아쉬움을 뒤로하고 CTO님이 발표하시는 키노트를 들으러 메인강의장에 들어갔다. (자칫&amp;hellip; 이것도 네이버 독과점(?) 이러면 할말이 없는데&amp;hellip;ㅠㅠ)&lt;/p>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/deview-2018/keynote.png" title="/images/deview-2018/keynote.png" data-thumbnail="/images/deview-2018/keynote.png" data-sub-html="&lt;h2>송창현 네이버 CTO님의 keynote&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/deview-2018/keynote.png"
 data-srcset="https://taetaetae.github.io/images/deview-2018/keynote.png, https://taetaetae.github.io/images/deview-2018/keynote.png 1.5x, https://taetaetae.github.io/images/deview-2018/keynote.png 2x"
 data-sizes="auto"
 alt="/images/deview-2018/keynote.png" />
 &lt;/a>&lt;figcaption class="image-caption">송창현 네이버 CTO님의 keynote&lt;/figcaption>
 &lt;/figure>
&lt;p>작년에는 거의 &lt;code>로봇잔치&lt;/code>로 느껴졌는데 올해는 그 기술들의 융합(?)잔치 로 받아들여졌다. &lt;code>Ambient Intelligence&lt;/code> 를 강조하시며 &lt;code>기술의 진정한 가치는 기술이 생활속으로 사라졌을 때 나온다&lt;/code>라는 명언같은 말씀도 해주셨다.&lt;/p>
&lt;ul>
&lt;li>연결 : 사물, 상황, 위치인식, 이해&lt;/li>
&lt;li>발견 : 적시에 답, 추천, 액션제공&lt;/li>
&lt;/ul>
&lt;p>그리고 그와 관련된 네이버 서비스를 공개 하셨는데, 네이버 지도 Map API를 무제한/무료로 사용할수 있게 된다고 한다. (박수 유도하심 ㅎㅎ) 또한 이번에 가장 크게 바뀌는 네이버 모바일 홈 페이지인 &lt;code>그린닷&lt;/code>, 지도 기술들의 종합 플랫폼인 &lt;code>xDM Platform&lt;/code>(측위, 지도, 내비), 그리고 자율주행과 로봇에 대해 연구결과 그리고 앞으로의 방향성에 대해 정리해주셨다. 집에 돌아와서 검색좀 하다보니 &lt;code>테크수다&lt;/code>에서 벌써(?) 영상을 하나 올린게 있어 공유해본다.&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/q2TM8KNnF14?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>키노트를 다 듣고 작년에는 그런가보다 하고 별생각이 안들었는데 올해는 저런 기술들이 서비스 레벨까지 가는데 이렇다할 허들없이 사용자들에게 보여질수만 있다면 개발자로서 보다 더 큰 자부심을 가지고 기술개발에 정진할텐데&amp;hellip; 하는 씁슬한 생각을 해보게 되었다. (물론 이런 부분들도 다 사정이 있을꺼라 생각이 들지만 안타까운건 감출수가 없을것 같다.)&lt;/p>
&lt;p>이틀에 걸쳐 이런저런 다양한 세션들을 들을수 있어 좋았는데 몇몇 세션들은 기본지식이 없어 (AI, 머신러닝 등&amp;hellip;ㅠ) 이해하기 힘들었다. 내년엔 이해할수 있도록 준비를 해서 오자며 &lt;code>또&lt;/code>다짐을 하고&amp;hellip; 그나마 조금이라도 이해할수 있었던 세션들 몇개만 정리해본다.&lt;/p>
&lt;h2 id="react-native-웹-개발자가-한-달-만에-앱-출시하기">React Native: 웹 개발자가 한 달 만에 앱 출시하기&lt;/h2>
&lt;figure>&lt;a class="lightgallery" href="https://taetaetae.github.io/images/deview-2018/session_1.png" title="/images/deview-2018/session_1.png" data-thumbnail="/images/deview-2018/session_1.png" data-sub-html="&lt;h2>React Native: 웹 개발자가 한 달 만에 앱 출시하기&lt;/h2>">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/deview-2018/session_1.png"
 data-srcset="https://taetaetae.github.io/images/deview-2018/session_1.png, https://taetaetae.github.io/images/deview-2018/session_1.png 1.5x, https://taetaetae.github.io/images/deview-2018/session_1.png 2x"
 data-sizes="auto"
 alt="/images/deview-2018/session_1.png" />
 &lt;/a>&lt;figcaption class="image-caption">React Native: 웹 개발자가 한 달 만에 앱 출시하기&lt;/figcaption>
 &lt;/figure>
&lt;p>지난팀에서 아주 잠깐 React를 경험해보긴 했지만 거의 hello world 수준이였기 때문에 이 세션 역시 이해가 잘 되지 못했다. 하지만 필자처럼 이해를 잘 못하는 사람도 발표자가 전달하려는 목적이 무엇인지 알수 있을 정도로 전체적인 흐름은 조금이나마 이해를 할수 있었고 특히 개발하면서 좋았던 것이나 경험담을 알려주며 &lt;code>삽질공유&lt;/code>를 해주는게 듣기 좋았다. React Native 는 빠른개발을 할수있고 코드공유가 쉬우며 개선이 쉽다는 장점이 있다고 한다. 또한 단기간에 크로스 플랫폼을 만들어야 할때 사용한다고 하니 나중에 참고해봐도 좋을듯 싶다.&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>2018 오픈소스개발자이야기 후기</title><link>https://taetaetae.github.io/2018/07/01/open-source-software-develpoer-story-review/</link><pubDate>Sun, 01 Jul 2018 10:00:00 +0000</pubDate><guid>https://taetaetae.github.io/2018/07/01/open-source-software-develpoer-story-review/</guid><description>&lt;p>Facebook그룹들을 눈팅하다(?) OSS개발자 포럼에서 &lt;code>오픈소스 개발자이야기&lt;/code>라는 주제로 세미나를 주최한다는 공지를 보게되었다. 언제부턴가 트랜드에 뒤쳐지지 않으려는 몸부림중 세미나같은 외부 개발 행사에 참여해보자는 마음으로 공지를 보자마자 홀린듯이 신청을 하게 되었고&lt;!-- more --> 세미나를 듣고 감흥이 가시기 전에 후기를 적고자 한다.(시간이 지나면 잊어버릴것만같은, 보고 들은 생생한 그 무언가를 얻었기에&amp;hellip;)&lt;/p>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/first.png" title="/images/open-source-software-develpoer-story-review/first.png" data-thumbnail="/images/open-source-software-develpoer-story-review/first.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/first.png"
 data-srcset="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/first.png, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/first.png 1.5x, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/first.png 2x"
 data-sizes="auto"
 alt="/images/open-source-software-develpoer-story-review/first.png" />
 &lt;/a>
&lt;p>비가오는 주말이였지만 많이 배우고 오자는 설레임을 갖고 서울 광화문 근처에 있는 한국마이크로소프트로 가게되었다. 말로만 듣던 MS사 로고를 보고 사람들이 하나둘씩 모이는걸 보니 뭔가 배울수 있겠구나 하는 기대감이 생겼다. 사실 오픈소스를 사용만 해본 입장이라 실제 오픈소스에 기여하시는 분들은 어떤 생각들을 갖고 계시는지가 가장 궁금했고 개발자인 나도 언젠간 오픈소스에 기여할수있지 않을까 하는 생각을 하며 발표를 들었다.&lt;/p>
&lt;h4 id="-회색지대--이상과-현실---오픈소스-저작권--신정규-님"># 회색지대 : 이상과 현실 - 오픈소스 저작권 / 신정규 님&lt;/h4>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/01.png" title="/images/open-source-software-develpoer-story-review/01.png" data-thumbnail="/images/open-source-software-develpoer-story-review/01.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/01.png"
 data-srcset="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/01.png, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/01.png 1.5x, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/01.png 2x"
 data-sizes="auto"
 alt="/images/open-source-software-develpoer-story-review/01.png" />
 &lt;/a>
&lt;p>오픈소스는 아무리 말그대로 &lt;code>Open&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>권리발생&lt;/td>
 &lt;td>출원, 심사, 등록&lt;/td>
 &lt;td>창작과 동시 발생&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>권리내용&lt;/td>
 &lt;td>독점배타적 실시권&lt;/td>
 &lt;td>인격권/재산권&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>효력범위&lt;/td>
 &lt;td>아이디어의 동일성&lt;/td>
 &lt;td>표현의 실질적 유사성&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>첫 시간이기도 하였고 아무래도 주제가 끝장토론을 해도 안끝날 주제였던지라 정해진 시간을 넘길정도로 이야기를 많이 해주셨다. 특히 오픈소스 관련된 이야기는 &lt;code>사례&lt;/code>를 이야기 해야 재밌다고 하셨는데 시간관계상 몇가지만 말씀해주셨다.&lt;/p>
&lt;ul>
&lt;li>링크가 맞는지는 모르겠으나 첨부해본다.
&lt;ul>
&lt;li>&lt;a href="https://ko.wikipedia.org/wiki/%EC%97%98%EB%A6%BC%EB%84%B7%EA%B3%BC_%ED%95%98%EC%9D%B4%EC%98%A8%EB%84%B7_%EC%82%AC%EA%B1%B4" target="_blank" rel="noopener noreffer ">엘림넷 vs 하이온넷 사건&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://namu.wiki/w/EFM%20%EB%84%A4%ED%8A%B8%EC%9B%8D%EC%8A%A4#s-3.3" target="_blank" rel="noopener noreffer ">EFM Networks&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.oss.kr/oss_guide/show/5a251739-761d-4199-a2ce-a6103f6b7ca0" target="_blank" rel="noopener noreffer ">오라클 VS 구글&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>오픈소스 개발에 대해 단순하게 누구나 수정할수 있는 환경 이라고만 생각을 하고있다가 이런 복잡한 라이센스 문제가 나오니 약간 어려웠지만, 오픈소스의 생태계를 알고 발을 들이기 위해서는 어느정도의 히스토리는 알아야 겠다고 느끼게 되었다.&lt;/p>
&lt;h2 id="elastic-에서-remote-로-일하기--김종민-님">Elastic 에서 Remote 로 일하기 / 김종민 님&lt;/h2>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/02.png" title="/images/open-source-software-develpoer-story-review/02.png" data-thumbnail="/images/open-source-software-develpoer-story-review/02.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/02.png"
 data-srcset="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/02.png, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/02.png 1.5x, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/02.png 2x"
 data-sizes="auto"
 alt="/images/open-source-software-develpoer-story-review/02.png" />
 &lt;/a>
&lt;p>그전부터 Elastic 제품들을 실무에서도 사용해 왔었기에 개인적으로 오늘 발표중에 가장 궁금했었고, 관심이 있던 시간이였다. 발표에 앞서 어떻게 Elastic에 들어가게 되셨고 회사 소개를 간단히 해주셨는데 생각보다 어마어마한 회사다고 느낄수 있었다.(800여명중 한국엔 9명 / 네덜란드 출신 스타트업 인데 본사는 캘리포니아 마운틴뷰에 있고 등등)
원격근무는 편하고 비용이 절약되는 장점이 있으나 동료들간의 유대감형성이 힘들거나 회의시 집중이 힘든 단점도 있다는 점을 말씀해 주셨다.
시간관계상 몇가지 링크들을 소개해주셨는데 나중에 봐볼 생각이다.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=dx65VsvWsuM" target="_blank" rel="noopener noreffer ">침대에서 회사까지 1분&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://it.chosun.com/site/data/html_dir/2018/05/03/2018050385084.html" target="_blank" rel="noopener noreffer ">[마소 392호] 리모트 워크의 중심에 서보다&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>원격 툴로는 다음과 같이 사용한다고 한다.&lt;/p>
&lt;ul>
&lt;li>github : 슬랙연동, 개발뿐 아니라 운영/기획/이벤트 공유시 활용&lt;/li>
&lt;li>Google Apps&lt;/li>
&lt;li>Slack : 봇 활용 (다양한 종류의 봇, 상황마다 특정 알림을 준다.)&lt;/li>
&lt;li>salesforce : CRM 툴&lt;/li>
&lt;li>zoom : 화상회의 200명 동시콜 가능, 회의가 끝나면 녹음/녹화/스크립팅까지 가능하다고 한다 (wow)&lt;/li>
&lt;li>pinboard : 근태/인사 관리용 앱&lt;/li>
&lt;li>jira는 사용 잘 안함&lt;/li>
&lt;/ul>
&lt;p>나중에 팀 내 Slack 봇으로 여러가지 다양한 자동화를 구성할수 있을것 같다는 생각이 들었다.&lt;/p>
&lt;h2 id="오픈소스-생태계-일원으로서의-개발자--변정훈-님">오픈소스 생태계 일원으로서의 개발자 / 변정훈 님&lt;/h2>
&lt;a class="lightgallery" href="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/03.png" title="/images/open-source-software-develpoer-story-review/03.png" data-thumbnail="/images/open-source-software-develpoer-story-review/03.png">
 &lt;img
 class="lazyload"
 src="https://taetaetae.github.io/svg/loading.min.svg"
 data-src="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/03.png"
 data-srcset="https://taetaetae.github.io/images/open-source-software-develpoer-story-review/03.png, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/03.png 1.5x, https://taetaetae.github.io/images/open-source-software-develpoer-story-review/03.png 2x"
 data-sizes="auto"
 alt="/images/open-source-software-develpoer-story-review/03.png" />
 &lt;/a>
&lt;p>사회자분이 &amp;ldquo;아웃사이더님&amp;quot;이라고 하시길래 설마 했다. 뭐가 잘 안되면 구글링을 하게되는데 내가 자주 보던 블로그를 운영하셨던 분이 내눈앞에 ㄷㄷ&amp;hellip;
언제부터 해야지~가 아니고 개발하다보니 어느새 오픈소스에 참여하고 있었다고 한다. 또한 참여하는게 아니고 이미 오픈소스 생태계속에서 살고있는 우리들이라 말씀하시고, 오픈소스 Contribution 방법으로는 사용/홍보/번역/리포팅/문서화/코드제출 등 다양하게 있으니 어렵게 생각하지 말자 라고 하셨다. 오픈소스에서 배울수 있는점은 커뮤니케이션의 방법, 협업의 방법과 중요성, 테스트코드의 중요성, 지속적 통합/지속적 배포, 코드의 품질관리 라고 한다.
점점 발표를 들으면서 오픈소스에 대한 생각이 바뀌고 있는 내 자신을 느낄수 있었다. 너무 어렵게만 생각해 온것 같다는.&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></channel></rss>