今さら改めてRack middleware

Understanding Rack Middleware

がとても分かりやすかったのでこれをもとにしたいと思う。

Rackの基本構造

  • Rack middleware はたまねぎ構造で rack app が他の rack app をくるんだ形
  • 受け取った request を外側の app が内側の app へバケツリレーし、response を内側の app から外側の app へリレーする
  • この組み立てのために Rack::Builder の use と run を使う

Rackアプリの基本形

  • callメソッドを持っていること

だけ。

call メソッドを持っていれば class だろうが Proc オブジェクトだろうが Method オブジェクトだろうがなんでもよい。

まぁ本当は call メソッドの引数の型と戻り値の型がどうってのはあって、Hash を受け取って Rack::Response を finish して返しなさいってことなんだけど、それは置いておく。

Rack middlewareは二種類ある

  1. バケツリレーする middleware
  2. response を直接返す middleware

どちらも Lint 的には valid なので少しややこしいのと、直接 response を返してるのは middleware と呼ぶのか?という気はするけど、まぁそういうものらしい。

Rack middlewareはclassで

説明は以下に続くんだけど、とにかく

middlewareはアプリと違ってcallメソッドを持っているだけではダメで、classにする

と覚えておくこと。

前者のバケツリレーを実現するたまねぎ構造のために

rack app のところでは call があればよいという話だったんだけど、たまねぎ構造のためには app を受け取ってリレーする必要がある。これをどう実現するのかというと、ちょうどいいサンプルが rack 2.0.7 の

Rack::Builder#use

のコメントに例が書かれているので貼っておく。

#   class Middleware
#     def initialize(app)
#       @app = app
#     end
#
#     def call(env)
#       env["rack.some_header"] = "setting an example"
#       @app.call(env)
#     end
#   end
  • initialize で app を受け取ってどこかのインスタンス変数に保存しておく
  • call で env を受け取って、call の中で保存しておいた app を取り出してそいつの call を呼ぶ

という形になっている。どこでこれが決まっているかというと、use の中のこれ。

@use << proc { |app| middleware.new(app, *args, &block) }

つまり、

use Middleware, *args, &block

と書くと

Middleware.new(app, *args, &block)

で他の app をくるむので、initialize で受け取る処理を書きましょう、となる。

直接返す

直接返すっていうか、直接ではないんだけど、要はこの @app.call(env) の実行がなければ内側の middleware, app は呼び出されないので、外側の middleware だけで処理が完結するという形。

例えば Rack::Attack という middleware があるんだけど、この 6.0.0 の call は以下のようになっている。

 def call(env)
   env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
   request = Rack::Attack::Request.new(env)

   if safelisted?(request)
     @app.call(env)
   elsif blocklisted?(request)
     self.class.blocklisted_response.call(env)
   elsif throttled?(request)
     self.class.throttled_response.call(env)
   else
     tracked?(request)
     @app.call(env)
   end
 end

アクセスを block するのが目的なので、 blocklist に引っかかった場合は中の app は呼ばれず、自分で用意している response を返す形になっている。

順番をあとから制御したい場合はどうしたらいいの?

基本的に rack middleware は use で並べた順番にだんだんと内側に middleware をくるんでいくので、この順番で実行される。

しかし例えば Rails では

middleware.insert 0, Middleware

という書き方で順番を後から制御できる。しかしこの記法、これは Rack の機能じゃないのです。Rails::Configuration::MiddlewareStackProxy ってやつがいい具合にやってくれている。

逆にこの機能に依存せずに request を受け取ってすぐに受け取る何かを作ろうと思ったらどうするか?

そう、Rack は app を middleware でくるめばいいだけなので、config.rb で

run Middleware.new(Rails::Application)

みたいなことをやればよい。なるほどなぁ。

middlewareの中身はこんな感じ

class Middleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    res = Rack::Response.new(body, status, headers)

    res.xxxxx  # なんかやる

    res.finish
  end
end

参考

async/awaitやPromiseで気をつけること

ES2017のasync/awaitのキソ練習 - あーありがち(2018-05-26) のあとにがっつりハマっていたのでそこで得た知見をメモ。

asyncの効果は各functionブロックで切れるので逐一指定が必要

