Note

3年後の自分のために書いています

WebRTC + Firebase のチュートリアルやってみた

See. https://webrtc.org/getting-started/firebase-rtc-codelab

ほぼ写経しただけ、👆 のチュートリアルの通りに書いても動かなかったので、サンプルリポジトリ の方を参考にした。

成果物

ブラウザで動くシンプルな 1 対 1 のビデオ通話アプリケーション。 チュートリアルではローカルでの動作確認までしか書いてないけど、せっかくなので Firebase Hosting にデプロイまでした。

使い方

  1. Open camera & microphone でカメラとマイクを許可
  2. Create room を押下すると Room ID が発行される
  3. 別の PC で Join room を押下し、Room ID を入力すると通話ができる
  4. Hang up を押下すると通話終了し、その Room ID は使えなくなる

同一 LAN 内じゃないと動作しなかった :sob:

いろいろ試した結果以下のような結果になった。

最初は OS やブラウザのせいかと思っていたが、途中からもしかしてネットワーク周りが原因では?と思い、最終的に家用 Wifi ルータとモバイルルータ使って LAN を分けて試した結果、Mac + Chrome x Mac + Chrome 環境でも LAN が違うと通信できないことが分かった。

現状 Google が提供している STUN サーバなるものを利用しているが、LAN 外のホストと通信するには TURN サーバなるものを用意しないといけないかもしれない。

ref. そして壁の向こうへ。 NAT/Firewallを越えて通信しよう―WebRTC入門2016

STUNでNATを越えられないとき

NATにはグローバルIPアドレスを共有するだけでなく、セキュリティ対策としての役割もあります。内部の端末を隠したり、通信できるポートを制限したり、一種の簡易Firewallとして利用されているケースもあります。その場合はFirewallの場合と同じく、次に説明するTURNを利用する必要があります。

またNATの構造によっては、接続先によって(今回の場合、STUNサーバーとPeer-to-Peerの通信相手)別のポートが割り当てられる Symmetric NAT という物があるようです。この場合もSTUNの仕組みでは通信することができません。やはりTURNの出番ということになります。

メモ

  • PC(Mac)同士だと動いたけどスマホiOS)では相手の画面が表示されない不具合が出た
  • Firestore は Room ID と offer の管理に使ってるだけ、あとはほぼ JS から呼べる MediaDevices と WebRTC の API で実装できてる
  • ICE と SDP あとで調べる

参考

gem 'her' コードリーディング

と言いつつ途中から依存してる 'faraday' のコードリーディングがメインになった。

her 側で faraday_options 引数として渡した Hash を faraday 側では url という名前で扱っており、混乱したので記事にしておく。

バージョン

  • her (1.1.0)
  • faraday (0.17.3)

url のみ渡して setup

以下を実行した時に何が起こるか。

require 'her'
Her::API.setup url: "https://api.example.com"

Her::API.setup -> Her::API#initialize -> Her::API#setup と処理が流れていく。

Her::API#setup 内で Faraday.new する時 の実引数 faraday_options{ url: "https://api.example.com" } という Hash になる。

Faraday.new の時には実引数 faraday_options は仮引数 url として渡ってくる、第二引数 options は渡してないので nil になる。(ここの名前の紛らわしさで混乱した)

Faraday.new 内の Faraday::Connection.new で呼ばれた Faraday::Connection#initialize の仮引数 url は Hash なので、ここ で値を抜き取られて url 変数にセット、最終的には #url_prefix= で url_prefix にセットされる。(呼び出し側のここインスタンスメソッドの呼び出しを url_prefix= とするとローカル変数への代入になってしまうので self.url_prefix= としている。)

戻り値を her_api とすると her_api.connectin.url_prefix のようにアクセスできる。

url の他にもオプション渡して setup

README#ssl を参考に。

require 'her'
Her::API.setup url: "https://api.example.com", ssl: { ca_path: "/usr/lib/ssl/certs" }

この場合 Faraday.new の仮引数 url{ url: "https://api.example.com", ssl: { ca_path: "/usr/lib/ssl/certs" } } } のような Hash となる。options は引き続き nil、さあこれでも動くのか。

結論からいうと渡ってきた url が Hash の場合は ここoptions に代入してオプションとしても扱えるようにしてる。わお。

一応 コメントにも書いてあるのね。わお。

Can also be the options Hash.

ぶっちゃけここの柔軟性は不要だと思うけど、歴史的経緯があるのかな。

メモ

  • 仮引数が定義側、実引数が呼び出し側
  • faraday は Faraday::Options で Hash を Struct に変換してメソッド呼び出しできるようにしてるの面白かった

参考

gem 'pg' コードリーディング

なぜだか急に pg gem の実装が気になった。

# コネクション張って SELECT するだけのコード
require 'pg'

conn = PG.connect(dbname: 'hoge')
conn.exec('SELECT * FROM fuga') do |result|
  result.each do |row|
    puts row
  end
end

👆 のコードを実行した時に何が起こっているかを追う。

バージョン

pg 1.2.3

