woshidan's blog

あいとゆうきとITと、とっておきの話。

irbの実行結果の出力が非常に長い場合、途中からカットしたい

最近しばしば行う作業のログが、おそらく人間の目には確認できないほど長く、また件数的におそらくログから確認を行うことはしない*1ということもあり、じゃあ使わないログはある程度短くしたいですね、ということで調べました。

具体的には、属性がたくさんあるクラスについてのログで、最初の方のフィールドしか確認する必要がない場合、後半は出さなくてもいいのでは...というケースを対象としています。

# この例だとuser_idあたりまでがあれば業務上十分
irb(main):045:0> Memo.new
=> #<Memo:0x007fcf43045500 @id=123456, @user_id=12, @category_id=3456, @sub_category_id=nil, @reminded_at=2016-10-22 10:00:00 +0900, @created_at=2016-10-21 10:00:00 +0900, @updated_at=2016-10-21 10:00:00 +0900>

動作確認用の雑なクラス定義はこちらのgistにあります。

irbの実行結果の出力形式を変更する

IRB::Inspector.def_inspectorメソッドで、irbの実行結果の出力形式を独自に設定することができます。具体的には、引数に設定の名前としてのkeyとブロックを与えることで、ブロックの返り値を出力として利用できます。

なお、このメソッドは .irbrcというファイルに記述します。

# ~/.irbrc
# たとえば出力を2回表示するように設定してみます
IRB::Inspector.def_inspector([:test]){|v| v.to_s * 2 }

.irbrcに書いた上記の設定を適用するには、起動時に--inspectオプションで指定するかirb起動後にIRB.conf[:INSPECT_MODE]変数から設定します。

$ irb --inspect test
irb(main):001:0> abc = "def"
=> defdef

この要領で、出力結果の表示を50文字までにするような設定を書いてみます。

IRB::Inspector.def_inspector([:test2]){|v| v.to_s.slice(0..50) }
$ irb --inspect test2
# (クラス定義中...)
irb(main):015:0> Memo.new
=> id: 123456 user_id: 12 category_id: 3456 sub_catego

これでいいかなと一瞬思ったのですが、この方法の場合、出力に使う返り値のクラスが適切にto_sメソッドを実装している前提なので、いちいち各クラスのto_sメソッドを確認したり定義したりする必要があって大変そうです。

また、ppメソッドなどの出力結果を利用することも考えたのですが、ppメソッドの戻り値は、出力対象のクラスなので思っていたより簡単にはいきません。

irb(main):015:0> require 'pp'
=> true
irb(main):017:0> res = Memo.new
=> #<Memo:0x007fefe5068ad8 @id=123456, @user_id=12, @category_id=3456, @sub_category_id=nil, @reminded_at=2016-10-22 10:00:00 +0900, @created_at=2016-10-21 10:00:00 +0900, @updated_at=2016-10-21 10:00:00 +0900>
irb(main):018:0> res
=> #<Memo:0x007fefe5068ad8 @id=123456, @user_id=12, @category_id=3456, @sub_category_id=nil, @reminded_at=2016-10-22 10:00:00 +0900, @created_at=2016-10-21 10:00:00 +0900, @updated_at=2016-10-21 10:00:00 +0900>
irb(main):019:0> res.class
=> Memo

また、IRB::Inspector.def_inspectorメソッドで紹介したirbの出力方式は当然独自方式だけではなく、yaml, ppなどいくつかのオプションがあり、そちらを使っている場合も多い中、出力方式そのものを変えることができない場合もあるでしょう。

では、最終的に出力が渡ってきた後で、その文字列を切り取る方法がないかな、と思って、irbのプロンプトそのもののカスタマイズについて調べました。

irbプロンプトのカスタマイズ

irbプロンプトの設定をする際も、.irbrcに記述します。

記述項目は、IRB.conf[:PROMPT]で、下記のような形式となっています。

