ジンジャー研究室

長めのつぶやき。難しいことは書きません。

Herokuと同程度に簡単なOpenShift使用メモ(on Windows)

Heroku有料化に伴いOpenShiftを試した。その時のメモ。

以下、簡単と言いながら色々躓いているがドキュメントは凄く親切なので、基本的にはこれで足りる。

developers.openshift.com

アカウントを作る

Webページから(省略)。

コマンドラインツールのインストール

コマンドラインツールrhcがgemで提供されているので、まずはRuby1系の最新をインストール。 2系だとエラー。

gem install rhc

依存関係で怒られた。

ERROR:  While executing gem ... (Gem::DependencyError)
    Unable to resolve dependencies: rhc requires highline (~> 1.6.11); commander requires highline (~> 1.7.1)

~> 1.6.11という指定は1.6.Xまでしか受け付けないようだ。

gem install highline -v 1.6.11

再度。

gem install rhc

また怒られた。

ERROR:  While executing gem ... (Gem::DependencyError)
    Unable to resolve dependencies: commander requires highline (~> 1.7.1)

gem install rhc failed. Gem::DependencyError · Issue #678 · openshift/rhc · GitHub gemをアップデートしろと。

gem install rubygems-update
update_rubygems
gem install rhc

また怒られた。今度はgemのエラーっぽい。

ERROR:  While executing gem ... (ArgumentError)
    invalid byte sequence in UTF-8

Windows固有の問題らしいので以下を参考に修正。 会社の PC で 2 時間ほど悶々と悩んでたこと。 - msfukuiの日記

win32/registry.rb

#FormatMessageA = Kernel32.extern "int FormatMessageA(int, void *, int, int, void *, int, void *)", :stdcall
FormatMessageW = Kernel32.extern "int FormatMessageW(int, void *, int, int, void *, int, void *)", :stdcall
def initialize(code)
  @code = code
  #msg = "\0".force_encoding(Encoding::ASCII_8BIT) * 1024
  msg = "\0\0".force_encoding(Encoding::UTF_16LE) * 1024
  #len = FormatMessageA.call(0x1200, 0, code, 0, msg, 1024, 0)
  len = FormatMessageW.call(0x1200, 0, code, 0, msg, msg.size, 0)
  #msg = msg[0, len].force_encoding(Encoding.find(Encoding.locale_charmap))
  msg = msg[0, len].encode(Encoding.find(Encoding.locale_charmap))
  #super msg.tr("\r", '').chomp
  super msg.tr("\r".encode(msg.encoding), '').chomp
end

再度。

gem install rhc

出来た。

コマンドラインツールのセットアップ

rhc setup

色々訊かれるので、問題なければデフォルト値とyesをひたすら選択。 途中、さっき作ったアカウントでログイン。 SSH鍵の作成から公開鍵を登録するところまでやってくれる(秘密鍵は手動で読み取り専用にしろと書いてある)。

Node.jsアプリケーションの作成

多分Node.js以外も同じようなプロセスになるとは思う。 URLはhttp://アプリ名-ドメイン名.rhcloud.com/になるので慎重につける。(後からでも変更できるけど面倒)

アプリケーション作成Webからでも出来るが、ここではコマンドで作成。 Node.jsの場合はカートリッジ名をnodejs-0.10とする。

rhc create-app home nodejs-0.10

最初のアプリケーションを作る場合はここで名前空間ドメイン)を訊かれるので入力。

怒られた。

The authenticity of host 'home-jinjor.rhcloud.com (54.166.221.17)' can't be established.
RSA key fingerprint is 
Are you sure you want to continue connecting (yes/no)?

ここに至る前に仮に作ったドメインが気に入らなくて消したりしたせいかも。 もう一度セットアップ。

rhc setup

Web画面からアプリを消して再度作成する。

rhc create-app home nodejs-0.10

今度は通った。IPアドレスやら何やらが付与される。

この時点で、http://home-jinjor.rhcloud.com/に既にデフォルトアプリが立ち上がっているので見に行く。

開発を進める

先ほど作ったアプリのルートに移動して、Gitのリモートリポジトリを一応確認しておく。

git remote -v

何故か.gitignoreが無かったので追加。

echo node_modules > .gitignore

後はいつものようにNode.jsアプリを起動。

npm install
node server

http://localhost:8080/にアクセスして確認。

ソースも確認。

self.ipaddress = process.env.OPENSHIFT_NODEJS_IP;
self.port      = process.env.OPENSHIFT_NODEJS_PORT || 8080;

Herokuと同じく、主要な情報を環境変数から取得しているのが分かる。 Herokuと同じなら、DB等のアドオンの情報も環境変数から取得できるはず。

修正したらプッシュ。

git push origin master

自動的に再デプロイされる。

感想

Herokuと同じ。

HTTP/2で再帰的にPUSH_PROMISEするための最速アルゴリズム

話の発端