PG.connect

PG.connect の定義は lib/pg.rb にある

はいはい PG::Connectionインスタンスを返してるのね。って PG::Connection クラス 見てみたらインスタンスメソッド一つもないし、example コードPG::Connection.open も定義されてない。

ここでハマってしまったが、結論から言うと全て ext/ 以下の C 言語のコードで定義されていた。

実装的には ext/pg_connection.c にあった。

まず .open 含むいくつかの特異メソッドは .new の alias だった

alias 張るのに使われてる SINGLETON_ALIAS は自前で定義したマクロだった。(内部的にはrb_define_alias 使ってる)

alias 張られてるクラスの rb_cPGconnPG::Connection なのではと思って読んでたら、ビンゴ。

rb_cPGconn 変数代入してるとこ 見ると、 rb_define_class_underrb_mPG に所属する定数として定義されていて、rb_mPG 変数代入してるとこ(C 拡張の初期化時に呼ばれる)を見ると、rb_define_module でモジュール PG として定義されていた。

で initialize の処理は rb_define_methodpgconn_init として定義されていた。

pgconn_init これが読みたかったんだよう 😭(ドキュメントもバッチリ書いてある…)

とりあえずいろいろやって conn オブジェクトを返してるね。(ここまで読んで満足してる)

conn.exec

ここpgconn_async_exec として定義されてる。

pgconn_async_exec これが読みたかry

大事そうな処理としては postgres にクエリ投げてるっぽい pgconn_send_query と非同期に結果取得してるっぽい pgconn_get_last_result かな。

pgconn_get_last_resultPG::Result が返る。(PG::Result の定義は ここ っぽいけど力尽きました)

所感

  • 途中から PostgreSQL とか pg gem とかどうでもよくなってた
  • ruby の C API の読み方がちょっと分かって良かった

参考

Docker 学習ログ

Docker 分からなさすぎるのでコツコツと学ぶ。 間違ってるところは随時直していく。

利用するメリット

  • 開発環境やテスト環境が構築・配布しやすくなる
    • Docker を実行できる環境さえあれば OS の違いも吸収する
  • VM(仮想化 OS)よりも起動が速い
    • VM(仮想化 OS)は OS 部分から仮想化する(VM の数だけゲスト OS が必要)のに対し、Docker は OS のカーネル部分はホスト OS のものを共有して利用するため

ソースコード

ref. 「Moby」ベースとなったオープンソース版Dockerの最新状況

バージョニング

https://www.publickey1.jp/blog/17/docker_v1703.html

前回のバージョンはDocker 1.13でしたが、今回からバージョン番号の制度が変更になり、バージョン番号は「年2桁.月2桁.リビジョン」となり…

2020 年 4 月現在の最新バージョンが 19.03.8 なので 👆ともちょっとずれてそうだけど…

基礎技術

Docker Engine
---
Linux VM(Docker for Mac に同梱) ← これがホスト OS の役割を果たす
---
macOS

コンポーネント