IRB.conf[:PROMPT][:SAMPLE] = {
  :PROMPT_I => "%N(%m):%03n:%i> ",  # 通常時のプロンプト
  :PROMPT_N => "%N(%m):%03n:%i> ",  # 継続行のプロンプト
  :PROMPT_S => "%N(%m):%03n:%i%l ", # 文字列などの継続行のプロンプト
  :PROMPT_C => "%N(%m):%03n:%i* ",  # 式が継続している時のプロンプト
  :RETURN => "==> s\n"   # メソッドから戻る時のプロンプト
}

%sとあって、あ、これCのprintfっぽいな、と思って http://d.hatena.ne.jp/iww/20090701/printf などを参考に、英数字40文字あたりで切れてください、というつもりで書いた設定は下記となります。

IRB.conf[:PROMPT][:MY_PROMPT] = {
  :PROMPT_I => "%N(%m):%03n:%i> ",  # 通常時のプロンプト
  :PROMPT_N => "%N(%m):%03n:%i> ",  # 継続行のプロンプト
  :PROMPT_S => "%N(%m):%03n:%i%l ", # 文字列などの継続行のプロンプト
  :PROMPT_C => "%N(%m):%03n:%i* ",  # 式が継続している時のプロンプト
  :RETURN => "==> %.80s\n"   # メソッドから戻る時のプロンプト
}

上記の設定を適用するには起動時に--promptオプションで指定します。

irb --prompt my-prompt
# (クラス定義中...)
irb(main):015:0> Memo.new
==> #<Memo:0x007f99b2010920 @id=123456, @user_id=12, @category_id=3456, @sub_categor

ためしに、出力形式をyml形式に変えても途中で出力をカットするように動いているか見てみます。

irb(main):017:0> conf.inspect_mode = :yaml
==> --- :yaml
...

irb(main):018:0> Memo.new
==> --- !ruby/object:Memo
id: 123456
user_id: 12
category_id: 3456
sub_category_id:

よさそうですね。

仕事で導入するのはやや怖いところもありますが、興味が出たので調べてみた結果はこんな感じです。

現場からは以上です。

参考

*1:ログは長いけれど、作業時間は短いのでやり直したほうが正確っぽい

OmniAuth(omniauth-twitter)での認証が401 Authorization Required invalid_credentialsで失敗する

OmniAuth系のライブラリでTwitterFacebookなどのサービスと連携しようとしたところ、401 Authorization Required invalid_credentialsでひたすら失敗してつらかったです。

特にTwitterAPIには、401でないエラーを401で返すという話もあり*1、結局関連するソフトウェアや設定を一つずつ検討することになりました。。

これを忘れて繰り返すと非常に辛いので、この場で一つずつ簡潔にまとめておきます。

https://github.com/omniauth/omniauth/wiki/FAQ#how-to-fix-oauthunauthorized-error-for-twitter-provider

検討事項

  • OAuthっぽいこと
    • アカウント情報
    • Twitterアプリの作り直し
    • ブラウザの時刻
  • memcached関連
    • memcachedサーバとの接続
    • provider_ignores_stateでsessionの値を確認を回避
    • memcachedのstats
  • omniauthの設定
    • OmniAuth.config.full_host

OAuthっぽいこと

アカウント情報

Twitterアプリなどで、OAuth用のアプリケーションの権限、利用ドメインなどの設定があればその登録、リダイレクトURI、アプリケーションのIDとシークレットなどの確認をします。

Twitterアプリの作り直し

参考: http://qiita.com/hirokishirai/items/5a43977a38ecd922bfb9

どうしてもうまくいかないときはTwitterアプリケーションを作り直すとうまくいくことがあるそうです。

ここではTwitterアプリの作り直しを試したりしました。

連携先のサービスによってはOAuth用アプリケーションごとに一人のユーザーを異なるアカウントIDで管理していたりするので、新しく連携させるわけでないときは注意しましょう*2

ブラウザの時刻

Twitterでは、0.1秒でもブラウザの時刻がずれていると認証に失敗してしまいます(アプリをエミュなどでテストをする際は割と辛い...)。

こちらのページなどでご利用のパソコンでの時刻にずれがないか確認できます http://www.nict.go.jp/JST/JST5.html

memcached関連

401のエラーを吐いている箇所のコードはこちらのようになっています。

