もちゅろぐ

iOS, Swift, Ruby, Rails, UI/UX design, etc.

正規表現で否定を表現する

常時必要ではないけれど、忘れた頃にやってきて、覚えようにも複雑な正規表現の否定表現。

いっつも検索していろんなサイトにお世話になっているけれど、訪れるサイトはいつもバラバラで、欲しい情報を見つけるのに時間がかかる。そんな毎日なので、自分用に否定表現用の記事を用意しました。

書き方

^(?!.*<ここに検索ワード>).+$

何が起きているのか?

  • ^ : 行の先頭を表す
  • ( : グルーピング
  • ?! : 否定の先読み. 部分式が後にマッチしないなら、真とする。
  • .* : 任意の文字列(0文字以上)
  • ) : グルーピング
  • .+ : 任意の文字列(1文字以上)
  • $: 行の末尾を表す

試してみる

色々と削って動きを見てみることしました。

あいうえお
かきくけこ
あい
あ
い
 いう 

に対して色々試してみる。

正規表現 結果
^(?!あ) 「あ」の入っていない文字列と空改行がヒットする
^(?!あ)$ 空改行のみヒットする
^(?!あい). 「あい」の入っていない行の1文字目と空改行がヒットする
^(?!いう).* 行の先頭に「いう」の入っていない行と空改行がヒットする
^(?!.*いう).* 行に「いう」の入っていない行と空改行がヒットする
^(?!.*いう).+ 行に「いう」の入っていない行がヒットする
^(?!.*いう) 行に「いう」の入っていない行の先頭がヒットする
  • ()は()内の文字を1文字として扱う. セットみたいなイメージ.
  • ?!は否定の先読み、後ろの続く言葉がヒットしないなら真となる
  • (?!.*いう)とは「いう」がないなら真となる、いわば「いう否定セット」みたいなもの

つまり^(?!.*いう).+$とは「いう否定セット」の入っていない1文字以上を検索となる

ngrokで楽にATS無効化せずローカルAPIにHTTPSでアクセスする

ngrok
ngrok

RailsアプリとiOSアプリを同時開発してると、iOSアプリからRailsアプリにアクセスしたらブレークポイントで止めたり、改修してすぐ動かしたりなど、フットワークの軽さが開発生産性に繋がるかと思います。

しかしLINE Botだったり、iOSのATSだったり何かとHTTPSが必須となってくる場面においてローカルAPIでは難しい話です。

そんな開発中にHTTPSが欲しいときに便利なのがngrokというサービスです。

ngrok

ngrok - secure introspectable tunnels to localhost

  • 認証不要(認証もあるけど)
  • ngrokサービスがランダムなHTTPSアクセスできるURLを用意してくれる
  • そのランダムURLにアクセスするとlocalhostフォワーディングしてくれる
  • 数時間立つとURLが無効化する

インストール

$ brew cask info ngrok
ngrok: latest
https://ngrok.com/
/usr/local/Caskroom/ngrok/latest (24.6MB)
From: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/ngrok.rb
==> Name
ngrok
==> Artifacts
ngrok (Binary)

使い方

$ ngrok http 3000localhost:3000のURLが発行されます。

$ ngrok http 3000
ngrok by @inconshreveable                             (Ctrl+C to quit)

Session Status                online
Session Expires               7 hours, 59 minutes
Version                       2.3.34
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://27f4620d.ngrok.io -> http://localhost:3000
Forwarding                    https://27f4620d.ngrok.io -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

この場合、https://27f4620d.ngrok.io というURLが一時的にHTTPSでアクセスできるようになり、アクセスするとhttp://localhost:3000フォワーディングしてくれます。 8時間経過するとURLは無効になります。

仕組み

本来であれば、ngrokはローカルネットワーク上にあるサーバーを遠隔地からアクセスできるようにする外から内へアクセスを可能にするサービスです。

これをローカルAPIサーバーと同一ローカルネットワーク上にあるiPhoneが、一旦外部のURLにアクセスし、再びローカルネットワーク上のAPIサーバーにアクセスすることで、開発中にローカルAPIHTTPSでアクセスする仕組みです。

