動かざることバグの如し

近づきたいよ 君の理想に

ExpressでHTTPリクエストが中断されたのを検知したい

Expressでよく使われてるreq, resの中身はなにか - 動かざることバグの如しの続き

環境

  • Nodejs 10
  • Express 4.x

やりたいこと

例えば以下のようなExpressを用いたサーバーのコードがあったとする

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  setTimeout( () => {
    console.log('5秒経過しました');
    res.send('Hello World!');
  }, 5000);
});

app.listen(3000, () => console.log('Example app listening on port 3000!'));

これを実行して http://localhost:3000 を開けば、なるほど確かに5秒後にターミナルに「5秒経過しました」の文字が表示されてブラウザでは「Hello World」が表示される。

がここで問題があって、5秒経過する前にリクエストを中止してしまっても、実行中の処理はそのまま実行されてしまう

例えば5秒の間にChromeではエスケープキーとか、curlだとCtrl + Cで中止するとレスポンスを返す事なく終わる。本来であればその時点で処理も中断され、5秒後の「5秒経過しました」は表示されなくなるようにしたい。

いや別に文字表示なら大したことないが、バッチ処理とか重いクエリを実行する必要があるとかなら話は別である。

対応した結果

以下が動作するコード

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  const timer = setTimeout( () => {
    console.log('5秒経過しました');
    res.send('Hello World!');
  }, 5000);

  req.on('close', function() {
    console.log('close');
    clearTimeout(timer);
  });
});

app.listen(3000, () => console.log('Example app listening on port 3000!'));

試してみるとわかるが、「5秒経過しました」は表示されなくなる。

ポイントはやはり req.on('close')で、さっきの記事でIncomingMessageにcloseイベントリスナーがあることを知って使った。abortedでも可能のようだが、正直abortedとcloseでどっちを使うべきかはよくわかっていない。。。

Expressでよく使われてるreq, resの中身はなにか

環境

  • Nodejs 10
  • Express 4.x

結論

で、nodejsの標準のオブジェクトで別にExpress特有のものではなさそう

経緯

よく見かけるNodejsのHTTPフレームワークことExpressのHello Worldのサンプル

const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(3000, () => console.log('Example app listening on port 3000!'))

Expressではreq, resが必ずと言っていいほど入っているが、その実体は何?って話になった。

console.log(req.constructor.name);
console.log(res.constructor.name);

で調べてみると、

IncomingMessage
ServerResponse

とでた。Expressのドキュメント探しても見当たらないなーとか思ってたら、どうやらNodejs標準のオブジェクトだったらしい

確かに https://nodejs.org/docs/latest-v10.x/api/http.html#http_message_httpversion を見て

console.log(req.httpVersion)

とかすると 1.1 と表示される

ソース

ちなみにExpress 4.xの公式ドキュメントにて

The req object is an enhanced version of Node’s own request object and supports all built-in fields and methods.

と実は書いてあったりする。いや小さすぎてわからんわ。。。

参考リンク

Rubyでattr_accessorを動的に追加したい

Rubyなんもわからん

環境

経緯

例えば以下のようなUserクラスがあったとする。ここでは検証のために attr_reader ではなく attr_accessor を使っているのでご了承

class User

  attr_accessor :name, :age

  def initialize
    @name = "太郎"
    @age = 20
  end

  def name
    "#{@name}さん"
  end

end

この状態で、以下を出力実行

u = User.new
puts u.name # => 太郎さん
puts u.age # => 20

まぁ納得である。

ここでは attr_accessor :name, :age と予め書いたが、このnameとageが変則でインスタンス化されたクラスごとに動的に定義させたい場合はどうすればいいのか

Rubyの大抵は send() を使うと解決するってマックのJKが言ってた。

class User

  def initialize
    self.class.send(:attr_accessor, "name")
    self.instance_variable_set("@name", "太郎")
    self.class.send(:attr_accessor, "age")
    self.instance_variable_set("@age", 20)
  end

  def name
    "#{@name}さん"
  end
