pulp を使った PureScript の開発
PureScript 用のビルドツールに pulp があります。
pulp を使えば PureScript v0.7 から多少面倒になったビルドや実行が比較的容易になります。
pulp インストール
Node.js の npm で purescript (コンパイラ) と pulp をインストールします。
pulp インストール例
> npm install -g purescript pulp
今回インストールした PureScript コンパイラ・pulp のバージョンは以下の通りです。
なお、PureScript コンパイラに関しては https://github.com/purescript/purescript/releases/ から各 OS 用のバイナリを直接取得する方法もあります。
npm でインストールしたものも実際は node_modules/purescript/vendor
ディレクトリへ配置された各 OS 用のバイナリファイル (例. psc.exe) を使っているようです。
pulp を使った開発
今回作成したソースは http://github.com/fits/try_samples/tree/master/blog/20160105/
プロジェクトの作成
任意のディレクトリ内で pulp init
を実行すると、必要最小限のファイルが生成されます。
その際に Bower を使って PureScript の主要ライブラリ (以下) を自動的に取得しますので、git コマンドを使えるようにしておく必要があります。
- purescript-console
- purescript-eff
- purescript-prelude
プロジェクト作成例
> pulp init * Generating project skeleton in ・・・ bower cached git://github.com/purescript/purescript-console.git#0.1.1 bower validate 0.1.1 against git://github.com/purescript/purescript-console.git#^0.1.0 bower cached git://github.com/purescript/purescript-eff.git#0.1.2 bower validate 0.1.2 against git://github.com/purescript/purescript-eff.git#^0.1.0 bower cached git://github.com/purescript/purescript-prelude.git#0.1.3 bower validate 0.1.3 against git://github.com/purescript/purescript-prelude.git#^0.1.0 bower install purescript-console#0.1.1 bower install purescript-eff#0.1.2 bower install purescript-prelude#0.1.3 purescript-console#0.1.1 bower_components\purescript-console └── purescript-eff#0.1.2 purescript-eff#0.1.2 bower_components\purescript-eff └── purescript-prelude#0.1.3
ディレクトリ・ファイル構成は以下のようになります。
- bower_components
- purescript-console
- purescript-eff
- purescript-prelude
- src/Main.purs
- test/Main.purs
- .gitignore
- bower.json
デフォルトで用意されている src/Main.purs の内容は以下の通りです。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import Control.Monad.Eff.Console main :: forall e. Eff (console :: CONSOLE | e) Unit main = do log "Hello sailor!"
ライブラリの追加には pulp dep install <ライブラリ名>
を実行します。
そうすると bower install が実施されます。
bower.json の依存パッケージ設定へエントリを追加するには --save オプションを付けます。
purescript-tuples の追加例
> pulp dep install purescript-tuples --save ・・・ bower install purescript-control#0.3.2 bower install purescript-invariant#0.3.0 purescript-tuples#0.4.0 bower_components\purescript-tuples └── purescript-foldable-traversable#0.4.2 purescript-foldable-traversable#0.4.2 bower_components\purescript-foldable-traversable ├── purescript-bifunctors#0.4.0 └── purescript-maybe#0.3.5 purescript-maybe#0.3.5 bower_components\purescript-maybe └── purescript-monoid#0.3.2 purescript-bifunctors#0.4.0 bower_components\purescript-bifunctors └── purescript-control#0.3.2 purescript-monoid#0.3.2 bower_components\purescript-monoid ├── purescript-control#0.3.2 └── purescript-invariant#0.3.0 purescript-control#0.3.2 bower_components\purescript-control └── purescript-prelude#0.1.3 purescript-invariant#0.3.0 bower_components\purescript-invariant └── purescript-prelude#0.1.3
ビルドと実行
Main.purs を以下のようにタプルを使った処理に書き換えて実行してみます。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import Control.Monad.Eff.Console import Data.Tuple main :: forall e. Eff (console :: CONSOLE | e) Unit main = do let t = Tuple "two" 2 print t let r = t >>= \x -> Tuple " + 1" (x + 1) print r
pulp run
を実行するとビルドした後に処理を実施します。
ビルドだけを実施したい場合は pulp build
を使います。
実行
> pulp run * Building project in ・・・\20160105\purescript psc: No files found using pattern: src/**/*.js * Build successful. Tuple ("two") (2) Tuple ("two + 1") (3)
ビルド結果は output ディレクトリへ生成されます。
パッケージング
pulp browserify
を実行すると Browserify を使って output ディレクトリ内のファイルをパッケージングしてくれます。
browserify によるパッケージング
> pulp browserify > sample.js * Browserifying project in ・・・\20160105\purescript * Project unchanged; skipping build step. * Browserifying...
パッケージングしたファイル (sample.js) の実行結果は以下の通りです。
実行結果
> node sample.js Tuple ("two") (2) Tuple ("two + 1") (3)
Web ブラウザで実行する事もできます。
index.html
<!DOCTYPE html> <html> <script src="sample.js"></script> </html>
備考 - pulp を使わない場合
最後に pulp を使わない場合のビルド・実行方法も書いておきます。
まずは、Bower を使って purescript-console 等の必要なライブラリを手動でインストールします。
ライブラリのインストール例
> bower install purescript-console --save
src/Main.purs を psc コマンドでビルドするには以下のようにします。
psc によるビルド例
> psc src/Main.purs bower_components/purescript-*/src/**/*.purs --ffi bower_components/purescript-*/src/**/*.js
bower_components 内の .purs ファイルと --ffi オプションで bower_components 内の .js ファイルを指定します。
ビルド結果はデフォルトで output ディレクトリへ生成されます。(-o オプションで変更する事も可能)
実行する場合は、NODE_PATH 環境変数へ output ディレクトリを設定し、node コマンドで require('Main/index.js').main()
を実行します。
実行例
> set NODE_PATH=output > node -e "require('Main/index.js').main()" Tuple ("two") (2) Tuple ("two + 1") (3)
Google Cloud Print を Web API で操作 - Unirest 使用
リフレッシュトークンを使って Google Cloud Print を Web API で操作してみます。
以前、「Google アカウントで Google API を利用 - google-api-services-gmail」 では Apache HTTPClient を使いましたが、今回は Unirest を使っています。
ソースは http://github.com/fits/try_samples/tree/master/blog/20151222/
はじめに
事前準備としてリフレッシュトークンを取得しておきます。
今回は、「Google アカウントで・・・」 にて発行したクライアント ID をそのまま使用します。
(1) コードの取得
下記パラメータを付け https://accounts.google.com/o/oauth2/auth
へ Web ブラウザ等でアクセスし API の利用を許可する事でコードを取得します。
パラメータ | 値 |
---|---|
response_type | code |
client_id | クライアント ID の client_id 値(※1) |
scope | https://www.googleapis.com/auth/cloudprint |
redirect_uri | oob (※2) |
(※1)クライアント ID 発行時にダウンロードした JSON ファイルの client_id 値 (※2)リダイレクトしない場合の設定。 何らかのアプリケーション内から実行する際は それに合わせたリダイレクト先を指定する
例えば、以下のような URL へアクセスします。
URL 例
https://accounts.google.com/o/oauth2/auth?redirect_uri=oob&response_type=code&client_id=xxx.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/cloudprint
Google アカウントでログインすると以下のような画面が表示されますので、「許可」ボタンを押下します。
コードが表示されるのでコピーしておきます。
(2) リフレッシュトークンの取得
次に、下記パラメータを https://www.googleapis.com/oauth2/v3/token
へ POST し、リフレッシュトークンを含んだ JSON を取得します。
パラメータ | 値 |
---|---|
code | (1) で取得したコード |
client_id | クライアント ID の client_id 値 |
client_secret | クライアント ID の client_secret 値 |
grant_type | authorization_code |
redirect_uri | oob (※1) |
(※1)(1) の場合と同様です。 ただし、以下のスクリプトでは urn:ietf:wg:oauth:2.0:oob を設定しています
Groovy スクリプト化すると以下のようになります。
get_refresh-token.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper addShutdownHook { Unirest.shutdown() } def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def code = args[1] def res = Unirest.post('https://www.googleapis.com/oauth2/v3/token') .field('code', code) .field('client_id', conf.client_id) .field('client_secret', conf.client_secret) .field('grant_type', 'authorization_code') .field('redirect_uri', conf.redirect_uris[0]) .asJson() println res.body
クライアント ID 発行時にダウンロードした JSON (ここでは client_secret.json) を第1引数、(1) で取得したコードを第2引数へ指定して実行します。
取得したリフレッシュトークン JSON は後で使うので保存しておきます。 (ここでは token.json へ保存)
実行例
> groovy get_refresh-token.groovy client_secret.json 4/9ic・・・ > token.json
プリンタ一覧の取得
リフレッシュトークンを使う場合は、以下のような手順で API を呼び出します。
- リフレッシュトークンからアクセストークンを取得
- アクセストークンを使って API を実行
リフレッシュトークンからアクセストークン取得
アクセストークンを取得するには、下記パラメータを https://www.googleapis.com/oauth2/v3/token
へ POST します。
パラメータ | 値 |
---|---|
client_id | クライアント ID の client_id 値 |
client_secret | クライアント ID の client_secret 値 |
grant_type | refresh_token |
refresh_token | リフレッシュトークンの値 (※1) |
(※1)リフレッシュトークン取得で保存した JSON の refresh_token 値
この処理は共通的に使用するため、Groovy の BaseScript として定義しました。
TokenScript.groovy
import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper abstract class TokenScript extends Script { // アクセストークンの取得 def accessToken(String clientId, String clientSecret, String refreshToken) { def res = Unirest.post('https://www.googleapis.com/oauth2/v3/token') .field('client_id', clientId) .field('client_secret', clientSecret) .field('grant_type', 'refresh_token') .field('refresh_token', refreshToken) .asJson() res.body.object } }
API 実行 (プリンタの一覧取得)
アクセストークンは HTTP ヘッダーへ Authorization: Bearer <アクセストークン>
のように設定して使います。(例. Authorization: Bearer ya26.pw・・・)
クラウドプリンタ一覧は https://www.google.com/cloudprint/search
へ GET すれば取得できます。 (q や type パラメータを付けて条件検索することも可能)
get_printers.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper import groovy.transform.BaseScript // TokenScript を BaseScript へ設定 @BaseScript TokenScript baseScript addShutdownHook { Unirest.shutdown() } def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def token = json.parse(new File(args[1])) // アクセストークン取得 def newToken = accessToken( conf.client_id, conf.client_secret, token.refresh_token ) // API の実行 def printers = Unirest.get('https://www.google.com/cloudprint/search') .header('Authorization', "${newToken.token_type} ${newToken.access_token}") .asJson() println printers.body
実行結果は以下の通りです。
まだプリンタを登録していないためデフォルトの 「ドライブに保存」 しかありません。
実行結果 (出力結果は加工済、実際は改行・字下げは無し)
> groovy get_printers.groovy client_secret.json token.json { "request":{・・・}, "printers":[{ "isTosAccepted":false, "displayName":"ドライブに保存", "capsHash":"", "description":"ドキュメントを Google ドライブで PDF として保存します", "updateTime":"1370287324050", "ownerId":"cloudprinting@gmail.com", "type":"DRIVE", "tags":["save","docs","pdf","google","__google__drive_enabled"], "proxy":"google-wide", "ownerName":"Cloud Print", "createTime":"1311368403894", "defaultDisplayName":"ドライブに保存", "connectionStatus":"ONLINE", "name":"Save to Google Docs", "id":"__google__docs", "accessTime":"1316132041869", "status":"" }], "success":true, ・・・ }
また、https://www.google.com/cloudprint/list?proxy=<プロキシ>
へ GET すると、指定プロキシへ属するプリンタ一覧を取得できます。
印刷処理 (Google Drive へ保存)
最後に、「ドライブに保存」 プリンタへファイルを印刷してみます。 (実際は Google Drive へファイル保存)
印刷は、下記の必須パラメータ(他にもパラメータあり)を multipart/form-data
で https://www.google.com/cloudprint/submit
へ POST します。
パラメータ | 値 |
---|---|
printerid | プリンタ ID (※1) |
title | 印刷タイトル |
ticket | 印刷設定 (※2) |
content | 印刷するファイルの内容 |
(※1)今回は __google__docs を使用 (※2)CJT (Cloud Job Ticket) フォーマットで印刷オプションなどを指定
submit_file.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper import groovy.json.JsonBuilder import groovy.transform.BaseScript @BaseScript TokenScript baseScript addShutdownHook { Unirest.shutdown() } def ticketBuilder = new JsonBuilder() // ticket の内容 ticketBuilder ( version: '1.0', print: {} ) def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def token = json.parse(new File(args[1])) def file = new File(args[2]) // アクセストークンの取得 def newToken = accessToken( conf.client_id, conf.client_secret, token.refresh_token ) // ticket の JSON 文字列化 def ticket = ticketBuilder.toString() // 印刷 def res = Unirest.post('https://www.google.com/cloudprint/submit') .header('Authorization', "${newToken.token_type} ${newToken.access_token}") .field('printerid', '__google__docs') .field('title', file.name) .field('ticket', ticket) .field('content', file) .asJson() println res.body
PDF ファイルを POST した結果は以下の通りです。
Google Drive で確認すると sample1.pdf ファイルが保存されていました。
実行結果 (出力結果は加工済、実際は改行・字下げは無し)
> groovy submit_file.groovy client_secret.json token.json data/sample1.pdf { "request":{・・・}, "success":true,"xsrf_token":"AIp・・・", "message":"印刷ジョブが追加されました。", "job":{ "ticketUrl":"https://www.google.com/cloudprint/ticket?format=xps&output=xml&jobid=0db・・・", "printerName":"", "errorCode":"", ・・・ "title":"sample1.pdf", "tags":["^own"], ・・・ "printerid":"__google__docs", ・・・ "contentType":"application/pdf", "status":"DONE" } }
また、試しに以下のような拡張子のファイルを POST してみたところ、自動的に PDF へ変換され Google Drive へ保存されました。 (例えば sample1.html は sample1.html.pdf で保存)
- .html
- .docx
- .odt
Android エミュレータの hosts ファイルを編集
Android エミュレータの hosts ファイルを編集する方法です。
以下のような方法が考えられます。
- (a) shell で hosts へ行を追加
- (b) pull と push で hosts を変更
(a) shell で hosts へ行を追加
行を追加するだけなら以下のように shell を使うだけです。
adb remount
しておかないと /system/etc/hosts ファイルを更新できずに Read-only file system
のようなエラーメッセージが表示される点に注意。 (既に adb remount 済みの場合は不要)
hosts への行追加
> adb remount remount succeeded > adb shell "echo 10.0.2.2 server1 >> /system/etc/hosts"
/system/etc/hosts の内容を確認してみます。
hosts の内容確認
> adb shell cat /system/etc/hosts 127.0.0.1 localhost 10.0.2.2 server1
10.0.2.2 はエミュレータから接続する際のホスト OS の IP アドレスなので、ping してみます。
ping の実行
> adb shell ping server1 PING server1 (10.0.2.2) 56(84) bytes of data. 64 bytes from server1 (10.0.2.2): icmp_seq=1 ttl=255 time=0.000 ms ・・・
remount に関して
adb remount
する事で以下のように /system が rw で再マウントされます。
adb remount 前 (ro)
/dev/block/mtdblock0 /system ext4 ro,relatime,data=ordered 0 0
adb remount 後 (rw)
/dev/block/mtdblock0 /system ext4 rw,relatime,data=ordered 0 0
(b) pull と push で hosts を変更
次は hosts ファイルを編集する方法です。
エミュレータには vi 等のエディタが用意されていないようなので、pull で hosts ファイルを取得して、ホスト OS 側で hosts ファイルを編集し、push でエミュレータへ反映します。
まずは adb pull
で hosts ファイルを取得します。
hosts を pull
> adb pull /system/etc/hosts
ホスト OS のカレントディレクトリへ pull された hosts ファイルを編集します。
hosts 変更例
127.0.0.1 localhost 10.0.2.2 server1
変更した hosts を push します。
まだ一度も remount していない場合は remount する必要があります。
hosts を push
> adb remount ・・・ > adb push hosts /system/etc/
Swift のビルド環境を Docker で構築
Ubuntu をベースとした Swift のビルド環境を Docker で構築してみました。
使用したソースは http://github.com/fits/try_samples/tree/master/blog/20151207/
Docker イメージ作成
ubuntu:15.10
のイメージをベースに Swift をセットアップしました。
clang 等の必要なパッケージをインストールした後、swift-2.2-SNAPSHOT-2015-12-01-b-ubuntu15.10.tar.gz をダウンロードして解凍し、環境変数 PATH へ swift-2.2-・・・/usr/bin
のパスを追加するだけです。
また、clang
をインストールしただけだと swiftc の実行時に /usr/bin/ld: warning: libicuuc.so.55, needed by /swift-2.2・・・/usr/lib/swift/linux/libswiftCore.so, not found (try using -rpath or -rpath-link)
のようなメッセージが出力されたので、libicu-dev
もインストールするようにしています。
Dockerfile
FROM ubuntu:15.10 ENV SWIFT_PACKAGE swift-2.2-SNAPSHOT-2015-12-01-b-ubuntu15.10 RUN apt-get update RUN apt-get -y upgrade RUN apt-get -y install curl clang libicu-dev # swift のパッケージをダウンロード RUN curl https://swift.org/builds/ubuntu1510/swift-2.2-SNAPSHOT-2015-12-01-b/$SWIFT_PACKAGE.tar.gz -o $SWIFT_PACKAGE.tar.gz RUN tar zxf $SWIFT_PACKAGE.tar.gz RUN rm -f $SWIFT_PACKAGE.tar.gz RUN apt-get clean # 環境変数 PATH の設定 ENV PATH /$SWIFT_PACKAGE/usr/bin:$PATH
docker build
で Docker イメージを作成します。
Docker イメージの作成
$ docker build --no-cache -t sample/swift:0.1 . ・・・ Successfully built ・・・
動作確認
作成した Docker イメージを使って以下のようなサンプルソースを実行してみます。
/vagrant/work/sample.swift
let a = 11 let b = 2 print("result = \(a * b)")
まず、sample/swift:0.1
イメージを使って Docker コンテナを起動します。
Docker コンテナの起動
$ docker run --rm -it -v /vagrant/work:/work sample/swift:0.1 bash ・・・ # cd /work
コンテナ内で sample.swift を実行します。
直接実行
# swift sample.swift result = 22
ビルドしてから実行
# swiftc sample.swift # ./sample result = 22
Objective-C のビルド環境を Docker で構築
CentOS をベースとした Objective-C のビルド環境を Docker で構築してみました。
使用したソースは http://github.com/fits/try_samples/tree/master/blog/20151130/
Docker イメージ作成
今回は clang と GNUstep を使う事にします。
バージョンは古くなってしまいますが、どちらも yum でインストールするようにしました。
make
をインストールしておかないと GNUstep の gnustep-config
コマンドが機能しない (結果が空になる) のでご注意ください。
また、gcc-objc
をインストールしておかないと、Objective-C のソースをビルドする際に必要なヘッダーファイルを参照できず、fatal error: 'inttypes.h' file not found
のようなエラーが発生しました。
Dockerfile
FROM centos RUN yum -y update RUN yum -y install epel-release RUN yum -y install clang make gcc-objc gnustep-base-devel RUN yum clean all
docker build
で Docker イメージを作成します。
Docker イメージの作成
$ docker build --no-cache -t sample/objc:0.1 . ・・・ Successfully built ・・・
動作確認
以下のようなサンプルソースをビルドしてみます。
/vagrant/work/Sample.h
#import <Foundation/NSObject.h> #import <Foundation/NSString.h> @interface Sample : NSObject { NSString* _name; } @property (nonatomic, copy) NSString* name; - (void)log; @end
/vagrant/work/Sample.m
#import <Foundation/Foundation.h> #import "Sample.h" @implementation Sample : NSObject @synthesize name = _name; - (void)log { NSLog(@"%@", _name); } - (void)dealloc { [_name release]; [super dealloc]; } @end int main(int argc, const char * argv[]) { @autoreleasepool { Sample* s = [[Sample new] autorelease]; s.name = @"test"; [s log]; } return 0; }
まずは sample/objc:0.1
イメージを使って Docker コンテナを起動します。
Docker コンテナの起動
$ docker run --rm -it -v /vagrant/work:/work sample/objc:0.1 bash
コンテナ内で clang を使ってビルドします。
コンパイラオプションに関しては以下で妥当なのかは不明ですが、一応ビルドには成功しました。 (場合によっては -objcmt-migrate-all
等の指定も必要かもしれません)
ビルド
# cd work # clang `gnustep-config --objc-flags` `gnustep-config --objc-libs` -lgnustep-base -I /usr/lib/gcc/x86_64-redhat-linux/4.8.3/include Sample.m -o Sample clang: warning: argument unused during compilation: '-shared-libgcc'
一応、それぞれのオプションを指定しなかった場合に発生したエラーをまとめておきます。
オプション指定 | 指定しなかった場合のエラー例 |
---|---|
`gnustep-config --objc-flags` | 無し(指定しなくてもビルドは成功) |
`gnustep-config --objc-libs` | undefined reference to symbol 'objc_getProperty' |
-lgnustep-base | undefined reference to `NSLog' |
-I /usr/lib/gcc/・・・/include | fatal error: 'objc/objc.h' file not found |
ビルド結果の Sample
を実行してみると一応動作しました。
実行例
# ./Sample 2015-11-30 20:15:00.121 Sample[32] test
なお、clang -v
の実行結果は以下のようになりました。
clang -v
# clang -v clang version 3.4.2 (tags/RELEASE_34/dot2-final) Target: x86_64-redhat-linux-gnu Thread model: posix Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.3 Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.8.2 Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.8.3 Selected GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.3
twemproxy + Redis 環境を Docker で構築
twemproxy(別名 nutcracker) + Redis の環境を Docker で構築してみます。 (twemproxy は memcached・Redis 用の軽量なプロキシです)
使用した設定ファイル等は http://github.com/fits/try_samples/tree/master/blog/20151124/
Redis の Docker イメージを取得
Redis の Docker イメージは docker pull で取得する事にします。
$ docker pull redis
twemproxy の Docker イメージを構築
twemproxy の Docker イメージは、下記 Dockerfile を使って centos のイメージをベースに twemproxy のソースをビルドして作成する事にします。
Dockerfile
FROM centos RUN yum -y update RUN yum -y install make automake libtool git RUN git clone https://github.com/twitter/twemproxy.git RUN cd twemproxy && autoreconf -fvi && ./configure && make && make install RUN rm -fr twemproxy RUN yum clean all
上記 Dockerfile で docker build を実行すれば Docker イメージを作成できます。
Docker イメージの作成
$ docker build --no-cache -t sample/twemproxy:0.1 . ・・・ Successfully built ・・・
twemproxy のビルドで warning は出ましたが、Docker イメージの作成に成功しました。
twemproxy + Redis × 2 の実行
twemproxy と Redis 2台を個別の Docker コンテナで実行してみます。
twemproxy の設定ファイルで接続する Redis サーバーを <ホスト名 or IPアドレス>:<ポート番号>:<重み>
で指定する必要があり、これが課題となります。
twemproxy 設定ファイル例
sample: ・・・ redis: true servers: - 127.0.0.1:6380:1 - 127.0.0.1:6381:1 ・・・
今回は、起動スクリプトを使って twemproxy と Redis の Docker コンテナをまとめて起動するようにしてみました。
該当コンテナを docker start (コンテナを再起動) してみて、失敗した場合は docker run を行うようにしています。 (docker start し易いように --name=<コンテナ名>
で名前を付けています)
また、コンテナ間の接続を容易にするため、docker run 時に -h <ホスト名>
でホスト名も付けました。 (コンテナ名をそのままホスト名に使っています)
/vagrant/tmp/start_twemproxy (twemproxy + Redis コンテナの起動スクリプト)
#!/bin/sh INDENT=" " CONF_DIR=/vagrant/tmp/conf CONTAINER_CONF_DIR=/conf TWEMPROXY_IMAGE=sample/twemproxy:0.1 CONTAINER_REDIS_LIST="redis1 redis2" CONTAINER_TWEMPROXY=twemproxy1 REDIS_SERVERS="" # Redis のコンテナを個々に起動 for s in $CONTAINER_REDIS_LIST do if test -z `docker start $s`; then docker run --name=$s -h $s -d redis else echo start $s fi REDIS_SERVERS="$REDIS_SERVERS$INDENT- $s:6379:1\n" done # twemproxy の設定ファイルを生成 sed -e "s/#{SERVERS}/$REDIS_SERVERS/g" `dirname $0`/nutcracker.yml.tpl > $CONF_DIR/nutcracker.yml # twemproxy のコンテナ起動 if test -z `docker start $CONTAINER_TWEMPROXY`; then docker run --name=$CONTAINER_TWEMPROXY -h $CONTAINER_TWEMPROXY -d -p 6379:6379 -v $CONF_DIR:$CONTAINER_CONF_DIR $TWEMPROXY_IMAGE nutcracker -c $CONTAINER_CONF_DIR/nutcracker.yml else echo start $CONTAINER_TWEMPROXY fi
twemproxy は nutcracker -c <設定ファイル>
で起動しています。
また、twemproxy 設定ファイル(nutcracker.yml)は Redis サーバーの構成に合わせて下記テンプレートの #{SERVERS}
を sed
で置き換えて生成するようにしています。
/vagrant/tmp/nutcracker.yml.tpl (twemproxy 設定ファイルのテンプレート)
sample: listen: 0.0.0.0:6379 hash: fnv1a_64 distribution: ketama timeout: 500 redis: true servers: #{SERVERS}
実行
それでは実行してみます。
初回起動時は docker start に失敗し docker run を実施する事になります。
twemproxy1, redis1, redis2 コンテナの実行例
$ /vagrant/tmp/start_twemproxy Error response from daemon: no such id: redis1 Error: failed to start containers: [redis1] bdc12db5・・・ Error response from daemon: no such id: redis2 Error: failed to start containers: [redis2] fd4546fc・・・ Error response from daemon: no such id: twemproxy1 Error: failed to start containers: [twemproxy1] 87bb567e・・・
redis-cli で twemproxy1 へ接続する Docker コンテナを起動し (Redis の Docker イメージを使っています) 、動作確認してみます。
動作確認
$ docker run --rm -it redis redis-cli -h twemproxy1 twemproxy1:6379> set a 123 OK twemproxy1:6379> get a "123"
twemproxy は正常に動作しているようです。
redis1・2 へ直接接続するコンテナをそれぞれ実行し確認すると、値は redis2 の方に設定されていました。
$ docker run --rm -it redis redis-cli -h redis1 redis1:6379> keys * (empty list or set) redis1:6379> exit $ docker run --rm -it redis redis-cli -h redis2 redis2:6379> keys * 1) "a" redis2:6379> exit
備考 - Vagrant 利用時
Vagrant で起動したゲスト OS 上で Docker を実行している場合、Vagrantfile へ以下のような設定を追加すれば provision で各コンテナを起動しホスト OS から twemproxy へ接続できるようになります。
Vagrantfile
・・・ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| ・・・ # twemproxy 用のポートフォワード設定 config.vm.network "forwarded_port", guest: 6379, host: 6379 # twemproxy + redis の起動スクリプト実行 config.vm.provision :shell, :inline => "/vagrant/tmp/start_twemproxy" end
provision 実施例
> vagrant up --provision ・・・ ==> default: Running provisioner: shell... default: Running: inline script ==> default: start redis1 ==> default: start redis2 ==> default: start twemproxy1
ホスト OS からの接続例
> redis-cli 127.0.0.1:6379> get a "123"
リストをN個に分割 - Groovy, Java, Python
リストをなるべく均等に N 分割する処理を Groovy・Java・Python で実装してみました。
今回は、[0, 1, 2, 3, 4, 5, 6, 7]
を 3分割した結果が [[0, 1, 2], [3, 4, 5], [6, 7]]
となるような処理を想定しています。 (余り分を先頭から順に1つずつ分配)
ソースは http://github.com/fits/try_samples/tree/master/blog/20151109/
Groovy の場合
- Groovy 2.4.5
畳み込みを使って実装してみました。
- (1) サブリストとして取り出す範囲を算出
- (2) 取り出したサブリストを結果へ追加
divide_list.groovy
def divideList = { xs, n -> int q = xs.size() / n int m = xs.size() % n (0..<n).inject([]) { acc, i -> // (1) サブリスト化する範囲の開始・終了位置を算出 def fr = acc*.size().sum(0) def to = fr + q + ((i < m)? 1: 0) // (2) サブリストを取り出し、結果へ追加 acc << xs[fr..<to] } } println divideList(0..<8, 3) println divideList(0..<7, 3) println divideList(0..<6, 3) println divideList(0..<6, 6)
実行結果
> groovy divide_list.groovy [[0, 1, 2], [3, 4, 5], [6, 7]] [[0, 1, 2], [3, 4], [5, 6]] [[0, 1], [2, 3], [4, 5]] [[0], [1], [2], [3], [4], [5]]
なお、上記の実装内容では divideList(0..<3, 5)
のように要素数よりも分割数を多くすると [[0], [1], [2], [], []]
のように不足分が空リストとなります。
場合によっては、以下のように小さい数に合わせた方が実用的かもしれません。
def divideList = { xs, n -> ・・・ nn = Math.min(n, xs.size()) (0..<nn).inject([]) { acc, i -> ・・・ } }
Java 8 の場合
- Java SE 8u66
Groovy 版と同様に実装しました。
Stream
の collect
メソッドを使っています。
DivideList.java
import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; public class DivideList { public static void main(String... args) { System.out.println(divideList(range(0, 8), 3)); System.out.println(divideList(range(0, 7), 3)); System.out.println(divideList(range(0, 6), 3)); System.out.println(divideList(range(0, 6), 6)); } private static <T> List<List<T>> divideList(List<T> xs, int n) { int q = xs.size() / n; int m = xs.size() % n; return IntStream.range(0, n).collect( ArrayList::new, (acc, i) -> { int fr = acc.stream().mapToInt(List::size).sum(); int to = fr + q + ((i < m)? 1: 0); acc.add(xs.subList(fr, to)); }, ArrayList::addAll ); } private static List<Integer> range(int start, int end) { return IntStream.range(start, end).boxed().collect(Collectors.toList()); } }
実行結果
> java DivideList [[0, 1, 2], [3, 4, 5], [6, 7]] [[0, 1, 2], [3, 4], [5, 6]] [[0, 1], [2, 3], [4, 5]] [[0], [1], [2], [3], [4], [5]]
Python の場合
- Python 3.5.0
Python も同様に実装しました。
変数 fr に値を保持させるため、ラムダを入れ子にして引数のデフォルト値を利用しています。
また、以下のように /
で割ると結果が実数になって都合が悪いため、//
を使っています。
式 | 結果 |
---|---|
5 / 2 | 2.5 |
5 // 2 | 2 |
divide_list.py
from functools import reduce def divide_list(xs, n): q = len(xs) // n m = len(xs) % n return reduce( lambda acc, i: (lambda fr = sum([ len(x) for x in acc ]): acc + [ xs[fr:(fr + q + (1 if i < m else 0))] ] )() , range(n), [] ) range_list = lambda n: list(range(n)) print(divide_list(range_list(8), 3)) print(divide_list(range_list(7), 3)) print(divide_list(range_list(6), 3)) print(divide_list(range_list(6), 6))
実行結果
> python divide_list.py [[0, 1, 2], [3, 4, 5], [6, 7]] [[0, 1, 2], [3, 4], [5, 6]] [[0, 1], [2, 3], [4, 5]] [[0], [1], [2], [3], [4], [5]]