!options.provider_ignores_state && (request.params["state"].to_s.empty? || request.params["state"] != session.delete("omniauth.state"))

の部分を参考にGoogleしたら、sessionの値がTwitterなどからPOSTされるまで、保持されてないのかもしれない、ということになりました。

自分の環境の場合は、sessionの保存にmemcachedを利用していたので、memcachedの確認をしていました。

memcachedサーバとの接続

とりあえず、pingした。

provider_ignores_stateでsessionの値を確認を回避

を参考に request.params["state"] != session.delete("omniauth.state") の判定をそもそも呼ばないようにしてみました。

memcachedのstats

sessionに利用してるmemcachedの利用出来るバイト数などは問題ないか、何かデータがうまく取れてないかなということで、memcachedstatsを確認してみました。

あんまりよくわかっていないので、get_misses, bytes, limit_maxbytes, accepting_conns, threadsなどを見てみたのですが、それらしい違いはなかったです。

omniauthの設定

config/initializers/omniauth.rbなどのinitializersのファイル中の設定を確認しました。

具体的には、

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, Rails.secrets.oauth.twitter["app_id"], Rails.secrets.oauth.twitter["app_secret"]
  provider :facebook, Rails.secrets.oauth.facebook["app_id"], Rails.secrets.oauth.facebook["app_secret"]
en

など(自分で書いておいて、おそらくdotenv、利用して環境変数で渡した方がセキュアでしょう)で書いてあると思うのですが、これが動作環境で所望の値が得られているか、railsコンソールで叩いて確認したりしていました。

結局は、OmniAuth.config.full_hostに設定するホストのスキーマhttpsにしなければいけないところがhttp になっているという話でした。

長々書きましたが、いままでの全部関係なかったのでつらいですね。

SSLに対応している部分と対応してない部分、対応している部分ではhttpsにリダイレクトするような設定なのですっかり頭から抜けていたのですが、リダイレクト先に設定したスキーマに合わせましょう。

デバッグ途中で開発環境で動かしてみたら401にならなかったので、localhostとリモートで何の設定が違うんだ、と眺めていたら発見しました。

memcachedの使い方について簡単にメモします

動作環境は、Max OS X EI Captain 10.11.4, memcached 1.4.24 です。

memcachedのインストール

# http://qiita.com/makotok7/items/9998b15f79fc7a53af24
brew install memcached

localhostの11211ポートでmemcachedサーバを起動

$ memcached -p 11211 -d

詳しいオプションについては

telnetmemcachedサーバに接続

localhostの11211ポートでmemcachedサーバが動作しているとします。

$ telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.

memcachedサーバにデータを入れる

参考: https://www.tutorialspoint.com/memcached/memcached_set_data.htm http://l-w-i.net/t/memcached/command_001.txt

set [KVSのキー] [非圧縮/圧縮などのフラグ] [データの有効期限(秒). 0の場合は永続的に保存] [保存するデータのバイト数](return/enter)
[KVSの値](return/enter)

123という3バイトの文字列をfooというキーに非圧縮で900秒保存させる
set foo 0 900 3(return/enter)
123(return/enter)
STORED

memcachedサーバにデータの取得と更新

参考: http://l-w-i.net/t/memcached/command_001.txt

get foo
VALUE foo 0(非圧縮) 3(データ長:3バイト)
123
END
gets foo   
VALUE foo 0(非圧縮) 3(データ長:3バイト) 2(cas ID)
123
END

getsで追加で表示されている最後の数字は、cas IDといい、replaceコマンドでmemcached上の同じキーに対する値を書き換えるたびに変化します。

replace foo 0 900 3
345
STORED
gets foo 
VALUE foo 0 3 3
345
END

ユーザーAがmemcachedからデータを取得して処理したあとに保存しようとしたら、すでに他のユーザーBによって変更が加えられており、素直にユーザーAの計算結果を保存したらBの変更の履歴が消えてしまって困る、という場合があります。

それを避けるため、casコマンドを使って、データを取得した時のcas IDをパラメータに与えて、「あの時からお変わりなかったら、変更したいんですけどー」という感じで変更コマンドを打ちます。