注意事項

ngrokに通信を介しているため、セキュアなデータなどもngrok側は見えているかと思われます。 しかし、ngrok側はそれを秘匿性を守秘していたとしても、ngrok側が脆弱性があれば、そこから意図せずデータが漏洩することは可能性はあります。

そのため、認証などテストする場合はどうでもいいテストアカウトでやることを紹介する以上、推奨します。つまり自己責任です。

Mac上でRailsとMySQLのサーバー分けたい

開発中はMacRailsアプリを開発してて、「いざ本番環境へデプロイ!完了!」って一息ついた矢先、「なんか重い...」「想定より遅い...」なんて経験ありませんか?

原因の1つとして、一般的な開発環境では1PCにWebアプリとDBアプリが共存しており、これら2点間の通信はUnixドメインソケット方式で通信が行われます。 一方本番環境ではWebアプリとDBアプリそれぞれにサーバーが立っていることが一般的な基本構成で、これら2点間の通信はTCP/IP方式で通信が行われます。 この通信方式の違いにより大きな速度差が発生し、開発環境ではなかった場所にボトルネックが生まれます。

例えば「SELECTクエリーを叩く回数が多くても、index効いてるから大丈夫だろう」と思っていても、WebサーバとDBサーバが分かれていると、二点間の通信時間が底上げされます。

本番環境ではDBとの通信コストの観点が必要なのに、開発環境ではそれが抜けた環境になっているため、慣れていないと気づきにくい観点とも言えます。

なので開発中でも通信コストが考慮された結果が分かる開発環境を2つのMacを使って実現してみようと思います。

ちなみに単純な興味です。

準備するもの

  • Mac 2台(名称としてMacWとMacDと名付けます)
  • 同じローカルネットワークに繋がっていること(つまり同じWi-Fiに繋がっていればOKです)

構築する環境の構成について

大枠で構成についてまとめるとこんな感じになります。

  • MacWにRailsアプリを立ち上げる
  • MacDMySQLサーバーを立ち上げる
  • ローカルの開発環境なのでセキュリティは意識しない
  • MySQLを外部アクセスを許可する設定にする
  • MySQLに外部アクセス用のユーザーを作成する
  • RailsアプリのDB接続先をMacDのホスト名、ユーザーに設定する

重要なキーワードは以下の3つとなります。

  • MacDが外部からアクセスされる準備
  • MySQLが外部からアクセスされる準備
  • Railsが外部DBにアクセスする準備

MacDがMacWからアクセスできる準備をする

アクセスするには2つの方法があります。

  1. ローカルIPアドレスを指定
  2. ローカルホスト名を指定

ローカルIPアドレスを指定

これはローカルネットワーク内においてアサインされているIPアドレスを直接使う方法です。 一時的であれば最も簡単です。

Macで自分のローカルネットワークを調べる方法は、 ifconfig で調べます。

$ ifconfig | grep 192
    inet 192.168.11.3 netmask 0xffffff00 broadcast 192.168.11.255

192.168.11.3 というのが、 ifconfig を叩いたPCにアサインされているローカルネットワーク上のIPアドレスになります。

メリット

とにかく楽。に尽きます。

デメリット

ルーターDHCPなどでIPアドレスを動的アサインしている場合だと、ルーターがリセットされるタイミングなどでIPアドレスが変わるため、変わるたびにIPアドレスの記入部を更新する必要があります。

ローカルホスト名を指定

こちらはMac自身にホスト名を定義することで、IPアドレスに依存せず常に固定のホスト名で接続することが可能になります。 こちらの記事にまとめてあります。

mothulog.hateblo.jp

MySQLが外部アクセスされる準備

そもそもMySQLが外部からアクセスを許可していないのは、そう設定がされているためです。 bind-address という設定がデフォルトは 127.0.0.1 つまり localhost からの接続のみ許可する設定になっています。

