Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

Headless Chrome으로 AWS Lambda에서 웹사이트 스크린샷 찍기 #1

이전에 Headless Chrome의 사용방법에 관한 글을 올리고 Chrome의 headless_shell을 직접 컴파일해서 사용하는 방법에 대해서도 올렸다.(이 글이 6월이었다니...)

이전 글에서도 얘기했듯이 headless_shell을 테스트해보는 이유는 AWS Lambda에서 사용해보기 위함이다. AWS Lambda에서 사용할 수 있으면 다른 환경에서는 훨씬 쉽게 사용할 수 있고 Headless Chrome 같은 경우 자동화에 활용할 가능성이 크므로 Lambda 같은데 올려서 쓸 수 있다면 제격이라고 생각했다. Chrome 버전이 계속 올라가긴 하겠지만 현재 Headless Chrome으로 어디까지 할 수 있고 어떻게 활용할 수 있는지 파악해 놓고자 함이었다.

웹페이지 스크린샷

Internet Archive Wayback Machine처럼 내가 원하는 사이트를 정기적으로 계속 스크린샷을 찍으려고 한다. 요즘은 잘 안 바꾸지만 내 블로그의 디자인을 변경하는 때도 있으므로 예전부터 로컬에서 스크린샷을 찍어서 보관하고 있었다. 로컬에서 돌리는 건 아무래도 환경마다 달라서 귀찮으므로 이를 Labmda에 올려서 알아서 돌아가게 하면 좋겠다는 생각을 했다. 뭐 이전에도 이런 작업은 충분히 가능했지만 귀찮아서 놔두다가 Headless Chrome을 사용해 보기 좋아서 선택했다.

물론 이는 사이트에 접속해서 스크린샷만 찍는 것이므로 브라우저의 동작까지 제어해야 하는 자동화보다는 훨씬 간단한 작업이지만 Lambda에서 Headless Chrome을 돌려보기에는 적당한 주제라고 생각했다.(기대보다 훨씬 험난했지만...)

이렇게 작성한 Lambda 함수는 GitHub에 올려두었지만 이 글 이후에도 소스를 계속 수정할 것 같으므로 여기서는 만들면서 겪은 이슈 위주로 정리해 보려고 한다. Lambda를 쓸 때 apex를 사용하고 있지만, 이 글에서는 설명이 복잡하므로 따로 만들어서 직접 Lambda에 업로드 하려고 한다.

스크린샷 Lambda

AWS Lambda에 올릴 Node.js 코드를 예시로 작성할 예정이고 환경은 Node.js 6.10을 사용할 것이다.

Chrome Launcher

크롬을 사용할 것이므로 일단 크롬을 실행해야 하는데 나는 chrome-launcher를 선택했다. 쉘 명령어로 직접 headless_shell을 실행해도 되지만 이경우 관리가 좀 귀찮을 것으로 생각했고 구글 크롬에서 관리하는 Lighthouse 아래 있는 chrome-launcher가 사용하기 적당해 보였다.

관련해서 몇 가지 참고할 부분이 있는데

  • 현재 0.6.0 인데 0.4.0 이전까지는 Lighthouse와 같이 배포되었으므로 require('lighthouse/lighthouse-cli/chrome-launcher') 처럼 가져와야 했지만, 이제는 별도의 npm 모듈로 배포되므로 바로 require('chrome-launcher')로 가져오면 된다. 0.4.0 이전 버전을 사용하는 글을 참고한다면 참고해야 한다.
  • 구글 크롬팀에서 chrome-launcher 외에 Headless Chrome을 Node.js로 제어할 수 있는 Puppeteer을 얼마 전에 공개했다. 이번에 만든 Lambda도 Puppeteer으로 변경할 예정이긴 하지만 아직 사용해보지 않았다. 새로 시작한다면 Puppeteer으로 바로 시작하는 게 더 좋을 수도 있다.