(ユーザーA)
gets foo
=> VALUE foo 0 3 7
=> 123
=> END

(ユーザーB)
replace foo 0 0 3
abc
=> STORED ← 他のユーザーによりデータは更新されてcas IDは変化

(ユーザーA)
cas foo 0 0 3 7
zzz
=> EXISTS 既に他のユーザがデータを書き換えたのでzzzは書き込めない

memcahcedサーバに入っているデータの状態について調べる

参考: http://taka512.hatenablog.com/entry/20100324/1269428213

stats
STAT pid 8365 
STAT uptime 2670 起動時間
STAT time 1476973152
STAT version 1.4.24
STAT libevent 2.0.22-stable
STAT pointer_size 64 OSが32bit又は64bit 
STAT rusage_user 0.037374 プロセスがユーザモードで動作した累計時間?(秒.マイクロ秒)
STAT rusage_system 0.061396 プロセスがカーネルモードで動作した累計時間?(秒.マイクロ秒)
STAT curr_connections 10 現在のデータ数
STAT total_connections 11
STAT connection_structures 11 memcacheが確保したコネクション構造体数
STAT reserved_fds 20
STAT cmd_get 6 GETコマンド発行の累計
STAT cmd_set 7 SETコマンド発行の累計
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 5 リクエストでキーが見つかった数
STAT get_misses 1 リクエストでキーが見つからなかった数
STAT delete_misses 0
STAT delete_hits 0
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 1
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 284 ネットワークから受信したバイト数
STAT bytes_written 292 ネットワークへ送信したバイト数
STAT limit_maxbytes 67108864 memcacheの最大容量(バイト)
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT malloc_fails 0
STAT bytes 71
STAT curr_items 1
STAT total_items 3
STAT expired_unfetched 1
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 1
STAT crawler_reclaimed 0
STAT crawler_items_checked 0
STAT lrutail_reflocked 0
END

MySQLで外部ホストのデータベースにクエリを送りたい

最近開発するために開発する環境を作っている私です。

MySQLで外部ホストにたいしてSQLを送るにはどうしたらいいのかなーと悩んでいたのですが、あっさり解決したのであっさりメモします。

まとめ

  • MySQLにおいて基本的に外部ホストへはアクセスできないようになっている
  • MySQLにおいてデータベースにどこのホストからアクセスするかはそのホストのmysqlデータベースのuserテーブルに入っているユーザー情報で管理
  • 外部ホストにアクセスしたい場合は、アクセスしたいホストにアクセス元のサーバからのアクセスを許可したユーザーを作ってあげる
// 接続先のホストにて接続元のホスト(client_host)からアクセスするユーザーを作成
mysql> GRANT ALL PRIVILEGES ON target_database.* TO access_user@client_host IDENTIFIED BY 'pass';
mysql> SELECT host,user FROM mysql.user;
+------------------------------------+---------------+
| host                               | user          |
+------------------------------------+---------------+
| client_host                        | access_user   |
+------------------------------------+---------------+
// client_host上から
mysql -u access_user -h target_host target_database < request.sql
// 省略してますが、パスワードの入力あり

参考

MySQL:外部ホストからのDBアクセス方法

MySQLに外部ホストから接続できるように設定する | WEBサービス創造記

MySQL ユーザの操作(作成、パスワード変更、削除)

SPFってなんですか?

お仕事でSPFについて確認したのでブログにもメモを置きます。

参考: http://salt.iajapan.org/wpmu/anti_spam/admin/tech/explanation/spf/

SPFについて

  • メールの送信元の詐称を防ぐための認証技術

SPFでメールを認証するとは

SMTPでメールを送信するときはメールの送信元に関する下記の3つのドメイン(あるいはドメインのIP)はすべて違っても良いそうです。

  • a1. SMTPでメールサーバに接続してメールを送るという意味で、メール(SMTPが動いてるサーバ?)を送り出すサーバのIP cf. example.jp は 192.0.2.1
  • a2. SMTPMAIL FROMコマンドの引数となる送信者のメールアドレスのドメイン(エンベローブに入ってるらしい) cf. MAIL FROM:<sender@exmaple.org>
  • a3. 電子メールのヘッダ cf. FROM: user1@exmaple.com