例えば

async foo() {
  return new Promise((resolve, reject) => {
    ..
  })
}

ってコードがあるじゃろ? この Promise の中では await できんのじゃ。正解はこうじゃ。

async foo() {
  return new Promise(async (resolve, reject) => {
    ..
  })
}

Promiseではちゃんとrejectする、rejectしてすぐreturnする

async foo() {
  return new Promise(async (resolve, reject) => {
    const bar = await this.bar()

    if ( !bar ) {
      return reject(new Error('message'))
    }

    resolve(bar)
  })
}

async function は Promise を返すので、通常の function のように値を返すことはできない。成功も返せないし失敗も返せない。

失敗は必ず reject() で教える必要がある。

そしてもう一つ、reject() は単なる function call であり、それが終わったら次に処理が進んでしまう。ちゃんと return して foo() を抜けなければいけない。

resolve ばかりを考えていると UnhandledPromiseRejectionWarning の警告が出まくることになる。ちゃんと reject してあげよう、そしてすぐ抜けよう。

async/awaitで待つコードを閉じ込める

async foo() {
  return new Promise(async (resolve, reject) => {
    let c = {}

    this.bar().forEach((e) => {
      c[e] = await this.baz(e)
    })
  })
}

みたいな、同期処理の中に非同期処理が紛れ込んでいてさらに全体のブロックの中で副作用ベースで変数を組み立てる、みたいなやつは大変あぶない。

async foo() {
  return new Promise(async (resolve, reject) => {
    // 非同期処理のカタマリ

    // 同期処理のカタマリ
  })
}

に分離しよう。

Promise.allで全部待て

上のコードはもっと言うとループの1回目は待つかもしれないけど全部は待ってくれないコードになり得る。そこで実際にやるとしたら Promise.all() を使って以下のような感じになる。