end

一見動きそうだが、動かない 実際に試してみると

u = User.new
puts u.name # 太郎 (????
puts u.age # 20

おかしい。インスタンス変数で定義したのが反映されれば本来「太郎さん」になるはず

おそらく、attr_accessorでnameのセッターが自動で定義されるので、 def name が上書きされてしまったんじゃないか。。。

変数をセットする側の self.instance_variable_set("@name", "太郎")@name = "太郎"self.send("name=", "太郎") に変更しても変わらずだった。。

うまく行った例

instance_variable_set("@#{k}", v)
unless respond_to? k
  define_singleton_method(k) { instance_variable_get("@#{k}") }
end

完成サンプルも載せとく

# 完成版
class User

  def initialize(args = [])
    args.each do |k ,v|
      instance_variable_set("@#{k}", v)
      unless respond_to? k
        define_singleton_method(k) { instance_variable_get("@#{k}") }
      end
    end
  end

  def self.fetchs
    # APIで取得したのを想定
    [
      {name: "太郎", age: 20 },
      {name: "次郎", age: 30 }
    ].map do |hash|
      User.new(hash)
    end
  end

  def name
    "#{@name}さん"
  end

end

User.fetchs.each do |a|
  puts a.name
  puts a.age
end

すると

# 太郎さん
# 20
# 次郎さん
# 30

と表示される。

追記

やはり attr_accessor がゲッターを上書きしてしまってる説は正しかった。以下でもうまく動作する。

class User

  def initialize
    self.class.send(:attr_reader, "name") unless respond_to? "name"
    self.class.send(:attr_writer, "name") unless respond_to? "name="
    self.class.send(:attr_reader, "age") unless respond_to? "age"
    self.class.send(:attr_writer, "age") unless respond_to? "age="
    self.instance_variable_set("@name", "太郎")
    self.instance_variable_set("@age", 20)
  end

  def name
    "#{@name}さん"
  end

end

u = User.new
puts u.name # => 太郎
puts u.age # => 20

書き直したテンプレも一応入れとく

class User

  def initialize(args = [])
    args.each do |k ,v|
      self.class.send(:attr_reader, k) unless respond_to? k
      instance_variable_set("@#{k}", v)
    end
  end

  def self.fetchs
    # APIで取得したのを想定
    [
      {name: "太郎", age: 20 },
      {name: "次郎" }
    ].map do |hash|
      User.new(hash)
    end
  end

  def age
    @age.to_i
  end

end

User.fetchs.each do |a|
  puts a.name
  puts a.age
end

お前らのf.radio_buttonとf.labelは間違っている

環境

経緯

Rails生涯費用シミュレーターというのを作っていた。

で、form_withで作ったフォームの中にラジオボタンを作りたかったのだが、これがなかなかうまくできない

先にゴールの生成されるべきHTMLを示す。

<input type="radio" value="month" checked="checked" name="cost[pay_type]" id="cost_pay_type_month">
<label for="cost_pay_type_month">月額払い</label>

ラジオボタンは地味に厄介で、input[type=radio]要素のid属性とlabel要素のfor属性が一致してなければならない

そうしないと、文字をクリックして切り替えできないという面倒な仕様がある。ここでもラジオボタンの「id=cost_pay_type_month」とラベルタグの「for=cost_pay_type_month」が同じなのがわかる。

悲しいことにググっても中々答えにたどり着けなかったのでメモ

方法1(うまくいく例)

Railsなんて知らねえ!ってことで labelタグを直書きする。

<label>
<%= f.radio_button :pay_type, "month", checked: true %>月額払い
</label>

仕様とは異なるが、これでもラジオボタン自体を囲っているので文字をクリックしても反応する。悪くはないけど、i18n対応とか考えると明らかにレールに乗ってない記述である。あとダサい

方法2(うまくいかない例)

f.labelを使ってみる。がこれは失敗例 どうもググるvalueで文字列を渡すといいらしい。