$ cat /usr/local/etc/my.cnf
# Default Homebrew MySQL server config
[mysqld]
# Only allow connections from localhost
bind-address = 127.0.0.1

my.cnfの場所が分からない場合は、こちらの記事で確認してみてください。

mothulog.hateblo.jp

my.cnfにbind-addressでIPを指定することで、そのIPからの通信を許可します。 ちなみに注意事項としては、MacDIPアドレスになります。 MacWのIPアドレスではありません。

まず前提知識として、PCには複数のネットワークインターフェイスがあります。 その中のどのネットワークインターフェイスを許可するのかがbind-addressになります。 先程bind-addressのデフォルトはlocalhostと説明したように、デフォルトではlocalhostからのアクセスのみを許可しています。

今回はパブリック環境でどこからでも誰からでもアクセスできる本番環境と違って、自宅や職場の同一Wi-Fi環境下での話なのでアクセス制限かけたりセキュリティの部分には気を使っていません。 なので bind-address には 0.0.0.0 を設定します。 これは制限なしの指定になります。どのネットワークインターフェイスからでもアクセスできます。 ちなみに全てのbind-addressコメントアウトすると同様に無条件で受け付けることになります。

$ cat /usr/local/etc/my.cnf
# Default Homebrew MySQL server config
[mysqld]
# Only allow connections from localhost
# bind-address = 127.0.0.1
bind-address = 0.0.0.0

設定したらリスタートします。

$ mysql.server restart 

一応はローカルネットワーク外からルーターのグローバルネットワークのIPとMacDのローカルIP、MySQLが解放してるポート番号などが分かれば、外部からのアクセスは恐らく可能かとは思いますが、 近辺依存ではありますが、自宅近辺の住民が自分のWi-Fi不正アクセスされるリスクは低いかなと思ってます。(ここらへんは詳しくはないので鵜呑みではなく、自己責任でお願いします)

MySQL内に外部からアクセスするユーザー用意する

MySQLの外部からアクセスする準備ができたら、今度は外部からアクセス用のユーザーを用意します。

例えば下記のようなユーザーを作る場合は

  • ユーザー名: hoge
  • パスワード: pass
  • 対象IP: 192.16配下全部
-- ユーザー作成
create user "hoge"@"192.168.%" identified by "pass";

-- 権限付与
mysql> grant all on *.* to "hoge"@"192.168.%" identified by "pass";

のようにすれば、ユーザー作成して対象DBへの権限設定がされます。

mysql> select user, host from mysql.user;
+------+-------------+
| user | host        |
+------+-------------+
| hoge | 192.168.%   |
+------+-------------+

mysql> show grants for 'hoge'@'192.168.%';
+----------------------------------------------------------------+
| Grants for hoge@192.168.%                       
+----------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'hoge'@'192.168.%' IDENTIFIED BY PASSWORD '*196BDEDE2AE4F84CA44C47D54D78478C7E2BD7B7' 
+----------------------------------------------------------------+
1 row in set (0.00 sec)

外部から接続をテストする

この段階で既に外部から接続できている環境になっているはずなので、他Macから接続をします。

IPアドレス指定

$ mysql -u hoge -h 192.168.11.6 -p

ローカルホスト名指定 ホスト名が fuga.local の場合

$ mysql -u hoge -h fuga.local -p

接続できれば外部からMySQLに接続できています。

Railsが外部DBにアクセスする準備

MySQLが外部からのアクセス準備が整えば後は簡単です。

dataases.yml で接続先を指定するだけになります。

IPアドレス192.168.11.6の場合

development:
  ...省略...
  username: hoge 
  password: pass
  host: 192.168.11.6

これでRails立ち上げてDBにアクセスできれば完了です。

気になる速度差を測ってみる

1PCでUnixドメインソケット通信した場合と2PCでTCP/IP通信した場合で平均値を出してみました。

1万件のUser(name: string)を事前に用意し、@users = User.all が呼ばれて一覧を返すcontroller に対し GET http://localhost:3000/users を5回呼び出し、最速と最遅を抜いた3つで平均値を出して測定しました。

