ダウンキャスト
Scala でプログラミングしていると型パラメータやパターンマッチがあるからか、ダウンキャストが必要になることがあまり無いです。
なのですが、パターンマッチの多用によりインデントが深くなってしまうのが悩ましかったりします。
例えば dispatch.json (sjson で利用している JSON のパーサ等) を利用して、すでに型がわかっているデータ構造をたどっている場合などです。 *1
Js(response) match { case JsObject(x) => x.get(JsString("foo")) match { case Some(JsString(foo)) => x.get(JsString("bar")) match { case Some(JsString(bar)) => Some((foo, bar)) case _ => None } case _ => None } case _ => None }
読みづらすぎる*2・・・ case _ => None の出現回数が多いのとインデントが深いのとで、なんだかいまいちです。
Scala には for 内包表記という便利なものがあるので、それを使用してなんとかインデントを浅くできないものかと思い、以下のように書いてみました。
for { JsObject(x) <- Some(Js(response)) JsString(foo) <- x.get(JsString("foo")) JsString(bar) <- x.get(JsString("bar")) } yield (foo, bar)
あぁ、すっきり。
なのですが、JsObject(x) <- ... や JsString(foo) <- ... のようにして受けているため、もし型が違っていた場合は例外がでてしまいます。
なんとかして想定と違う型の場合はそのデータは無視して処理を続行したいです。
というわけで、以下のような型検査&変換をするメソッドを作ってみました。
implicit def any2Caster[T](value: T): Caster[T] = new Caster[T](value) class Caster[T](value: T) { def as[U<:T:Manifest]: Option[U] = if (manifest[U].erasure.isInstance(value)) Some[U](value.asInstanceOf[U]) else None }
使用例は以下のような感じになります。
val value: Any = "Hello World" // value は Any 型 val text = value.as[String] // text は Option[String] 型、値は Some("Hello World") val integer = value.as[Int] // integer は Option[Int] 型、値は None
この as[T] と JsObject -> Map、String -> JsString の暗黙の型変換を定義しておくことで、以下のような記述ができます。
for { x <- Js(response).as[JsObject] foo <- x.get("foo").flatMap(_.as[JsString]) bar <- x.get("bar").flatMap(_.as[JsString]) } yield (foo, bar)
あるいは flatMap の記述も排除するならば以下のようにします。。
for { x <- Js(response).as[JsObject] foo <- x.get("foo") bar <- x.get("bar") fooStr <- foo.as[JsString] barStr <- bar.as[JsString] } yield (fooStr, barStr)
ちなみに Js* との型変換は以下のような感じで定義しています。
implicit def jsObject2Map(js: JsObject): Map[JsString, JsValue] = js.self implicit def jsString2String(js: JsString): String = js.self implicit def string2JsString(value: String): JsString = JsString(value)
def as[T]:Option[T] は Scala に標準で用意されていても良さそうな気もするのですが、私が知らないだけなのかも・・・?
リスト操作対応表
おもいつきでだらだら書いたので、足りていない可能性の方が高いです。
ちなみに Perl には Scala の List 見たいなものや、Stream みたいなものは標準では用意されていないため、通常は配列を使います。
@ で始まる変数が配列です。でも配列への参照も良く使うので、$で始まってても参照先が配列というのはよくあること。
操作名 | Scala | Perl |
---|---|---|
map | xs.map { _ + 1 } |
map { $_ + 1 } @xs |
flatMap | xs.flatMap { x => x*2-1::x*2::Nil } |
map { ($_ * 2 - 1, $_ * 2) } @xs |
filter | xs.filter { _ % 2 == 0 } |
grep { $_ % 2 == 0 } @xs |
reduce | xs.reduceLeft { _ + _ } |
List::Util::reduce { $a + $b } @xs |
fold | (1 /: xs){ _ * _ } |
List::Util::reduce { $a * $b } (1, @xs) |
zip | xs.zip(ys) |
List::MoreUtils::pairwise { [$a, $b] } @xs, @ys |
find | xs.find { _ % 2 == 0 } |
List::Util::first { $_ % 2 == 0 } @xs |
max | xs.max |
List::Util::max @xs |
Extractor | val y::ys = xs |
my ($y, @ys) = @xs |
とりあえずいま思いつくところだとこんな感じです。
Perl では配列内に配列をいれても展開されるので、flatten と同じ効果が欲しい場合はそのまま配列を、二次元配列にしたい場合は配列への参照を格納する必要があります。
なので、flatMap 相当の処理は map のブロックが返す値を配列にすれば実現できます。
また、zip 相当の処理は pairwise で配列への参照を返すようにしています。[$a, $b] の部分が $a と $b を要素にもつ配列への参照を表します。
NFS クライアント設定
NFS サーバは FreeNAS でさくっと用意できているので問題なし。
FreeNAS で公開しているディレクトリを、CentOS5.5 のサーバ上でマウントした手順。
- nfs 等のデーモンを停止してあったので、再開する
- chkconfig portmap on
- chkconfig nfslock on
- chkconfig nfs on
- マウントポイントとなるディレクトリを用意する
- mkdir /mnt/repositories@chinstrap
- マウントしてみる
- mount -t nfs chinstrap:/mnt/tank/repositories /mnt/repositories\@chinstrap
- fstab に以下を追記して起動時にマウントされるようにする
- chinstrap:/mnt/tank/repositories /mnt/repositories@chinstrap nfs defaults 0 0
- mount -a で fstab の記述内容を試せる
- 再起動して確認
- reboot
- マウントされていなかったorz
- netfs が動いていないのが原因
- chkconfig netfs on
- 再起動して確認
- マウントされた!!
リバースプロキシ設定
私の環境では外部向けに公開している Apache の入っているホストとは別のホスト上に、SVN のリポジトリを作成しました。
このままでは外部から SVN リポジトリにアクセスできないため、リバースプロキシを設定して https://svn.〜/ のときに SVN リポジトリの入っているホストにリクエストを転送するようにしました。
SVN リポジトリの入っているホストでは、WebDAV プロトコルを使用した SVN との連携を設定済みです。
snares:/etc/httpd/conf.d/vhosts/vhosts.conf
<Location /> DAV svn SVNPath /mnt/repositories@chinstrap/svn #<LimitExcept GET PROPFIND OPTIONS REPORT> SSLRequireSSL AuthType Basic AuthName "Authorization Realm" AuthUserFile /mnt/repositories@chinstrap/svn/conf/htpasswd Require valid-user #</LimitExcept> </Location>
Basic 認証をつかっているため、SSL 以外ではコミットなどの作業をできないように設定しています。
このため、リバースプロキシの設定もアクセスされたのが http の場合は http に、https の場合は https に転送するようにしています。
http://d.hatena.ne.jp/NetPenguin/20081127#p1 にあるように、ファイルを分割して仮想ホスト+SSLの設定をしていますのでリライトの設定についても SSL の有無に関係なく一か所で済むようにします。編集対象としているファイルの詳細については、http://d.hatena.ne.jp/NetPenguin/20081127#p1 のエントリを参照してください。
rockhopper:/etc/httpd/conf.d/vhosts/vhosts.conf
外部からの SVN リポジトリへのアクセスを受け付ける仮想ホストを用意します。
<VirtualHost *:80> Include conf.d/vhosts/svn.netpenguin.org.part ErrorLog logs/svn.netpenguin.org.error_log CustomLog logs/svn.netpenguin.org.access_log combined </VirtualHost> <VirtualHost *:443> Include conf.d/vhosts/ssl.part Include conf.d/vhosts/svn.netpenguin.org.part ErrorLog logs/svn.netpenguin.org.error_log CustomLog logs/svn.netpenguin.org.access_log combined CustomLog logs/svn.netpenguin.org.ssl_request_log ssl_request
rockhopper:/etc/httpd/conf.d/vhosts/svn.netpenguin.org.part
外部からの SVN アクセスを、実際に SVN リポジトリを公開しているサーバに転送します。
外部向けに登録している SVN サーバのホスト名も、内部のホスト名も svn.netpenguin.org なので紛らわしいですが、RewriteRule に記述している https://svn.netpenguin.org/ は内部向けに用意している DNS によって解決され、そのアドレスはプライベート IP (SVN リポジトリを公開しているホストの IP アドレス)になります。
ServerName svn.netpenguin.org SSLProxyEngine On RewriteEngine On ProxyRequests Off RewriteCond %{HTTPS} on [NC] RewriteRule ^/(.*) https://svn.netpenguin.org/$1 [P] RewriteCond %{HTTPS} off [NC] RewriteRule ^/(.*) http://svn.netpenguin.org/$1 [P]
この設定では、HTTPS によるアクセスを扱うために SSLProxyEngine On を指定し、また http でのアクセスは http のまま、https でのアクセスは https として転送するために、RewriteCond %{HTTPS} on (あるいはoff)の指定によって適用するリライトルールを分けています。
実際に外部(e-mobile経由)から試してみましたが、問題なく SVN リポジトリにアクセスできました。
ちなみに、単純にリバースプロキシを構築するだけであれば、RewriteRule ではなく ProxyPass の設定だけで十分かと思います。
あと、本来であれば ProxyPassReverse も設定した方が良いのですが、SVN リポジトリへのアクセスでリダイレクト等は発生しないだろうと思い未設定です。これを設定する場合、同じ記述での http/https 両対応が難しそうなので http アクセスの場合は https にリダイレクトさせてしまい、それから ProxyPass によって https://〜 に転送、ProxyPassReverse も https://〜 で設定という流れにするかと思います。(ためしてないけど)
一つのグローバルIPで自鯖を立てている環境では、仮想ホストとリバースプロキシは必須だなぁと実感してたりします。あと、無料の DynamicDNS サービスも。
仮想マシン増殖
私にとっての最小構成のサーバを作ったので、これをコピーして増殖させます。
あと、後々用にスナップショット*1をとっておきます。
- 仮想マシンをシャットダウン
- 一応、イベントリから削除する
- ストレージプールをホストしている FreeNAS にシェルログイン
- 目当てのディレクトリをコピー
- ファイル名はそのままでも問題無い
- ファイル名を変更する場合は、vmx と vmxf ファイル内の参照箇所も修正する
- スワップファイル名は修正しなくて大丈夫だった(初回起動時に適切なものに書き換わった)
- コピーしたvmxファイルをデータストアブラウザで選択し、イベントリに追加する
- インスタンス名を聞かれるので適切なものに修正する
- コピーしたインスタンスを起動する
- 初回起動時にコピーされたものかどうか聞かれるので、「I copy it.」を選択
- MAC アドレスとかを変更してくれるっぽい
- system-config-network を起動し、ホスト名を修正する
- tripwire のキーファイル名はホスト名から付けているので、ファイル名を変更または新たなキーファイルを作成する
- キーファイルを再作成した場合、cfg ファイルもつくり直す必要があるはず
- tw.pol 内にホスト名が書かれていて、キーファイル名と紐づくため tw.pol は生成しなおす
- twadmin -m p -p tw.pol > twpol.txt
- twpol.txt 内の HOSTNAME を修正
- twadmin -m P -c tw.cfg -p tw.pol twpol.txt
- tripwire --init しておく
FreeNAS のバージョンアップ
いままで FreeNAS の 0.7.0 の stable 「じゃない」 ものを使い続けていたので*1、いいかげん stable な最新版に入れ替えました。
- 最新版のイメージをダウンロード
- 新しい USB メモリに WinDD をつかってイメージを書き込み*2
- 現在の設定をバックアップ
- 電源落として USB メモリ入れ替え、再起動
- WebUI にログイン*3して、設定をリストア
- ZFS のプールをインポート
- シェルログイン
- "zpool import" でインポート可能なプール一覧を表示し、プール名を確認
- "zpool import tank" とうつと、export していないプールのため警告(他のシステムで使用中)
- "zpool import -f tank" と -f を指定して、インポート
とまぁ、以上の手順で問題なくバージョンアップ完了
ZFS はもともと WebUI から作っていなかったので、今回もコンソールから操作。案外 WebUI から作っていたら設定のリストアだけでインポートもされるのかも?
でも、ZFS の簡単な操作に関しては WebUI はいらないかな・・・