<%= f.label :pay_type, :month, value: "月額払い" %>

で、生成されたLabelタグが以下

<label for="cost_pay_type_月額払い">month</label>

は???なんでfor属性に日本語が入ってるんだ???ってことで却下

方法3(うまくいかない例)

オーケーオーケー、どうやらvalueを指定するとfor属性のサフィックスに使われるらしい。ってことで monthを指定してみる。

<%= f.label :pay_type, :month, value: :month %>
<label for="cost_pay_type_month">month</label>

いけたやん!っと換気したのもつかの間、Labelタグ内の文字列を変更できないのである。けどタグ内の文字列を変更するにはvalue指定するしか、、とジレンマが発生する

方法4(うまくいく例)

キレた。forを直接書く

<%= f.label :pay_type, "月額払い", for: "cost_pay_type_month" %>

これでも以下のようなちゃんとラジオボタン用のラベルが生成される。

<label for="cost_pay_type_month">月額払い</label>

うーん、けどなんか違うんだよなぁ

方法5(最適解)

色々調べた結果、Railsのレールに一番乗った書き方は以下

<%= f.radio_button :pay_type, :month %>
<%= f.label :pay_type_month, "月額払い" %>

するとちゃんと以下のようなHTMLが生成される。

<input type="radio" value="month" name="cost[pay_type]" id="cost_pay_type_month">
<label for="cost_pay_type_month">月額払い</label>

おおー、あれ結局valueの指定はいらなかったの????

ずばり、

<%= f.radio_button ①, ② %>

① + ② の値を f.label の第1引数に渡せば良い。(_で連結させて

これ得るまで長かった、、、てかRailsのソース内のドキュメントにもlabelタグの書き方なかったし、なんなんだ、、

参考サイト

GCP Cloud SQLでIP制限をなんとかする

経緯

Googleが出しているMySQLライクなDBクラウドサービスこと、Cloud SQLがあるが、IP制限が必須である。

もちろんセキュリティ的には重要なのだが、自宅のIPがしょっちゅう変わる身としては毎回設定し直さないといけないので面倒だったりする。

仕方ないなぁと思ってたらどうやら公式で出しているプロキシ機能を使うと簡単にIP制限を回避できるらしい ってことで試してみた

準備

認証

当然どのサーバーからも接続させるわけにはいかないので何らかで許可されたものか確認を担保する必要がある。Cloud SQLではgcloudコマンドで認証できる

すでにインストール&認証済みならスキップ

Macなら以下でインストールできる

curl https://sdk.cloud.google.com | bash

で、初回のみ認証

gcloud auth login

プロキシのインストール

curl -L https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64 > /usr/local/bin/cloud_sql_proxy

M1Macの場合

curl -L https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.arm64 /usr/local/bin/cloud_sql_proxy

実行権限の付加

chmod +x /usr/local/bin/cloud_sql_proxy

これで cloud_sql_proxy -h とかするとコマンドが叩けることが確認できるはず

やり方

プロキシを起動する際は以下のコマンド

cloud_sql_proxy -dir ~/ -instances <プロジェクトID>:<リージョン>:<データベースのインスタンスID>

以下のように表示されれば成功 Macだと /Users/thr3a/my-project:asia-northeast1:my-database にソケットファイルが作成されている

2019/10/15 08:36:53 current FDs rlimit set to 12800, wanted limit is 8500. Nothing to do here.
2019/10/15 08:36:54 Listening on /Users/thr3a/my-project:asia-northeast1:my-database for my-project:asia-northeast1:my-database
2019/10/15 08:36:54 Ready for new connections

で、接続 ここではSequel Proでの設定例を教示

コマンドで接続したい場合はソケットを -S で指定する。

mysql -u thr3a -p -S /Users/thr3a/my-project:asia-northeast1:my-database

あとはSQLの接続前にこのプロキシを起動しておけば、どこからでもSQLに接続できる やったね

参考サイト