2021년 7월 17일 토요일

feedly.com의 피드목록 정리

기술 블로그와 관련 피드들을 feedly.com으로 열심히 챙겨읽던 시절이 있었다.

최근 1-2년간은 잘 챙겨읽지 못 했는데...

이번에 모종의 계기로 인하여 다시 열심히 챙겨 읽고자 하는 마음이 생기게 되었다.


오랜만에 feedly.com을 자세히 살펴보니 구독중인 피드들을 한번 정리해야 겠다는 생각이 들었다. 

feedly.com은 구독 중인 소스를 정리해주는 기능을 가지고 있는데, 

지금 내 소스는 이 모양 이 꼴이 되었다.

Organize Sources
Following 52 sources with 14 inactive and 10 unreachable.

하지만 inactive와 unreachable의 피드 목록을 하나씩 클릭해서 들어가면,

실제로 사이트가 죽었거나 글 작성이 뜸해진 피드도 있었지만 그렇지 않은 경우도 있었다.

테크잇같은 경우에는 RSS 주소가 바뀐 경우였다. (어떤 이유로 밑줄이 제거되었다.)

  • 내가 알고 있던 주소 : http://feeds.feedburner.com/techit_kr
  • 지금 주소 : http://feeds.feedburner.com/techitkr

실용주의 이야기의 경우에는 RSS 제공자가 바뀐 경우

  • 내가 알던 주소 : http://feeds.feedburner.com/moai?format=xml
  • 지금 주소 : http://pragmaticstory.com/?feed=rss2

토비님의 블로그였던 Toby's Epril은 이제 온데간데 없이 사라져있었다. (이 피드는 차마 못 지우진 못 하겠더라...)


반대로 예전엔 뜸하게 글이 올라오던 피드가 성장한 경우도 있는데, 바로 OUTSTANDING이다.

사이트가 처음 생기던 시절에는 몇일에 한번 글이 작성되더니 이제 하루에도 몇 개씩 글이 올라오는 미디어로 성장했다.

그리고 유료고객 위주로 서비스가 재편되어 이제는 내 피드목록에서 놓아주어야 했다.


정리 결과!

Organize Sources
Following 38 sources with 5 inactive and 2 unreachable.



2019년 11월 19일 화요일

mitmproxy로 https를 forward proxy하면서 URL whitelist로 일부만 허용하기

인터넷이 안되는 특정서버에서 인터넷이 되는 DMZ영역의 서버를 통해서 구글스토리지를 gsutil로 접속해서 업로드/다운로드 등의 기능을 사용해야하는 상황이 생겼다. 이에 시행착오를 거쳐 mitmproxy라는 오픈소스로 해결한 사례를 공유한다.

인터넷이 안되는 특정서버 : A서버와 B서버
인터넷이 되는 DMZ영역의 게이트웨이서버 : G서버

1.
우선 DMZ영역도 외부 인터넷이 기본적으로 차단되어 있으므로 G서버에서 URL기반으로 외부 인터넷 방화벽을 443포트로 뚫어야한다.
  • https://accounts.google.com
  • https://oauth2.googleapis.com 
  • https://www.googleapis.com

2.
G서버에서 운영중인 nginx가 있는데 이게 ngx-connect 모듈을 넣어서 설치한 것이라 이걸 그대로 프락시 서버로 사용했다.

3.
A서버와 B서버에서 gsutil을 설치하기 위해 gsutil만 바이너리를 받아서 넣어도 되지만, gcloud의 인증관련 설정과 환경설정값을 그대로 활용하기 위해 gcloud를 설치해서 gsutil을 사용하기로 했다. gcloud는 conda로 설치가능하지만, gcloud가 python 2.x를 기본적으로 지원하고, 3.x는 일부만 지원하고 있기 때문에, python2.x로 포함하고 있는 버전지정 보관파일로 설치했다. 

4.
gcloud 설치 후 필요한 설정 (설정 페이지 참고)

proxy 설정
gcloud config set proxy/type http
gcloud config set proxy/address G서버IP
gcloud config set proxy/port G서버포트

그리고 아래 2개 설정을 추가해줘야 불필요한 접근이 사라진다.
gcloud config set core/disable_usage_reporting True
gcloud config set component_manager/disable_update_check True

5.
gsutil ls gs://버킷이름
이런 명령으로 구글스토리지의 특정 버킷에 존재하는 파일 목록을 조회할 수 있다.

그런데 이렇게 하면 모든 구글스토리지에 권한만 있다면 A서버와 B서버의 데이터를 업로드할 수 있게된다. 데이터보안측면에서 특정 버킷에만 접근을 허용해야 한다. 그런데 G서버의 프락시서버 역할을 하는 nginx의 ngx-connect 모듈은 CONNECT 이후의 https request에 대해서는 SSL 터널 내부에서 이뤄지기 때문에 request header나 body를 접근할 수 없다.

그래서 https request를 forward proxy할 수 있는 다른 오픈소스인 mitmproxy를 찾게되었다.

6.
mitmproxy는 다운로드 페이지에 각 OS마다 실행파일이 미리 컴파일되어 있고 파이썬 설치파일 형태로도 배포하고 있어서 쉽게 설치가 가능하리라 생각했다. 하지만 리눅스를 위한 실행파일은 GLIBC 2.18 버전을 찾으며 실행이 되지않고, 파이썬 설치파일은 파이썬 3.6 이상에서만 수행이 된다.
G서버는 게이트웨이서버라서 파이썬도 OS와 함께 설치된 2.7버전이었기 때문에 mitmproxy의 메이저버전을 하나씩 낮추면서 현재 G서버환경에서 실행가능한 2.0.2버전을 사용할 수 밖에 없었다. 2.0.2버전의 mitmproxy도 내가 원하는 기능은 모두 가능하기에 큰 문제는 없었다.

2.0.2버전은 문서페이지에 대한 링크가 공식홈페이지에 없는데, 구글링을 하다보니 1.x와 2.x 버전에 대한 문서페이지도 찾을 수 있었다. 오히려 일부 내용은 여기가 더 자세한 경우도 있었다.

7.
mitm은 man in the middle 공격의 약자라서 이름을 아주 잘 지었다고 생각했다.
mitmproxy는 대화형콘솔이 나타나면서 커맨드라인 콘솔환경에서 모니터링이 가능하다. mitmweb은 대화형콘솔을 web으로 제공하여 브라우저에서 모니터링이 가능하다. mitmdump는 로그만 남게되어 백그라운드로 실행하기에 적합하다.

G서버에서 아래 3가지 mitmdump를 nohup으로 실행한다.
./mitmproxy -p 8000 -s block.py
./mitmweb -p 8000 --no-browser --web-iface 0.0.0.0 --web-port 9000 -s block.py
./mitmdump -p 8000 -s block.py

8.
특정 버킷에만 접근을 허용하고 싶다면 아래처럼 block.py를 생성해서 mitmxxxx 실행시 -s 옵션으로 지정하면 된다. mitimproxy github의 샘플을 참고했다. 특정 버킷이 아닌 경우 glcoud는 403을 만나게되어 처리되지 않는다.

from mitmproxy import http
def requestheaders(flow):
    if "/버킷이름/" not in flow.request.path:
        flow.response = http.HTTPResponse.make(403)


9.
A서버나 B서버에서 whitelist로 넣은 버킷은 정상적으로 접근되지만, 아래 처럼 허용되지 않은 버킷에 접근하면 403 에러를 만나면서 아무 처리도 되지 않는다.
gcloud ls gs://허용되지않는버킷
AccessDeniedException: 403