현재 chrome-launcher 버전은 0.6.0이다. 이제 Headless Chrome을 사용할 코드를 작성하기 위해서 chrome.js을 다음과 같이 만든다. 경험상 Lambda를 쓸 때 위의 index.js는 아주 간단하게만 둔 채로 다른 로직은 별도의 파일로 작성하는 게 테스트도 작성하기 쉽고 훨씬 낫다. 먼저 chrome-launcher를 설치하고(npm install chrome-launcher) chrome.js에 다음과 같은 코드를 작성한다. 현재 버전은 0.6.0이다.

// chrome.js
const chromeLauncher = require('chrome-launcher');

const launchChrome = () => {
  return chromeLauncher.launch({
    chromeFlags: [
      '--headless',
      '--disable-gpu',
      '--no-sandbox',
      '--vmodule',
      '--single-process',
    ],
    logLevel: 'verbose',
  })
  .then((chrome) => {
    console.log(`Headless Chrome launched with debugging port ${chrome.port}`);
    return chrome;
  });
};

launchChrome();

간단히 chromeLauncher.launch()로 원하는 플래그와 옵션을 지정해서 크롬을 실행하는 코드이다.

  • chromeFlags은 크롬에 직접 전달하는 플래그이다. chromeLauncher에서 지원하지 않는 옵션은 이렇게 지정해야 한다.

    • --headless은 Headless 모드로 띄우기 위해 실행했다.
    • --disable-gpu는 GPU의 사용을 끈 것이다. 문서에 따르면 Mesa 라이브러리가 없는 경우 오류를 피하려면 필요하다는데 내 테스트에서는 없어도 큰 문제는 없었다. 일단 문서에 그렇게 나와 있으니 지정했다.
    • --no-sandbox는 macOS에서는 괜찮았지만, Linux 환경(특히 Lambda)에 가면 이 옵션이 없으면 No usable sandbox!라는 오류가 발생하고 문서를 참고하라고 나온다.
    • --vmodule--single-process는 좀 묘한 옵션인데 macOS와 로컬에서 Linux 환경에서도 다 괜찮았는데 Lambda에만 올리면 Headless Chrome을 정상적으로 띄웠음에도 chrome devtools protocol에 접속하지 못하는 문제가 생긴다. 좀 더 정확히는 크롬을 띄우고 chrome devtools protocol로 리스트를 요청하면 chrome devtools protocol에 접속 가능한 URL과 웹소켓 주소 등이 나와야 하는데 아무것도 주지 않는다. 크롬 버그인지 어떤지까지는 추적을 못 했지만 이 두 옵션을 지정하면 Lambda에서도 정상 동작한다.
  • logLevel은 런처의 로그 메시지 설정이다. 문제가 생길 때마다 디버깅을 해야 해서 verbose로 아예 켜두었다.

위 코드를 실행해보자.

$ node chrome.js
  ChromeLauncher:verbose created /var/folders/k8/j1j0fjkn5_vdzxybfqq0r7lc0000gn/T/lighthouse.XXXXXXX.8tqw7dbE +0ms
  ChromeLauncher:verbose Launching with command:
  ChromeLauncher:verbose "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary" --disable-translate --disable-extensions --disable-background-networking --safebrowsing-disable-auto-update --disable-sync --metrics-recording-only --disable-default-apps --mute-audio --no-first-run --remote-debugging-port=50069 --user-data-dir=/var/folders/k8/j1j0fjkn5_vdzxybfqq0r7lc0000gn/T/lighthouse.XXXXXXX.8tqw7dbE --headless --disable-gpu --no-sandbox --homedir=/tmp --data-path=/tmp/data-path --disk-cache-dir=/tmp/cache-dir --vmodule --single-process about:blank +588ms
  ChromeLauncher:verbose Chrome running with pid 7834 on port 50069. +4ms
  ChromeLauncher Waiting for browser. +1ms
  ChromeLauncher Waiting for browser... +0ms
  ChromeLauncher Waiting for browser..... +511ms
  ChromeLauncher Waiting for browser.....✓ +3ms
