// not working
class TestEntity(
@NotEmpty
val testVal: String
)
// working
class TestEntity(
@field:NotEmpty
val testVal: String
)
// working
class TestEntity(
@get:NotEmpty
val testVal: String
)
Validation을 실행해도 Constraint에 걸리지 않는 문제가 있었다. 자바로 비슷한 코드 짜서 테스트해보니 잘되길래 아 코틀린에서 뭔가 문제가 있다는걸 알아내고 코틀린 문서를 자세히 읽어본 결과 annotation use-site target을 지정 할 수 있다는 것을 알았다. Kotlin의 클래스 프로퍼티는 자바의 여러개의 엘리먼트에 대응되기 때문에 (예를 들면 필드 하나를 만들면 코틀린이 getter, setter도 내부적으로 알아서 추가해주는?) annotation 별로 타겟을 지정하기 위한 것이다.
굉장히 친근한 화법으로 동네 카페에서 개발경력 화려하신 아저씨를 만나 얘기하는 느낌이다. 루비라는 언어를 왜 만들게 되었는지 부터 프로그래밍 언어를 디자인 하는 방법, 실제로 구현하는 방법까지 설명도 해준다. 어려운 내용이지만 굉장히 재밌다. 사실 루비라는 언어를 개인적으론 좋아하진 않지만, 프로그래밍 언어를 개발한 그 열정과 천재성, 끈기에 리스펙..!!
… 의욕이 충만했다. 나는 프로그래머의 3대 미덕의 하나인 오만을 충분히 유지한 인물이기 때문에, 만들려면 펄이나 파이썬에 지지 않는 언어를 만들겠다고 결심했다. 또, 그것이 가능할 거라 믿었다. 근거 없는 자신감은 무섭지만, 이러한 근거 없는 자신감이야말로 종종 동기 부여의 원천이 된다.
어떤 앱을 크롤링(?) 해보고 싶어졌는데 나쁜짓 할려는건 아니고.. 아무튼 이게 웹사이트는 없고 앱만 있는 경우라 분석이 쉽지 않았다. 2시간 정도 삽질을 했는데 까먹을까봐 정리.
일단 HTTPS 통신을 가로채서 내용물을 까보려면 Man In The Middle Attack 방식을 쓰는게 좋다. 한마디로 프록시서버 구축하면 된다. A라는 서버랑 B라는 내 폰이랑 통신을 하는데 C라는 프록시서버가 끼어서 대충 A <-> C <-> B 이런 그림이다. C에서 우리는 HTTPS 통신의 내용물을 열어볼수 있다.
근데 자꾸 신뢰할 수 없는 인증서라고 SSL Handshake가 실패해서.. 프록시에서 사용하는 CA 인증서를 앱이 신뢰하도록 만들기 위하여 앱을 언패킹한후 약간 수정해줘야한다. 맥 기준으로 모든 과정을 정리해보도록 한다.
준비물
Charles proxy: 유료인데 이거 진짜 편한듯. 무료 버전이라며 30분마다 꺼지지만.. 불편함을 감수하고 쓸만하다.
안드로이드 apktool
이거 두개를 먼저 설치하자.
과정
APK 파일 가져오기
먼저 본인의 핸드폰이나 에뮬레이터에서 열어볼 앱을 앱스토어를 통해 설치한다.
adb로 앱의 패키지명(ddd.xxx.com 뭐 이런식..) 을 알아낸다.
adb로 앱이 설치된 경로를 알아낸다.
adb를 이용해 apk파일을 pc로 옮긴다.
2~4 번의 과정을 쉘에서 실행하면 아래와 같다.
# 디바이스에 설치된 패키지 리스팅
adb shell pm list packages
# 특정 앱의 설치된 경로 파악
adb shell pm path com.xxx.aaa
# 해당 경로의 파일 가져오기
adb pull /data/app/com.xxx.aaa/base.apk
APK 파일 수정
APK 파일 언패킹
apktool d xxx.apk
언패킹된 디렉토리의 res/xml 경로에 network_security.xml 추가
<network-security-config>
<base-config>
<trust-anchors>
<!-- Trust preinstalled CAs -->
<certificates src="system" />
<!-- Additionally trust user added CAs -->
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
언패킹된 디렉토리의 AndroidManifest.xml 수정
<application> element 를 찾아서 android:networkSecurityConfig="@xml/network_security_config" attribute를 추가해준다. 이 과정이 뭔지 간략히 설명하면, 후술할 charles proxy의 CA 인증서를 앱에서 신뢰하도록 만드는 것이다.
그리고 다시 apk파일로 패킹
apktool b xxx -o xxx-modified.apk
그 후에 adb를 이용해 apk 파일을 디바이스에 설치하려고 하면 사인되지 않은 apk 파일이라며 설치가 되지 않는다. 사인해주자
keytool -genkey -alias test -keyalg RSA -validity 20000 -keystore test
jarsigner -verbose -keystore test xxx-modified.apk test
그 다음, adb로 디바이스에 설치 (그 전에 앱스토어를 통해 설치했던 기존 앱은 삭제해주자)
그 다음 이 프록시에 연결 할 디바이스에서 network settings에 들어가서 proxy 를 설정한다. charles proxy 에서 help -> Local IP Address 에 들어가면 아이피를 확인할 수 있다. 해당 아이피를 적고 포트는 8888로 설정한다.
그 다음, 디바이스에서 브라우저를 열어서 charlesproxy.com/getssl 으로 들어간다. 여기서 키 파일 하나가 자동으로 다운로드 받아지는데, 이건 charlesproxy 에서 사용하는 CA 인증서로서 폰에 설치하도록 하자. (proxy를 통해서 들어가야만 받을 수 있다)
드디어!
아까 다시 패킹해서 설치한 앱을 실행하고 charlesproxy에서 잘 잡히는지 확인하면 된다. 참 쉽죠?
얼마전 요즘 핫한 모 기업의 전화면접에서 광탈한 후 당시 받았던 질문이 문득 다시 떠올랐다. 사전을 구현해야 한다면 어떤 자료구조로 구현을 할거냐는 질문에 해쉬맵을 쓸거라고 답했고.. 면접관은 그럼 일부만이라도 포함된 단어를 정렬하여 리스팅 할려면 어떤 구조로 짤건지 질문했다. 여기서 나는 머리속이 하얘지면서 아무 생각도 나지 않고 트리(Tree) 밖에 생각이 나지 않아서 트리로 구현하겠다며 아무말 대잔치를 했는데.. 지금 찾아보니 내가 당시 생각했던 알고리즘과 대충 비슷하긴 하다. 발음도 비슷하다. Trie. 문자열 저장과 prefix 검색에 많이 쓰인다. 나무위키에 따르면 트라이라고 읽어도 되고 트리라고 읽어도 된단다.
특징
기본적으로 트리(Tree) 기반의 자료구조다.
자식 노드의 개수는 가변적이다. 즉 배열을 쓰거나?
트리의 높이는 곧 자료구조 내 저장되어 있는 가장 긴 문자열이 될것이다.
각 노드는 한개의 문자만을 표현한다.
한개의 글자씩 노드에 연결되어있기에 N의 길이의 문자열을 검색시 O(N)의 시간복잡도를 가진다. 빠르다.
라라벨이나 스프링같은 프레임워크를 써본사람들은 한번쯤은 들어본 개념일테지만 내 머리속에서는 두루뭉실하게 윤곽만 그려져 있을뿐, 이게 뭔지 도대체 왜 써야하는건지, DI와 IoC의 관계는 어떻게 되는지 간략하게 정리해봐야겠다.
IoC: Inversion of Control
일명 제어의 역전 원칙이다. 소프트웨어 개발에서 프레임워크를 사용하게 되면 프로그램의 흐름이 프레임워크에 의해서 제어된다. 프레임워크는 프레임워크 사용자가 구현한 코드를 호출한다. 사용자는 프레임워크에 의해 주어진 인터페이스를 구현하고, 해당 구현체를 IoC Container에 등록한다. 그러면 그 구현체를 프레임워크가 필요할때마다 호출함으로써 개발자의 코드가 실행된다.
라라벨은 인터페이스를 Contract(계약) 이라고 부르는 관습이 있는데 인터페이스를 프레임워크 사용자(개발자)와 프레임워크간 계약이라고 본다고 생각하면 어느 정도 수긍이 간다.
Service Locator, DI 등의 방식으로 구현할 수 있다.
그럼 DI는 뭐지?
DI: Dependency Injection 은 클래스가 의존하는 클래스의 인스턴스를 주입받는 것이다. 내가 이해한 바로는 DI는 구체적으로는 IoC 컨테이너를 사용하는 방법 중 하나이며 크게는 클래스간의 결합도를 낮추는 디자인 원칙 중 하나다.
IoC 컨테이너는 여러 인터페이스에 대응하는 구현체를 담고있다고 앞서 설명했는데, 핵심은 사용자가 구현하는 클래스가 의존하는 클래스의 객체를 생성자로 주입받는다는 것이다. 누군가는 Dependency injection is passing an argument 라고 했다. 링크
사실 난 이말에 크게 공감한다. 이름만 멋드러지게 지어놨을뿐 사실 별거 아닌 개념이다. 의존성을 클래스 외부에서 주입하고, 클래스의 생성자에서 요구하는 의존 클래스는 추상화 되어야 좋다. 클래스를 변경하지 않고도 의존하는 추상 클래스의 구현체를 변경하는 것, 그게 사실 DI를 쓰는 이유니까..
마지막
결국 DI는 IoC 컨테이너를 쓰지 않더라도 생성자로 의존하는 클래스를 넣어주기만 하면 대충 DI라고 볼수 있다. (여기에 의존 클래스의 추상화가 더해지면 완벽한 DI) 하지만 스프링 등의 프레임워크는 생성자에 넣어두면 자동으로 의존성을 해결해주기 때문에 DI, IoC의 개념이 혼동될 수 있을것 같다. 나도 헷갈려서 생각을 정리하면서 몇글자 끄적여봤지만, 혹시 잘못 이해한 부분이 있을지 모르겠다.
PHP 8에서는 JIT 기능이 추가되면서 성능이 한번더 대폭 상향될거라고 한다. 얼마 전 이 얘기를 들었을 때 순간 헷갈렸던게.. 지금껏 PHP의 opcache가 곧 JIT 구현체라고 생각했었기 때문이다.
궁금해서 여기저기 찾아보니 PHP의 opcache, 즉 opcode를 캐시하는 기능은 비유하자면 Java에서 Java 파일을 .class 파일로 컴파일 하는 과정과 같다. 자바는 JVM에 의해 그 옵코드를 최종적으로 해당 머신에 맞는 기계어로 런타임에 컴파일한다. 즉 옵코드는 머신에 종속적이지 않은, 아직 실행될수 없는 코드인거고 일종의 코드를 인터프리팅 하기 쉽게 번역해놓은 수준이라고 이해하면 쉽다.
현재의 PHP에서 (7 버전대) Opcode를 실행하는 것은 결국 Opcode를 인터프리팅 하는것이라고도 할 수 있다. 이게 문자열 파싱해가면서 인터프리팅 하는것보다야 빠르겠지만, 머신 레벨 기계어로 컴파일된 코드에 비할바가 못되는 것이다!
한편으로 의문인 것이.. PHP는 정적타입의 언어가 아니라, 런타임에 변수의 타입이 결정되기에 컴파일 타임에는 변수의 타입을 알 수가 없을텐데 어떻게 기계어로 미리 컴파일 해둘 수 있을지 궁금하다. 곧 나올 PHP 7.4버전에서 타입힌팅이 대폭 강화되는 것으로 알고 있긴한데.. JIT 도입을 위한 초석이었을까? 관련 자료를 찾아보니 이런 문제를 nodejs는 Adaptive JITC라는 기능을 통해, 자주 호출되는 곳만 런타임에 기계어로 컴파일해두는 기능을 도입해서 해결했다고 한다.
아무튼.. 2021년에 릴리즈 예정이라던데.. 궁금하고 기대된다. 번외로, PHP 7.4에서는 opcache preload 기능이 추가되면서 30%정도의 성능향상이 있을것이라고 한다. 언제부턴가 PHP 코어팀이 정말 열일하는 느낌..
docker 사용시 호스트 네트워크에 접근해야 하는 경우가 있다. 예를들면 도커 컨테이너 안에서 로컬(Host machine)에 설치된 DB에 접속하려는 경우, localhost로는 접근이 안되고 실제 아이피를 입력해야한다. 아이피는 환경에 따라 매번 바뀌기 때문에 환경설정이 자꾸 바뀌면 골치아픈데, 도커 18.03버전부터는 host.docker.internal 이라는 dns name으로 접속이 가능하다.