G서버에서는 아래처럼 로그가 생성된다.
127.0.0.1:58186: clientconnect
127.0.0.1:58186: GET https://www.googleapis.com/storage/v1/b/허용되지않는버킷/o?delimiter=%2F&projection=noAcl&versions=False&fields=prefixes%2CnextPageToken%2Citems%2Fname&alt=json&maxResults=1000
              << 403 Forbidden 0b
127.0.0.1:58186: clientdisconnect


10.
인증을 위한 whitelist를 도메인단위로 추가해야 하고, 위 block.py에서는 버킷명을 디렉토리명으로 하는 다른 버킷도 접근이 허용되는 버그가 있으므로 url과 path를 확인하는 조건을 강화할 필요가 있겠다.


2017년 12월 18일 월요일

나눔스퀘어 글꼴에서는 안보이는 글자들

요즘 만들고 있는 웹어플리케이션에서 나눔스퀘어를 한글의 기본 글꼴로 사용하고 있다. 그런데 최종테스트 중에 특정 글자가 보이지 않는다는 결함이 발생해서 그 원인과 해결책을 블로그로 남긴다.

1.
결함의 내용은 이러했다.
한자 입력하면 글자색이 흰색이라 보이지 않음
"넫넫"  등 잘못된 한글 입력해도 글자색이 흰색으로 보임
한자를 입력하거나 넫넫을 입력해봤더니 정말 보이지 않는 것이었다. 이게 뭔가라는 생각에 나눔스퀘어 사이트에서 입력을 해보았더니 여기서도 보이지 않게 된다!



이번에는 "덜거덕"을 입력해보았다. "덜거덕"을 입력하려다 보면, "덝"까지만 입력한 상태가 만들어지는데, 이때 "덝"글자가 보이지 않게된다.



이럴수가! 나눔스퀘어 글꼴에 뭔가 문제가 있을 거라고 생각하고 글꼴 파일을 파헤쳐보기 시작했다.


2.
나눔스퀘어 글꼴은 "꼭 필요한 2,350자만을 추렸기" 때문에 한자나 "넫"같은 글자가 제외되어 만들어진 것이다. 그런데 제외된 글자들이 글꼴 파일에서도 제외되어야 브라우저가 다른 글꼴로 표시할 텐데, 실제로 제외하지 않고 투명한 글자로 포함시켜서 만들어진 것이 문제였다.

글꼴의 상세한 내용을 볼 수 있는 FontForge로 나눔스퀘어 글꼴 파일의 내용을 열어보면 이렇다. 가운데에 파란색으로 선택된 부분이 "덝"글자가 표시되는 자리이다. 그런데 다른 여러 빈 공간처럼 "덝"부분도 비어있다. 즉, 나눔스퀘어 글꼴이 "덝"글자를 빈 공간으로 표시하는 것이다.


다른 글꼴 중에도 2350자만 추려서 "경량화"된 글꼴이 존재한다. 최근에 글꼴과 관련되어 좋은 글이 여러번 올라와 알게된 스포카한산스도 그 중 하나이다.  아래 캡쳐를 자세히 보면 "덝"에 해당하는 부분이 붉은색 X 표시되어 있어서, 글꼴 파일에 포함되지 않은 것을 알 수 있다.










참고로 경량화 되지 않은 글꼴은 아래와 같이 모든 문자가 포함되어 있다.










3.
결국 나눔스퀘어가 실제로는 포함하지 않는 2350자 이외의 문자에 대해서 실제 파일에서도 포함하지 않도록 하는 것이 해결책이라고 할 수 있겠다.

우선 나눔스퀘어에 포함된 2350자가 명확하게 어떤 글자들인지 그 목록이 명시되어 있지 않지만, 일반적으로 완성형 한글의 2350자일 것이라 추측했다. 그리고 실제 완성형 한글의 2350자의 목록은 나무위키에서 얻을 수 있었다.

2350자만 추출하여 새로운 글꼴 파일을 만들어내는 방법을 통해서 투명한 글자가 생기지 않도록 새로운 나눔스퀘어 글꼴 파일을 만들 수 있었다. 그리고 2350자가 올바르게 추출되었는지를 확인하기 위해 SIL ViewGlyph를 이용해서 글꼴에 포함된 글자의 수를 확인했다.


4.
이 문제를 나눔스퀘어 글꼴이 가지고 있다는 것을 어딘가에 알려주고 싶은데, 알려줄 방법이 없다.


2017년 12월 3일 일요일

spring boot + gradle + proguard + 실행가능한 jar

spring boot + gradle + proguard를 사용하여 웹어플리케이션의 실행가능한 jar를 만드는 방법을 소개한다.


1.
spring boot를 사용하면 보통 gradle을 빌드도구로 사용하게 되고, 웹어플리케이션을 만들 때 보통 실행가능한 jar를 만들어서 쓰곤 한다. 아래와 같이 build.gradle을 구성하고 gradle assemble 명령어로 실행가능한 jar를 만든다.