Headless Chrome launched with debugging port 50069

로그를 켜두었으므로 실행과정을 볼 수 있는데 정상적으로 Headless Chrome이 뜬 것을 볼 수 있다. --remote-debugging-port의 기본 포트는 9222인데 여기서는 지정하지 않았으므로 동적으로 할당해서 사용한다. 크롬이 실행되면서 반환한 객체는 { pid: 5891, port: 62993, kill: [Function: kill] }처럼 생겼다. 프로세스의 pid와 디버깅 포트 정보가 있고 실행한 크롬을 죽일 수 있는 kill() 함수가 있다.

headless_shell

chrome-launcher는 설치된 크롬을 자동으로 찾는다. Chrome Canary가 설치되어 있다면 이를 먼저 사용하고 아니면 Chrome을 찾는다.

사용 중인 데스크톱에는 보통 크롬이 설치되어 있겠지만 서버의 경우는 다르다. 나 같은 경우도 서버, 여기서는 AWS Lambda를 사용하기 위해서 headless_shell만 별도로 컴파일했다. 이렇게 별도로 있는 크롬 바이너리를 실행하려면 CHROME_PATH로 환경 변수로 크롬의 위치를 지정해 주어야 한다.

AWS Lambda라면 코드를 업로드 했을 때 /var/task에 올려지므로 업로드하는 코드에 headless_shell을 포함한다면 CHROME_PATH=/var/task/headless_shell로 환경변수를 지정해야 Lambda 내에서도 chrome-launcher가 정상적으로 크롬을 찾아낸다. 물론 환경변수보다 OS 설치된 크롬을 먼저 찾는다.

참고삼아 얘기하지만, chrome-launcher 0.4.0 이전 버전에서는 LIGHTHOUSE_CHROMIUM_PATH 환경변수를 사용했는데 지금은 CHROME_PATH로 바뀌었다.

Chrome Devtools Protocol

크롬에는 Chrome DevTools Protocol이 있다. 이는 크롬에서 개발자도구로 웹사이트를 디버깅, 프로파일링, 검사하듯이 원격에서 이 프로토콜을 사용하면 크롬으로 똑같은 일을 할 수 있다. 많은 도구가 이 프로토콜을 이용하고 있다.

이 프로토콜의 동작을 확인해보자.

macOS에서 bash를 쓴다면 설치된 크롬 카나리를 터미널에서 실행할 수 있도록 다음과 같이 별칭을 준다.

$ alias chrome-canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"
$ chrome-canary \
  --headless --disable-gpu \
  --remote-debugging-port=9222

[0824/223949.914930:WARNING:dns_config_service_posix.cc(156)] dns_config has unhandled options!

DevTools listening on ws://127.0.0.1:9222/devtools/browser/a2c2fe73-a8bc-49c8-84aa-61a251a7def9

헤드리스 모드로 크롬 카나리를 실행하고 원격 디버깅 포트를 9222로 실행하자 개발자 도구가 9222 포트를 리스닝하고 있는 것을 볼 수 있다. 이렇게 실행해 놓은 채로 터미널을 하나 더 열어서 이 디버깅 포트에 요청을 보내보자.

$ curl http://localhost:9222/json/version
{
   "Browser": "HeadlessChrome/62.0.3194.0",
   "Protocol-Version": "1.2",
   "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3194.0 Safari/537.36",
   "V8-Version": "6.2.333",
   "WebKit-Version": "537.36 (@c14400a94cb4f43e0e9b02e59e93cf5a082c6408)",
   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/a2c2fe73-a8bc-49c8-84aa-61a251a7def9"
}

