@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Rack::Proxy でプロキシ認証が必要な forward proxy の習作

習作なので RFC を読んでいません。実用しないほうがよいです(僕も実用してないです) Squid などを使いましょう。

https://github.com/kyanny/playground/tree/gh-pages/rack-proxy-auth

仕事で IP アドレス制限のかかった API と HTTP でやり取りするために forward proxy を使う必要がありそうで、 curl の -x オプションと -U オプションを使うことを知り、実際それでうまく動いたのだが、プロキシ認証というものを初めて使ったのでいろいろ気になることがでてきて、 Squid の設定とかせずにプロキシ認証の動作検証(主にクライアント側が正しくプロキシサーバを利用できているかの検証)ができないかと思って Rack で書いてみた。

Rack::Proxy はこの記事で書いたようにリクエストとレスポンスを加工するのが目的なので、プロキシ認証の機構は組み込まれてない。そこで call を上書きして、クライアントが送ってきたプロキシ認証の情報をリクエストヘッダから取り出し、正しい認証情報でなかったら 407 Proxy Authentication Required を返す、としてみた。実験用なのでいろいろ決め打ち実装。

最初は何を思ったのか rewrite_env の中で認証情報のチェックをし、 NG だったら Rack::Proxy がリクエストを送り直す処理をする perform_request メソッドを差し替える、という変なことをしていた 6404fe2b1c7ad93302f032d61d86b74df8d9012c 差し替えちゃうと一度でも認証失敗した場合に以後正しい認証情報つきでリクエストしてもずっと 407 を返してしまうので差し戻したりと、謎に謎を重ねる感じになっていた。

これと直接関係ないが、 Ruby プログラムから子プロセスとして Rack アプリケーションを起動し、その HTTP サーバに対してリクエストを送ったりしたあとちゃんと kill して自分自身も終了する、というのがなかなかうまく書けなくて苦労したが、そのおかげで Ruby でのプロセス操作についていい復習になった。 Working With Unix Processes 読んだけど身についてなかったみたいでちょっと悲しい。

最近思ったこと

ユニットテストの話題読んだ。

テストを書くか書かないかの判断の話 · GitHub

フロントエンドに秩序を取り戻す方法 // Speaker Deck

仕事でよくコード書くアプリケーションが五個か六個くらいあって、三個は CoffeeScript と Marionette.js でフロントエンドをほぼ全部書いてるシングルページアプリケーションみたいなやつで、二個はフロントエンド部分と JSON API 部分が一個の Rails アプリケーションのリポジトリ内にそれぞれ同居してて、残りの一個はフロントエンドのみで JSON API は別アプリケーションになってる。

シングルページアプリケーションみたいなやつはユニットテストけっこう頑張って書いてて、フロントエンドのみのやつが 2152 passing (54s) 1 pending 1 failing で、 Rails と同居してるやつが 1866 examples, 2 failed, 0 pending と 873 examples, 0 failures, 1 pending だった(失敗してるやつはタイムゾーン絡みでイギリス人の手元だと成功するけど日本人の手元だと日付が変わって一日の半分で落ちる、みたいなやつ)

コードの意味ある行数とかカバレッジとか測ってないけど、体感で C0 70% くらいはいってると思う。ただテスト書くときにいつも迷いがあって、イベントハンドラとして実行される関数に三行実装があるとその一行の副作用ごとに一個ずつテストケース分けて書いたりしてて、詳細すぎる気がする。いちステップごとにテストしていく感じなので、なんか自分がデバッガか何かになってソースコードを一行ずつ検査してるような気持ちになってくる。

ソースコードを一行ずつ検査すること自体はよいと思うけど、人間がそんなローレベルな仕事してはいけないと思う。ハイレベルな表現で記述したらソフトウェアがローレベルな処理に翻訳・展開してソースコードを細かい粒度で検査してくれるべきだと思う。テストフレームワークの語彙とかでうまく吸収して欲しいし、たぶんできるはずなんだけど、なぜか自分(たち)がテスト書いてると職人が丹精込めて手打ちしたローレベルな検査コードです、みたいになってしまってる気がする。