async foo() {
  return new Promise(async (resolve, reject) => {
    // 非同期処理のカタマリ
    const result = await Promise.all(this.bar().map(async (e) => {
      return ..
    })

    // 同期処理のカタマリ
    result.forEach((e) => {
      ..
    })

    ..
  })
}

ここに上で述べた resolve, reject を加えると以下のような感じがいったん完成形と言えそう。

async foo() {
  return new Promise(async (resolve, reject) => {
    // 非同期処理のカタマリ
    const result = await Promise.all(this.bar().map(async (e) => {
      return .. await this.baz() ..
    })

    // 同期処理のカタマリ
    let c = {}

    result.forEach((e) => {
      const [key, value] = e

      .. // 同期処理だけなら副作用ベースでも従来通り大丈夫

      if ( ) {
        return reject(new Error())
      }
    })

    resolve(c)
  })
}

Kanazawa.rb meetup #10に飛び入りしてまたノースライドで喋ってきた

今回は実は以前から分かっていた個人的な事情とさらにやんごとなき事情が重なって、準備の手伝いもしてないし、不参加を最初から決めていたんだけど、どうもこれは企画の重さで大変なことになってないかな?と思い、無理矢理 meetup だけ 参加表明せずに参加してきた。

そう、つまり飲んでないのです。ビールクズの名折れ…。

そこで

  1. Git入門
  2. GitHub入門
  3. Pull-req入門

の話があったんですが、どうもこの 3 の pull-req の話の前に branch の話がこなれてなくて、branch, clone, fork, push, rebase, merge の話がやや渾然一体となった感じで受け止められている気がしたので、以下のような話を飛び入りでしました。

そして以下のような話を。

  • 「branch の正体を知るといいよ」「branch は commit の alias なんだよ!」「???」「branch は名前を付けた何かの commit、その commit に次の commit を重ねれば名前の指す先が変わる」「だから pull-req 用の branch を作って放置するのがベストプラクティス」

結局は Pro Git のこの話

Git - ブランチとは

なんですが。

で、atomic commit の話を少々。

  • 「”明日の自分”を含む他人が読んで分かることが大事」「読みやすく分かりやすい commit こそ受け入れられやすい。pull-req の話は結局それ」
  • 「branch って大げさに聞こえるけど git の (local)branch はとてもお手軽で便利」「だから”あれもこれも branch をうまく使えば実現できるよね?”と気づいたコミュニティのノウハウが多い」
  • 「”今からやる作業の名前を付けた branch”を作るという習慣」「余計な commit を入れにくくなる」「大きな commit は扱いにくい。小さく」

てな感じ。

Kanazawa.rb でがっつり Git の話をしたのは初めてで、今までにない面白さだった。普段から commit 単位とか branch の運用(これは学習機会など様々な要因で考え方が変わる)とか頭を悩ませているので、ここぞとばかりに喋ることができて

個人的には面白かった

んだけど、ちゃんとウケたのかどうか、そう言えばあんまり気にしてなかった…。

どうもすいません、またリベンジしましょう。

RDoc::usage がやっぱりあった。けど。

先日 pragger を眺めていて

またドキュメントのためにコードを丸ごと文字列で持つのは RD とか RDoc をうまく使えば避けられるんじゃないかと思ったけど、よく分かんないorz

なんてことを書いていたけど Pod と同じ発想で探したらあった。

require 'rdoc/usage'
RDoc::usage( [[STATUS,] SECTION_NAME, ...] )

するだけで、起動したスクリプトに書かれている RDoc コメントを表示することができる。

んが。

この rdoc/usage.rb の中身を見ると、gets( file ) 以降は private なので任意のファイルの usage を生成することはできないようになっている。うーむ。まぁ RDoc module をゴニョゴニョして plugin の usage を表示させる方法もなくはないけど、そこまでコスト掛けるなら現状の pragger のように妥協案として

## コメント

行を抽出して表示するってのはアリなのかもなぁ。現状でも一応 rdoc コマンドでドキュメントを生成することはできるんだし。

システムファイルを避けるために別のシステムからデフラグ掛けたらダメなの?

先づ隗より始めよ。1

つーことで昨日の失敗の話。

昨日、Windows のシステムの入ったドライブに対して、最大限の効果が発揮されるデフラグだぁということで別のシステムから立ち上げてデフラグを試みた。

結果、壊れた。

(ただし、未使用なので被害はゼロね。各種セットアップをしたあとでデフラグしたってだけ。もちろん丸ごとコピーも取ってある。2台のディスクで試してどっちもダメだったのでたまたまってことはないと思う。)

ファイルシステムに矛盾が見つかるらしく、起動もできない。調べてみると

のような情報は見つかるが、いずれも別なシステムから立ち上げてシステム入りのドライブをデフラグするという挑戦的な内容は触れられていない。触れられていないのでダメなのかどうかも分からないんだけど、やっぱダメなんですよね。Excel を方眼紙みたいに使うような変な裏技開発してないで売り物を使えということでしょうか。

えー現段階での自分の推測を書いておきます。

  • Windows のシステムには MFT, ページングファイルなどシステムのロックするファイルがあり、これは標準のデフラグでは最適化できない。
  • ロックしないように別のシステムからデフラグを掛けてみたわけだけど、これにはまず前提があって、ページングファイルは別に壊れても作り直せるだろうし、まったく起動しないってことはなかろうと思っていた。
  • MFT は本当に最初のセットアップ後なので断片化が見つかって最適化されて壊れるということはちょっと考えにくい。
  • ちゅーことは何かまだ自分の知らない謎の領域をデフラグでぶち壊してしまったんだろうか?

壊れた状況は

  • デフラグ終了直後からデフラグツールでディスクのレポートを正常に生成できない
  • chkdsk で不明なエラー
  • 何やらファイルシステムに矛盾があると言われる
  • 起動できない2

です。

起動できないだけならまだ分かるんだけど、ファイルシステムに矛盾つーのが分からないんだよなぁ。データだけのドライブでは矛盾は起きないのにシステムの入ったドライブだと矛盾が起きるの? まぁ Windows の出すエラーメッセージを真に受けて悩みすぎるのもよくないんだけど。

以下、言い訳。なんでデフラグにこだわったかというと、sysprep の話にまた戻るんですが、sysprep で展開するシステムのマスタイメージを DVD に保存しようと思ったの。ディスク丸ごとでもセットアップ直後ならほとんどは空き領域だし、圧縮すれば焼けるだろう、と。3ついでにデフラグも掛けておけば圧縮率も上がるし、そうだ、システムファイルなんかもデフラグできたらいいじゃーんと思ったと、こういうわけです。

さらに言い訳。実際には、これは自分のアイディアじゃないです。正直イヤな予感はしてたので。ただ実行に移したのは自分です。

今のところの結論。

システムの入ったドライブはそのシステムで起動してデフラグを掛けろ。最大限の効果が欲しければ売り物のデフラグソフトを使え。

  1. なんか意味が違うような。ただの言い出しっぺの法則だよな、これ。 

  2. エラーメッセージはメモするの忘れました、ごめんなさい。 

  3. 遊んでる HDD がないというのが根本的な理由。 

Mozilla Firefox 0.9

Alt 問題は解決していた。スピードは、Firefox を常用していないので比較できない。

相変わらず設定のダイアログがモーダルで Cookie Manager を見ながらブラウザを動かすことができない、Web 開発的にはダメダメな仕上がりになってますが。これって cookie 周りの動作を確認したければ別な extension を入れろってことか?

CUI なメーラ

自分のマシンを持ち込んだときに職場のデスクトップからメールの情報を取り出しにくいので、Unix の CUI 上で完結するメール環境を作ろうかなと思い立つ。(いや以前から気にはなっていたが。)

  • cmail
    • あのまつもとゆきひろ氏の噛んだプロジェクト。Emacs 上で動作。フォルダの説明のところで言葉が揺れているので、結局1メール1ファイルなのかどうかは不明。できれば検索性などを考えると富豪的に1メール1ファイルであってほしいが。
  • vm-Emacs
    • これも Emacs 上で動く
  • Mew
    • Win32 バイナリもあるな。
  • MH Message Handler
  • mh-plus project
    • 今さら使おうと思っているわけではないが、どんなものなのかは知っておきたい。
  • nmh New Message Handler
    • mh improved ?
  • mutt
  • Cone
    • おや courier にこんなものが。
  • elmo
    • クッキー食べますか?

ソーシャルネットワーキングネタ

そろそろチェックせなあかんかなぁ。

ソーシャルネットワーキング(social networking)整理。3/9

blog もそうだったけど、あんまり興味涌かないんだよな。特に blog はコンテンツが増えていくことに期待できるけど、日本では「面白そうなテーマに本格的にコミットしてる人間の数なんてたかが知れてる」ので、そんなに必要性がないというかなんというか、、、まぁその人がちゃんとしたサイトを用意してなくてもその人の興味、活動をチェックできるって意味ではよいのかなぁ。Web のおかげで細かい情報は拾いやすくなったけど、逆に Web に乗らない情報との流通速度、伝搬範囲の差がものすごくでかくなってるよな。

Web にそれほど興味のない人ほど blog ツール使ってくれ、ってことやろか。

「転職時減収やむなし」で欧米化だ?

転職時に「減収やむを得ず」、労働意識も欧米化のきざし

就業の安定性が低下しただけで、中途入社の敷居の高さや周囲の環境・文化が変わったわけではない。職を移ることへの無用のストレスが日本には潜在的にたくさん存在していることを抜きにして欧米化などと軽々しく言うべきではないだろう。

もう一つ、収入の水準が下がる一方で日本は世界一物価の高い国だということも忘れてはならない。日本人の生活水準は落ちる一方やんけ。ただでさえ家も持てず、持てても「うさぎ小屋」だった日本の生活水準がですよ。

国は分かっとんのかと。

ck と IP Messenger の話

ck は掲示板へ、IP Messenger は ML へそれぞれ要望を提出。

Mac を通じてブラウザを考えよう、、、かな

http://slashdot.jp/comments.pl?sid=100863&cid=337894

ははぁ、なるほど。たまにこういう意見が出ると非常に参考になる。

Mac プラットフォームからデベロッパを締め出しているのはApple ではなくユーザーだと。そう言われればそういうとこ、けっこうあるかもしんないなぁ。

それとは別に msn for OS X の出現で、Microsoft は AOL と同じ方向を向いていて、Apple や Mozilla コミュニティとは正反対に向かっているような気がしている。

ユーザーが望んでいるのはブラウザなのか、統一的なインターネットプラットフォームなのか。

About

例によって個人のなんちゃらです