buildscript {
    repositories {
     mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.8.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

springBoot {
    executable = true
} 

2.
만들어진 jar를 보면 BOOT-INF/clesses에 내가 만든 클래스들이, BOOT-INF/lib에 내가 사용하는 라이브러리들이 포함된다.

여기서 내가 만든 클래스들을 proguard로 난독화를 적용하고 싶으면 proguard가 제공하는 gradle 플러그인을 이용하면 손쉽게 만들수 있다.

buildscript {
    repositories {
     mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.8.RELEASE")
        classpath("net.sf.proguard:proguard-gradle:5.3.3")
    }
}

def proguardJarPath = jar.archivePath.absolutePath.replace(".jar", "-proguard.jar")

task proguard(type: proguard.gradle.ProGuardTask, dependsOn: jar) {
    configuration "proguard.conf"

    injars jar.archivePath.absolutePath
    outjars proguardJarPath

    libraryjars "${System.getProperty('java.home')}/lib/rt.jar"
    libraryjars project.configurations.compile
}


3.
그런데 난독화된 jar를 실행가능한 jar에 포함시키려면 gradle 설정이 조금 더 필요하다. 실행가능한 jar를 만드는 gradle의 spring boot 플러그인의 bootRepackage 태스크가 사용하게 되는 jar를 난독화된 jar로 바꿔줘야 한다.

bootRepackage {
 withJarTask = proguardJar
}

이때 withJarTask에 대입이 가능한 값은 Jar 타입의 gradle 태스크여야 한다. ProGuardTask는 Jar 타입이 아니다. 그래서 proguardJar라고 내가 만든 Jar 타입의 태스크가 필요하다. 이미 난독화된 jar 파일이 있으므로 zipTree로 파일 구조를 유지하면서 새로운 jar 파일을 만들어야 한다.

task proguardJar(type: Jar, overwrite: true, dependsOn: proguard) {
 from zipTree(proguardJarPath)
}

4.
다 합치면 아래와 같은 gradle 설정이 된다.




2017년 11월 27일 월요일

모달 팝업뜰때 바닥 스크롤 막기

모달 팝업이 표시될 때 브라우저 스크롤을 막는 방법

브라우저 스크롤이 있는데 모달팝업의 내부 스크롤도 있는 경우, 모달팝업의 내부 스크롤을 마우스로 내리다가 끝에 다다르면, 브라우저 스크롤이 내려간다. (어둡게 처리된 내용이 스크롤되는 현상)

이런 현상을 막기위해 모달팝업이 표시될 때는 body의 overflow를 hidden으로 변경해서 브라우저 스크롤을 없에는 방법을 사용했더니, 브라우저 스크롤바가 사라지면서 그만큼 브라우저의 가로 길이가 늘어나면서 body의 컨텐츠가 움찔하면서 새로 확보된 공간만큼 재배치가 일어난다. 모달팝업이 닫히고 다시 브라우저 스크롤바가 생기면 반대방향으로 움찔하는 현상이 일어난다.


이런 현상을 막기위해 body의 position을 fixed로 만들고, overflow를 scroll로 변경해서 스크롤바 영역은 남기지만 실제로 스크롤은 되지 않도록 하는 방법도 있다. 하지만 바닥의 스크롤이 맨위로 올라가버리는 부작용이 있고, 바닥의 스크롤바가 없는 경우에는 오히려 불필요하게 스크롤바가 표시되는 문제가 있다.

body의 스크롤을 일으키는 이벤트를 모두 막아버리는 방법도 있다. 하지만 컨텐츠가 스크롤되는 경우는 마우스 휠뿐만이 아니라 키보드의 화살표입력, 터치 이벤트, 마우스의 가운데 버튼을 클릭하고 움직이는 경우 등 모든 것을 제어하기가 쉽지 않다.

가장 코드가 깔끔하고도 동작이 정확한 방법은 body의 스크롤바를 없에고 자식들에게만 스크롤바를 만들어주는 것이다. javscript 없이도 css만으로 원하는 상태를 만들 수 있고, 바닥의 스크롤바의 표시여부와 스크롤 위치도 보존되는 방법이다.

body를 브라우저 꽉차게 만들어서 스크롤바가 생기지 않게 만들고
body 하위에 바닥용 div와 모달팝업용 div를 자식으로 만들고
바닥용 div (아래 예시의 .content)는 스크롤바를 생기게 만들고
모달팝업용 div (아래 예시의 .modal-background)는 스크롤바를 생기지 않게 만든다.

모달팝업이 화면을 꽉 채우기 떄문에 스크롤이 위나 아래에 닿아도 부모 body로만 스크롤 이벤트가 올라가기 때문에 body의 자식인 바닥용 div는 스크롤되지 않는다.




2017년 4월 1일 토요일

[번역] 객체로서의 함수 [FunctionAsObject]

이 포스트는 FunctionAsObject를 2017년 3월 30일 새벽에 번역한 글입니다. 원작자인 Martin Fowler의 허락을 받아 블로그에 게시합니다. 원문의 영어 단어나 표현이 필요하다고 생각한 경우에 괄호안에 원문을 같이 표기했습니다.

객체로서의 함수

프로그래밍에 있어서, 객체(object)의 가장 기본 개념은 데이터와 행동방식(behavior)을 함께 묶는 것입니다. 이렇게 하면 서로 관련있는 여러 함수를 만들 때, 공통의 데이터 컨텍스트(common data context)를 만들 수 있습니다. 그리고 그 데이터를 조작하는 인터페이스를 만들어 내서, 객체 내의 데이터에 대한 접근을 해당 객체가 제어할 수 있도록 할 수 있습니다. 그러면 관련있는 다른 데이터(derived data)를 만들어내기도 쉬워지고, 데이터를 잘못 수정하는 일도 미리 막을 수 있게 됩니다. 많은 프로그래밍 언어가 객체 정의를 위한 클래스를 선언하는 명시적인 방법을 가지고 있습니다. 하지만 1급함수(first-class function)와 클로저(closure)를 사용할 수 있는 프로그래밍 언어에서는, Function As Object 패턴을 이용해서 객체를 만들어 낼 수 있습니다. (Eugene Wallingford에 의해 처음 제안되었습니다.)

간단하게 사람을 객체로 만든 예제를 들어보겠습니다. 객체로서의 함수(function-as-object) 스타일을 JavaScript에서 사용했습니다. [1]

function createPerson(name) {
  let birthday;
  return {
    name: () => name,
    setName: (aString) => name = aString,
    birthday: () => birthday,
    setBirthday: (aLocalDate) => birthday = aLocalDate,
    age: age,
    canTrust: canTrust,
  };
  function age() {
    return birthday.until(clock.today(), ChronoUnit.YEARS);
  }
  function canTrust() {
    return age() <= 30;
  }
}

가장 바깥쪽의 형태가 객체로서의 함수인 함수입니다. 이는 생성자 함수(constructor function)라고도 불립니다. 이 함수의 호출 결과는 (엄밀히 따지면) 함수들의 해쉬맵(hashmap)입니다. [2] 이 해쉬맵은 메서드 셀렉터(method selector)의 역할을 합니다. 이 해쉬맵은 변수들의 상태를 클로저안에 있는 함수에 보관합니다. 이로써 각각의 함수 호출과는 상관없이 데이터를 유지할 수 있게 합니다. 그러면 이 해쉬맵은 전통적인 객체처럼 취급될 수 있습니다.

const kent = createPerson("kent");
kent.setBirthday(LocalDate.parse("1961-03-31"));
const youngEnoughToTrust = kent.canTrust();

객체로서의 함수를 전통적인 객체지향관점에서 분석해보겠습니다.
  • 객체의 필드(field)는 위 예제의 name처럼 생성자 함수의 인자의 형식, 또는 birthday처럼 지역 변수의 형식으로 표현됩니다.
  • 객체의 메서드는 생성자 함수 내부의 함수입니다. 보통의 객체의 메서드처럼, 내부의 함수들은 서로 자유롭게 호출이 가능하며 지역변수로 선언된 데이터를 조작할 수 있습니다.
  • 생성자 함수의 바깥에서는 내부의 변수에 접근할 수 없습니다. 이로 인해 데이터의 캡슐화(encapsulation)가 지켜집니다.
  • 객체의 퍼블릭(public) 메서드는 생성자 함수가 리턴하는 해쉬맵에 포함된 함수들입니다.
  • 생성자 함수 내부에서 선언된 함수들 중에서, 리턴 해쉬맵에 포함되지 않은 함수들이 바로 객체의 프라이빗(private) 메서드입니다.
  • 퍼블릭 메서드의 함수 이름은 리턴하는 해쉬맵의 키와 같습니다. 생성자 함수 내부에서 선언한 함수 이름이 아닙니다. 저는 불필요한 혼란을 피하기 위해서 내부의 이름과 외부로 노출되는 함수 이름을 같게 유지하는 것을 더 좋아합니다. (필요하면 간단한 방법으로 별도의 함수 별명을 지정할 수 있습니다.) [3]

이 패턴의 또 다른 일반적인 구현방법은 바로 메서드 셀렉터를 가진 함수를 리턴하는 것입니다. 해쉬맵처럼 JavaScript의 기본적인 메서드 셀렉터가 아니어도 됩니다. 메서드 셀렉터로 함수를 사용하기 위해서는, 첫번째 인자로 사용하려는 메서드의 이름을 받아들이는 함수를 리턴하면 됩니다. 함수의 내부 구현에서 인자로 들어온 메서드의 이름을 기준으로 조건을 주어 구별하면 됩니다. (Wallingford가 더 자세하게 작성한 문서가 있습니다.)

위와 같은 객체로서의 함수를 사용하는 방법은 예전 부터 있었습니다. lisp에서도 많이 볼 수 있었고, JavaScript에서도 폭넓게 사용되어 왔습니다. (ES6 이전에는 클래스에 대한 아주 제한적인 표현법만 있긴 했습니다.) 클래스를 위한 특별한 문법이 불필요하다는 논쟁이 종종 있었습니다. 이건 마치 객체애호가들이 메서드 1개짜리 클래스를 사용하면 되기 때문에 1급 함수가 필요없다고 했던 논쟁과 같은 것입니다. 그 결과로 JavaScript 진영의 많은 사람들이 ES6의 클래스 문법을 사용하는 것에 대해 아직 논쟁을 벌이고 있습니다. 1급 함수와 1급 클래스가 모두 있어서 좋습니다만, 개인적으로는 ES6의 클래스 문법을 더 좋아합니다.

더 읽어보기

Eugene Wallingford가 "객체로서의 함수"라는 이름을 만든 것은 1999년의 패턴 언어 "Envoy"에서 였습니다. 이 글을 보면 더 상세한 내용을 알수 있습니다. 함수를 메서드 셀렉터로 사용하는 것과 상속의 일부 개념을 지원하는 위임 형태로 사용하는 것에 대한 내용이 있습니다. Scheme을 예제로 사용하고 있습니다.

감사의 글

이 글의 초안에 대해서 의견을 보내준 Chris Ford, Fred George, James Shore, Kevin Yeung, Lucas Lego, Matteo Vaccari, Rob Miles, 그리고 Eugene Wallingford에게 감사드립니다.

주석

1. 날짜를 다루기 위해 js-joda를 사용했습니다. js-joda는 Java가 날짜와 시간에 대해서 엉망이었던 것을 깔끔하게 정리한 Joda-Time 라이브러리의 JavaScript 버전입니다. js-joda로 인해 JavaScript의 날짜와 시간에 대한 문제를 제대로 사용할 수 있게 되어 다행이라 생각합니다.

2. JavaScript 용어로 그걸 객체라고 부릅니다. 비록 우리가 만들려고 했던 전통적인 객체는 아니지만, 그래도 JavaScript의 객체입니다. 그래서 혹시 모를 혼란을 줄이기 위해 해쉬맵이라고 지칭했습니다.

3. ES6에서는 "age : age," 처럼 중복되는 내용은 그냥 "age,"로 대체 할 수 있습니다.


2017년 1월 15일 일요일

underscore 스터디 중 Collection 함수 유형별 분류 결과

개발팀내에서 underscore.js 스터디를 하고 있는데, Collection 함수들에 대해서 유형별로 정리하면서 진행을 했다. 아래는 그 결과이다.

filter ( [a,b,c] -> [b,c] )
_.filter
_.where
_.reject
_.sample
_.first
_.last
_.initial
_.rest
_.compact
_.without
_.union
_.intersection
_.difference
_.uniq


map ( [a,b,c] -> [A,B,C] )
_.each
_.map
_.invoke
_.pluck
_.sortBy
_.groupBy
_.indexBy
_.shuffle
_.toArray
_.partition
_.flatten
_.zip
_.unzip


reduce ( [a,b,c] -> abc )
_.reduce
_.reduceRight
_.find
_.findWhere
_.every
_.some
_.contains
_.max
_.min
_.countBy
_.sample
_.size
_.first
_.last
_.indexOf
_.lastIndexOf
_.sortedIndex
_.findIndex
_.findLastIndex


object ( [] -> {} )
_.object


range ( 3 -> [1,2,3] )
_.range

2016년 12월 19일 월요일

AES 암복호화 보완, (AES/CBC/PKCS5Padding 방식으로)

특정 DB컬럼의 값을 암호화하여 저장하라는 회사의 아키텍쳐 요건을 만족시키기 위해서 AES로 암호화하여 사용하고 있었다. 그런데 기존 방식이 AES/ECB/PKCS5Padding를 사용하고 있었는데 ECB가 문제가 되서 AES/CBC/PKCS5Padding로 변경하는 작업을 진행했다.
 

1.
암호화로 사용할 키는 KeyGeneratorSecureRandom으로 생성한다.

```
KeyGenerator generator = KeyGenerator.getInstance("AES");
SecureRandom random = new SecureRandom();
generator.init(128, random);
Key secureKey = generator.generateKey();

System.out.println(Base64.encodeBase64String(secureKey.getEncoded()));
```

2.
같은 문자열도 암호화할때마다 다른 문자열을 만들어내기 위해서 IV값을 랜덤하게 만들어준다.

```
/**
* 랜덤하게 IV 생성
*
* 같은 평문도 암호화할 때마다 암호문을 다르게 만들어 낸다.
* @return 랜덤하게 생성된 IvParameterSpec
*/
private static IvParameterSpec getRandomIvParameterSpec() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
```

3.
IV값과 암호키로 암호화한 값을 합쳐야 하는데 java byte concat으로 검색해보니 무려 자바 System 클래스에 배열복사가 있었다!

```
// IV + 암호문으로 출력할 Byte를 붙인다.
byte[] ivcipherByte = new byte[16 + cipherByte.length];
System.arraycopy(iv.getIV(), 0, ivcipherByte, 0, 16);
System.arraycopy(cipherByte, 0, ivcipherByte, 16, cipherByte.length);
```

4.
다시 복호화할때는 입력된 byte를 IV와 암호값으로 다시 나눈다.

```
// 입력된 Byte를 IV + 암호문으로 나눈다.
byte[] originalIvByte = Arrays.copyOfRange(cipherByte, 0, 16);
byte[] originalCipherByte = Arrays.copyOfRange(cipherByte, 16, cipherByte.length);
```

5.
이렇게 만든 encrypt와 decrypt를 IN/OUT을 모두 String으로 만들어서 쓰기 좋게

```
/**
* 암호화하고, Base64로 인코딩
* @param plainText - 평문
* @return 암호문
*/
public static String encryptAndEncoding(String plainText) {
// 암호화하고 나서, 다시 문자열로 만들기위해 Base64 인코딩
return Base64.encodeBase64String(encrypt(plainText));
}

/**
* Base64로 디코딩하고, 복호화
* @param cipherText - 암호문
* @return 평문
*/
public static String decodeAndDecrypt(String cipherText) {
// 다시 Byte로 만들기위해 Base64 디코딩하고 복호화
return decrypt(Base64.decodeBase64(cipherText));
}
```


2016년 12월 18일 일요일

[예전글] 업무연락하기 노하우

2012년 8월에 후배들에게 보여주기 위해 썼던 내용을 옮겨와 본다. 당시 부서 스텝에서 신입사원에게 교육할 때 사용한다고 정리해 달라고 했던 내용이다. 지금 다시 보면 좀 다르게 정리할 수도 있는 내용이 섞여 있겠지만, 그 때의 내가 가진 기준이나 느낌을 그대로 살리기 위해 변경없이 블로그에 남긴다. 원본에는 예시로 실제로 내가 당시에 주고 받은 메일을 캡쳐하여 추가했었지만, 그건 이해를 돕기 위한 것이고 본문만으로도 충분히 의도가 전달되므로 블로그에는 생략한다.

# 업무연락하기 노하우

## 보고 + 기록 + 자료

보고 : 본인이 수행한 업무 결과를 타인에게 알려줌
>> 타인의 입장에서 필요한 정보만 기술한다.

기록 :업무관계자끼리 결정된 내용을 글로 남김
>> 결정된 내용과 그에 따른 할일을 표시한다.

자료 :업무수행을 위해 필요한 자료를 공유
>> 자료에 대한 설명 위주로 작성한다.

## 한번에 한가지 내용만

수신자가 같더라도 서로 다른 종류의 업무에 대한 내용이 하나의 메일/쪽지에 담기지 않도록 주의한다.

특히 답장을 보내면서 원문과 상관없는 내용을 추가하여 질문하는 것을 피한다.
이런 경우 별도의 메일/쪽지를 작성하여 보낸다.

## 제목은 구체적으로

ex) 회의결과 공유합니다.
어떤 주제에 대한 회의였는지 없음
회의결과에 대한 내용이 없음
수신인이 내용을 저장할때 난감함
나중에 검색할때 제목으로 검색이 불가

ex) 주민등록번호 입력은 숫자만 13자리로 결정함