$ curl http://localhost:9222/json/list
[ {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/e873e5cb-f1db-4892-87cf-5296fbc7485c",
   "id": "e873e5cb-f1db-4892-87cf-5296fbc7485c",
   "title": "about:blank",
   "type": "page",
   "url": "about:blank",
   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/e873e5cb-f1db-4892-87cf-5296fbc7485c"
} ]

/json/version으로 요청을 보내면 현재 크롬의 정보를 볼 수 있고 /json/list를 이용하면 접속할 수 있는 Chrome Devtools Protocol 목록을 반환한다. 목록이 배열인 이유는 크롬에서 탭을 여러 개 열 수도 있기 때문이다. 각 클라이언트는 이 정보 등을 이용해서 Chrome Devtools Protocol을 구현하고 프로토콜에서 제공하는 객체로 크롬을 제어할 수 있다. DOM, Page, Network 등 세세하게 다룰 수 있게 모두 제공하는데 직접 클라이언트를 개발하는 게 아니라면 세부 내용을 다 알 필요는 없어 보인다. 필요할 때 위 문서를 참고해보는 정도로 충분하다.

chrome-remote-interface

Chrome DevTools Protocol을 직접 사용하는 것은 어려우므로 이미 존재하는 많은 라이브러리가 존재하는데 공식 문서에서 권장하는 chrome-remote-interface를 사용했다.

chrome-remote-interface를 설치하고(npm install chrome-remote-interface) 앞에서 작성한 launchChrome()를 이용해서 Chrome Devtools Protocol에 연결해보자. 현재 버전은 0.24.4다.

// chrome.js
const CDP = require('chrome-remote-interface');

// launchChrome 부분 생략

let launchedChrome = null;

const connectChrome = () => {
  return launchChrome()
    .then((chrome) => {
      launchedChrome = chrome;
      return CDP.Version({ port: launchedChrome.port });
    })
    .then(info => console.log('version: ', info))
    .then(() => CDP({ port: launchedChrome.port }))
    .then((client) => {
      console.log('client connected');
    });
};

connectChrome();

이를 실행하면 다음과 같이 실행된 크롬에 접속해서 정보를 가져오고(CDP.Version) 연결을 하는 것을 볼 수 잇다. 여기서 CDP({ port: launchedChrome.port }))에서 포트만 지정했는데 host를 지정하지 않으면 localhost를 사용한다.

node chrome.js
  ChromeLauncher:verbose created /var/folders/k8/j1j0fjkn5_vdzxybfqq0r7lc0000gn/T/lighthouse.XXXXXXX.rNE9ehBV +0ms
  ChromeLauncher:verbose Launching with command:
  ChromeLauncher:verbose "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary" --disable-translate --disable-extensions --disable-background-networking --safebrowsing-disable-auto-update --disable-sync --metrics-recording-only --disable-default-apps --mute-audio --no-first-run --remote-debugging-port=64165 --user-data-dir=/var/folders/k8/j1j0fjkn5_vdzxybfqq0r7lc0000gn/T/lighthouse.XXXXXXX.rNE9ehBV --headless --disable-gpu --no-sandbox --homedir=/tmp --data-path=/tmp/data-path --disk-cache-dir=/tmp/cache-dir --vmodule --single-process about:blank +528ms
  ChromeLauncher:verbose Chrome running with pid 29094 on port 64165. +16ms
  ChromeLauncher Waiting for browser. +0ms
  ChromeLauncher Waiting for browser... +0ms
  ChromeLauncher Waiting for browser..... +512ms
  ChromeLauncher Waiting for browser.....✓ +2ms
Headless Chrome launched with debugging port 64165
version:  { Browser: 'HeadlessChrome/62.0.3196.0',
  'Protocol-Version': '1.2',
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3196.0 Safari/537.36',
  'V8-Version': '6.2.363',
  'WebKit-Version': '537.36 (@1bdf82f83c51a1f42672bc8e59494d238269fc6f)',
  webSocketDebuggerUrl: 'ws://localhost:64165/devtools/browser/87f5f38e-cae0-43c2-b595-d199d2c73d86' }