Unixドメインソケット通信

time
8.6ms
8.5ms
8.5ms
8.3ms
7.6ms

(8.5 + 8.5 + 8.3) / 3 = 8.4333333333ms

TCP/IPドメインソケット通信

time
291.9ms
289.9ms
194.4ms
159.9ms
142.7ms

(289.9 + 194.4 + 159.9) / 3 = 214.7333333333ms

およそ 26倍差がついています。 MySQLサーバーがRailsサーバーよりスペックが劣ってはいますが、それを考慮しても大分差が開いているでしょう。

まとめ

本番環境にできるだけ寄せる構成をしておくことで、本番でしか発覚しない問題を開発段階で気づいて排除できるので、PC台数に余裕があるなら、リリース前だけでも試す価値はあるかなと思いました。

注意事項

開発環境前提においての構成であるため、セキュリティとしてはGoodではありません。 特に公共のネットワーク配下ではやめたほうがいいです。

ActiveRecordでpluckとselectしてpluckに変化はあるか?

ちょっとした疑問になりますが、明確な違いなどは生まれるのか気になるコードがあります。

User.select(:name).pluck(:name)

User.pluck(:name)

に速度差や消費メモリ差はあるのか?

SQLは同じ

(55.2ms)  SELECT `users`.`name` FROM `users`
↳ app/controllers/users_controller.rb:10

(60.7ms)  SELECT `users`.`name` FROM `users`
↳ app/controllers/users_controller.rb:9

速度は微妙な変化

90万件に対して行った結果

pluckのみ select+pluck
3228 3349
3152 2972
3126 3370
3101 3144
3271 2945
3454 3227
3120 3306
3488 3577
3046 3072
3228 3413

pluckのみ平均: 3210.0ms select+pluck平均: 3231.625ms

しかしselectは確実に処理されてる

SQLは同じ、速度差も大きな違いはないが、トレースしたところ 確実はselectメソッドは処理されてからpluckを呼び出されている。 速度で微妙にpluckのみが勝っているのもこのselectメソッド処理が呼ばれているからではないかと予想。

結論

実践に影響を及ぼす変化は起こりにくいと考えてよいが、 確実に無駄な処理は起きていることは事実であり、表面上のコード量も増えるので、 pluckを呼ぶのであれば,selectは呼ぶ必要はない。

iPhoneからMac上のRailsアプリにlocalhostでアクセスする方法

MacでサーバーサイドとiPhoneAndroidなどのクライアントサイドを同時に開発していると、デバッグをしながらフットワーク軽く開発をしたいですよね。

わざわざテストサーバーにデプロイして動作確認するよりも前に、開発中にMac上に立ち上げてるRailsアプリに対して、iPhoneから接続できたほうが楽ですよね。

今回はその方法についてまとました。

localhost接続には条件があります

この方法には1つ条件があります。

MaciPhoneのネット回線が同じWi-Fi環境下であること

満たさないと今回説明する方法では実現できません。

Macのシステム環境設定で共有を設定する

システム環境設定の共有画面
システム環境設定の共有画面

上図はシステム環境設定の共有を開いた画面になります。

共有を有効にする

まず左側のチェックボックスいずれかをONにしてください。 1つだけでいいですし、例えば下図のように実質共有不可能にしても構いません。

f:id:motom552:20190802135716p:plain

ローカルホスト名を設定する

f:id:motom552:20190802140037p:plain

例えば上図のようになっていた場合、ローカルホスト名とは「mothule-mbp.local」のことを言います。 これが実際のアドレス代わりとなり、http://mothule-mbp.local:3000 のようにアクセスするとポート3000に対して GET / リクエストを投げます。

しかし注意事項があります。
上記のようにハイフン(-)が入ってると動かないので取り除く必要があります。

画面右の「編集...」ボタンからローカルホスト名を変更することができるので、ハイフンを除去した名前にしてください。