## 수신자와 참조자를 구분

수신자
연락을 기다리는 사람
관련된 할일이 있는 사람

참조자
할일은 없지만 내용을 알고 있어야 하는 사람

수신/참조자 목록을 공유할 필요가 있을 경우
본문 시작에 수신자와 참조자를 구분하여 표시

## 표현은 간결하게

최대한 짧고 간단한 문장으로 표현한다.

자기소개를 장황하게 하지 않는다.

과도하게 높임을 사용하지 않는다.

## 번호로 내용 구분

여러 항목에 대한 내용이 포함된 경우
각각의 답변이 필요한 경우
쪽지/메일 안에서 각 항목을 지칭해야 하는 경우
ex) 1번과 2번이 결정된다면 3번은 생략하겠습니다.

한 번호에 내용이 너무 길지 않도록 주의. 그런 경우 번호를 나누면 된다.
번호마다 꼭 소제목을 달 필요는 없다.
번호마다 시공간상 흐름이 있다면 흐름대로 순서

## 중요한 내용은 색으로 표시

최종 결정된 내용
수신인이 해야 하는 일
이슈로 제기된 내용

붉은색, 파란색 글자색 활용
노란색, 연두색 바탕색 활용

## 첨부파일에 대한 설명필수

첨부파일에 대한 설명을 본문에 포함시킨다.
파일이 여러개일 경우, 파일명을 정확하게 명시하여 구분한다.