client connected

여기서 CDP({ port: launchedChrome.port }))가 반환한 client 객체는 Chrome DevTools Protocol에 있는 Page, Memory, Inspector, CSS, CacheStorage같은 객체가 있으므로 이를 이용해서 크롬을 조작하면 된다.

웹사이트 스크린샷 찍기

이제 준비가 다 되었으니 chrome-remote-interface를 이용해서 스크린샷을 찍을 차례다.

// chrome.js
const chromeLauncher = require('chrome-launcher');
const CDP = require('chrome-remote-interface');

// launchChrome 부분 생략

const connectChrome = () => {
  return launchChrome()
    .then((chrome) => {
      launchedChrome = chrome;
      return CDP.Version({ port: launchedChrome.port });
    })
    .then(info => console.log('version: ', info))
    .then(() => CDP({ port: launchedChrome.port }))
    .then((client) => {
      console.log('client connected');

      const { DOM, Emulation, Network, Page } = client;

      return Page.enable()
        .then(DOM.enable)
        .then(Network.enable)
        .then(() => {
          const deviceMetrics = {
            width: 1280,
            height: 1000,
            deviceScaleFactor: 0,
            mobile: false,
            fitWindow: false,
          };
          return Emulation.setDeviceMetricsOverride(deviceMetrics);
        })
        .then(() => Page.navigate({ url: 'https://github.com' }))
        .then(Page.loadEventFired)
        .then(() => Emulation.setVisibleSize({ width: 1280, height: 1000 }))
        .then(Page.captureScreenshot)
        .then(screenshot => new Buffer(screenshot.data, 'base64'))
        .catch((err) => {
          client.close();
          throw err;
        });
    })
    .catch((err) => {
      console.log(err);
      launchedChrome.kill();
      throw err;
    });
};

module.exports = {
  connectChrome,
};

위 코드가 CDP로 웹페이지를 띄워서 페이지 로딩이 끝나면 스크린샷을 찍어서 Buffer를 반환하는 코드이다. 간단히 설명하자면...

  • const { DOM, Emulation, Network, Page } = client;는 클라이언트에서 사용할 객체만 가져왔다.
  • Page.enable()에서 페이지를 활성화한다.
  • DOM.enable에서 해당 페이지에 DOM을 활성화한다.
  • Network.enable에서 해당 페이지의 네트워크 활동을 추적할 수 있게 한다.
  • Emulation.setDeviceMetricsOverride에서 디바이스의 스크린 설정을 지정한다. 여기서는 width, height를 지정하고(뒤에서 재지정한다.) 모바일 여부 등을 설정할 수 있는데 여기서는 특별한 설정은 하지 않았다.
  • Page.navigate에서 지정한 URL로 페이지를 이동한다.
  • Page.loadEventFired는 웹페이지에서 load 이벤트가 발생하면 실행된다.
  • Emulation.setVisibleSize에서는 스크린샷을 찍기 위해서 페이지의 viewport 크기를 지정한다. 문서를 보면 deprecated로 표시되고 있는데 chrome-remote-interface에서 아직 이 방법으로 안내하고 있어서 그대로 사용했다. 아직 다른 방법은 찾지 못했다.
  • Page.captureScreenshot에서는 현재 페이지의 스크린샷을 찍는다.
  • screenshot => new Buffer(screenshot.data, 'base64')에서는 Page.captureScreenshot가 반환한 base64 데이터를 Buffer로 생성해서 반환한다.