そうなると、なんだか電子メールのヘッダをちょこちょこといじってメール送信元詐称が簡単にできそうですね。

SPFではメールを受信したサーバが a2 のメールアドレスのドメインSPFレコードのIPアドレスを見て、a1のサーバから送り出されたメールかどうか確認(認証)します。

SPFでメールを認証する手順

  • b1. a2ドメインで利用している権威DNSに、a2ドメインでメールを送り出すときに使うサーバのIPはこれです、とa1のメールサーバのIPアドレスを登録する(SPFレコードの登録)
  • b2. a2ドメインのアドレスがエンベローブのFROMには入っているメールを受け取ったメールサーバは、ドメインを見て、DNSexample.orgSPFレコードを問い合わせる
  • b3. b2の問い合わせの結果に、メールの送信元のサーバのIPが含まれていたら認証成功

認証に失敗したらどうなるの?

  • SPFレコードの登録がなくて失敗した ... 認証結果: None
    • 受け取り拒否にならず、迷惑メールフィルタでフィルタリングされるのに任せる場合が多いが、途中で受け取り拒否になる可能性もある
  • SPFレコードの登録があって失敗した ... 認証結果: Fail
    • RFCにおいてはこの結果に従って通信中のSMTPセッションにおいて該当メールの受信拒否を行う場合、550番エラーの利用を勧めているとあるので受け取り拒否になる可能性高そう

他にもありますが、参考元をご確認ください。

SPFでメールを送れなくなる理由

メールを送信するメールサーバのIPアドレスが、SMTPMAIL FROMコマンドの引数となる送信者のメールアドレスのドメインSPFレコードに入っていない?

DNSサーバの種類とDNSサーバの受ける問い合わせの種類についてまとめました

speakerdeck.com

参考のところに載せてるんですけど、DNSについて勉強する前に読みたかった資料です*1。もし、他の人がこの記事を見てDNSについてちょっと調べてみようと思ったら3分くらいは眺めてから調べ始めるとはかどると思います。

http://2014.seccon.jp/dns/dns_basics_in_30minutes.pdf

ただ、問い合わせの特徴によって覚えて区別つけるのもいいんですけど、基本的にどのプロトコルのアプリケーションやそれらのアプリケーションたるWebサービスなんかもサーバがクライアントから受けるリクエストによって挙動を変えるものだと考えると、上位から下位へ問い合わせを続ける過程で再帰的に動いて欲しいフルサービスリゾルバに飛んでくるリクエストが再帰問い合わせということになりわかりやすいのかな、と Webエンジニアは思いました。

特徴はどっちにしろ覚えないといけないんですけどね(苦笑)

DNS周りの情報は、後でDNSキャッシュポイズニングについて再帰問い合わせ周りの分も追加してやり直そうと思っているんですが、自分がググった限りJPRSの資料が一番はっきりしています。

ネットワークエンジニアとして(個人運営サイトらしいのですが、これでネットワーク勉強してました系のブログ記事とかでよく紹介されてた) => JPRSの資料(初心者網羅的な情報は多くないですが、専門家集団が出しているので概要であったり、個別トピックがすぱっとしてめっちゃわかりやすい) => @IT(個別の質問とかの記事がめっちゃ助かる) => BINDなど実装用ソフトのノウハウ系の記事、の順で巡回すると混乱が少ないのではないでしょうか。

*1:お約束としてある程度勉強してグーグル力が上がらないと当たれないんですね

DNSの概要と耐障害性、リソースレコードの読み方(BIND式)をまとめました

再帰問い合わせとかまで行こうとしたらスライドが50枚超えてたから打ち切り。

自分のスライドの作り方どこかで見たな、と思ってたんですが、ニコ動の攻略解説動画だ、たぶん。 少しずつ説明してもらわないとわかんない残念な頭をしている...!

speakerdeck.com

それにしても、実はドメインを扱ってる会社に勤めているんですが、これを作りはじめる前はDNSのAレコードって聞くと寒気してたとかは内緒にしておいてください。