첨부파일의 특정부분이 중요한 경우, 위치를 명시한다.

본문에 첨부파일의 모든 내용이 포함된 경우, 본문에 동일함을 명시
수신자가 본문내용과 첨부파일 중에서 선택하여 읽을수 있도록

## 답장하는 내용은 색으로 구분

답장시에는 본인이 작성한 부분을 원문과 구분
특수기호를 넣거나 색을 다르게 표시

2016년 12월 16일 금요일

maven central에 없는 라이브러리 github에서 가져오기

개발셀에서 Gephi라는 프로그램의 Toolkit을 쓸 일이 생겨서 Toolkit으로 오픈된 라이브러리를 이용해서 커맨드라인으로 파일을 변환하는 자바 어플리케이션의 개발환경을 구축하게 되었다.

1.
라이브러리는 github에서 제공되고 있었는데, 다운로드를 받아보니 Fat Jar 형태였다. 즉, 라이브러리에서 직접 제공하는 자바 클래스와 실행을 위해 필요한 다른 라이브러리의 클래스까지 모두 하나의 jar로 압축되어 있었다.

2.
담당 개발자가 POC처럼 이것저것 해볼때는 fat jar를 다운받아서 로컬에서 이클립스 프로젝트를 구성하여 사용하였는데, 이걸 다시 github에 올리기 전에 빌드를 gradle로 구성하기로 했다.

3.
우선 Gephi Toolkit이 maven에 올라가져 있길래 gradle dependency에 추가했다.

```
dependencies {
// gephi toolkit
compile group: 'org.gephi', name: 'gephi-toolkit', version: '0.9.1'
}
```

4.
수많은 다른 라이브러리들을 하나씩 내려받다가 2개 라이브러리를 내려받지 못해서 에러가 발생했다. 찾아보니 정말로 maven central에 각각의 라이브러리는 있지만 찾고 있는 버전은 등록되어 있지 않았다. stax-utils-snapshot-20100402.jartrove-2.1.0.jar이 그것들이다.

5.
검색을 좀 해보니 당연히 나만 이런 것이 아니었고, 해결책으로는 
들이 이미 덧글로 달려있었다. 2개만 별도로 다운로드 하라는 것은 그 2개의 라이브러리가 가지고 있을지도 모르는 depencency는 무시하게 될 것이다. 그래서 all 버전을 사용하기로 결정했다.

6.
그렇다고 all 버전을 git에 올릴 수는 없었다. 그래서 github에 제공된 링크를 통해서 gradle 빌드시점에 다운로드 받는 방법을 찾아보기로 했다. 조금 검색해보니 Gradle Download Task라는 plugin을 찾을 수 있었다. Java 컴파일 하기전에 다운로드 받도록 gradle 파일을 구성했다.

```
apply plugin: 'de.undercouch.download'

import de.undercouch.gradle.tasks.download.Download

task downloadGephiToolkit(type: Download) {
    src 'http://github.com/gephi/gephi-toolkit/releases/download/v0.9.1/gephi-toolkit-0.9.1-all.jar'
    dest 'lib/'
    overwrite false
}

compileJava.dependsOn downloadGephiToolkit
```

7.
그리고 gradle의 dependency도 아래처럼 변경하고, 마지막으로 gradle 파일이 이렇게 된 이유를 설명하는 주석을 달아두었다.

```
dependencies {
// gephi toolkit
compile name: 'gephi-toolkit-0.9.1-all'

//XXX 아래처럼 선언하면 stax-utils와 trove를 maven에서 찾다가 에러난다. 그래서 gephi-toolkit-0.9.1-all을 사용한다.
// compile group: 'org.gephi', name: 'gephi-toolkit', version: '0.9.1'
}
```




2016년 12월 13일 화요일

Underscore.js 스터디 계획

자주 사용하게 되는 Underscore.js에 대한 스터디를 시작하게 되어, 그 계획을 짧게 적어본다. 다양한 API에 대해서 인지하고, 사용법을 숙지하고, Functional Programming에 대해서 간단히 감을 잡는 시간이 될 것 같다. 그리고 사용법만 보지 않고 소스코드도 같이 보면 좋을 것 같다.

1. 첫째날
- Introduction : 같이 읽어 보고 어떤 느낌의 라이브러리인지 공감한다.
- Collections : 이때 filter, map, reduce 유형으로 크게 나누고, 각 함수마다 어느 유형에 속하는지 이야기해본다.


2. 둘쨰날
- Arrays
- Objects
- OOP Style
- Chaining

3. 셋째날
- Functions
- Utility

4. 넷째날
- Change Log
- Links : Underscore.string, Underscore.java
- 다른 라이브러리 : Lodash

2016년 12월 1일 목요일

git repo로 구성된 소스코드의 Tab과 Space 일괄 통일 작업 실행


예전에 작성한 git repo로 구성된 소스코드의 Tab과 Space 일괄 통일 작업 계획을 오늘 실제로 실행했기에 블로그를 적어 본다.

작업 전에 미리 생각해볼 문제들


Tab과 Space 중 Tab으로 결정
- 이유 1 : 개발팀원이 모두 Tab을 선호함
- 이유 2 : 이클립스의 기본 설정
- 이유 3 : 키보드의 Tab키를 눌러서 들여쓰기를 하니까 실제 행동과 결과를 맞추려고

Space 4개를 Tab 1개로 하고, 4개 미만의 Space는 버림
- 이유 1 : 단순한 계산이 가능해서
- 이유 2 : 이클립스의 기본 설정이 Space 4 = Tab 1

변경 후 혹시 틀어지게 되는 들여쓰기는 무시했다.
- 이유 1 : 나중에 그 부분을 수정할 때 고치겠지.
- 이유 2 : 어차피 지금도 일관된 규칙으로 들여쓰기 되지 않음
- 이유 3 : 일괄로 들여쓰기 정도를 고치면 들여쓰기가 틀어질 수 있음

일괄 작업 날짜는 특정 버전 릴리즈 직후로 했다.
- 이유 1 : 개별 개발자가 별도 branch를 만들 경우가 거의 없다.
- 이유 2 : 일괄 작업한 결과를 master와 development 모두 merge 가능

작업하는 김에 줄끝의 공백문자도 같이 지워주고, 줄바꿈문자도 UNIX(\n)로 같이 통일했다.


작업 방법