참고로 스크린샷을 찍는 작업을 하면서 여러 웹사이트를 한 번에 찍으려고 할 때 매번 크롬을 띄우고 DevTools Protocol에 붙을 필요가 없으므로 한번 연결해 놓고 URL만 변경하면서 계속 스크린샷을 찍으려고 할 때 Emulation.setVisibleSizePage.loadEventFired 이후에 하지 않으니까 처음 스크린샷만 지정한 사이즈로 찍히고 이후부터는 기본값인 800x600으로 찍히는 문제가 있었다. 이 문제가 Chrome의 버그인지 chrome-remote-interface의 버그인지는 아직 잘 모르겠다.

파일을 S3에 업로드하기

이 코드를 Lambda에서 실행할 것이므로 파일을 로컬에 저장할 수 없으므로 다른 스토리지에 저장해야 한다. 여기서는 S3에 저장하려고 아래와 같은 s3.js를 만들었다.

// s3.js
const AWS = require('aws-sdk');

const s3 = new AWS.S3({ apiVersion: '2006-03-01' });

module.exports = {
  uploadFile: (buffer) => {
    return new Promise((resolve, reject) => {
      const params = {
        Bucket: 'outsider-demo',
        Key: 'screenshot.png',
        Body: buffer,
      };
      return s3.putObject(params, (err) => {
        if (err) { return reject(err); }
        return resolve();
      });
    });
  },
};

aws-sdk를 이용해서 전달받은 Buffer를 S3 버킷(여기서는 outsider-demo)에 저장하는 Promise 코드이다. 예시를 간단하게 하려고 파일명 등은 하드 코딩했다.



이 글은 Headless Chrome으로 AWS Lambda에서 웹사이트 스크린샷 찍기 #2로 이어진다.

2017/08/26 23:02 2017/08/26 23:02

기술 뉴스 #84 : 17-08-15

웹개발 관련

  • Top Resources to Explore React Fiber : React v16부터 도입되는 Fiber를 설명한 자료를 정리한 글이다. 처음 Fiber를 소개한 자료부터 Fiber가 어떤 의미가 있고 Fiber의 구조와 동작을 잘 설명한 자료를 링크 걸고 각 글이 어떤 설명을 하고 있는지 잘 정리되어 있다.(영어)
  • Learning React.js is easier than you think — Interactive Examples : React 엘리먼트를 만드는 과정을 기본적인 부분부터 props와 state의 사용 및 생명주기까지 설명한 글이다. 각 과정에서 글 중간에 직접 실행해 볼 수 있도록 코드 스니팻과 결과가 나와 있어서 직접 사용해보면서 설명을 이해하기에 좋다.(영어)
  • All About React Router 4 : 버전 3에서 완전히 새로 작성된 React Route 4를 설명한 글이다. 사용하는 방법부터 완전히 달라졌기 때문에 3에 익숙했던 글쓴이가 4를 어떤 관점으로 접근해야 하는지 기본적인 라우팅과 레이아웃을 섞는 예제부터 중첩된 레이아웃, URL을 매칭시키는 방법까지 4에 대해서 자세히 설명한 글이다.(영어)
  • React Animations in Depth : React에서 애니메이션을 사용할 수 있는 방법을 비교 정리한 글이다. CSS, JS로 구현하는 방법부터 React Motion, Animated, Velocity-React 라이브러리의 장단점 및 사용 예제까지 제공해서 참고하기 좋다. 이 글은 React Native까지 고려하고 있어서 해당 방법이 React Native에서 사용할 수 있는지도 정리되어 있으므로 해당 부분까지 고려한다면 더욱 유용하다.(영어)
  • Bootstrap 4 Beta : 2년 만에 CSS 프레임워크의 대표 격인 Bootstrap 4의 베타버전이 나왔다.(영어)

