Mockを使って、Facebookログイン部分のRSpecを書いてみた!!
こんにちは、エンジニアの神山です。 最近、テストカバレッジを上げるためRSpecを書きまくっています。ちなみに最初は90%でしたが、苦闘の末95%まで上がりました。結構骨が折れましたね。
その中でも大変だったのがFacebookログイン部分のテストです。外部APIを使っており、そこの部分のテストの書き方が分からなくて悩んでいました。 色々と調べてみるとモックを使うとうまいことテスト出来るよという文献を見つけました。
ということで今回は、外部API部分のテストの問題点、モックとは何か、またそれをどのようにテストに使うのかにフォーカスして記事を書きました。
外部API部分のテストの問題点
今回悩んだのは外部APIを使用している部分のテストをどのように書くかということです。
例えば、「ログインしようとしているユーザーのFacebookのアカウント情報を取得し、すでにDBに登録されていればログインさせる」ということです。 しかしこのテストを行うには、実際にFacebookAPIよりアカウント情報を取得しなくてはいけません。
しかしテストで外部APIを利用することには、幾つかのデメリットがあります。
- 外部APIやその周辺で、予測や対応ができないエラーが起きることがある
- 外部APIの使用に制限があったり、金銭などが発生する場合がある
- 外部APIに紐付いているプロダクトにリアルに影響が起こる(Facebookの場合、実際に投稿がされてしまうなど)
では、どうすれば外部APIを利用せずに、外部API部分を使用している部分のテストを出来るのでしょうか。 そのようなときに使うのが、「モック」です。
モックとは何か。
モックを直感的に説明すると、「本物のふりをするニセモノのプログラム」です。下記の記事の言葉を使わせて頂きました。
例えばユーザーのFacebook情報を取得するとき、FacebookAPIにリクエストが発生したら予め用意したニセモノのプログラムを呼ぶようにすることが出来ます。 また「aが呼ばれたときに本当はbを返すのだけど、ここのテストだけはcを返したい」というときに使用したりもします。
つまりモックとは、外部APIなどテストを行うために必要だけれど使用できない、再現することが難しい場合に、その役割を担ってくれるものです。
ちなみにモックとスタブの違いは?
モックと似た言葉にスタブというものがあります。気になって調べてみたのですが、両者の違いは使用目的だそうです。基本的にどちらもニセモノを作り出すことには変わらないようです。
ただその違いを明確に理解するのはなかなか難しく、またあまり意識する必要もないという意見もあったので、今回は保留にしました。時間があるときに色々と調べてみようと思います。
ちなみに、分かりやすく書いてある記事があったので、載せておきます。
モックの準備
Omniauth
部分のテストに関しては以下に従っております。
まずモックにすべきところを整理しましょう。 今回はユーザーのFacebook情報を取得する際にFacebookAPIを用います。そしてFacebookに登録されている名前やEmailを取得します。 そのため、今回モックにするところはFacebookAPIにリクエストがあったときに返す部分です。 以下をHelperに記します。
def facebook_mock(name, email) OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new( provider: 'facebook', uid: 1234567890, info: { name: name, email: email }, credentials: { token: 'hogepiyo1234' } ) end OmniAuth.config.test_mode = true
たとえば、facebook_mock('foo', 'bar')
とすれば、以下の値が得られます。
{ provider: 'facebook', uid: 1234567890, info: { name: 'foo', email: 'bar' } , credentials: { token: 'hogepiyo1234' } }
またOmniAuth.config.test_mode = true
はOmniauthを用いたテストの際に必要になるので、一緒に記述しておいてください。これを記述すると、FacebookAPIにリクエストが送られそうになると、それを中止してすぐにコールバックしてくれます。
RSpec
では、Facebookログイン部分の一連のテストを書いていきます。
まず、ユーザーを作成します。
let(:user) { User.create(name: 'hoge', email: 'hoge@hoge.com') }
テストの前にOmniAuth.config.mock_auth[:facebook]
を初期化し、facebook_mock
をセットします。
OmniAuth.config.mock_auth[:facebook] = nil Rails.application.env_config['omniauth.auth'] = facebook_mock( name: user.name, email: user.email )
準備はこれだけです。では実際にFacebookログインをしてみます。今回は分かり易くボタンを押してFacebookログインをする形式にしてみました。
click_link 'Facebookを利用してログインする'
さて、ボタンが押されると通常はFacebookAPIにリクエストが走りますが、テストではすぐにコールバックされます。
ただモックのおかげで、request.env['omniauth.auth']
には下記の値が入っております。
{ provider: 'facebook', uid: 1234567890, info: { name: 'hoge', email: 'hoge@hoge.com' } , credentials: { token: 'hogepiyo1234' } }
そのため、request.env['omniauth.auth']['info']
でname: 'hoge', email: 'hoge@hoge.com'
の組み合わせを取得できるようになります。あとはこれをDBと照合して、有効であるかどうかを確かめればテスト終了です。
テスト全体は以下のようになります。
discribe 'login via Facebook' do let(:user) { User.create(name: 'hoge', email: 'hoge@hoge.com') } before do OmniAuth.config.mock_auth[:facebook] = nil Rails.application.env_config['omniauth.auth'] = facebook_mock( email: user.email, name: user.name ) click_link 'Facebookを利用してログインする' end it 'should succeed' do expect(page.status_code).to eq 200 end end
ちなみに検証部分がステータスコードの判別しかないですが、ログインされているかどうかを簡単に確認できる指標が欲しいですね。
例えば、expect(current_user).to eq user
のようなものがあれば。。
以上、Mockを使ったRspecの書き方でした。読んで頂きありがとうこざいました。 アドバイスなどありましたら是非お願いします!
We're hiring!
Cluexではビジネスサイド、エンジニアサイド共にメンバーを募集しています! お気軽にご連絡下さい!
参考文献
Docker for Macが遅い問題をdocker-syncで解決する
こんにちは。エンジニアの志村です。 Docker for Mac便利ですね!
日本語でも様々な記事が出てきています。
私もVagrant + Dockerをメインに使用しておりましたが、ついにDocker for Macに乗り換えました。
弊社では、dev環境をDockerにしているのですが、Vagrant + Dockerの時は快適に開発が出来ました。
ただ、VMを使わなくなった途端にめちゃくちゃ動作が重くなりました。
docker-compose --service-port ●●
という感じで、rails serverとwebpack-dev-serverを立ち上げるのですが、ブラウザからの読込が劇的に遅い…。seedデータ突っ込むのもめちゃくちゃ遅い…
フォーラムでも話題になっていますね。
私の環境でいうと、Vagrant + Dockerの時よりも、体感10倍近く速度が落ちました…。
なぜ遅いのか
このDockerの中の人の回答を見ると、osxfsを用いてファイルシステムイベントを検知・監視していることが主な原因っぽいですね。 勿論、その他環境により差異はありますが、ファイルシステムを監視する方法を替えれば解決できそうです。
docker-sync
ファイル同期で速度早いと良く言われているのがrsyncですね。 docker-dev-osxという選択肢もありますが、これはDocker for Mac未対応… どうしようかなーとフォーラムを眺めているとdocker-syncなるものがありました。 フォーラムの中でも評価が良く、結構な人が使ってそうなのでdocker-syncを使用したいと思います。
docker-syncはRubyベースで書かれています。
docker-syncの使い方
- docker-sync, fswatchをインストールします。
$ gem install docker-sync $ brew install fswatch
ファイルの同期に関してはrsync, unisonが選択出来ます。 rsyncがホスト→ゲストの一方向の通信なのに対し、unisonはNFSのように双方向の通信が可能なようです。 今回はrsyncを使用します。 unisonに関してはまた記事を書ければと思います。
$ brew tap homebrew/dupes $ brew install rsync
※ schema.rb, Gemfile.lock等、ゲスト(Docker)側で生成されるファイルに関してはdocker cp
コマンドを使用するとホスト側に持ってくることが出来ます。
# Docker内のschema.rbをPC側に持ってくる
$ docker cp コンテナ:/var/www/db/schema.rb ./db/schema.rb
- docker-syncの設定 docker-syncはymlファイルに設定を記述します。 Railsを触っている身としては普段から使用しているのでありがたいです。
docker-syncはDocker Composeを使用します。 production環境やstaging環境でもDockerを使用している方は、docker-compose.ymlの他に、開発環境用のymlファイルを用意する必要があります。 私は開発環境のみなので、docker-compose.ymlを直接編集してしまいます。
公式のwikiに載っているので参考下さい。
docker-sync.yml
syncs: # docker-syncで使用するvolume名を記載 web-sync: notify_terminal: true # ホスト側(PC)のパスを記載 今回はRailsのルートディレクトリを指定 src: './' # ゲスト側(Docker)にマウントするパスを記載 Docker内の/var/wwwにマウントされる dest: '/var/www' # IPの設定 sync_host_ip: '127.0.0.1' # rsyncのポート設定 sync_host_port: 10871 # rsyncさせないファイル類を記載 sync_excludes: ['Gemfile.lock', 'Gemfile', 'config.rb', '.sass-cache/', 'sass/', 'sass-cache/', 'composer.json' , 'bower.json', 'package.json', 'Gruntfile*', 'bower_components/', 'node_modules/', '.gitignore', '.git/', '*.coffee', '*.scss', '*.sass'] sync_excludes_type: 'Path' sync_args: '-v' # ファイル同期方法の選択 rsync or unison sync_strategy: 'rsync' # ゲスト側でのユーザー名の指定 sync_user: 'test' sync_userid: '5000' # ゲスト側でのgroupの指定 sync_group: 'testgroup' sync_groupid: '6000' # fswatchで変更を検知させないファイルやディレクトリ sync_excludesとかぶせとけばよいかと watch_excludes: ['.*/.git', '.*/node_modules', '.*/bower_components', '.*/sass-cache', '.*/.sass-cache', '.*/.sass-cache', '.coffee', '.scss', '.sass', '.gitignore'] watch_args: '-v' watch_strategy: 'fswatch'
docker-compose.yml
docker-compose.ymlにはversion1, version2と2種類の記述方法があります。 ここではversion2を使用します。
version: '2' services: database: image: postgres:latest volumes_from: - datastore expose: - '5432' elasticsearch: image: elasticsearch:latest volumes_from: - datastore ports: - '9200:9200' redis: image: redis:latest ports: - '6379:6379' volumes_from: - datastore memcached: image: memcached:latest ports: - '11211:11211' volumes_from: - datastore datastore: build: docker/datastore web: build: . command: bash -c 'rm -f tmp/pids/server.pid && bundle exec foreman start && bundle exec sidekiq -C config/sidekiq.yml -L tmp/sidekiq.log -d' volumes: # 下記で宣言したvolume名を指定 - web-sync:/var/www:rw - /var/www/client/node_modules ports: - '3000:3000' - '8080:8080' links: - database - elasticsearch - redis - memcached # docker-syncで使用するvolume ここで宣言した名前がdocker-sync.ymlで使用される volumes: web-sync: external: true
このような感じです。 環境によって差異があると思いますので、適宜調整をお願いします。 一番重要なのは、volumesのsync volumeの設定と、アプリケーション(Rails)側のコンテナでvolumesを指定する部分です。
docker-syncの起動
$ docker-sync start
もしくは
$ docker-sync-stack start
で起動します!
docker-sync-stack start
の場合は、docker-sync start
と同時にdocker-compose up
も行われます。
私はpry-byebugを使用したいので、普段docker-compose run --service-port web
で起動しているため、ターミナルを一枚docker-sync startに使用し、さらにdocker-compose run --service-port web
を起動させるようにしています。
ベンチマークはとっていないのですが、Vagrant + DockerをNFSで同期させたのと同じ、もしくはそれ以上に早くなりました! GUIで様々な設定が出来、VMとかも必要ないのでDocker for Macは便利ですね。 是非docker-syncを使って快適な開発環境を構築してみては如何でしょうか?
We’re hiring!
Cluexではビジネスサイド、エンジニアサイド共にメンバーを募集しています! お気軽にご連絡下さいませ!
今年2回目の開発合宿に行って来ました!@土浦・筑波
こんにちは、エンジニアの高橋です。
最近都内は急に肌寒くなってきましたが、みなさんお元気でしょうか?
今回は茨城県の土浦・筑波エリアで開催!
先日、2泊3日で茨城県の土浦・筑波の方に開発合宿に行ってきました!
台風が近づいている中での開催となり、心配な所もありましたが無事に終わったので、 今回はその様子をまとめようと思います。
2月の開発合宿に続いて、今年2回目の開催となります。 Cluexでは合宿を利用して 普段なかなか手のつけられない技術的課題や技術的負債への取り組みや、 理念会議といったメンバー全員でじっくり話し合う場を設けるなど といったことをしています。
ちなみに前回(2016年2月)の日光での合宿の様子はこちら cluex-developers.hateblo.jp
古民家ゲストハウス「jicca」
今回の宿泊先には2泊3日の全日貸切で、筑波山の近くにある 茨城県石岡市のゲストハウス「jicca」さんを使わせてもらいました。
ここのゲストハウスはクラウドファンディングを活用して、 もともと実家の母屋だったものをゲストハウスに改装したそうです。
ここの近辺は豊かな自然が溢れていて、とても落ち着いている長閑な場所で、
jiccaは最近は雑誌やラジオなどで度々話題になっているそうです。
こちらを運営されている比企(ひき)さんという方は、もともと大手企業で活躍された後に 現在はこちらの「jicca」を運営されつつ個人で中小企業診断士として中小企業コンサルをされている方で、とても暖かい方でした。
今回の合宿でのTRY
合宿に参加したのはエンジニア4人・ディレクター3人の合計7名。
合宿中はエンジニア陣は2人で1チーム、ディレクター3人で1チームの全部で3チームに別れて目標を立てました。 合宿の目標はそれぞれのチームであらかじめ決めておき、合宿当日はすぐに作業が開始できる様にしておきました。
ディレクターチーム 売上を上げるためのコンテンツマーケティングの運用方針を決める
今後の事業成長戦略を各々が考えて発表
Web appチーム Rspecのテストカバレッジを95%達成
インフラ&ネイティブアプリチーム CoffeeScriptからES2015(ES6)への完全移行
開発合宿1日目
台風が近づいてる影響で初日は曇り空。
午前9時、都内某所に集まり出発です!
都内から土浦までは高速道路を使用して、車でおよそ1時間半ほどの距離にあります。
新卒3人組
前日も遅くまでオフィスに残っていたみたいで非常に眠そうですね。
土浦までの道中は高速道路も渋滞なく順調に進み、
守谷SAで途中休憩を挟んでほどなくして土浦に到着です。
宿に着く前に、激安ジャングルで物資調達です。
車内では眠そうだった彼も元気になったみたいです。
そして11時過ぎにjiccaへ到着。
到着してからまずは全体で各チームの合宿中の目標を共有し、 終わり次第すぐに各自作業に取り掛かります。
ディレクターチームは主に話し合いが多いので1階にて作業。
エンジニアの方は机と椅子が用意されている2階に上がり作業を開始。
ディレクターチーム
エンジニアチーム
1日目の夜は夕食と温泉を兼ねて、
車で10分ほど移動して「やさと温泉 ゆりの郷」へ行きました。
この近辺は日帰り温泉が500~1000円くらいで点在しているので、 その中から一番近場の場所を選びました。
「jicca」のオーナーの比企さんも一緒に温泉にお供して頂きました。
こちらの常陸牛は柔らかくてとても美味しいお肉でした。
夕食後
本日はアルコールは一切なしで、 夕食を食べた後はすぐ宿に戻って作業再開です。
ディレクターチームは順調に進んでいるようで、午前12時頃には就寝。
エンジニアの方はまだまだ続きます。
午前4時
まだ続きます。
初日が終わり、全員が寝静まったのは午前6時頃。
長い長い1日目が終了です。
開発合宿2日目
台風は進路を変えていなくなってしまい、 晴れ晴れとした天気になりました。
午前9時、起床。
全員で集まり1日のスケジュール確認です。
午前中は引き続き各チームごとで作業。
昼食は手打ち蕎麦が有名とのことで、宿の近くの蕎麦屋さん「かまたや」へ
創業60年以上の老舗蕎麦屋だそうです。
蕎麦はもちろん、天ぷらがめちゃめちゃ美味しいです。
宿に戻り、今回の合宿のもうひとつの一大イベントに入ります。
Cluex理念会議
2日目は昼食後から6時間ほどに渡り、
ディレクター・エンジニア含めて全員で「理念会議」といったものを行います。
前回の合宿に続き、
今回も個人個人の将来・会社の将来、自分がこれからどうしていきたいか・どうなりたいのかといったことを発表して、
それをどうすれば実現できるのかということをみんなで話し合っていきます。
・・・5時間経過後
ということで、写真にはあまり変化がないですが、
一人一人にフォーカスして個人の思想やその背景にある思い、
そしてそれぞれの将来に関してじっくり話し合ってきました。
理念会議が終わった頃には日がすっかり暮れてしまいました。
これからやや遅めの夕食です。
2日目の夕食はBBQ!
「jicca」の方で茨城県産の食材をたくさんご用意して頂いて、みんなでBBQです。
今回のBBQに望むためのチーム編成はこちら
調理部隊
バーベキュー屋の店員
火起こし部隊
おじさんたち
火起こし部隊の後ろで待機です。
そして・・・・
いざBBQ!
普段はこういった機会がなかなか無いので、
みんなでお酒を飲みながら語り合って、貴重な時間を過ごせました。
BBQを堪能し、ほどなくして2日目も終了です。
今日はみんな疲れが出てきたようで、午前12時にはみんな就寝したみたいです。
開発合宿3日目 (最終日)
3日目も天気に恵まれた、気持ちのいい朝を迎えました。
午前9時に起床し、10時にチェックアウトです。
3日目は土浦市街の方で貸会議を借りました。夕方まではこちらで各チーム作業を行い、最後に経過発表です。
お借りしたのは、「ワークヒル土浦」の特別会議室。社内の会議などであれば法人でも利用ができます。
10時~17時の利用で4000円ほどと、なかなかのお手頃価格です。
人数のわりに、とても立派な会議室でした。
こちらで一通り作業をした後、
各チームからの合宿目標の結果発表と振り返りを行いました。
今回の合宿で取り組んでいた、 「ES2015(ES6)の導入」や「Rspecのカバレッジ」に関しては、追ってブログにまとめたいと思いますので、しばしお待ちください。
観光編
JAXA(筑波宇宙センター)に入ってきました!
合宿最終日、会議室で結果発表などを終えた後、
20分ほど車を走らせて茨城県つくば市にある「JAXA 筑波宇宙センター」に行ってきました。
日本の宇宙産業を牽引するJAXA。
アメリカではNASAのスペースシャトルが引退し、SpaceXなどの民間企業へのシフトが進んでいますが、そんな宇宙を相手にする仕事はロマンがあってすごいなあと個人的には思うところです。
最先端の技術たちを駆使して宇宙という過酷な環境に挑むって本当にすごいです。
教育にも最先端の技術をどんどん導入していきたいです。
という訳で、JAXA見学です!
ISSの日本実験棟「きぼう」
中にも入れちゃいます。
宇宙服 (1着あたり10億円するそうです)
興味津々です。
その他にも衛星やロケット、エンジンなどが実物大で見れます。
施設の方(JAXAを引退された方?)に、
宇宙に関して、衛星や衛星に使用している技術など
色々なお話を付きっきりで伺うことができました。
ありがとうございました。
合宿のまとめ
前回に引き続き、今回も有意義な合宿となりました。
今回の合宿での反省点や改善点は次回以降にまた活かしていきたいと思いますが、
これから開発合宿をやろうと考えている方にも、気をつけるポイントなどをあげておこうと思います。
Keep
・メンバーのチームワーク強化に繋がった。
・宿泊代がお手頃。
・旅のお供に温泉は大事。
・ご飯が美味しい。
・宿にネット環境が整っている。
・自然溢れる環境なのでとてもリフレッシュできた。
・近くに娯楽施設がないので、逆に集中できた。
Problem
・床に座って作業するときは座椅子があると良かった。
・作業に当てられる時間が意外と短い。合宿中にやることの選択と集中が大事。
開発合宿のポイント
合宿にあたって気をつけていたことは、
・合宿の目標には数値的な結果が見えやすい目標を。
→ 加えて、合宿の開始と同時に、すぐに作業に取り掛かれる様に準備しておくと時間を無駄にせずに済みます。
→ 開発合宿の醍醐味として合宿中に立てた目標を達成することがとても大事だと思うので、どうしても終わらなそうであれば合宿前にある程度の下準備をしておくといいかと思います。
・お酒は最終日の前夜のみに抑える。
→ ダメになりやすい人は控えましょう。お酒を飲むとせっかくの目標もどうでもよくなっちゃいます。
・ネット環境の確認。
→ wifiの有無はもちろん、速度面なども事前に確認しておくと安心です。
→ もしもの時に、モバイルwifiを各自で持参しておくと何かと便利ですが、圏外になることもしばしばあります。
・電源の確保。
→ 作業する場所で電源が使用できるかはもちろんですが、延長ケーブルを持参すると便利なこともよくあるので、事前に確認しておくと安心です。
・お昼に温泉は入らない。
→ 温泉に入ると気持ちが満たされてせっかくの目標もどうでもよくなっちゃいます。ダメになりやすい人は控えましょう。
・近隣の施設や観光地を調べておく。
→ 特に宿泊施設内もしくは近場にコンビニがあるか確認しておくと安心です。24時間営業ではないコンビニもあるので気をつけておくといいかと思います。
・予定は詰め込み過ぎない。
→ 食事処の予約や会議室の予約など、時間的な余裕を持って対応できる様にしておくといいかと思います。
→ 合宿中にやることも基本的には1~2つに絞ると雑念なく集中できるのではないかと思います。
番外編
懐かしきスマブラ!!
実は遡ること2日目のBBQの後、懐かしきスーパーファミコンと任天堂64が出現!
jiccaの方で貸していただきました!
プロジェクターも用意して頂いて懐かしい気分に浸りつつ、ガチバトル。
猫
jiccaで飼われている猫です。
猫かわいいですね、猫。
いかがでしたでしょうか?
日々の業務では対応できない経営課題や技術課題に取り組むために、新しい技術の試験の場に、新しい事業の創出の場に、開発合宿やってみてもいいかもしれません。
個人的にはリフレッシュもできてES6の導入も無事に進み、温泉に入れて美味しいご飯を食べれていい合宿でした。 これからまた更に事業スピードあげていきたいところです。
We're hiring!!
Cluexではビジネスサイド、エンジニアサイド共にメンバーを募集しています!
気になった方はご連絡ください!!
ビジネスサイド
エンジニア
SPDYも廃止されたのでnginxでhttp2使いたいけど、そもそもhttp2ってなに?っていうお話
こんにちは、高橋です。最近はnginxとrailsとswiftをぐるぐるしています。
nginxは非常にパワフルなwebサーバーで、アクセス数が増大した時した時でも非常に安定してトラフィックを捌いてくれています。
Ruby on Railsを使用する場合には、unicornやpumaなどのアプリケーションサーバーと併用して使うことが多いかと思いますが、どうしてもアプリケーションサーバーの方がボトルネックになってしまいます。nginxの処理能力をどれだけ極限まで高めても、結局はアプリケーションサーバーの方で処理しきれずに503が返ってきてしまうので、どうやってアプリケーションサーバーの方を安定的に処理能力をあげるか最近は悩むところです。。
最近ではAWSのEC2などでautoscalingもよく使われているので、1台あたりのトラフィックの処理能力はそれほどカリカリにまで高める必要はないのかもしれません。
ということで前置きはこんな所で、今回は(僕が勝手に)社内にもhttp2を取り入れたいなーと思い、色々調べてサーバー上で試験的に試してみたので、まとめてみました。
nginxでHTTP/2を使用する
Googleが提唱していたSPDYを取り込んだ形でhttp/2が標準化される流れとなり、これから本格的に広まって行きそうですが、nginxではバージョン1.9.5からhttp/2をサポートするようになりました。
HTTP/2とは
http/2は、http/1.1でプロトコルレベルでの問題となっていたデータ送受信における効率の悪さを克服する形で、http/1.1ではクライアントとサーバーがシングルシーケンスでデータをやりとりを行なっていたのが、http/2ではmultipleにシーケンスのやりとりを行うことができるようになりました。これによってデータ転送と処理の効率が上がりwebページを表示するまでのスピードが上がるというわけです。
http/1.1の場合 http/1.1ではブラウザのTCP同時接続は最大4~6に制限されています。 HTTP Head of Line Blockingによりブラウザのネットワークはこんな感じに、ひとつのファイルの読み込みが終わるまでは、それ以降のファイルの読み込みはブロックされます。
http/2を使用した場合 同時に複数のファイルの読み込みを行うようになります。http/2ではブラウザの最大同時リクエスト数は100以上が可能とされていています。
webブラウザのHTTP/2対応状況
2016年9月現在のブラウザの対応状況はこんな感じです。ChromeやFirefoxなどのメジャーなブラウザではほぼほぼ対応していますが、IEやsafariなどでは一部対応といった感じのようです。
Can I use... Support tables for HTML5, CSS3, etc
HTTP/1.1からHTTP/2への主な変更点
- 根本となる部分はhttp/1.1と同じ
- http/1.1のセマンティックスは変更しない
- サーバーへのTCP接続数を1つに限定
- TLSの機構を使用してプロトコルを自動選択
- バイナリープロトコル
- 多重化送信
などといったことが挙げられます。
HTTP/2を使用すると現在のTLSは一部機能が使用できない
よりセキュアな通信を確立するために、http/2は現在のTLSの一部機能を制限することで、セキュアなプロトコルとして現在は使用されています。今後http/2に対応した形の新しいTLS、もしくはそれに替わるものが出てくるかと思います。
制限される内容はこちら
- プロトコル選択にはNPNではなくALPNを使用
- サーバー認証を共有できる接続は接続共有が可能
- TLSプロトコルは1.2以上のみが使用可能
- TLS Compressionの禁止
- Renegotiationの禁止
- DH鍵は2048bit以上を使用
- AEAD(認証付き暗号)のうち、GCMとCCM以外の暗号方式は利用できない
NPN (Next Protocol Negotiation) からALPN (Applicaiton-Layer Protocol Negotiation) へ
http/2に先行して、NPN (Next Protocol Negotiation) はTLS false start方式の次の規格としてgoogleが提唱し、SPDYを支える次期アプリケーション層プロトコルネゴシエーションとして2010年ごろに登場しました。
背景的にはwebサイトの表示速度にはネットワークの回線速度よりもRTTの影響が大きく関わっており、TCP接続のハンドシェイクを毎回行うところに非効率さがあったと判断したのかと思います。
これに関しては2011年にAkamaiが行なった調査を簡単にまとめた2012年の記事があるので、下記の引用画像を見ていただくとwebサイトの表示速度がRTT依存というのがよりわかりやすいかと思います。
引用元: Latency: The New Web Performance Bottleneck - igvita.com
これまではNPNが使用されていましたが、今後の流れとしてはhttp/2での後押しもありALPNが一般的となりそうです。
NPNとALPNの簡単な違いは、サーバーとクライアントのどちらが使用するアプリケーションプロトコルの決定を行うかというところで、NPNではクライアント側によるプロトコルの選択が行われていましたが、ALPNではサーバー側がクライアントの使用できるアプリケーションプロトコルの中から使用するプロトコルを選択するという形になります。
ALPNを使用するにはOpenSSLのバージョン1.0.2以上が必要となりますが、サーバーのOSによっては最新のOpenSSLを入れてもALPNを使用できないこともあります。
CentOSでHTTP/2とALPNを使う
CentOSでhttp/2とALPNが使用できるのはCentOS 7 以上のバージョンとなります。
UbuntuでALPNを使う
Ubuntuでhttp/2とALPNを使用する場合は、今年リリースされた Ubuntu 16.04 LTS (Xenial) 以上のバージョンになります。
nginxでのhttp2を設定
現在、nginxでhttp/2を使うには、ngx_http_v2_moduleが必要となります。
再ビルドなどに関しては他にも記事があるので割愛しますが、 サーバーのOSとOpenSSLのバージョンがhttp/2に対応していれば、nginxで使用するのは非常に簡単かと思います。
最後の最後で、もはやおまけみたいな感じになってしまいますが、listenディレクティブにhttp2と入れるだけで、基本的には完了です。
server { listen 443 ssl http2; # ~~~~~ }
付け加えるとしたら、SSLのプロトコルの設定や、ssl_ciphersなどの設定などでしょうか。その辺りに関しましては、こちらの記事を流し読み程度にご参考にしていただけると嬉しいです。
googleはもちろんのこと、facebookやtwitterといった名だたるwebサービスで既にproductionへ導入されているhttp/2で、 徐々に様々なwebサービスで導入され始めていますが、本格的に普及するまではもうしばらく時間がかかりそうな気もします。
また進展があったら、記事にしたいと思います!
Cluexではエンジニアの方を探しています!
AWS Lambdaを使用し、CloudfrontのInvalidationを走らせる
こんにちは。エンジニアの志村です。
先日assets on S3についての実装を行いました。
その際の記事は下記になります。
今回はasset_syncにより、S3にassetファイルがアップロードされた段階でCloudfrontのInvalidationをLambdaを使用して走らせるという処理を実装したいと思います!
Invalidationとは
Cloudfrontのキャッシュを明示的に消す機能です。
クラスメソッドさんの上記の記事が非常に分かりやすいかと思います。
キャッシュを使用するのに重要なポイントは適切なキャッシュ期間を設けることです。それを行わないと古いファイルがいつまで経っても配信され続けてしまいます。
特にassets on S3を使用する場合には、明示的にファイルをInvalidationしないといつまで経っても最新のassetsファイルがユーザーに配信されなくなります。
このような状況を防ぐために、S3にファイルが転送された段階で、assetsのキャッシュを消去する必要があります。
Lambdaの実装
ではLambdaの実装を始めていきます。
AWSコンソールからLambdaを選択します
「Create Lambda Function」を選択します
今回はBlueprintは使用しないので「Skip」を選択します
S3を選択します。
項目 | 説明 |
---|---|
Bucket | assets用のBucketを選択 |
Event Type | S3にassetsファイルがCreateされた段階でInvalidationを走らせたいので「Object Created(All)」を選択 |
Enable Trigger | チェックを入れると、設定したイベントが走るようになります(production等で設定する場合は注意!) |
※Prefix, Suffixは必要であれば入力して下さい。
LambdaFunctionを作成します
項目 | 説明 |
---|---|
Name | 適当な名前を入力 |
Description | 分かりやすい説明を入力 |
Runtime | 今回はPythonにします |
コードは下記のコードが非常に分かりやすかったのでそれを使用します。
※DistributionIDは自身のCloudfrontディストリビューションのIDを入力して下さい
from __future__ import print_function import boto3 import time def lambda_handler(event, context): for items in event["Records"]: path = "/" + items["s3"]["object"]["key"] print(path) client = boto3.client('cloudfront') invalidation = client.create_invalidation(DistributionId='ディストリビューションID', InvalidationBatch={ 'Paths': { 'Quantity': 1, 'Items': [path] }, 'CallerReference': str(time.time()) })
因みにですが、boto3はPython用のAWS SDKです。 Lambda FunctionをPythonで書く際には必ずと言って良いほど良く使用します。 ドキュメントも充実しているので非常に使い勝手が良いと思います。
Boto 3 Documentation — Boto 3 Docs 1.4.0 documentation
ロールやその他の設定をします
「Lambda function handler and role」のRole→「Create a custom role」を選択します。
そうすると下記のような画面が現れるので、下記のCloudfrontのInvalidationを許可するロールを作成します。
下記のようなポリシードキュメントになります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "cloudfront:CreateInvalidation" ], "Resource": [ "*" ] } ] }
最後に「Create function」を押下すれば完成です!
使い方
asset_syncでデプロイすると勝手にInvalidationが走ってくれます。 下記の画面で確認が出来ます。
AWSコンソールからCloudfrontを選択します
該当するディストリビューションのIDを選択します
「Invalidations」タブを選択すると、Invalidationsの状態が閲覧出来ます。ここでデプロイ直後にStatusがProgressとなっていればトリガーがきちんと走っています。
以上になります。 キャッシュの扱いって本当難しいなーと感じますが、この様に自動化してしまえば特に意識することも少なくなって、よりCloudfrontが使いやすくなりますね。
Rackの基礎
こんにちは!! エンジニアとしてインターンしている村田です。
今回はズバリRackについて書いていきたいと思います。
Rackに関しては日本語の資料もあまり多くないため、特にRailsの初心者の方はよく分からないまま見過ごしていることも多いかもしれません。
私も初心者なので探すのに苦労しました。。
そこで自分もまだまだではありますが、これを読んで自分と同じような初心者の方々がRackのぼんやりとしたイメージをつかんでいただけるように書いていきたいと思います。
それでは早速ですがやっていきましょう!
Rackが誕生した背景
まず何故Rackが必要なのか? ここがわからないと、話が頭に入ってこないですよね。
これはPSGIというPythonのインターフェースの仕様に影響を受けました。
以前のPythonフレームワーク界は、様々フレームワークが開発されたものの、そのフレームワークが特定のWebサーバーに依存した状態でした。 ただし、これではサーバーの乗り換え時の負担が大きかったりして、開発者も頭を悩ませることが多かったようです。 そこで、ある特定のルールを定めて(インターフェース)、そのルールのもとフレームワークとWebサーバーの開発を進めることにしました。
これがPSGIの正体です。
RubyのフレームワークもRailsとSinatraを筆頭に多様性に富みますし、WebサーバーもWebrickをはじめとして様々な独自のデプロイ方法を持っています。
すると、当然PSGIの代わりとなるシステムが必要になりまして、そうやって検討されていった結果現在のRackが誕生していくことになります。
Rackってなに??
さていよいよ本題にいきましょう。
Rackとは、rackup
というコマンドを打つことで指定したファイルを参照しながらWebサーバーを立ち上げられるようにしているライブラリ(gem)です。 もう少し機能自体の説明をするならば、HTTPの送受信処理を担当するモジュールです。
指定したファイル(デフォルトではconfig.ru。ちなみにruはruckupの略。)には独自のRuby DSLで様々なミドルウェアやアプリケーションを参照するように書かれています。 これをもとにWebサーバーを立ち上げていくことになります。
ビジュアル的に説明をするとこんな感じになっています。
このようにミドルウェアを介して、RailsなどのRackアプリケーションに対してWebサーバーから来たHTTPをパースしたりしています。
試しにbundle exec rake middlewareと打ってみましょう。 例えばこのような感じになって出てくると思います。
use Raven::Rack use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fd239f96d58> use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use BetterErrors::Middleware use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CacheStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Remotipart::Middleware use Rack::Head use Rack::ConditionalGet use Rack::ETag use Warden::Manager use ExceptionNotification::Rack use Bullet::Rack use OmniAuth::Strategies::Facebook run Rails::Application.routes
ここに書いてあるuse
の対象がRackミドルウェアです。
そして最後に書いてあるrun
の対象はRackアプリケーション(Rails)になります。
ちなみにuse
やrun
はRack DSLなのですが、今回は詳しく触れませんので興味がある方は調べてみてください。
ruckupの正体
こちらをご覧いただければお分かり頂けると思いますが、実はrackupコマンドがやっているのはRack::Server.start
をしているだけなんですよね。
と言われてもRack::Server
とかよく知らないって方がこの記事を読んでくださっていると思いますので、説明します。
Railsで開発をしているとrails s
、省略しないで書くと、rails server
というコマンドを打つことがあると思います。 このrails server
というコマンドも実は省略形でして、正確にはRails::Server.start
というコマンドを打っています。
何か先ほどとても似ているコマンドを見ましたよね? そうRack::Server.startです。
もちろんこの二つは関係がないわけではなく、Rails::ServerはRack::Serverのサブクラスになっているのです。
継承する中で、ポートを3000番にするなど様々な違いを作っているようですが基本的な動きの部分は引き継いでいることがわかっていただけたと思います。
Rackミドルウェアの話
Rackミドルウェアはもちろん自分で追加することもできますが、そのためには簡単なルールを幾つか覚えなくてはなりません。
1つのHashオブジェクトを引数に取る「call」メソッドが実装されていること callメソッドは「ステータスコード」、「ヘッダーを表現したHash」、「eachに反応するオブジェクト」の3つの要素を持った配列を返すこと
上にある2つがそのルールになります。
また慣習的にcallメソッドの引数にはenv
を用いることも覚えておくと得をするかもしれません。
詳しく話を始めるとDSLの説明やその他いろいろな話を書かないといけないので割愛しますが、Railsのようなアプリケーションも最後はcallで呼ばれるので
lamda do |env| [200, {"Content-Type" => "text/html"}, ["Hello, world!"]] end
簡単に言ってしまえばこのような形で動いていることになります。 これは先ほどの条件を満たしていますよね。
最後に
私の説明では物足りなかった部分も多かったと思います。 でもこれをきっかけにしてRackのイメージを掴めた方が一人でもいらっしゃればとても嬉しいです。
私もわからないことだらけなのでもっと勉強していこうと思いますが、最後にこの記事を作る上で参考にさせていただいた記事を載せておこうと思います。
それでは失礼いたします。
vertical-alignの使い方と別の実装方法について
こんにちは、エンジニアの井戸田です。
先日 vertical-align
ではまり、検索してみると予測変換で vertical-aling 効かない
と出てくるので、結構皆さんもはまっているんだなと感じました。
なので今回は僕もはまったcssのプロパティである、 vertical-align
の値・使い方や、垂直方向の調整の別の実装方法を紹介したいと思います。
コードについて、htmlはslim、cssはscssを使用しているので注意してください。
vertical-alignの説明
vertical-align
は行内のテキスト、画像の垂直方向の揃え位置を指定するプロパティです。
vertical-align
は適用できるのが、 inline要素
と テーブルセル
です。なので divタグ
や pタグ
などの ブロック要素
に対して vertical-align
を指定してもうまくいきません。
divタグ
や pタグ
に vertical-align
を指定したい時は diplay: inline
や, display: table-cell
, diplay: inline-block
を指定し、要素自体を変更する必要があります。
値 | 説明 |
---|---|
baseline | 適当した要素のベースラインを親要素のベースラインに合わせる(初期値) |
top | 適用した要素の上端を、行ボックスの上端に合わせる |
middle | 適用した要素の中心を、親要素の中心に揃える |
bottom | 適用した要素の下端を、行ボックスの下端に合わせる |
text-top | 適用した要素の上端を、親要素の文字の上端に合わせる (テーブルセルへの指定はできない) |
text-bottom | 適用した要素の上端を、親要素の文字の下端に合わせる (テーブルセルへの指定はできない) |
super | 上付き文字 (テーブルセルへの指定はできない) |
sub | 下付き文字 (テーブルセルへの指定はできない) |
pxなどの単位 | px, emなどの単位を使用しての指定(ベースラインが揃った状態を0、正の値は上方向、負の値はした方向になる) |
- 初期値が
baseline
なのですが、画像などはbaseline
を持たないため、下端が親要素のベースラインに揃えられます。 - middleとは、フォントの場合
0.5ex
。小文字の「x」の半分高さです。
top
と text-top
、 bottom
と text-bottom
、 baseline
と text-bottom
の違いについて、勘違いしやすいので下記にまとめておきます。
top と text-topの違い
.verticalAlign.verticalAlign-top span.example | Example span.parent | parent span.child | top .verticalAlign.verticalAlign-textTop span.example | Example span.parent | parent span.child | text-top
.verticalAlign { margin: 0 0 8px 0; background-color: #f93; &-top { .child { vertical-align: top; } } &-textTop { .child { vertical-align: text-top; } } } .example { background-color: #fd7c79; font-size: 5rem; } .parent { background-color: #33cfff; font-size: 2.5rem; } .child { background-color: #24ff18; font-size: 1rem; }
上が top
で 下が text-top
になります。
text-top
は親要素の文字の上端(一番高い場所)を基準とします。なので上端を基準として配置することが可能です。
又 top
は適用した要素の上端を、行ボックスの上端に合わせるため上記のような表示になります。
bottom と text-bottomの違い
.verticalAlign.verticalAlign-bottom span.example | Example span.parent | parent span.child | bottom .verticalAlign.verticalAlign-textBottom span.example | Example span.parent | parent span.child | text-bottom
.verticalAlign { margin: 0 0 8px 0; background-color: #f93; &-bottom { .child { vertical-align: bottom; } } &-textTop { .child { vertical-align: text-bottom; } } } // 省略 以下はtopとtext-topと指定の仕方は同じです。
上が bottom
で下が text-bottom
になります。
text-bottom
は親要素の文字の下端(ディセンター文字の下端)を基準として配置することが可能です。
また bottom
は適用した要素の下端を、行ボックスの下端に合わせるため上記のような表示になります。
baseline と text-bottom
次は baseline
と bottom-text
に対してどのような違いがあるのか、説明します。
.verticalAlign.verticalAlign-baseline span.example | Example span.parent | parent span.child | baseline .verticalAlign.verticalAlign-textBottom span.example | Example span.parent | parent span.child | text-bottom
.verticalAlign { margin: 0 0 8px 0; background-color: #f93; &-baseline { .child { vertical-align: baseline; } } &-textBottom { .child { vertical-align: text-bottom; } } } // 省略 以下はtopとtext-topと指定の仕方は同じです。
上記を見てみるとどのような違いがあるのか迷うと思うのですが、親要素( .parent
)を対して line-height
を入れてみると違いが良く分かります。
.verticalAlign.verticalAlign-baseline span.example | Example span.parent | parent span.child | baseline .verticalAlign.verticalAlign-textBottom span.example | Example span.parent | parent span.child | text-bottom
.verticalAlign { margin: 0 0 8px 0; background-color: #f93; &-baseline { .child { vertical-align: baseline; } } &-textBottom { .child { vertical-align: text-bottom; } } } .example { background-color: #fd7c79; font-size: 5rem; } .parent { background-color: #33cfff; font-size: 2.5rem; line-height: 2; // ← 追加 } .child { background-color: #24ff18; font-size: 1rem; }
.parent
に対して line-height: 2;
を指定すると、 baseline
は親のフォントのベースライン(基準線)に合わせるので、ズレは生じません。ただ text-bottom
は親の要素の文字の下端に合わせるので、ズレが生じてしまいます。
上記の例の様に親の要素で line-height
(や height
などの縦幅調整をするプロパティ)を指定している時は注意が必要です。
(実際に実装する時には 初期値が baseline
なので、 vertical-aling: baseline;
の指定は必要ありません。)
ブロック要素への対応
テーブルセルとdisplay: table-cellの使い方
テーブルセルとは tableタグ
で使用する tdタグ
, thタグ
のことです。こちらの要素に対しても vertical-align
を使用すことができます。但し text-top
, text-bottom
, super
, sub
は指定することができません。
display: table-cell
での実装方法は下記の様になります。下記の例では .block
を垂直方向に中央揃えにしています。
.tableCell .block | block
.tableCell { display: table-cell; height: 100px; background-color: #f93; vertical-align: middle; }
block要素
に対して vertical-align
を使用する場合には、垂直方向の調整をしたいタグの親要素に対して display: table-cell
, vertical-align: ◯◯
を指定することで、使用することができます。
ただ表に関係ない要素に display: table-cell
を指定するのはバットな気がしますね。
display: inline-block
display: table-cell
を使用しないで display: inline-block
を使用する方法もあります。inline-block要素
とは inline要素
と block要素
を兼ね備えた要素です。下記の例では2つの inline-block要素
を垂直方向に中央揃えにしています。
.block .inlineBlock1 | inlineBlock1 .inlineBlock2 | inlineBlock2
.inlineBlock1 { display: inline-block; height: 8rem; background-color: #fd7c79; font-size: 2rem; vertical-align: middle; } .inlineBlock2 { display: inline-block; background-color: #33cfff; font-size: 2rem; vertical-align: middle; }
block要素
に対して display: inline-block
、 vertical-align: ◯◯
を指定することで、使用することができます。
inline-block要素
と inline要素
で異なるのが、 inline-block要素
の場合は横幅・縦幅が指定が可能なので、その分 inline要素
より使い易いかも知れません。
block
inline
inline-block
の違いについては、また別の機会に詳しく話したいと思います!
vertical-alingを使用しないで垂直方向を調整する方法として
- line-height
line-height
を使用して、親要素のheightに調整する。なんとなくバットな調整方法だと思います・・・。 - position
親のblock要素に対してposition: relative
、子要素に対してpostion: absolute
を指定する。
ただ垂直方向の中央揃えにしたいと思っていても、postion:absolute
は指定した要素の本来の位置からの相対位置なのでtop: 50%
という指定をしていても、その要素の上端が真ん中にきてしまうので、top: 50%
ではなく調整する必要があります。
上記2つを挙げましたが、それぞれ調整が難しいと思います。そこで僕個人がおすすめしたいのが FlexBox
を使用する方法です。
FlexBoxを使用し垂直方向を調整する
FlexBox
とは「Flexible Box Layout Module(フレキシブルボックスレイアウトモジュール)」のことで、要素をFlexコンテナというボックス内にフレキシブルで簡単に整列する方法です。
FlexBoxに対応しているブラウザは下記のようになります。
参考) Can I use... Support tables for HTML5, CSS3, etc
- IE11から正式対応。
- safariには
-webkit-
のベンダープレフィックス必要のようです。
垂直方向の調整をするために align-items
又は align-self
というプロパティを使用します。
align-items と align-self
- align-items
flexコンテナに対して指定することで、コンテナ内のすべてflexアイテム(子要素)に対して、同じ位置に調整します。 - align-self
flexアイテムに対して指定することで、個別に位置を調整します。
値 | 説明 |
---|---|
baseline | flexアイテム内の文字のベースラインを揃える。 最初のflexアイテムのbaselineに揃うようです。 |
flex-start | flexアイテムの上端を揃える( flex-direction が column や column-reverse の場合は右側を揃える) |
flex-end | flexアイテムの下端を揃える( flex-direction が column や column-reverse の場合は右側を揃える) |
center | 適用した要素の中心を、親要素の中心に揃える( flex-direction が column や column-reverse の場合は横幅の中央を揃える) |
stretch | フレックスアイテムの高さを揃える( flex-direction が column や column-reverse の場合は横幅を揃える) |
下記の例では、 align-items: center
を使用して実装してます。
.flexBox .flexItem1 | flexItem1 .flexItem2 | flexItem2 span.flexItem3 | flexItem3
.flexBox { display: flex; align-items: center; } .flexItem1 { background-color: #fd7c79; height: 200px; } .flexItem2 { background-color: #33cfff; height: 80px; } .flexItem3 { background-color: #24ff18; height: 160px; }
.flexBox
に対して display: flex
, align-items: center
を指定することで、flexコンテナ内の全てのflexアイテムが垂直方向に中央揃えになります。
このように簡単に垂直方向を調整することができるので、ブラウザの対応に気をつけなければいけませんが、 FlexBox
はとても便利です。
もし vertical-align
での調整が難しい場合には、 FlexBox
の導入をぜひ検討してみてください。弊社が運用しているmamanokoでも flex
を多用してます。
余談
株式会社Cluexではエンジニアの方を探しています!