소스코드를 변경하는 java 프로그램을 작성해서 일괄로 작업했다. 이런 작업을 일괄로 하위 디렉토리까지 해주는 도구를 찾아볼까도 생각했는데, 프로그램을 직접 만드는게 간단할 것 같아서 그냥 구현했다.

파일을 한줄씩 읽어서 처리하고 다시 한줄씩 쓰는 형태인데, java7 nio 패키지의 Files를 이용해서 한번에 읽어서 처리하고 다시 한번에 쓰는 형태로 구현했다.

그리고 Files.write를 할 때 UNIX 줄바꿈 문자를 사용해야 하는데, 작업을 해보니 그대로 DOS형식이 유지되어 파일이 생성되고 있었다. javadoc을 보면 line.separator라는 시스템 프러퍼티에서 지정한 플랫폼별 줄바꿈문자를 사용한다고 한다. 그래서 시스템 프러퍼티를 강제로 \n으로 바꿔 주어야 한다.

```java
System.setProperty("line.separator", "\n");

List<String> readAllLines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);

for (int i = 0; i < readAllLines.size(); i++) {
String line = readAllLines.get(i);
String changedline = StringUtils.stripEnd(line, null);
changedline = changeToTab(changedline);
readAllLines.set(i, changedline);
}

Files.write(file.toPath(), readAllLines, StandardCharsets.UTF_8);
```

이 프로그램이 정말 소스코드의 앞뒤 공백문자와 줄바꿈문자만 변경했는지를 확인하고 싶었다. 그래서 생각해 낸 것이 git diff를 공백을 무시하여 확인하는 방법인데, command line에서 아래 명령을 실행해서 변경된 것이 아무것도 없는 것을 확인하여 예상대로 작업이 되었다고 확인할 수 있었다.

```
git diff --ignore-all-space
```


작업 후 처리


앞으로 변경되는 소스코드에서 일괄작업에서 적용한 규칙을 지켜서 git 형상에 들어올 수 있도록 개별 개발자의 개발편집환경을 맞출 필요가 생겼다. 우리 개발팀은 모두 이클립스를 사용하므로, 이클립스에서 각 프로젝트의 환경설정과 AnyEdit라는 플러그인의 옵션을 설정하는 것으로 이를 맞출 수 있었다.

.settings/org.eclipse.core.resources.prefs 파일 내용
eclipse.preferences.version=1
encoding/<project>=UTF-8

.settings/org.eclipse.core.runtime.prefs 파일 내용
eclipse.preferences.version=1
line.separator=\n

.settings/de.loskutov.anyedit.AnyEditTools.prefs 파일 내용
activeContentFilterList=
addNewLine=false
convertActionOnSaave=AnyEdit.CnvrtSpacesToTabs
eclipse.preferences.version=1
fixLineDelimiters=true
ignoreBlankLinesWhenTrimming=false
inActiveContentFilterList=
javaTabWidthForJava=false
org.eclipse.jdt.ui.editor.tab.width=4
projectPropsEnabled=true
removeTrailingSpaces=true
replaceAllSpaces=false
replaceAllTabs=false
saveAndAddLine=false
saveAndConvert=false
saveAndFixLineDelimiters=true
saveAndTrim=true
useModulo4Tabs=false

2016년 11월 23일 수요일

90년대 음악과 영화에서 찾아낸 상대방을 위로하는 말 3가지

예전부터 누군가를 위로한다는 것을 항상 어려워 했다. 상대방을 걱정하는 마음, 슬퍼하는 마음을 말이나 행동으로 전하는 것이 너무 어려웠다. 막연히 누군가가 위로하는 방법을 알려줬으면 했다.

나름의 나이를 먹고, 세월을 지내고 보니 나만의 방법이라는 것이 생겼다. 방법이 생겼다기 보다는 어떤 말로 시작해야 할지 알게 되었다. 이 말로 위로를 시작하면 좋을 것 같다는 생각을 하게 되었다. 이 3가지 말을 나에게 알려준 90년대 음악과 영화를 함께 적어 본다.

1. You are not alone

우선 당신은 혼자가 아니에요. 당신이 느끼는 어려움은 다른 사람들도 다 느끼는 것이에요. 당신이 처한 상황에 당신만 있는 것이 아니에요. 그리고 당신이 혼자 그 어려움을 견디는 것이 아니에요. 내가 있어요.

Michael Jackson - You Are Not Alone (1995)

2. It's not your fault

그리고 당신 잘못이 아니에요. 당신이 이런 상황을 만든 것이 아니에요. 당신도 어쩔 수 없었어요. 당신이 바꿀 수 없었을 거에요. 당신의 잘못이 아니라는 것을 알고 있어요.

Good Will Hunting (1997)


3. Everything's gonna be alright

그리고 다 잘 될 거에요. 어려운 일을 해야 하나요? 당신은 할 수 있을 거에요. 걱정되는 일이 있나요? 잘 해결될 거에요. 잘 될 거라는 생각을 해보세요. 그렇게 될 거에요. 

Sweetbox - Everything's Gonna Be Alright (1997)



2016년 11월 1일 화요일

Nodejs 서버에 Let's Encrypt 설치해서 https 적용 후기

daag.pe.kr에 https를 적용해달라는 요청이 있어서 그동안 봐두었던 Let's Encrypt를 적용한 후기를 남긴다.

1.
우선 Let's Encrypt에 접속해서 Getting Started를 가보니 shell 접근이 가능하면 certbot을 사용하면 편할거라고 한다. certbot 페이지에 가보면 nodejs는 선택지에 없다. 그리고 난데없이 certonly를 하라고 하고, webroot를 하라고 하는 등... 난 nodejs인데.. 하면서 letsencrypt와 nodejs, express로 검색을 하다보면 letsenrypt-express가 검색이 되서 나오게 된다. 설명을 보면 등록부터 사용, 갱신까지 다 알아서 해주는 것 처럼 보인다. 이렇게 생각하면 여기서 헤매게 된다.

2.
우선 키발급을 먼저 하고, 그 다음에 서버에 적용할 생각을 해야 하는데, 키발급을 인터넷 사이트에서 하려고 생각하는 순간 역시 헤매게 된다. 그러다가 certbot의 문서 중 이곳을 천천히 읽다가 키발급의 원리를 깨닫고 나서야 진행이 쉽게 되었다.

This will carry out the steps needed to validate that you control the domain(s) you are requesting a cert for, obtain a cert for the specified domain(s), and place it in the /etc/letsencryptdirectory on your machine

키발급을 요청하는 사람이 그 도메인에 대한 진짜 주인인지를 판단을 해야 하는데, 그 방법을 해당 도메인의 루트에 특정 파일을 심어 놓고 외부에서 접속해서 그 파일을 확인하는 방법을 사용한다. (아마 그런 것 같다)

3.
그래서 certbot에게 certonly라는 명령으로 키발급만 한다고 알려주고, --standalone으로 직접 80/443 서버를 띄워서 도메인을 확인하거나, --webroot-path 옵션으로 기존에 떠있는 서버를 이용해서 도메인을 확인하는 과정을 거쳐야 /etc/letsencrypt 디렉토리에 pem 파일이 생성된다.

sudo ./certbot-auto certonly --standalone -d 도메인주소

또는

sudo ./certbot-auto certonly --webroot-path 도메인주소로접근시서비스되는디렉토리 -d 도메인주소

이렇게 하고, 이메일 주소를 더 넣거나 하는 등의 과정을 거치면 아래처럼 정상메시지를 볼 수 있다.

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/도메인주소/fullchain.pem. Your cert will
   expire on 2017-01-29. To obtain a new or tweaked version of this
   certificate in the future, simply run certbot-auto again. To
   non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate

   Donating to EFF:                    https://eff.org/donate-le