サーバプッシュするリソースの関連付けを全部手動で書くのが面倒だから、動的に中身を読んで解決したい。 その時に、依存の深いところにあるものでも、リクエストのストリームを閉じずに待たないといけないという制約があった。

jinjor-labo.hatenablog.com

というわけで、依存が深くてもなるべく早くコンテンツを返すためのアルゴリズムを考えた。形式的な説明が思いつかないので具体例を挙げる。

例1

index.htmlがstyle.cssを必要とし、style.cssがback.pngを必要としている。(階層の深さ:2)

index.html

<link rel="stylesheet" href="style.css"></link>

style.css

body { background: url("back.png"); }

最適なレスポンス順序

index.htmlに直接関連するリソース(ここではstyle.css)をPUSH_PROMISEした時点でindex.htmlのコンテンツを返す(この時点ではストリームを閉じない)。それ以上深いリソースのPUSH_PROMISEがすべて終わってからストリームを閉じる。

f:id:jinjor:20150407115139p:plain

back.pngを待たずにindex.htmlの全体を送って良い。なぜなら、ブラウザがindex.htmlをパースしてもback.pngが必要だという情報は得られないから。

例2

index.htmlがstyle.cssとapp.jsを必要としている。(階層の深さ:1)

index.html

<link rel="stylesheet" href="style.css"></link>
...
<script src="app.js"></script>

最適な順序

ストリームで細切れにすればより速い。ブラウザがサブリソースをリクエストしないことを保証したら、その部分のコンテンツを直ちに送りつける。

f:id:jinjor:20150407121006p:plain

まとめ

  • なるべく早くコンテンツを返してブラウザのパースを早める
  • 実装大変そう

権限の委譲と表現の自由度

難しい話ではないが、思考の整理のために書いてみる。


f:id:jinjor:20150327013947j:plain

図は、左の人が右の人に「この材料で何か喋って」と頼む様である。 下に行くほど右の人に大きな権限と表現の自由が与えられている。


f:id:jinjor:20150327014313j:plain

同じ挨拶でも「Hi」と言わせたい時は、権限の委譲が不十分だと左の人が面倒を見ることになる。


f:id:jinjor:20150327014726j:plain

逆にもっと丸投げしてみる。ユーザーIDだけ渡すから何とかしろ。 DBにアクセスしに行って、このユーザは現在英語で表示する設定にしているから名前は「Taro Yamada」が適切だろう。


f:id:jinjor:20150327015155j:plain

でもそれだと通信が沢山発生するのでキャッシュから取ってもらおう。


f:id:jinjor:20150327015254j:plain

いやもうむしろ全部お前に任せた。 必要なものはSessionスコープなりrequestスコープから全部取れ。


f:id:jinjor:20150327015658j:plain

そうなると必然的に任された方はアプリケーションについて十分な知識を持っている。 そうではなく、汎用的なユーティリティとして振舞う場合は細かくデータを渡す形式が向いている。


f:id:jinjor:20150327015854j:plain

React.jsみたいに、コンポーネントを組み立てるフレームワークが増えてきた。 図では、検索を行うために<TodoSearch>コンポーネントに上からオプションを渡している。


f:id:jinjor:20150327020116j:plain

<TodoSearch>コンポーネントの位置が変わって、上位のノードに変更が入ってしまった。


f:id:jinjor:20150327020224j:plain

これを防ぐひとつの解決策としては、contextのような何でもボックスを全てのコンポーネントに渡すようにする。


f:id:jinjor:20150327020308j:plain

もうひとつは、グローバルに何でもボックスを転がしておく。


f:id:jinjor:20150327020343j:plain

やはり、汎用的なコンポーネントとは区別する。


以上。

proxyquireでNode.jsのrequireをモックする

発端

React+Fluxのアプリのテストをするのに、requireのモックが必要だった。 だけどJest使いたくない。

そこでこれを使う。

github.com

簡単な使い方

something.js

module.exports = 'music';

love.js

var something = require('./something.js');

console.log('I love ' + something + '!');

index.js

var proxyquire = require('proxyquire');

require('./love.js');// => I love music!

proxyquire('./love.js', {
  './something.js': 'shit'
});// => I love shit!

require('./something.js');をモックして何でも言わせちゃう。

ユニットテスト用途には十分

requireで指定する文字列の曖昧さを許容しないのがちょっと微妙だけど、ユニットテストに使う分にはまぁ問題にはならない。

呼ばれた回数をカウントしたり色々したい場合は、Sinon.jsを使うのが便利。

DOM要素のidはグローバル変数

DOM: element IDs are global variables

知らなかった…orz

とはいえ普段は使わないほうがよさそう。

既に定義されているグローバル変数と被っていると上書きされない。

  <div id="a">Hello!</div>
  <script>console.log(a);//<div id="a">Hello!</div></script>


  <script>var b;</script>
  <div id="b">Hello!</div>
  <script>console.log(b);//undefined</script>


  <script>c = null;</script>
  <div id="c">Hello!</div>
  <script>console.log(c);//null</script>


  <script>d = undefined;</script>
  <div id="d">Hello!</div>
  <script>console.log(d);//undefined</script>


  <script>e = undefined;</script>
  <script>delete e;</script>
  <div id="e">Hello!</div>
  <script>console.log(e);//<div id="e">Hello!</div></script>