CoffeeScript で書いたものを JavaScript にコンパイルしてさらに圧縮してるからものすごく読みづらいけど、サーバサイドと違ってフロントエンドは基本的に動いてるソフトウェアのソースコード全部読める。本体のソースコードが読み放題なんだからそれのテストコードも読み放題でもいいのではと思う。そうしたらテストコードを社外の識者に自由に読んでもらって、この書き方いけてないですよとか、こういうテストはこういう考え方で書けば記述量少なくても意味ある内容にできるとか、アドバイスもらえる。そういうアドバイスが欲しい。いまの自分(たち)はこういうやり方に慣れすぎてあんまり疑問もなくて、それはいいことではないと思うけど、斬新な考え方とか思いつける気がしない。

就職まではしないけどちょっとソースコード読んだり会社とチームの雰囲気知ったりするのにインターンとかいいと思うけど、あんまりインターンで短期に週二とかで働ける人いないと思う。日本だとだいたい実力あってソースコード読んでみて欲しい人はフルタイムの会社員だったりして、こっちはインターンで来てくれたら嬉しいけど相手にあんまりメリットがない。仕事辞めて次探してる無職のときとかは便利かもしれないけど、普通に働きながらインターンもやるのは厳しいと思う。インターンにすらならなくていいからソースコードだけ読んで気が向いたらコメントしてもらえるようになると、ソースコード読みたい人は楽しみが増えるし、アドバイス欲しい側は助かるので一石二鳥だと思う。

テストの話、 Ruby とかだとユニットテストは書くのが当たり前で、エンドツーエンドテストもだいたい多少は書くものと思われていて、テストの質とか、一歩進んだ内容について議論していると思う。フロントエンドの場合だとそもそもテスト書くのがまだ当たり前じゃなくて、書いたほうがいいとわかってるけど難しくてうまくいかない、みんな知見がたまってないのでそれ以上の議論にならない、みたいなところが多いと思う。十年前のウェブアプリケーションがそういう感じだったので十年後くらいにはフロントエンドでも知見がたまって踏み込んだ内容の議論が出てきて今の悩みも解決しそうだけど、十年も悩みながらテスト書きたくないので困る。

RIP

友人が亡くなった。

訃報はいつだって急なものだけど、その人はいかにも健康そうで病と無縁に思えたので、意外すぎてとても驚いた。病気で入院するとの知らせをうけてからわずか一月余りの出来事だった。いまだに信じられない。

おれは薄情な人間だから、見知らぬ人の身に不幸があってもお悔やみなんて言わないし、いい人だと思っていない人のことを「いい人だ」とうそぶいたりもしない。けれどもその人は掛け値なしにいい人だったし、その人がこの世を去ってしまったことが本当に悲しい。

友よ、どうか、安らかに眠れ。

Rack::Proxy で forward proxy

https://github.com/kyanny/playground/tree/gh-pages/rack-proxy

rack-proxy はサブクラスで rewrite_envrewrite_response をオーバーライドするのが作法(そこでなにかおもしろいことをする) rewrite_env は outgoing request を加工するフックで rewrite_response は incoming response を加工するフック。

require 'rack-proxy'
require 'pp'

class MyProxy < Rack::Proxy
  def rewrite_env env
    pp env
    env
  end

  def rewrite_response triplet
    pp triplet
    triplet
  end
end

run MyProxy.new
$ rackup
$ curl --proxy http://localhost:9292 http://example.com

rackup なしで Rack アプリケーションを起動するには Rack::Server.start を使う

https://github.com/kyanny/playground/blob/gh-pages/rack-server-start/app.rb

require 'rack'
Rack::Server.start(
  app: ->(env) { [200, {}, ["OK\n"]] },
  Port: 9292,
  environment: 'development'
)

rackup の中身も単に Rack::Server.start を呼び出してるだけだった。 ARGV のパースやもろもろのデフォルト値の設定・解釈などは奥のほうでなされている。