4.
이 때 standalone으로 하는데, 80이나 443포트를 다른 프로세스에서 사용중이면 안된다. 그리고 443포트가 외부에서 접근이 가능한 상태인지, AWS의 경우 Security Group을 확인해줘야 한다.

5.
이제 nodejs 서버의 express에 letsencrypt-express를 적용하면 된다. 예제코드처럼 express()를 한번 래핑하는 식으로 쉽게 구현이 가능하다. 물론 실제 코드에서는 config 파일을 이용해서 아래처럼 구현한다.

```
var express = require('./config/express');

var app = require('letsencrypt-express').create({
        server: config.https.server,
        email: config.https.email,
        agreeTos: true,
        approveDomains: config.https.domains,
        app: express()
}).listen(config.port, config.https.port);


6.
nodejs 서버는 ubuntu 계정에서 실행하기 때문에 포트포워딩도 해줘야 한다.

```
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3080
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 3443

7.
서버를 띄우면 알아서 /etc/letsencrypt/ 에서 발급된 키파일을 이용해서 서버를 띄워주는 것 같다. 그리고 초반에 server를 'staging'으로 해서 한번 띄우고 나면 다시 운영모드로 server를 바꾸고 서버를 재시작해도 키발급자가 "Fake LE Intermediate X1"으로 뜨면서 인증되지 않은 인증서라고 나오는 경우가 있다. 이럴 때는 ~/letsencrypt 디렉토리를 한번 삭제해주면 된다.

8.
그래서 이 작은 초록색 자물쇠를 결국 얻어냈다. ㅎㅎㅎ


2016년 10월 18일 화요일

작은 윈도우 배치 파일 만들기 - 변수,부모디렉토리,아규먼트전달

오랜만에 작은 윈도우 배치 파일을 만들 일이 있어서 그 결과를 공유한다.

1.
@ECHO OFF 말고는 생각이 안나서 일단 검색을 해보니 한글 블로그도 있고, 위키피디아도 있지만 세번째 검색결과로 나온 외국 개발자의 블로그가 오버뷰로 제일 볼만한 것 같다.

2.
윈도우 배치 파일에서는 SET 변수명=값 으로 변수를 설정하고, %변수명%으로 변수를 사용한다. 주의해야할 점은 리눅스와 달리 값을 쌍따옴표로 묶으면 쌍따옴표가 그대로 값으로 사용된다.

```
SET JAVA_OPTIONS="-Xms128M -Xmx512M -Xmx1024M"
java %JAVA_OPTIONS%        ===> java "-Xms128M -Xmx512M -Xmx1024M"
SET JAVA_OPTIONS=-Xms128M -Xmx512M -Xmx1024M
java %JAVA_OPTIONS%        ===> java -Xms128M -Xmx512M -Xmx1024M
```

3.
배포중인 어플리케이션의 구조가 /bin, /conf, /lib 와 같은 구조라서 실행파일의 부모디렉토리까지의 경로가 필요한데, 이것도 검색을 통해 아래처럼 구현했다.

```
for %%i in ("%~dp0..") do set "parent=%%~fi"
SET APP_PATH=%parent%
```

4.
배치 파일에 입력된 아규먼트를 갯수와 상관없이 그대로 사용하고자 할 때는 %* 를 사용하면 된다.

2016년 10월 6일 목요일

Java에서 Exabyte 급의 File System의 Free Space 구하기

특정 디렉토리가 속한 파일 시스템의 전체 용량과 사용가능한 용량을 주기적으로 체크해서 데이터베이스에 기록하는 프로그램이 있다. 처음 개발한 이후로 2년여 동안 별다른 문제가 없었는데, 지난주에 AWS에 배포된 시스템에서 문제가 생겼다.

전체 용량이 -999999999999999 비슷한 수의 음수로 저장되고 있었다. 로그를 확인해보니 시스템이 계산한 전체 용량은 -9223372036854775808 이었다. 이 또한 무슨 일인지?

시스템에서 df로 확인해 보았더니, 전체 용량이 8 EB였다. 이건 자바의 long 데이터타입의 최대값을 벗어나는 값이다.

[ec2-user@hostname]$ df
Filesystem                                                  1K-blocks      Used        Available Use% Mounted on
xxx.amazonaws.com:/ 9007199254740992 238655488 9007199016085504   1% /efs

찾아보니 프로그램에서 사용한 자바의 File 클래스의 getTotalSpace 메서드의 리턴이 long이고, 이 메서드가 음수를 리턴하고 있었다. 조금더 검색해보니 이미 2016년 7월 26일에 버그로 리포팅된 내용이었다. 여기여기에서 확인이 가능하다. 내용을 보면 리눅스는 64-bit의 unsigned integer 2개로 블록사이즈와 블록갯수로 파일시스템의 용량을 표현하는데 자바는 이걸 곱한 값을 64-bit signed long으로 리턴한다는 것이다.

버그가 고쳐지길 기다릴 수는 없기에 검색을 해보니 apache common io 라이브러리가 만들어놓은 FileSystemUtils 클래스의 freeSpaceKB 메서드가 있었다. 설명을 읽어보니 내부적으로 리눅스의 df 명령의 결과를 파싱해서 리턴하는 것 같다. 그래도 KB로 리턴하니까 overflow는 발생하지 않을 것 같다.

그런데 사용가능한 용량외에도 전체 용량도 KB단위로 리턴하는 메서드가 필요한데 그것은 아무리 찾아봐도 나오지가 않는다.

결국 자체적으로 df를 파싱해서 전체 용량과 사용가능한 용량을 구해야 할 것 같다. 그리고 이 기능은 df가 가능한 리눅스에서만 정상적으로 동작할 것이다.



2016년 9월 19일 월요일

Apache Common IO의 BOMInputStream의 버그

Java는 BOM을 별도로 처리해주지 않는다고 한다.
http://bugs.java.com/view_bug.do?bug_id=4508058
http://bugs.java.com/view_bug.do?bug_id=6378911

유니코드에서는 UTF-8인 경우 BOM을 넣지 말도록 권장한다.
하지만 넣는다고해서 BOM규칙을 어기는 것도 아니다.
https://en.wikipedia.org/wiki/Byte_order_mark

즉, BOM은 있을 수도 있고, 없을 수도 있고, Java가 알아서 처리해주지도 않는다.
그래서 아파치에서 Common IO에 BOMInputStream을 만들어놨다.
https://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/input/BOMInputStream.html

그런데 BOMInputStream을 사용하면서 뜻밖의 사소한 버그로 고생을 좀 했다.

첫번째 버그
BuffredReader로 감싸서 readLine()을 호출하면 파일의 마지막에서 null을 리턴하지 않고, IOException을 발생시킨다.
이건 2.0.1에서 고쳐진 버그이다.
https://issues.apache.org/jira/browse/IO-257
http://svn.apache.org/viewvc?view=revision&revision=1052095

두번째 버그
hasBOM(ByteOrderMark) 메서드가 올바른 값을 리턴하지 않는다. hasBOM()을 한번 호출하고 나면 올바른 값을 리턴한다. 이건 누군가가 아파치 JIRA에 리포팅해서 수정도 했지만, 반영되진 않은 상태이다.
https://issues.apache.org/jira/browse/IO-482


2016년 9월 7일 수요일

git repo로 구성된 소스코드의 Tab과 Space 일괄 통일 작업 계획

소스코드의 들여쓰기를 Tab과 Space 중에 무엇으로 하는가에 대한 이야기가 있다. 설문조사도 있고, github의 현황을 통계낸 자료도 있다. 업무로 개발중인 소스코드에서는 표준없이 마구 개발하다보니 통일되어 있지 않은데, 관련해서 생각을 정리해본다.