これでローカルネットワークにおいてアドレスの準備が済みました。 しかしこのままでは何も反応しないので、上モノとしてRailsアプリを用意します。 ちなみにRailsアプリでなくともPHPアプリでも何でもいいです。

Railsアプリを適当に用意する

今回の目的はlocalhostへのアクセスなので、ここは適当なアプリを用意していいです。

ただし立ち上げ時のオプションに -b つまりバインディングを次のように指定してください。 -b 0.0.0.0

$ bin/rails s -b 0.0.0.0
=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.2-p47), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

アプリを起動すると localhost:3000ではなく0.0.0.0:3000 でリッスンしました。 ここで先程設定したローカルホスト名を使います。 仮にローカルホスト名が「mothule.local」だとして、http://mothule.local:3000/ にアクセスしてみます。

f:id:motom552:20190802153429j:plain

無事アクセスできました。

-b 0.0.0.0 の意味

これはデフォルトでは localhost になっており、localhost経路以外でのアクセスはできません。 しかし 0.0.0.0 を指定することでこれを全公開することになり、同一ネットーワークでかつIPが分かれば、どの端末でも誰でもアクセスできます。 そのため注意事項としては、公共のWi-Fi環境下ではやらないほうがいいです。

ローカルネットワークのIPアドレス直でもアクセスできる

今回はローカルホスト名を使いましたが、そもそも上記で説明したように全公開になっているので、Railsアプリが立ち上がっているPCのIPアドレスでもアクセスできます。 「http://192.168.11.3:3000」みたいに。 IPは ifconfig で確認できます。

SafariではなくアプリならATS無効化する

もしiPhoneアプリからRailsアプリに接続する場合は、 ATSを無効化することでアクセスできます。

下図はXcodeでInfo.plistを開いた画像です。

f:id:motom552:20190802154059p:plain

MacからMacへscpを使う方法

MacからLinuxに対してscpを使うことをよくあるのですが、Macに対してscpを使おうとしたときに躓いたのでまとめました。 ちなみに送信元はMacでなくとも何でもいいです。

躓いた原因はSSH設定がGUIだったから

scpはsshを利用して暗号化通信上でファイルコピーをするコマンドなので、ssh/sshdが必要になります。 なので当初は「sshdなどをサービスをコマンドラインから立ち上げて、ポート解放の資料探せばいけるだろ〜」って考えていたのですが、実際はMacの共有GUIを使ってやる想定外の方法だったので、躓きました。

システム環境設定の共有でSSHログインを許可する

  1. システム環境設定を開く
  2. 共有を開く
  3. リモートログインをONにする

f:id:motom552:20190802115922p:plain

リモートログインをONにすると、右側に接続方法が表示されるので、それを他PCからssh接続すること成功します。 f:id:motom552:20190802115940p:plain

これで無事scpを使うことができる。

$ scp -C /tmp/hoge.txt mothule@192.168.11.6:/tmp
hoge.txt                                  100%  227    17.8KB/s   00:00

ちなみにssh許可していないとエラーになる

$ scp -C /tmp/hoge.txt mothule@192.168.11.6:/tmp
ssh: connect to host 192.168.11.6 port 22: Connection refused
lost connection

MySQLのmy.cnfの場所を見つける

初期環境構築時など必ずと言っていいほど弄る多いmy.cnfですが、パスがなんだったか毎回忘れてしまっています。 しかし、この方法ならどこにパスが書いてあるかを覚えていれば環境毎にパスが変わっても同様に見つけ出すことができます。

結論

$ mysql --help | grep my.cnf

ヘルプに書いてる

実はヘルプを見ると長文の中にひっそり書かれています。

$ mysql --help
...省略...

Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf /usr/local/etc/my.cnf ~/.my.cnf
The following groups are read: mysql client

...省略...

なので後は検索して抽出すれば欲しい情報をちょっとブサイクですが表示することができます。 ↓だと3行目ですね。左から順に読み込まれます。

$ mysql --help | grep my.cnf
                      order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf /usr/local/etc/my.cnf ~/.my.cnf