HTTP/2で再帰的にPUSH_PROMISEする場合の注意点

タイトルの通りで、今ぶち当たっている問題を共有するのが目的。

f:id:jinjor:20150311140752p:plain:w400

図のように、あるHTMLを起点にして依存しているすべてのリソースに対して、再帰的にPUSH_PROMISEしたい。

PUSH_PROMISEはPROMISEであるため、サブリソース本体が起点となるリソースよりも早く返却される必要はない。つまり、ブラウザはサブリソースの返却にブロックされずに、先に目的のリソースの評価を開始できるというメリットがある。

f:id:jinjor:20150227224512p:plain:w400

再帰的なPUSH_PROMISEとは

ただし、これを再帰的にやろうとした場合に問題が生じる。 実は、PUSH_PROMISEはクライアントからのリクエストにあたるストリームからしか発行できない。 だから、Stream 2からStream 4を生み出すのは無理。

f:id:jinjor:20150311143601p:plain:w500

そうすると、Stream 1からPUSH_PROMISEを発行しなければならないのだが、この時にStream 1が既に閉じられているとエラーになる。

f:id:jinjor:20150311143606p:plain:w430

つまり、依存するすべてのリソースに対してPUSH_PROMISEするまで、Stream 1を閉じてはいけないことになる。 Stream 1を閉じずにindex.htmlを送ることもできるが、そうするとPUSH_PROMISEされる予定のリソースを先にリクエストして干渉する恐れがあるので避けたい(SHOULD)。

そもそもの動機

これがいつ問題になるかというと、動的にHTMLなりCSSなりをパースしてPUSH_PROMISEしたい場合、パース中は最初のストリームが返却待ちになってしまう。依存ツリーがが深ければ深いほど待ち時間は長い。

かといって、事前に静的に依存関係を調べる方法だと、そのタイミングとキャッシュの管理を考える必要が出てくる。

まとめると、

  • 動的に依存を調べる場合はリクエストの返却が遅れる(PROMISEの恩恵が受けられない)。
  • 静的に依存を調べる場合はキャッシュの管理などが必須。

こんな感じなので注意が必要。もう仕様もFIXしちゃったし…。

そしてこのエントリは、最初の図のように出来ると思って色々作り始めてしまった愚痴でもある。

シングルトンモジュールのテスト

シングルトン(singleton.js)。

module.exports = {
    value: 0
};

その値をインクリメントするやつ(incr.js)。

var singleton = require('./singleton.js');

module.exports = function incr() {
    singleton.value++;
};

incr()を呼んだら値が増えますというテストを2つ記述。

var assert = require('assert');
var incr = require('../src/incr.js');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        incr();
        assert(singleton.value === 1);
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');
        incr();
        assert(singleton.value === 1);
    });
});

なんと同じテストなのに2つ目だけ失敗!ガーン!

改良?

シングルトンだからそりゃそうだよねと思って、調べてみたらrequireにキャッシュされているモジュールを削除できるらしい。というわけで、使い終わったシングルトンを削除っと。

var assert = require('assert');
var incr = require('../src/incr.js');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        incr();
        assert(singleton.value === 1);
        delete require.cache[require.resolve('../src/singleton.js')];//追加
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        incr();
        assert(singleton.value === 1);
    });
});

また失敗!ズコー!

ちなみにこの時のsingleton.value0。実はincrが参照しているのは最初にrequireしたシングルトンなので、消したつもりで消えていなかった。

これも駄目

内部でrequireしてみたけど、incr自体がキャッシュされているのでこれも駄目。

var assert = require('assert');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');//移動
        incr();
        assert(singleton.value === 1);
        delete require.cache[require.resolve('../src/singleton.js')];
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');//移動
        incr();
        assert(singleton.value === 1);
    });
});

直った

最終的にこうすると直る。

var assert = require('assert');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');
        incr();
        assert(singleton.value === 1);
        delete require.cache[require.resolve('../src/singleton.js')];
        delete require.cache[require.resolve('../src/incr.js')];//追加
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');
        incr();
        assert(singleton.value === 1);
    });
});

より一般的には、require.cacheのkeyで回して全部消す。中には初期化がクソ重たいモジュールがあるかもしれないが、そんなことを気にしてはいけない。

というわけで、この例くらい単純ならいいんだけど、思わぬところに思わぬ依存があって死ぬので確実に全モジュールを削除 & 毎回関数内でrequireを徹底する必要があるという話。最近Fluxなアプリがシングルトンモジュールをせっせと作っていて、こういうテストをするためにJestに依存するの嫌だなーと思っている。

あと、効果音がちょっと古い。