왜 통일하려고 하는가?

  • 머지 리퀘스트 diff 중에 공백문자로 인한 불필요한 diff를 보기 싫어서
  • 에디터 설정에 따라서 인덴트가 둘쑥날쑥하면 소스 보기 어려워서
  • 마음속 깊은 곳의 불편함을 없에고 싶어서
  • 그냥 해보고 싶어서

통일작업시 고려할 것은?

  • Tab과 Space중 무엇으로 할 것인가? (개발팀의 의견 중요)
  • Space로 하면 몇 칸으로 할 것인가? (2 or 4)
  • Space를 Tab으로 바꾸면 Space 몇 칸을 Tab 1개로 할 것인지?
  • Space 나머지는 어떻게 할 것인지? (ex: Space 4이 Tab1개인데, Space가 14칸일때 Tab은 몇개?)
  • 바꾸고 나면 인덴트가 틀어지는데, 파일 전체의 자동 인덴트를 할 수 있을지?

통일작업 이외에 고려할 것은?

  • 언제 BigBang 날짜를 잡을 것인가?
  • 작업중인 feature 들은 어떻게 할지? (머지 리퀘스트 할때 conflict 날텐데 ㅠㅠ)
  • 하는 김에 줄끝의 공백문자도 같이 지워주면 좋다.
  • 하는 김에 줄바꿈 문자도 통일하면 좋다.
  • 공백문자만 변경된 머지 리퀘스트는 공백문자만 바뀌었는지를 어떻게 보장할지?
  • development에서 작업하고나서, 다음 master 머지전까지 master에서 변경되는 hotfix는 develoment로 어떻게 (자동으로) 머지할지?

통일작업 완료되면 할일은?

  • 개발표준 문서/위키 변경 및 공지
  • 모든 개발자의 에디터의 Save Action 설정 통일
  • 커밋 훅 추가해서 안지켜진 소스는 reject
  • 머지 리퀘스트에서는 머 해줄거 없을까....
  • 작업 일지 남기기

2016년 8월 23일 화요일

gradle로 application 배포zip 파일 만들기

회사 업무 중에 linux에서 stand alone으로 실행되는 java 프로그램이 하나 있다. 이걸 개발하고 빌드하고 배포해서 실행하는 일련의 과정이 옛날의 방식으로 되어 있어서 최근 몇일동안 요즘의 방식을 적용하면서 자동화를 했다.

1. ant -> gradle
이전에는 ant로 빌드 task를 하나씩 정의해서 순서대로 실행되도록 depends를 걸고 실행해서 jar 파일을 만들었다.
이제는 gradle로 빌드환경을 바꿔서 gradle의 java 플러그인이 가진 convention을 활용하여 별도로 정의한 task 없이 jar task에 속성이나 메서드를 정의해서 jar를 만들게 변경했다. 이변경을 통해서 ant xml 파일의 수십줄의 보통의 빌드스크립트를 삭제할 수 있었다.

```
apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.7
targetCompatibility = 1.7

compileJava.options.encoding = 'UTF-8'

jar {
baseName 'sds-genomics-analysis'
    manifest {
        attributes 'Main-Class': 'aaa.bbb.ccc.DddMain'
    }
    exclude (['spring/**', 'properties/**', 'logback.xml'])
}
```

2. dependency
이전에는 라이브러리에 대한 dependency를 관리하지 않고 있었다. 필요한 라이브러리가 있을 때마다 jar 파일을 다운로드 받아서 lib 폴더에 넣고 classpath에 추가했었다. 물론 jar파일까지 git repo에 커밋했어야 했다.
이제는 gradle을 적용하면서 사내 nexus를 repository로 지정하고 dependency를 선언하여 빌드시점에 자동으로 다운로드 받도록 변경했다. 당연히 jar 파일들은 git repo에서 빠지게 되었다. 이제 라이브러리 버전업을 큰 걱정없이 시도해볼 수 있게 되었다.

3. distZip
jar 라이브러리파일, xml 설정파일, 프러퍼티파일, 시작/종료 쉘스크립트파일을 적절한 폴더구조로 만들어서 배포판을 만드는 것도 모두 ant에 xml로 정의된 스크립트로 존재했었다.
이제는 gradle의 distZip task를 활용해서 간단하게 배포판 zip파일을 만든다.

처음에는 gradle의 application 플러그인을 적용했었다. application 플러그인은 startScripts task와 distZip task를 가지고 있는데, 각각 시작 쉘스크립트 파일을 만들고, 배포 zip파일을 convention을 따라 만들어준다. 그런데 startScripts가 시작 쉘스크립트만 만들고 종료 쉘스크립트는 만들지 않고, 만들어지는 쉘스크립트 내용도 내맘대로 구성하는 것이 잘 안되서 나만의 distZip을 만들어서 사용했다. 그리고 쉘스크립트는 실행권한을 주기 위해서 zip으로 만들때 755권한을 준다.
```
task distZip(type: Zip){
def baseDir = { archiveName - ".zip" }
into("/") {
// shell 파일은 실행권한을 주기 위해 별도로 포함시킨다.
from(project.file("src/dist")) {
        exclude('**/*.sh')
    }
from(project.file("src/dist")) {
        include('**/*.sh')
        fileMode = 0755
    }
}
into("conf"){
from(project.file("src/main/resources")) {
        exclude('sqlmap')
    }
}
into("lib") {
    from(jar)
    from(project.configurations.runtime)
}
}
```

4. start.sh
이전에는 시작 쉘스크립트에 실행디렉토리 위치가 하드코딩되어 있었다.
이제는 쉘스크립트가 실행되는 위치를 인식해서 경로를 찾아내도록 변경하였다. $0 파라미터를 사용하는 것이 기본 아이디어인데, 심볼릭링크 제거를 위해 readlink를 사용하고, 부모 디렉토리를 얻기 위해 dirname을 2번 사용했다.
```
#!/bin/sh
APP_PATH=$(dirname `readlink -f $0`)
APP_PATH=$(dirname $APP_PATH)
echo $APP_PATH
```


2016년 8월 17일 수요일

study내용 jquery의 ajax

Ajax
  • Global Ajax Event Handlers
  • Helper Functions
  • Low-Level Interface
  • Shorthand Methods

1.
먼저 Ajax Event에서 ajax가 발생할 때마다 호출되는 이벤트 핸들러의 종류와 순서를 먼저 인지한다. 모든 ajax 호출에 대해서 같이 발생하는 ajaxStart와 ajaxStop 이벤트는 발생시점을 다른 것과 분리해서 생각하면 된다. 
그리고 나서 Global Ajax Event Handlers를 본다. 개별 ajax의 이벤트는 ajax()를 볼 때 본다.

2.
ajax()가 포함된 Low-Level Interface를 본다. 길고긴 ajax()를 먼저 본다. 특히 중간에 ajax()의 리턴 객체인 jqXHR를 주의깊게 본다.
ajax()를 보고나서, ajaxSetup()와 ajaxPrefilter()를 비교하면서 본다. ajaxSetup()은 option 객체를 받아서 디폴트를 변경하는 것, ajaxPrefilter()는 함수를 받아서 ajax실행될 때마다 전달된 option을 변경하는 것.

3.
그 다음 Showhand Method를 본다. 각각 무엇이 단축된 것인지 확인한다.

4.
마지막으로 Helper Functions을 본다. 3개 함수의 호출관계를 파악하면 좋다. serialize()의 소스코드를 보면 제일 간단하다. serializeArray()를 호출한다음 param()을 호출한다.