プログラマが知るべき97のことを読みました
今月全然ブログ書いてなかったので書きます。
最近はObjectiveCやC言語の勉強をやっていてそちらを書けばいいじゃない、という気がしたのですが、 少し疲れてしまった。だいたい最初に詳解ObjectiveCから読んだせい(自業自得)。
その後少しずつObjectiveCプログラミングを読んでいるのですが、既視感と逆だヨォォォという気持ちでいっぱいです。 春まではObjectiveC, SwiftとiOSの勉強してるんじゃないかな...。
さて、きのこ本を読み終わったので印象に残った章をメモしておきます。来年とかに振り返って変わっていたら楽しいので。
- 誰にとっての利便性か
- 言語だけでなく文化も学ぶ & プログラミング言語は複数習得すべき
- 「魔法」に頼りすぎてはいけない
- プロセス間通信とアプリケーションの応答時間の関係 & 並行処理に有効なメッセージパッシング
- ポリモーフィズムの利用機会を見逃さない & リンカは魔法のプログラムではない
- 車輪の再発明の効用
- 「イエス」からはじめる
- ロールプレイングゲーム
- 育ちの良いコード
誰にとっての利便性か
最近、プロジェクト外の人が使うコードを書くということを意識していて、機能追加の時、つい一つのメソッドで色々できるようにしないか考えそうになりますが、呼び出し側にとっては読みにくくなっていないか、ということをいつも頭に置いておきたいな、と思いました。
言語だけでなく文化も学ぶ & プログラミング言語は複数習得すべき
ObjectiveCやC言語といったいままで使っていたJavaやRubyとは異なる毛色の言語を学んで色々思うところがあったので。 現状は、ObjectiveCかわいいよ、ObjectiveC(ぐったり)という気持ちなのですが、また来年書きます。
「魔法」に頼りすぎてはいけない
チームの雰囲気や文化が良いとかそういうのも魔法なんですよね、誰かがちょっと声をかけたりとか、そういうので成り立っていて、それはほんの少しの気遣いによるもので、ただではない、みたいな、そんな気持ち。
プロセス間通信とアプリケーションの応答時間の関係 & 並行処理に有効なメッセージパッシング
とりあえず並列処理をプロセスで書くというErlangを触ってみたいなぁ、という気持ち。
ポリモーフィズムの利用機会を見逃さない & リンカは魔法のプログラムではない
本体の文章もそうだけど、用語の説明がとてもわかりやすくてありがたかった。 こういう説明をできるようになりたいですね。
育ちの良いコード
このコードはいいのか悪いのか、がよくわからない時、他のコードをいじる必要があるかをパッチの単位から確認するやり方はいいな、と思いました。
JavaScriptでファイルのバイナリを送信するときと文字列のパラメータのみを送信するときに利用するリクエストボディの構成が全然違う
WebWorkerからサーバーへ通信を行いたくて、SafariだとWebWorkerではまだFormDataが使えない*1ので手動であれこれ頑張って調べていて*2面白かったことがあったので、メモ。
調査していた趣旨としては、FileAPIのFileReaderAPIのreadAsBinaryString()*3やreadAsArrayBuffer()でファイルの内容を読み込んだものをRailsのサーバへPOSTする、そのときよきにファイルとして扱ってもらう*4ためにうまいことリクエストパラメータを組み立てる、という話だったのですが、リクエストボディの文字列の構成からがらっと変える必要があったようです。
参考
http://api.rubyonrails.org/classes/ActionDispatch/Request.html
普通の文字列を送信するフォームの場合のリクエストボディ
<form method="post" action="/texts/upload"> <input type="text" name="string_param" value="文字だよ" /> <input type="hidden" name="authenticity_token" id="authenticity_token" value="<%= form_authenticity_token %>"/> <input type='submit' value='送信' /> </form>
上記のようなフォームの送信ボタンからサーバーへリクエストを送った場合、ActionDispatch::Request#raw_post
メソッドで取得できるリクエストボディは下記のようにURIエンコードされたキーと値の組を&
でつないだものとなります。
string_param=%E6%96%87%E5%AD%97%E3%81%A0%E3%82%88&authenticity_token=ymyqd1fC0%2FbOWLdEO546DPd%2BOUm9ljC1Qu0rYvErwBVtRJjUG4O%2BRsH7%2FZemkU8yIR3s7MLuujtiAex89NqkUA%3D%3D
ファイルなどのバイナリを送信するフォームの場合のリクエストボディ
var formData = new FormData(); formData.append("string_params", "文字だよ"); // バイナリデータ以外のパラメータがある場合の実験用 formData.append("file", file); // fileはFileクラスのインスタンス $.ajax( { url: "/files/upload", type: 'POST', contentType: false, processData: false, headers: { 'X-CSRF-Token' : $('#authenticity_token').val() }, data: formData, dataType: 'json', success: function(response) { console.log(response); }, error: function(error) { console.log(error); } });
上のように、適当にフォームからFileインスタンスを取得してFormDataを利用してPOSTした場合のリクエストボディは下のようになります。
# パラメータごとに # ------boundary(WebKitFormBoundar乱数文字列)------\r\n # Content-Disposition: form-data; name="パラメータ名"\r\n\r\n # 値(バイナリ?)\r\n------ # という構成になっている ------WebKitFormBoundarypUNpzkXNsa1wjYql\r\nContent-Disposition: form-data; name=\"string_params\"\r\n\r\n\xE6\x96\x87\xE5\xAD\x97\xE3\x81\xA0\xE3\x82\x88\r\n------WebKitFormBoundarypUNpzkXNsa1wjYql\r\nContent-Disposition: form-data; name=\"file\"; filename=\"UPLOAD_TEST.JPG\"\r\nContent-Type: image/jpeg\r\n\r\n\xFF\xD8\xFF\xE1\x00TExif\x00\x00MM\x00*\x00\x00\x00\b\x00\x03\x012\x00\x02\x00\x00\x00\x14\x00\x00\x002\x87i\x00\x04\x00\x00\x00\x01\x00\x00\x00F\x01\x12\x00\x03\x00\x00\x00\x01\x00\x01\x00... (中略) ...xD1\x7F\x90\xA2\x85{\x14\x7F\xFF\xD9\r\n------WebKitFormBoundarypUNpzkXNsa1wjYql--\r\n
全然違いますね。文章力がなくてあれなんですけど、これ気づいたときすごい面白くてはしゃいで走り回りそうでした。
普段はFormDataオブジェクトを使うか、Androidやってるときもライブラリを使うので意識したことがなかったので、なるほどーという感じでした。
仕事の進め方としては、途中でしばらくhexdumpでバイナリの最初の方を読んでたけど、どう考えても一気に下のレイヤーまで遡り過ぎであり、先にHTTPのリクエストボディを読んだ方が筋が良かったので反省...。
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:
よさそうですね。
仕事で導入するのはやや怖いところもありますが、興味が出たので調べてみた結果はこんな感じです。
現場からは以上です。
参考
- https://docs.ruby-lang.org/ja/latest/library/irb.html
- https://docs.ruby-lang.org/ja/2.0.0/method/IRB=3a=3aINSPECTORS/s/def_inspector.html
- http://d.hatena.ne.jp/iww/20090701/printf
*1:ログは長いけれど、作業時間は短いのでやり直したほうが正確っぽい
OmniAuth(omniauth-twitter)での認証が401 Authorization Required invalid_credentialsで失敗する
OmniAuth系のライブラリでTwitterやFacebookなどのサービスと連携しようとしたところ、401 Authorization Required invalid_credentials
でひたすら失敗してつらかったです。
特にTwitterのAPIには、401でないエラーを401で返すという話もあり*1、結局関連するソフトウェアや設定を一つずつ検討することになりました。。
これを忘れて繰り返すと非常に辛いので、この場で一つずつ簡潔にまとめておきます。
検討事項
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の値を確認を回避
- https://github.com/intridea/omniauth-oauth2/issues/32
- https://github.com/tdiary/tdiary-core/issues/494
を参考に request.params["state"] != session.delete("omniauth.state")
の判定をそもそも呼ばないようにしてみました。
memcachedのstats
sessionに利用してるmemcachedの利用出来るバイト数などは問題ないか、何かデータがうまく取れてないかなということで、memcachedでstats
を確認してみました。
あんまりよくわかっていないので、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
詳しいオプションについては
- http://blog.nomadscafe.jp/2013/12/memcached-2.html
- http://gihyo.jp/dev/feature/01/memcached/0001?page=2
telnetでmemcachedサーバに接続
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 // 省略してますが、パスワードの入力あり
参考
SPFってなんですか?
お仕事でSPFについて確認したのでブログにもメモを置きます。
参考: http://salt.iajapan.org/wpmu/anti_spam/admin/tech/explanation/spf/ 他
SPFについて
- メールの送信元の詐称を防ぐための認証技術
- 他に似た目的で「DomainKeys Identified Mail(DKIM)」、「Sender ID」など
SPFでメールを認証するとは
SMTPでメールを送信するときはメールの送信元に関する下記の3つのドメイン(あるいはドメインのIP)はすべて違っても良いそうです。
- a1. SMTPでメールサーバに接続してメールを送るという意味で、メール(SMTPが動いてるサーバ?)を送り出すサーバのIP
cf. example.jp は 192.0.2.1
- a2. SMTPの
MAIL 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には入っているメールを受け取ったメールサーバは、ドメインを見て、DNSに
example.org
のSPFレコードを問い合わせる - b3. b2の問い合わせの結果に、メールの送信元のサーバのIPが含まれていたら認証成功
認証に失敗したらどうなるの?
- SPFレコードの登録がなくて失敗した ... 認証結果: None
- 受け取り拒否にならず、迷惑メールフィルタでフィルタリングされるのに任せる場合が多いが、途中で受け取り拒否になる可能性もある
- SPFレコードの登録があって失敗した ... 認証結果: Fail
RFCにおいてはこの結果に従って通信中のSMTPセッションにおいて該当メールの受信拒否を行う場合、550番エラーの利用を勧めている
とあるので受け取り拒否になる可能性高そう
他にもありますが、参考元をご確認ください。
SPFでメールを送れなくなる理由
メールを送信するメールサーバのIPアドレスが、SMTPのMAIL FROM
コマンドの引数となる送信者のメールアドレスのドメインのSPFレコードに入っていない?