Docker Engine

  • クライアント(CLI
  • サーバ(Docker デーモン)
  • クライアントからコマンド叩くことでローカルやリモートの Docker デーモンと通信するアーキテクチャになっている

Docker Compose

  • Docker Engine とは独立したソフトウェア(Docker for Mac だと同梱されてるけど)
  • yaml ファイル一つで複数コンテナを定義できる
  • 環境変数、ポート設定なども yaml に記述
  • 同じ compose ファイル内であれば自動的に同じネットワークに繋がるので内部的なポート設定は不要

イメージとは

  • オブジェクト指向でいうとクラス
  • Dockerfile からビルドされるやつ($ docker build
  • Registry(DockerHub)を使ってイメージそのものを共有可能

コンテナとは

ref. https://stackoverflow.com/questions/23735149/what-is-the-difference-between-a-docker-image-and-a-container

ボリュームとは

  • データを永続化できる場所で、コンテナから見ると外部 HDD のようなイメージ
    • ボリュームを使わないとコンテナ破棄時にコンテナ内のデータは消える
    • DB(Postgres や MySQL)のコンテナを利用する時などによく利用する
  • 実体はホスト(Docker が動いてるコンピュータ)にファイルとして保存されている
    • 名前付きボリュームの場合は、Docker 内のリソースとして管理されるので正確な保存場所は分からないっぽい?
    • 名前つけずにパス指定した場合はそのパスにファイルが作られる
  • Docker におけるデータ永続化の方法としては バインドマウント なるものもあるらしいが、ボリュームの方が推奨されてるっぽい
  • また docker-compose では COPY や ADD が使えないが、volumes を使って代用できる
    • 👆 ができるのはボリュームには以下の仕様があるため
  • 参考

Dockerfile

docker-compose.yml

  • volumes
    • https://docs.docker.com/compose/compose-file/#volumes
    • https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/#volumes (非公式っぽい日本語訳ドキュメント)
    • SOURCE:TARGETの形式で指定して、SOURCE にはホスト側のパスまたはボリューム名を指定、TARGET にはボリュームをマウントするコンテナのパスを指定する
      • 例: namedvolume:/var/lib/mysql, ./cache:/tmp/cache
    • 名前付きボリュームを使うにはトップ・レベルの volumes キーを指定する必要がある
    • たまに見かける SOURCE:TARGET:cached などの cached オプションはパフォーマンス向上のためのオプションっぽいが、最新の公式ドキュメントには記載が見当たらない

コマンド

その他

  • コンテナ削除しても、イメージ削除しても上手く動かない… :sob: って時はボリューム削除してみるべし
  • docker-compose.yml で image を指定しておくと、ローカルに同名イメージがあればそちらを使ってくれる
    •   services:
          web:
              build: .
              image: shopify-app
              # ...
      
    • 普通に image に指定してある名前(ここでいうと shopify-app)でタグ付けしてやれば同名の image を指定している service 全てで同じイメージを参照する挙動だった(_{service} はつけてもつけなくても動く)、、賢い :sob:
  • docker-compose.yml のボリュームマウント時に一部のディレクトリだけマウントしない方法
    • マウント後に無名ボリュームで一部のディレクトリだけ再マウントしてやれば OK

参考

非効率なコードレビューをなくしたいというポエムを書くためのメモ

前提

  • 現在 Web 業界で主流(?)な 100 % PR を完成させてから(マージできる状態にしてから)In Review にするやり方は非効率
  • PR はざっくりできた 〜 In Review までがけっこう作業ある(commit 粒度の確認、YARD などのコメント、GitHub の 0 コメ、動作確認手順記載)
  • レビュー時に設計レベルのちゃぶ台返しをされた時に時間的無駄や、精神的疲労が蓄積される
  • 👆 のしんどさは主にジュニア~メンバークラスの人々が感じることが多く、シニアになると指摘される数が減り修正も早いのであまり危機意識を感じなくなるのではないか(だから自分が考えてみる)

こうした方がよいのではないかという案

  • 新機能や広範囲に渡るリファクタリングはモブ設計(クラスやインターフェースの設計)を行い、可能ならばテストまで書く
  • 実装は一人に任せてもよいし、新規の外部 API などを叩くのであれば、モブで続けてもよい
  • レビューは実装でバグやパフォーマンス的に問題になる部分のみする(N+1、O(n2) など)、基本は動いていれば OK
  • 改行などの指摘は linter が通っていればスルーする
    • どうしても気になるならばレビュアーが別途 PR を作る or 「どうしても気に入らなかったら revert してください」と commit してしまう
  • typoGitHub の suggestion を使う or 自明ならばレビュアーが commit
  • コンフリクトも自明ならばレビュアーが commit しても良い
  • どうしても設計レベルの指摘を行う場合は代替の PR もしくはペアプログラミングで修正する

メモ

  • どこまでモブでやるかの線引き難しい、どこまでが設計でどこから実装なのかはっきりさせたい(例: 関数名を考えるのは設計か?引数と返り値を考えるのは実装か?)
  • 設計部分をモブでやることでジュニアの自力で考える力がつかないのではないか?
    • 自力で叩き台まで作って PR などにして相談する、という時も必要だと思う、その際も 100 % 作りきる前に相談するのが良い
    • また効率的なレビューをした結果、時間が空くはずなのでその分を自学に充てるのもあり

触発された記事とか

Pull Request レビューの限界|qsona|note

むしろ、根底に関わるような重要な設計相談はそれ以前に会話なりモブプログラミングなりですべき

コードレビューで指摘するたびにいちいちイライラされるプログラマーには致命的なバグでもない限り何も言わないのが正解だと思いますか? - Quora

動いているなら何も言わないのが正解のひとつと思います。 また、かわりに自分が直してあげたり、ペアプログラミングした方がいいでしょう。

【コードメモ】JS で Object を forEach する方法

$ node
> let obj = { hoge: "hogehoge", fuga: "fugafuga" }
> Object.keys(obj).forEach(key => console.log(key, obj[key]))
hoge hogehoge
fuga fugafuga

参考

How to loop through a plain JavaScript object with the objects as members? - Stack Overflow

Object.keys() - JavaScript | MDN

【コードメモ】FizzBuzz 別バージョン

1から100までの数字を標準出力に改行つきで出力するプログラムを作ってください。 3の倍数と3がつく数字の時だけ、数字のあとに!をつけてください。

出力例: 1 2 3! 4 5 6! 7 8 9! 10 11 12! 13! 14 ...以下100まで続く

コンソールで実行しやすいようにワンライナーで。

Ruby

$ irb

> (1..100).each { |n| puts ((n % 3).zero? || n.to_s.include?('3')) ? "#{n}!" : n.to_s }
# or
> 1.step(100) { |n| puts ((n % 3).zero? || n.to_s.include?('3')) ? "#{n}!" : n.to_s }

JavaScript

$ node

> for (let n = 1; n <= 100; n++) { let v = (n % 3 === 0 ||  n.toString().includes("3")) ? `${n}!` : n.toString(); console.log(v) }