VPC についてまとめ
イントロ
VPC 周りの知識が曖昧なままここまで来てしまったので、整理する。
なんとなく知っていた用語や単語について、一般的な概要、及び AWS ではどういう特徴をもっているか、を大雑把にまとめたもの。
VPC
概要
Virtual Private Cloud の略。Vrtial Private Cloud とは、クラウド環境に構築された仮想的なプライベートネットワークのこと。
VPC 自体が 1 つの IP アドレスを持っていて、VPC は沢山の private ip を持っている。
そんなインターネット上のでっかい領域を指す。
AWSでは
- VPC は CIDR =
x.x.x.x/16
~x.x.x.x/28
の間で作成可能 - デフォルトの VPC というものが存在する
- CIDR は一度作成したら変更不可
- 後からレンジを変えられないので注意
- VPC 削除のタイミングで、関連のAWS リソースは削除されてしまう
- subnet とか Internet Gateway とか色々。
- 削除して作りなおすと、全て作り直しになってしまうので、CIDR は余裕を持たせるのが基本
VPC の作成方法
VPC とサブネット - Amazon Virtual Private Cloud
インターネットゲートウェイ
インターネットゲートウェイ - Amazon Virtual Private Cloud
- VPC のルーティングを各サブネットのルーティングテーブルに追加できる
- NAT 変換を行い、public IP を持つ instance がインターネットに接続する
という機能を持つ。
AWS では
- デフォルトの VPC というものが存在する。
- インターネットゲートウェイがデフォルトの VPC にアタッチされている
- デフォルトのサブネットの中で起動したインスタンスは、public IP が付与される
- public の IP が付与されると、インターネットゲートウェイが NAT 変換を行える。
- つまりインターネットに接続できる
というわけで、何も考えずに EC2 インスタンスをポチると、外部と通信することができるようになるのは、AWS の素晴らしい挙動によるもの。
インターネットゲートウェイの作成
インターネットゲートウェイ - Amazon Virtual Private Cloud
ルートテーブル
ルートテーブル - Amazon Virtual Private Cloud
インターネットトラフィックがどこからどう流れていくか、を記した経路のこと。
どこからどこにトラフィックを流す、という対応表で表現される。
AWS では
ルートテーブル - Amazon Virtual Private Cloud
にまとめられてる。全部重要だけど最低限の機能としては次の通り。
- VPC を作成すると、ルートテーブルが自動で割り振られて、インターネットゲートウェイへのルートが追加されている。これを メインルートテーブル と呼ぶ
- サブネットを特定のルートテーブルに紐付けないと、メインルートテーブルに紐付けられる。
- つまり作成したサブネットはデフォルトでインターネットに通信できる。
- 新たに追加する場合、ルートは CIDR で指定する
ルートテーブルを操作する
ルートテーブル - Amazon Virtual Private Cloud
サブネット
VPC の中で作成するグループのこと。 VPC が持つ IP address の範囲を超えない程度に、自由にグループ化可能。
VPC が都道府県とするなら、サブネットは市区町村みたいな立ち位置。
サブネットの性質上、ルートテーブルに関連付けることにより、初めてサブネット内にトラフィックを流すことができる。
{public,private} サブネット
public サブネット
インターネットゲートウェイにルーティングされているサブネットのこと。 public サブネット内に作成された EC2 インスタンスが public ip もしくは elastic ip を持っている場合、インターネットに接続することができる。
private サブネット
インターネットゲートウェイに routing されていないサブネットのこと。 このサブネット内に EC2 インスタンスを作成しても、通常はインターネットに接続できない。 インターネットに接続するためには、NAT gateway を構築したり、インターネットゲートウェイをルートテーブルに追加する必要がある。
予約された ip address
- 10.0.0.0
- ネットワークアドレス。ネットワーク自身を表すアドレス
- 10.0.0.255
- ネットワークブロードキャストアドレス。ローカルネットワークのホスト全てに通信するためのアドレス
これ以外にも AWS が独自に予約しているアドレスが存在する。
AWS では
- AZ(Availability zone) にまたがってサブネットを構築することはできない
AWS が予約している ip address
参考
まとめ
AWS のドキュメントは入門書として優秀。 勿論周辺情報を理解するためには、別の文献や本で知識を補わないとダメだけど、AWS なら実際に手を動かしながら試せる環境があるし、概要の把握という意味では理解が進みやすい。
本当はもっと書きたかったのだけど、一旦ここまで。
ActiveRecord::Base.connection#table_exists? と ActiveRecord::Base#table_exists? の違い
イントロ
似ているメソッドで、微妙に挙動が異なり、ちょっとハマったのでメモ
違い
引数、結果をキャッシュするかどうかが違う
引数
ActiveRecord::Base.connection#table_exists?
- 引数を一つ取る
ActiveRecord::Base.connection.table_exists?("users") #=> 引数で受け取ったテーブルが存在していれば true, なかったら false
ActiveRecord::Base.table_exists?
- 引数を取らない
class User < ActiveRecord::Base end user = User.first user.class.table_exists? #=> モデルに対応したテーブルが存在していれば true, なかったら false
キャッシュするかどうか
ActiveRecord::Base.connection#table_exists?
- キャッシュしない
- 毎回 database とやり取りして、テーブルが存在するかどうかを確認するメソッド
ActiveRecord::Base.connection.table_exists?('users') #=> true ActiveRecord::Base.connection.drop_table('users') ActiveRecord::Base.connection.table_exists?('users') #=> false
ActiveRecord::Base.table_exists?
- 一度呼びだされたらキャッシュされる
- キャッシュ先は
ActiveRecord::Base.connection
- キャッシュ先は
- 一度呼びだされたらキャッシュされる
user = User.first user.class.table_exists? #=> true ActiveRecord::Base.connection.drop_table('users') user.class.table_exists? # => true # DROP TABLE `users` したのに true が返る! ActiveRecord::Base.connection.schema_cache.table_exists?('users') # => true # # * `ActiveRecord::ConnectionAdapters::SchemaCache` によってメソッド呼び出し結果が保存されている。 # * `ActiveRecord::ConnectionAdapters::SchemaCache` インスタンスは `ActiveRecord::Base.connection` のインスタンスが保持している
結論
上にも書いたとおり、引数と、メソッド呼び出しの結果をキャッシュするかどうかが違う。
ActiveRecord::Base.table_exists?
は通常ActiveRecord::Base
継承したモデル(例だとUser
)が対象となるので、引数を取らない- また、table の存在判定は、一般的には変更されにくい値であるので、一度
table_exits?
を呼び出したらキャッシュされる
と覚えれば良い。
私は動的テーブルを作ったり消したりしていてハマってしまいました。
ActiveRecord を使って、頻繁にテーブルを作成したり削除したりする場合は注意が必要。かも。
Mac で apery を compile する
イントロ
最近、職場の後輩に将棋で負けたことをきっかけに、将棋がマイブーム。
apery って?
将棋プログラムの一種。2014 年の世界大会で優勝したとかなんとか。
一部界隈では、あの ponanza 先生をも凌ぐとかなんとか言われている?らしく、とにかくすごく強いソフト。
で、そんな世界大会で優勝した将棋エンジンが github 公開されているのを知ったので、compile してみました。
そのままやると...
すさまじい警告が出て、失敗する
$ make 24 warnings generated. g++ -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native -o ../obj/common.o -c common.cpp g++ -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native -o ../obj/pieceScore.o -c pieceScore.cpp g++ -o apery ../obj/main.o ../obj/bitboard.o ../obj/init.o ../obj/mt64bit.o ../obj/position.o ../obj/evalList.o ../obj/move.o ../obj/movePicker.o ../obj/square.o ../obj/usi.o ../obj/generateMoves.o ../obj/evaluate.o ../obj/search.o ../obj/hand.o ../obj/tt.o ../obj/timeManager.o ../obj/book.o ../obj/benchmark.o ../obj/thread.o ../obj/common.o ../obj/pieceScore.o -lpthread -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native ld: library not found for -lgomp clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [apery] Error 1
gomp
というライブラリが無いと言われているようだ。
gomp とは
GOMP — An OpenMP implementation for GCC - GNU Project - Free Software Foundation (FSF)
openMP で実装している GNU project らしい。
openMP とは
複数CPU を効率的に計算するライブラリのこと。所謂スレッド。
http://www.cc.u-tokyo.ac.jp/support/kosyu/03/kosyu-openmp_c.pdf
計算を早く行うためにマルチスレッドで処理しているのだろう、と想像。
それと URI protocol の仕様として、計算中でも入力を受け取れるようにしなければならないので、そういうところでもスレッドの出番はありそう。
Mac でどう compile するか
g++-4.9 で compile する
環境
OSX 10.11.2
$ brew install gcc49
$ g++-4.9 --version g++-4.9 (Homebrew gcc49 4.9.3) 4.9.3 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
--- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -COMPILER = g++ +COMPILER = g++-4.9 #COMPILER = mpicxx CFLAGS = -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp CFLAGS += -march=native
結果
$ cd src $ make g++-4.9 -o apery ../obj/main.o ../obj/bitboard.o ../obj/init.o ../obj/mt64bit.o ../obj/position.o ../obj/evalList.o ../obj/move.o ../obj/movePicker.o ../obj/square.o ../obj/usi.o ../obj/generateMoves.o ../obj/evaluate.o ../obj/search.o ../obj/hand.o ../obj/tt.o ../obj/timeManager.o ../obj/book.o ../obj/benchmark.o ../obj/thread.o ../obj/common.o ../obj/pieceScore.o -lpthread -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native
というわけでうまくいった。
うまくいったけど、どういうことなのか
HiraokaTakuya/apery の README.md より。
Linux のディストリビューションによっては、Makefile に記述されている '-lpthread' を '-pthread' にしなければ、 実行時にエラーになってしまう場合があります。 Linux, Windows で G++ 4.8 以上のバージョンで動作確認をしています。 Clang では正しくビルド出来ているか確認出来ていません。 Visual Studio でビルドすることは現状では出来ません。 Windows でビルドする場合は、MinGW64 をお使い下さい
というわけで、g++4.9
を使うとうまくいく、と。
mac のデフォルトでは、gcc コマンドが clang コマンドの alias になるという罠があり、明示的に 4.9 以上の gcc を指定する必要があった、というオチ。
普段の生活では gcc
に馴染みがないので、少しハマった。
起動を早くする
初回起動時は評価関数生成のため、起動が非常に遅い。
ので、以下を行って評価関数のみ生成しておく。
linux,macでのコンパイルと使い方 · HiraokaTakuya/apery Wiki
$ git submodule init $ git submodule update --depth=1 # コンパイル済みの apery を bin 以下に移動させる $ cp ../src/apery . $ ls bin 20151105 Readme.txt apery book make_synthesized_eval.bat make_synthesized_eval.sh # bin の directory 上で `make_synthesized_eval.sh` を実行する。 $ cd bin ./make_synthesized_eval.sh info string start setting eval table info string end setting eval table
で成功。
make_synthesized_eval.sh
が、bin
以下で実行されること前提の実装になっていたのが難しかった。
動かしてみる
# 入力待ちになる $ ./apery # 標準入力で通信開始の合図を入れる usi # apery が標準出力でレスポンスを返してくれる id name Apery Debug Build id author Hiraoka Takuya option name Best_Book_Move type check default false option name Book_File type string default book/20150503/book.bin option name Byoyomi_Margin type spin default 500 min 0 max 2147483647 option name Clear_Hash type button option name Emergency_Base_Time type spin default 200 min 0 max 30000 option name Emergency_Move_Horizon type spin default 40 min 0 max 50 option name Emergency_Move_Time type spin default 70 min 0 max 5000 option name Eval_Dir type string default 20151105 option name Max_Book_Ply type spin default 32767 min 0 max 32767 option name Max_Random_Score_Diff type spin default 0 min 0 max 32600 option name Max_Random_Score_Diff_Ply type spin default 40 min 0 max 32767 option name Max_Threads_per_Split_Point type spin default 5 min 4 max 8 option name Min_Book_Ply type spin default 32767 min 0 max 32767 option name Min_Book_Score type spin default -180 min -32601 max 32601 option name Minimum_Thinking_Time type spin default 1500 min 0 max 2147483647 option name MultiPV type spin default 1 min 1 max 594 option name OwnBook type check default true option name Skill_Level type spin default 20 min 0 max 20 option name Slow_Mover type spin default 100 min 10 max 1000 option name Threads type spin default 4 min 1 max 64 option name Use_Sleeping_Threads type check default false option name USI_Hash type spin default 256 min 1 max 65536 option name USI_Ponder type check default true option name Write_Synthesized_Eval type check default false usiok
というわけで無事動かせた。
これで世界レベルの将棋ソフトが動かせる!!!
参考
homebrew の install path を変更して、install 済みパッケージを入れなおすの巻
イントロ
年末になったので、自宅 PC を EL Captain
にしました。
噂には聞いてたが、/usr/local
周りの権限問題により、普通に install すると躓くところが出てくる。困る。
おまけに解決方法が sudo chown -R $(whoami):admin /usr/local
ってどうことだよ、と。
こんな理不尽な方法があっていいのか。
homebrew/El_Capitan_and_Homebrew.md at master · Homebrew/homebrew
自分以外 mac 使わないし、一度は面倒さに屈して、上記のコマンドをしぶしぶ受け入れましたが もう我慢ならぬ ということで、install path を変更して homebrew を再 install しました。
brew bundle
まずは落ち着いて brew bundle
を入れる。
% brew bundle ==> Tapping homebrew/bundle Cloning into '/Users/argerich/.homebrew/Library/Taps/homebrew/homebrew-bundle'... remote: Counting objects: 49, done. remote: Compressing objects: 100% (46/46), done. remote: Total 49 (delta 2), reused 12 (delta 0), pack-reused 0 Unpacking objects: 100% (49/49), done. Checking connectivity... done. Tapped 0 formulae (111 files, 472K)
とやると、勝手に tap してくれる。 引き続き、今 install している package を dump する。
% brew bundle dump
Brewfile
が作成されていることを確認する。ついでに中身もチェックしておく。
install path を決める
適当に決める。
Homebrew のインストール先を変更する - Qiita を見ると /opt/homebrew
とあるが、私は ~/.homebrew
とした。
理由はルートディレクトリのすぐ近くに、ユーザー固有の権限を持つディレクトリを掘りたくなかったから。/usr/local
の二の舞い感がする。
どうせユーザー固有の権限を持つディレクトリを掘るなら、ホームディレクトリでいいかなと考えた。本当に適当です。
決めたら先に path を通しておく。 一時的にとはいえ、brew を uninstall すると色々使えなくなるので、やっておくのが無難。
export PATH=$HOME/.homebrew/bin:$PATH
uninstall するその前に
zsh
homebrew で install した zsh を使っている場合は、shell が起動できなくて詰みそうになるので、デフォルトの shell を変えておく。
もし変更しないとターミナルが起動しなくなり、コマンドを打たせてもらえない。
(クリーンインストールをマジで考えた)
この場合は冷静に mac 標準のターミナルから起動する shell を指定し対処する。
Terminal - Mavericksにしたらターミナルが - Qiita
こういう状態にならないためにも、予め適当に変えておくと良いと思う
echo $SHELL # => /usr/local/bin/zsh なら homebrew で install した zsh を使っている chsh -s /bin/bash # デフォルトシェルを bash に変更
git
alias git="hub"
とやっているそこの君! 安易の homwebrew を uninstall すると、git clone
すらできないので、気をつけよう。
% cd ~ && git clone https://github.com/Homebrew/homebrew.git .homebrew zsh: command not found: hub _direnv_hook:1: command not found: direnv
uninstall
ようやく homebrew を消し去る。 homebrew/FAQ.md at master · Homebrew/homebrew に方法が書いてある。
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
勿論いきなりこれを実行するのではなく、curl 先の ruby スクリプトを念のため読む。 良さそうだと思ったら、勇気を出して実行する。
/usr/local/bin/ /usr/local/etc/ /usr/local/lib/ /usr/local/share/ /usr/local/var/ You may consider to remove them by yourself. You may want to restore /usr/local's original permissions sudo chmod 0755 /usr/local sudo chgrp wheel /usr/local _direnv_hook:1: command not found: direnv # direnv 様がお怒りに….
ゴミが残ってしまった。これは適当に掃除する。
(/usr/local
って 0755 だっけ...?)
再 install
消し去ったら homebrew を 再 install する。
ここで注意するのが、tarball を使った install を行わないこと。
homebrew/Installation.md at master · Homebrew/homebrew
tarball なので、.git
ディレクトリが含まれてないからです。.git
がないと、brew bundle
を install できない。
なので普通に clone する。
% cd ~ % /usr/bin/git clone https://github.com/Homebrew/homebrew.git .homebrew
ちゃんと install 先の path が変わっているか確認する
HOMEBREW_PREFIX
まわりを見れば良さそう。
% brew —config HOMEBREW_VERSION: 0.9.5 ORIGIN: https://github.com/Homebrew/homebrew.git HEAD: 8eda6f6417c09a03486353f1c4565bfd2bdbc84b Last commit: 13 minutes ago HOMEBREW_PREFIX: /Users/argerich/.homebrew HOMEBREW_REPOSITORY: /Users/argerich/.homebrew HOMEBREW_CELLAR: /Users/argerich/.homebrew/Cellar HOMEBREW_BOTTLE_DOMAIN: https://homebrew.bintray.com CPU: quad-core 64-bit haswell OS X: 10.11.2-x86_64 Xcode: 7.2 CLT: 7.2.0.0.1.1447826929 Clang: 7.0 build 700 X11: N/A System Ruby: 2.0.0-p645 Perl: /usr/bin/perl Python: /Users/argerich/.pyenv/shims/python => /Users/argerich Ruby: /usr/bin/ruby => /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby Java: N/A _direnv_hook:1: command not found: direnv
再度 brew bundle
Brewfile
が置かれているディレクトリに移動して以下を実行。
% brew bundle Succeeded in tapping benswift/extempore Succeeded in tapping caskroom/cask Succeeded in tapping homebrew/binary Succeeded in tapping homebrew/boneyard Succeeded in tapping homebrew/bundle ...
後は待つだけ。
まとめ
brew bundle
と組み合わせれば、sudo chown -R $(whoami):admin /usr/local
に屈しないで済む。
package を install すると環境がパワーアップして便利だけど、こういうぬるま湯に浸かってしまうと、イザという時に普段やってることが何もできなくて、とても困ることになる。
そういう時に備えて、せめて vi
は素で使えるように訓練したい(実際している)と常々思う。
幸い普段は emacs
使ってるので、vi
は何もカスタマイズせずとも普段の生活に支障はない。
みんなも emacs
使おう。
babelify7.0.2 で jsx の compile に失敗する件
現象
- babelify 7.0.2
- browserify 12.0.1
- react 0.14.1
var React = require('react'); var Container = React.createClass({ render: function() { <div>test</div> } }); React.render( <Container />, document.getElementById('container') );
% browserify -t babelify src/app.jsx -o build/bundle.js SyntaxError: /Users/horowitz/dev/poochie/src/app.jsx: Unexpected token (9:2) 7 | 8 | React.render( > 9 | <Container />, | ^ 10 | document.getElementById('container') 11 | ); 12 |
SyntaxError
になってしまう。
方法
2つほど。
1. babelify
の version 6.4.0
に戻す
戻すと上手く動いた。
世の中に出回ってる記事のサンプルも動く。
2. presets を指定する
要は追加のオプションを付ける必要があるとのこと。
コマンドラインから実行する場合は、以下のような感じ
npm install --save-dev babel-preset-react echo '{"presets":["react"]}' > .babelrc browserify -t babelify src/app.jsx -o build/bundle.js
gulp の場合は configure
から presets
を渡す
debug: true }) - .transform(babelify) + .transform(babelify.configure({ + presets: ["react"] + })) .bundle() .pipe(source('bundle.js')) .pipe(gulp.dest('build'));
関係ないけど es6
でコードを書いた場合は、babel-preset-es2015
の install と presets: ["es2015"]
の指定が必要になるみたい。
まとめ
js 初学者にはキツイお仕置きでした。
babelify7
から babel6
を内部で使っていて、babel6
から新しく option 付ける必要があるらしい、という雑な理解をしています。
参考
respond_to vs routes 指定の format
イントロ
例えばAPI サーバーを作るときに「この URL にアクセスする時は、常に json
でアクセスしてほしい」みたいな事を実現したい。
それを実現する具体的な方法としては namespace :api, format: 'json'
と routes を書けばいいと思ってました。
(デフォルトの format が json になるので)
ただ
- それ以外の format を渡した時にどう動くか
- 似たような事ができる実装とどう違うのか
がよくわかってなかったので、その時にした実験をもう一度やってみたメモ。
パターン1
class Api::AccountsController < ApplicationController def index @accounts = Account.all respond_to do |format| format.html { render } format.json { render } end end end
Rails.application.routes.draw do namespace :api, format: 'json' do resources :accounts end end
- controller 側では
respond_to
で複数 format を受けつける準備ができている - namespace では
json
を指定
結果
% curl -I http://localhost:3000/api/accounts HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Type: application/json; charset=utf-8 Cache-Control: max-age=0, private, must-revalidate X-Request-Id: 01d34bba-60c5-4185-9e96-e343d179c45b X-Runtime: 0.014599 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Content-Length: 10 Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html HTTP/1.1 404 Not Found Content-Type: text/html; charset=utf-8 Content-Length: 41119 X-Web-Console-Session-Id: 939cd9fb2834ea8353d1332a62d97e15 X-Request-Id: a77ea27e-f5c0-4766-b357-09b964188e55 X-Runtime: 6.597595 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Connection: Keep-Alive
- controller で受け取る
format
はjson
になる - response の
Content-Type
もapplication/json
json
以外の format の request が投げられたら、404 Not found
- controller 側では html をの時は html の render をする準備ができているのにも関わらず
- routes 側で 404 になってしまうので、controller に到達しない
- controller 側に到達した時には、常に
json
形式- (書いてしまってるけど) 実質
renspond_to
で複数 format 対応は 不要
- (書いてしまってるけど) 実質
パターン2
class Api::AccountsController < ApplicationController def index @accounts = Account.all respond_to do |format| format.html { render } format.json { render } end end end
Rails.application.routes.draw do namespace :api do resources :accounts end end
- format 指定を削除
- controller 側は変わらず
結果
% curl -I http://localhost:3000/api/accounts HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Type: text/html; charset=utf-8 # text/html に注目! Cache-Control: max-age=0, private, must-revalidate X-Request-Id: 4db51f09-53cc-42ca-a6a0-49e4080b5030 X-Runtime: 0.492789 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Content-Length: 1715 Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Type: text/html; charset=utf-8 Cache-Control: max-age=0, private, must-revalidate X-Request-Id: 7d446561-2768-4f20-8832-4d6476115d45 X-Runtime: 0.050431 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Content-Length: 1715 Connection: Keep-Alive
json
が通るか確かめる
% curl -I -X GET http://localhost:3000/api/accounts.json HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Type: application/json; charset=utf-8 Cache-Control: max-age=0, private, must-revalidate X-Request-Id: c3c4a0fb-847c-4240-9470-606695c6b935 X-Runtime: 0.023534 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Content-Length: 10 Connection: Keep-Alive
respond_to
での指定以外のformat
% curl -I http://localhost:3000/api/accounts.xml HTTP/1.1 406 Not Acceptable Content-Type: text/html; charset=utf-8 Content-Length: 115323 X-Web-Console-Session-Id: bfb96347bd3ab4058a468ebb5762595f X-Request-Id: 74ee3306-aa22-4615-a72d-c1a7602ef434 X-Runtime: 0.640820 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Connection: Keep-Alive
respond_to do ~ end
で指定する format の内、 最初に書いてある方が default になるrespond_to
ブロックで指定している format 以外の request が投げられたら406 Not acceptable
responders
class Api::AccountsController < ApplicationController respond_to :json def index @accounts = Account.all respond_with @account end end
Rails.application.routes.draw do namespace :api do resources :accounts end end
結果
% curl -I http://localhost:3000/api/accounts HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Type: application/json; charset=utf-8 Cache-Control: max-age=0, private, must-revalidate X-Request-Id: f7a06375-320d-4d37-a93c-12d9d3c17cf0 X-Runtime: 0.299781 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Content-Length: 0 Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html HTTP/1.1 406 Not Acceptable Content-Type: text/html; charset=utf-8 Content-Length: 115331 X-Web-Console-Session-Id: 780f1e5e4050cb0b7e72d36f60841f4c X-Request-Id: b0f92ee8-c6fa-4dc4-872e-f60adc942104 X-Runtime: 0.379881 Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18) Connection: Keep-Alive
- パターン2 とほぼおなじ
respond_to
も複数個 format が渡せるが、format
指定無しでリクエストされた場合、最初に指定されてある format が利用されるrespond_to :html, :json
なら、html
がデフォルト値
- 指定以外の
format
でのリクエストは、406 Not acceptable
responders
の利点は、controller 毎に format の指定をしなくて済むことなのかな?- 一箇所にまとめられていれば、controller 毎にデフォルトの
format
が異なるという間違いを避けられる。 - (一瞬触っただけなので、よくわかってない)
- 一箇所にまとめられていれば、controller 毎にデフォルトの
結論
サーバーが指定外の format
で request を受けた時、どう返したいかで実装が変わる
406 Not acceptable
を返す場合はrespond_to
(あるいはresponders
)- 複数 format を受け付ける URL なら、こちらのほうが自然
404 Not Found
を返す場合はnamespace :prefix, format: 'format名'
- 文字列 で指定すること。(
symbol
じゃダメ) - 例えば「
json
しか絶対受け付けない」なら、こういうのもありかも。
- 文字列 で指定すること。(
参考
emacs in terminal に移行しようと思ったけど、GUI の emacs に戻った話
イントロ
今まで GUI の emacs を使ってたけど、terminal の中で emacs を開くようにして、暫く使ってみた。
要するに emacs -nw
です。
けど色々難しいことがあって、元に戻ったという話。
苦労したところと、経緯を残します
環境
emacs 24.4
OSX 10.10.4
iterm2 2.1.1
なんで terminal の中で開きたいの?
カッコイイからです!
理由はなんとなく。
やったこと
特定のキーバインドが emacs に渡らない
ターミナル上のEmacsで本来使えない"Ctrlキー+何か"を使う方法 - すぎゃーんメモ を見て解決。
これで C-:
, C-;
, C-y
等よく使うキーバインドを機能させることができた。
コピペができない
emacs の kill-ring
と clipboard
の共有ができてなくて困った。
emacs でコピーした時に、pbcopy
を呼ぶことで、無理やり同期させるような elisp を書いた。
Emacs と Mac のクリップボードを共有する と似たような感じ。
-nw
にした時に同期できてないことに気づいたけど、特に GUI では意識してなかったポイント。
tmux prefix key と emacs のキーバインドが競合する
私は tmux の prefix key として C-q
を使っていたけど、emacs の quoted-insert
と競合する。
悩んだ末 quoted-insert
の prefix を変更した。
emacsclient の起動
# emacs -nw function emacs () { EMACS_CLIENT='/Applications/Emacs.app/Contents/MacOS/bin/emacsclient' EMACS='/Applications/Emacs.app/Contents/MacOS/Emacs' if ! pgrep Emacs; then $EMACS --daemon fi $EMACS_CLIENT -nw -q } function ekill () { pkill Emacs }
この辺りはまだ泥臭くやってます。
emacs が立ち上がってなかったら、 --daemon
付きで起動し、その後 emacsclient を立ち上げる、というスクリプトを使って凌いでいる。
terminal の中で起動してよかった事
太字は特に良かった(悪かった)ところ
- カッコ良くなった
emacsclient
の起動がかなり早くなった- パッと開いて、パッと閉じる事が可能になって気持ちいい
- いい感じに emacs で完結するようになった
- 操作性うp
shell-command
を以前より多用するようになった- だけどまだまだ shell 周りは課題が多い
よくなかったこと
- 設定で無茶をしている
- 特に「ターミナルから特定のキーバインドが渡らない」現象を解決するのに色々苦労した
- この環境を再現するために、iterm2 の設定をいじらないといけなくて微妙な感じがする
- コマンド + TAB で emacs に移れない
- emacs で完結している限りは困らないけど、時々困る(かも)
color-theme
が好みなやつがない- 自分の中でコレ!っていうのが無かった
- GUI の時と、terminal の時で、色合いが結構違う
elscreen-server
が地味に機能してない気がする件- 別の
emacsclient
を立ち上げても tab が共有されてない - 色々なところで emacsclient 立ち上げると頭使うから、こういう使い方をしたくなかった
- 別の
結局どうなったか
GUI に戻りました...
自分は zsh と emacs を両方よく使っていたのと、zsh in emacs
をイメージ通りに使えなかったのが大きかったです。
なので iterm2
(zsh 使う) と emacs
スイッチを コマンド + TAB
で行えないのが辛かった。
これがうまく機能しないと、効率が落ちてしまいます。
けどいつかは使いこなしたいこの環境、またいつか挑戦したいと思います。
delayed_job まとめ
イントロ
なんとなく使ってきて、なんとなくわかってるつもりの delayed_job
について、改めてまとめてみたもの。
休日暇だったのでソースコード読んでみたり、step 実行したりして、何処に何が書いてあるかをざっくりとまとめたものです。
環境
rails (4.2.1)
delayed_job (4.0.6)
delayed_job_active_record (4.0.3)
daemons (1.2.3)
導入
- Gemfile
gem "delayed_job_active_record"
- ActiveJob
ActiveJob のバックエンドとして、delayed_job
を指定する
config.active_job.queue_adapter = :delayed_job
- command
rails generate delayed_job:active_record rake db:migrate
基本的な仕組み
Queue
- queue は database に保存される
- テーブル名は
delayed_jobs
- scheme は gory-details に書かれている
rails generate delayed_job:active_record
で自動生成される
serialize
- ruby の object を yaml 形式で dump し、DB に保存する
handler
カラムに保存する
- 取り出す時に dump された yaml を load し、ruby のオブジェクトに戻す
worker
- worker が
delayed_jobs
テーブルを数秒おきにポーリングする - レコードがあったら、yaml をロードして ruby のオブジェクトに戻し job を実行する
- job を実行し終わったらレコードは削除される
- worker はポーリングを続ける、の繰り返し
worker の詳細
要の worker について詳しく見てみる。
woker の起動方法
rake jobs:work
bin/delayed_job start
の二通り
共通してやっていること
- worker プロセスの起動
rake jobs:work
bin/delayed_job start
- https://github.com/collectiveidea/delayed_job/blob/v.4.0.6/lib/delayed/command.rb
- 起動した worker プロセス自体の管理は
daemons
gem の仕事
両者の違いは?
rake jobs:work
は worker を起動するだけ- worker プロセスは一つだけ
bin/delayed_job
は 起動オプションを受け取り、柔軟な worker プロセスの設定ができるdaemons
gem がないと 起動不可能
なので
- 開発環境で軽く使う場合は
rake jobs:work
(もしくはrake jobs:workoff
) - 本番環境で運用する場合は、オプションを指定して
bin/delayed_job
のようなイメージ
worker の option
Delayed::Worker
クラスのアクセサとして定義する。
README に書いてあるとおり、rails なら config/initializers
の下に適当に書いておけばいい。
default_queue_name
queue
の名前。queue
カラムに入る。
destroy_failed_jobs
- true だと、失敗した job を DB から削除する(デフォルト true)
max_run_time
- job の timeout を設定(デフォルトは4時間)
- この時間より長いジョブは
Timeout::Error
を継承したDelayedJob::WorkerTimeout
をraise する
raise_signal_exceptions
- シグナル
INT
,TERM
を受け取った時の挙動を制御する - https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/worker.rb#L53-L58 にコメントで書かれている
- シグナル
bin/delayed_job のオプション
https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/command.rb#L26-L71
書いてあるとおり。重要そうなものをピックアップ
--number_of_workers
- 起動する worker 数
- ただし
--identifier
option との併用は不可(identifier 指定とプロセスの自動連番が衝突するから) - stop と start の worker 数が等しくないと、
bin/delayed_job stop
で stop できない
--pid-dir
- pid ファイルのディレクトリを指定
--log-dir
- log ファイルのディレクトリを指定
--identifier
- プロセス名を指定できる。指定すると
delayed_job.#{@options[:identifier]}
というプロセス名になる
- プロセス名を指定できる。指定すると
--prefix
- プロセス名を指定できる。指定すると
File.join(options[:prefix], process_name)
というプロセス名になる
- プロセス名を指定できる。指定すると
bin/delayed_job stop で何が起こるか
起動中の delayed_job
プロセスには TERM
が送られる。(daemons
gem の仕事)
https://github.com/thuehlinger/daemons/blob/v1.2.3/lib/daemons/application.rb#L374
発生する例外
起動中のプロセスが TERM, INT を受け取った時、job の挙動はDelayed::Worker.raise_signal_exceptions
の値で変わる
false
TERM
を受け取った場合- 例外は出ない
INT
を受け取った場合- 例外は出ない
:term
TERM
を受け取った場合SignalException
を raise
INT
を受け取った場合- 例外は出ない
true
TERM
を受け取った場合SignalException
を raise
INT
を受け取った場合SignalException
を raise
コードを見たほうが腑に落ちるかも https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/worker.rb#L132-L142
例外が出た後に何が起こるか
:error
の callback が登録されていれば、それを実行する- Job を DB に戻して、再度実行し直す(
handle_failed_job
)
callback について
- error のフックとしては
:before
,:after
が空席handle_failed_job
の前と後に実行されるcallback
- callback の定義方法はコードは
Lifecycle
というクラスに書かれてある
書き方は以下の様な感じ
Delayed::Worker.lifecycle.before(:error) do |worker, job| # puts worker # puts job end
ブロック引数に
run_callbacks(:error, self, job)
の第二引数以降、即ちDelayed::Worker
,Delayed::Job
インスタンスを受け取れる- ただし、
callback
を定義する場所に注意- 実質
config/initializer
しかない? - アプリ側で動的に callback を定義しても、既に起動中の worker には伝わらない
- ワーカーに依存しない処理が書ける場所
- 実質
Capistrano
https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/recipes.rb
- 各 host で
bin/delayed_job
コマンド + オプションを実行する
だけ。
つまり bin/delayed_job
の章で述べたとおり、capistrano で deploy した時、実行中の job は中断し DB に戻される。
そして新しいコードを load した状態で、再度 job が実行される、という流れになる。
細かすぎて伝わらない delayed_job のネタ
実は Delayed::Job
というクラスはない
正確には collectiveidea/delayed_job に定義されていない、ということ。
実体は collectiveidea/delayed_job_active_record になっていて、このクラスが include Delayed::Backend::Base
している
>> Delayed::Job => Delayed::Backend::ActiveRecord::Job
なので Delayed::Job
インスタンスのメソッドを調べる時は delayed_job_active_record か Delayed::Backend::Base を見ると良い。
find_available メソッドの謎
Delayed::Backend::Base.reserve
に定義されている find_available
メソッド、よく調べてみたけど、実は何処にも定義されていない。なので NoMethodError
が発生するはず。
https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/backend/base.rb#L43
けど上記で述べたとおり、実体は collectiveidea/delayed_job_active_record のオブジェクトになっていて、https://github.com/collectiveidea/delayed_job_active_record/blob/v4.0.6/lib/delayed/backend/active_record.rb#L43 で reserve
を上書きしているから、実質問題は起きていない。
delayed_job
バックエンドによっては find_available
を上書きしているものもあるらしい
まとめ
- delayed_job の仕組みについて調べました
- DB に queue を溜めて、worker がそれを取り出して実行する
- worker の起動方法は2種類ある
rake
とbin/delayed_job
の2つ
- worker の設定は
Delayed::Worker
クラスのアクセサとして定義- worker の設定は沢山あるんやで
- worker を stop させると、worker が既に動いていても DB に一旦戻されて再実行を行う仕組みになっている
- デフォルト
authlogic で、ユーザーの password を編集した直後に強制ログアウトされる件
イントロ
authlogic
でハマったネタ第二弾。
まとめると以下の様な感じ。
- authlogic でログインユーザーを作成
- よくある 名前と password で認証するユーザー
- ログインユーザーを編集する画面を作る
- password を編集可能にする
- ログインユーザーの password を更新すると、何故か強制的にログアウト状態になる
- ログインを要求する url に強制 redirect される
解決法
class User < ActiveRecord::Base acts_as_authentic do |c| c.session_class = UserSession end end
この解決方法はあくまで一例。
原因
authlogic
のログイン状態は、DB に保存されているpersistence_token
とsession[:user_credentials]
の一致により判定される。user_credentials
という session の key はログイン用のモデルによって変更の可能性あり
authlogic
で password を更新すると、強制的にpersistence_token
も再生成される- この時 session の値も再生成された文字列に更新しなければならない
- 設定が甘くて、password を更新した時に session の値が更新されなかったため、ログイン状態が維持されなかった
- authlogic がログイン画面にリダイレクト
のような感じ。
肝心の session の値も再生性された文字列で上書きしなければならない
設定は以下のメソッドで行っている。
恐らくこの値は true でなければいけないが、false になっているとこんなおかしな状況になる。
def update_sessions? !skip_session_maintenance && session_class && session_class.activated? && self.class.maintain_sessions == true && !session_ids.blank? && persistence_token_changed? end
今回私が遭遇したケースでは session_class
が何故か nil を返すようになっていたので、session が update されず、ログアウト状態になってしまっていた。
session_class について
session_class
とは
Authlogic::Session::Base
を継承した session クラスを返すメソッド- 普通は自動で判定される
User
がacts_as_authentic
ならUserSession
という風に
ので、普段は意識しなくていいはず。
例えば Admin
モデルを作って、Admin
専用の管理画面を作ろう、となった時に Session クラスを AdministratorSession
としてしまうと、session_class
を指定する必要がある。
設定を忘れるとこのような目にある。
まとめ
Authlogic::ActsAsAuthentic::SessionMaintenance::Methods#update_session?
メソッドを調べればよろし
authlogic で blank の password で update 出来てしまう問題
イントロ
情報が古いけど
改めてまとめると、以下の様な状態。
authlogic
でログインユーザーを作成- ログインユーザーは
password
を form に入力する
- ログインユーザーは
- ログインユーザーを編集する画面を作る
- 新規作成ではないので注意
password
を編集可能にする
- ログインユーザーを編集するとき、パスワードを空にして保存できてしまう
validates_length_of
は何も設定しないなら 4 文字が minimum なので、空で保存できるのはおかしい- https://github.com/binarylogic/authlogic/blob/23751e3eaba4c2b09751505079d7264fd1cc8b72/lib/authlogic/acts_as_authentic/password.rb#L95
解決法
class User < ActiveRecord::Base acts_as_authentic do |c| c.ignore_password_blank = false end end
とする。
よく見ると
のコメントに書いてある。
前提条件も含めて説明すると...
- 編集画面において、
password
を最初から入力済みにすることはできない- DB に保存されている
password
を復号することはできないため(暗号化方式によるが) - つまり編集画面を開いた直後はどうしても空になってしまう
- DB に保存されている
- 状況として「
password
は変えたくないけど、ページを遷移してしまう」ケースが存在するだろう- どういうケースかは明記されてないけど、例えば間違って「更新」ボタンを押してしまったりするとか?
- そうすると、空のパスワードが保存されてしまい、セキュリティ的に甘くなる
- そういうケースは避けたい
- オプションで変更できる
ということらしい。(多分)