그 밖의 프로그래밍 관련

  • AWS 비용 얼마까지 줄여봤니? : AWS 비용을 줄이려고 개발 서버로 쓰는 EC2 같은 경우는 업무시간에만 사용하면 되므로 DynamoDB, Lambda로 스케쥴러를 구현해서 의존관계에 따라 정해진 시간에만 EC2 인스턴스를 키도록 설정한 내용이다.(한국어)
  • AWS Auto Scalinging Group 을 이용한 배포: 레진에서 AWS의 오토 스케일링 그룹을 2개 만들어서 블루/그린 배포 전략을 이용하는 방법을 소개한 글이다. 오토 스케일링 그룹을 2개 만든 이유와 어떻게 처리해서 배포하고 롤백하는지까지 나와 있다.(한국어)
  • Up — Deploy serverless apps in seconds : 전에 gh-polls을 공개해서 Up이라는 프로젝트의 존재에 관해서 얘기했던 TJ Holowaychuk가 Up을 공개하고 그 사용방법을 공개했다. Apex보다 간단하게 AWS Lambda와 API Gateway를 이용해서 웹 애플리케이션을 올릴 수 있게 하는 간단한 도구로 AWS 인프라를 추상화해서 아주 간단하게 사용할 수 있다.(영어)
  • ES Modules in Node Today! : 현재 Node.js는 CommonJS의 모듈을 사용하고 있는데 이제는 ECMAScript Modules가 생겼으므로 이 둘을 모두 지원하는 데 어려움을 가지고 있다. .mjs 방식으로 가고 있는 것 같지만 정식 도입되려면 시간이 더 필요한데 @std/esm는 현재 Node 프로젝트에서 바로 설치해서 CommonJS와 ES Modules를 같이 사용할 수 있게 하는 방식이다. ES Modules는 import방식을 사용하고 간단한 변환을 통해서 사용할 수 있게 하는데 이 방식이 현재 왜 좋은 방법인지까지 설명되어 있다.(영어)
  • Node.js Errors — Changes you need to know about : Node.js 8.x.x 부터 오류를 비교하기 쉽도록 오류 code가 들어가게 된 배경과 이를 어떻게 사용할지를 설명한 글이다.(영어)
  • Elixir School 한국어판 : Elixir 프로그래밍 언어를 배울 수 있는 Elixir School의 한국아 버전이다.(한국어)

볼만한 링크

  • 리디에서 CTO가 하는 일 : Ridi의 CTO가 자기 일에 대해서 사내 발표한 발표자료다. 리디북스의 서비스를 좋아하는 편인데 사내에서 개발자 조직을 어떻게 7년 동안 키워왔는지를 볼 수 있는 재미난 자료다.(한국어)
  • 우리은행 + Eversafe == NULL : 우리 은행에 적용된 Eversafe라는 보안 앱이 절대 뚫리지 않는다는 기사를 보고 글쓴이가 Eversafe를 리버스 엔지니어링 해서 내부 구조를 분석한 글이다. 회사가 언급되어 논란에 관한 얘기가 뒷부분에 나오지만 보안 쪽은 자세히 알지 못해서 해당 앱을 분석해서 구조를 찾아가는 과정만으로도 아주 재미있었다.(한국어)

IT 업계 뉴스

프로젝트

  • share-this : Medium처럼 텍스트를 선택하면 툴팁으로 선택한 텍스트를 공유할 수 있게 하는 JavaScript 라이브러리.
  • send : Mozilla에서 만든 실험적 프로젝트 일정 시간 동안만 유효한 링크로 파일을 공유할 수 있게 하는 서비스다.
  • Ring UI : JetBrains에서 웹 프로젝트에 사용하는 React UI 컴포넌트를 오픈소스로 공개했다.
  • Hazel : Electron 앱의 업데이트 서버.
  • s2client-api : Blizzard에서 공개한 스타크래프트 2 API 클라이언트.
  • Kompose : Kubernetes에서 공개한 프로젝트로 Docker Compose파일을 Kubernetes에서 띄울 수 있는 도구.
  • TimescaleDB : PostgreSQL에서 타임시리즈 데이터를 사용할 수 있게 하는 확장.

버전 업데이트

2017/08/15 19:59 2017/08/15 19:59