インターネット向け通信にプロキシはどのように関わるのか(Webexの特殊な例付き)
プロキシって?
家庭などのPCでインターネットに接続する際はあまり関係のない話だが、いち企業がインターネットに通信をしたいとなるとプロキシ(サーバ)を設置することは多い。
プロキシとは、端末からインターネット網の間に、通信の中継役として設置されるサーバのこと。
プロキシを設置すると例えば以下のいいことがある。
- 一度アクセスしたサイトへの再アクセスが速くなる
これまでにWebサーバから受信したWebサイトの情報をプロキシ自身のHDDに一時保存(≒キャッシュ)することで、次そのサイトにアクセスする際は、Webサーバまで行かずともプロキシが情報を持っているので、通信の高速化を図ることができる - 特定のURLをアクセス禁止にできる
有害とされるWebサイトやアドレスをリスト化し、それに該当する通信は全て遮断などすることで、セキュリティを脅かすコンテンツにそもそもアクセスできないようにできる
メリットは他にもあるが一旦はこんな感じ。
プロキシを経由した場合の端末からインターネットまでの通信
プロキシ経由のインターネット通信は図に表すとこんな感じ。
①〜④がどのような通信なのかを、上図で各機器に振っているIPも交えて説明する。
①PC→プロキシサーバ
ブラウザでプロキシ用の設定を行っている場合は、PCからのパケットの宛先IPはWebサーバではなくプロキシサーバとなる。
送信元IP:172.10.1.1 宛先IP:10.1.1.1
②プロキシサーバ→DNSサーバ
パケットの中にあるHTTPリクエストデータの中には「最終的な目的地であるWebサーバのドメイン」が入っている。で、そのドメインをWebサーバのグローバルIPに名前解決するため、プロキシサーバはDNSサーバにパケットを投げる。
送信元IP:10.1.1.1 宛先IP:10.2.1.1
③DNSサーバ→プロキシサーバ
DNSサーバはドメインから名前解決をし、WebサーバのIPをプロキシサーバに教える。
送信元IP:10.2.1.1 宛先IP:10.1.1.1
④プロキシサーバ→Webサーバ
Webサーバに向けてパケットを飛ばす。
送信元IP:10.1.1.1 宛先IP:143.100.25.2
Webexアプリの通信の場合
という感じでプロキシ周りの通信は通常上図の流れになるのだが、今日たまたまWebexアプリの通信について調べている中で、少し仕様の異なる話に出会った。
一応説明しておくと、Webexはオンラインミーティング用のアプリ。
Webexアプリの通信は図に表すとこんな感じらしい。
この図ではDNSサーバは省略している。
ざっくり言うと、まずPCは通信①を走らせ、自身のローカルIPやグローバルIPをCisco社クラウドにあるサーバに伝える。そして参加者全員のPCがサーバに自身の情報を伝えて会議の準備ができ次第、映像音声データを届ける用途で通信②が走る。
通信①と②の特徴はそれぞれ以下のように説明できる。
通信①
通信②
要は同一アプリの通信だが、タイミングによってプロキシを経由するものとしないものがあるということ。
通信②はインターネットに出るにもかかわらずプロキシを経由しない。これは参考リンクにあるCiscoのページに書いてあったことだ。その理由については、はっきりとした理由を見つけられなかったので以下勝手な推測だが・・・
OSやブラウザのプロキシ設定を有効にすると、中にドメインの情報を含む通信はプロキシを経由してインターネットに出る仕組みになっているのだと思う。
で、ドメインを持つ通信のアプリケーション層はHTTP/HTTPSであるため、アプリケーション層がSRTPの通信②はドメインを持たない。なので通信②はプロキシを経由せずにインターネットに出ていくのではないかと考えている。
まとめていないまとめ
調べているとMicrosoftTeamsやGoogleMeetsなど、メジャーなWeb会議ツールは大体WebRTCという仕様に則って作られていて、上図のWebexの通信フローとおおよそ似た形式になっているようだった。調べてたら少し面白さを感じたので、またいつか時間があったらWebRTCでWeb会議アプリ作りたいと思う。
参考
【図解】httpプロキシサーバの仕組み(http GET/https CONNECTメソッド)や必要性・役割・メリットデメリット・DNSの名前解決の順序 | SEの道標
WEBプロキシとは? 仕組みや機能・メリットについて解説|セキュリティコラム|株式会社網屋
https://www.cisco.com/c/dam/m/ja_jp/solutions/webex/pdf/guide-to-using-webex-safely.pdf
IPv6による通信がIPv4よりも速いことが多い理由
IPv6の通信がIPv4よりも速いと言われる所以を2点整理した。
正確でない情報もあるかと思うので、発見した方はご指摘いただけると幸いです。
IPoEによるインターネット通信がPPPoEよりも通信が速い
PPPoEおよびIPoEとは、家庭や企業からインターネットに接続するまでの通信方式のこと。PPPoEはIPv4およびIPv6に対応しているが、現在一般的に用いられてるのはIPv4版。一方でIPoEはIPv6のみに対応している。
結論を言うと、PPPoEを用いたIPv4通信よりも、IPoEを用いたIPv6通信の方が速いことが多いらしい。以下でもう少し詳細に説明する。
PPPoEは、ユーザの持つCPEルータからNGN網(NTTの回線)にある網終端装置までトンネルを張り、その後はISP(インターネットサービスプロバイダ)のネットワークを経てインターネット網に接続する方式。 本筋ではないので、CPEルータ、トンネルとはどういうものか、などの説明はここでは省略する。
図に表すとこんな感じ。
どうやら上図にも描いている「網終端装置」とやらがポイントらしく・・・。PPPoEの場合は必ずここを通過するので、ここが混み合っていると通信が遅くなる。
対してIPoEはこの網終端装着を通過しない通信方式。
流れとしては、CPEルータからPPPoEで張ったようなトンネルなしでNGN網を通過したのち、VNEネットワークの入り口にあるホームゲートウェイを経由し、VNEネットワークを通ってインターネットまで出る。
VNEネットワークはISPが他事業社から借りているIPv6ネットワークで、正確ではないが、PPPoEの図で出てきた「ISPネットワークのIPoE版」というイメージ。
図に表すとこんな感じ。
IPoEの場合は以下の理由でPPPoEよりもインターネット通信がスムーズになっている。
- NGN網とVNEネットワークの間にあるホームゲートウェイが複数存在し、NGN網を通過してきたパケットがそれぞれのホームゲートウェイに分散されるため、輻輳が起こりづらい
- ゲートウェイルータの通信容量が網終端装置よりもかなり大きい(こちらのスライド3枚目にて、見方が正しければ通信容量は10倍)
- 単純にIPoEユーザの数がまだ少ないため、ホームゲートウェイに集まるパケットの絶対量が少ない
というような感じで、インターネットに関しては、PPPoEを用いたIPv4通信よりもIPoEを用いたIPv6通信の方が速い場合が多いらしい。
※「IPv6通信」というよりも「IPoE通信」が速い場合が多いという話なので、厳密に言うとIPv6であれば必ずしも速度が保証されるわけではない。例えばPPPoEを用いたIPv6通信は従来の速度とおそらくあまり変わらないはず
ルータでの処理速度がIPv6の方が速い
ルータの処理速度がIPv4よりもIPv6の方が速い理由は以下の2つ。
- ルータがチェックすべきヘッダが限定されたため
IPv6ヘッダの中身は基本ヘッダと拡張ヘッダに分けられるが、拡張ヘッダはオプションという形で必要な場合のみ追加することとなっている。そのためIPv4パケットでは必ずルータが処理する必要のあった項目もIPv6パケットでは基本的にスルーでよいのでルータの処理が速い
- ヘッダチェックサムフィールドが省略されているため
IPv4ヘッダには、ヘッダ内の情報が前のルーティングポイントで送信された時と比べて変わっていないかを調べるヘッダチェックサムというフィールドがあるが、IPv6ヘッダではこの項目は省略されている。このフィールドはルータを通過するごとに値が変わるためルータに負荷をかけていたが、省略されたためルータの処理が減っている
まとめていないまとめ
他にも理由を見つけたら追記します。
参考
localhostとアプリケーションサーバとWebサーバと
久しぶりにRailsアプリを開こうとしてlocalhost:3000叩いたら、接続できませんと言われた。結局別のアプリで立ち上げてた、ポート番号8080のサーバをダウンさせた後にRailsのポート番号3000のサーバを計2度立ち上げたら、正常にアプリを開くことができた。
ローカルでサーバは普通に複数立てれるっぽいので、この解決法が何か今後の参考となるかはかなり微妙だけれど、解決するまでの過程でlocalhost、Webサーバ、アプリケーションサーバあたりの話を少し調べたので、備忘録として記録する。
localhostとは
localhostは自分自身(のPC)を表すホスト名のこと。IPで表すと127.0.0.1で、このIPは自分しかアクセスしないのでプライベートアドレス。
ブラウザのアドレスバーにlocalhost:XXXと打ち込むと、自PC内で立ち上げているアプリケーションサーバにアクセスすることができるよう。XXXはポート番号で、Railsアプリではよく3000番を使用するらしい。
ちなみにポート番号は、通信においてアプリケーション層に非カプセル化を行う際、どのアプリケーションに向けて通信を送ればよいかを識別するために用いられるもの。普通であればHTTPは80番、HTTPSは443番だけど、コード側でそこは変えることができる。
アプリケーションサーバとWebサーバ
Rails開発においてWebサーバとアプリケーションサーバというのは異なるものであるらしく・・・
Webサーバは「ユーザーから送られてきた自サイトへのリクエストを受け取り、なんらかの処理を加えるプログラムであり、場合によってはRailsアプリケーションにリクエストを投げる」もので、アプリケーションサーバは「Railsアプリケーションを動かしている」ものらしい。
開発環境ではWebサーバを使うことは少ないが、本番環境ではWebサーバを立てるのが一般的らしいので、今自分がPC内で立ち上げているRails用のサーバはおそらくアプリケーションサーバだと思う。
ただサーバ立ち上げのために使っているPumaというソフト、調べていると「Webサーバ」と説明されているページと「アプリケーションサーバ」と説明されているページに分かれており、ちょっとこの辺はよく分からない。アプリケーションサーバにWebサーバの機能を組み込める、みたいな話も調べる中で見たので、もしかするとそういう構成になっているのかもしれない。
Railsアプリにおける通信の遷移
ブラウザ
→Webサーバ(Nginxなど)
→アプリケーションサーバ(Pumaなど)
→Rack
→アプリケーション(自分で書いたコード)にHTTPリクエストが届き、処理される
→行きと逆の順でブラウザまでHTTPレスポンスを届ける・・・
本番環境ではおそらく主にこういう通信になっていて、開発環境はここからWebサーバを引いた形になるのかなと。
まとめていないまとめ
Rackはボリュームある内容っぽかったので調べるのはまた今度に。
参考
127.0.0.1とlocalhostと0.0.0.0の違い - Qiita
サーバーレスにおいてロジックとは何か
知り合いのエンジニアの方に教えていただいた、サーバーレスアーキテクチャの構成要素の1つである「ロジック」の内容を整理していきます。この記事で説明する「ロジック」は、一般的にサーバーレスの分野で語られるロジックとは異なる点もあるとのことなのでご了承ください。
ただ筆者の理解不足と知識不足でどう見ても間違ったことを言っている、という箇所もあると思われるので、気づいた方はご指摘いただけると幸いです・・・。
サーバーレスアーキテクチャとは
こちらの記事にも書きましたが、まずサーバーレスアーキテクチャとは、サーバーの存在を意識する必要のない構成のことを指すそうです。
具体的にどのような構成かというと・・・。
構成は大きく、フロントエンド、ロジック、バックエンドの3つの要素に分けられます。
フロントエンドはWebアプリケーションで直接ユーザーの目に触れる部分を指します。例えばメールを送信するアプリで考えると、テキストを入力するスペースや送信ボタン、文字の大きさを変えるボタンなど、ユーザーが直接見て操作できる部分は全てフロントエンドに該当します。
それに対してバックエンドとは、Webサーバーやデータベース、およびその辺りで行われる処理のことを指します。
ロジックとは
上記のフロントとバックエンドは一般的にもよく聞く概念だと思われます。
ではロジックとはどういったものなのでしょうか。
ロジックとは、フロントからバックエンドまでの処理(機能)を1つのライブラリにまとめたものになります。ここにはフロントからバックエンドまでのデータ通信もその役割に含まれます。
なので例えば、「テキストを暗号化してサーバに送る」というロジックがあったとすると、①テキストを暗号化する機能、②暗号化されたテキストをサーバに送信する機能、③実際にフロントの端末からサーバに向けてデータ通信をする機能の3つが1つのライブラリにまとめられているイメージになります。
本来であればバックエンドに通信するまでに行う処理はフロント側のコードで書くものなのですが・・・
このようにその処理をロジックという要素に分離することで、フロント側のエンジニアはサービスの見た目に注力したコードを書けるようになります。
厳密にいうと見た目以外にも、ロジックに向けての入力値とバックエンドからの戻り値は意識したコードにする必要がありますが、従来のアーキテクチャと比較すると役割分担をはっきりさせることができるというわけです。
またロジックはJSで書かれたコードです。ブラウザからWebサーバにHTTPリクエストを投げた際に、HTMLやフロント用のJSなどと一緒に取得されます。そしてフロントとロジックの接点となるボタンをユーザがブラウザで押すと、ロジックのコードが走り出して色々処理してくれるという流れです。
またこれは本筋ではないですが、1つ1つの機能を実現するためにそれぞれ別に作成したJSのコードを1つのライブラリにまとめる処理のことをwebpackと呼ぶとのことです。
通信しないロジック
ロジックはフロントからバックエンドまでの処理をまとめたものだと説明しましたが、中には例外的にバックエンドに通信をしに行かないロジックもあります。
例えば、ログインのためメールアドレスを入力する際に、アドレスが特定の形にはまっていない場合などはバックエンドにアクセスしに行く必要がないため、通信をせずにブラウザに「正しいアドレスを入力してください」などと表示をする流れになります。
CRCとは何か
CRCとは
Cyclic Redundancy Checkの略。あるネットワーク機器から別のネットワーク機器に通信する際、送信データに誤りを検出する符号を付加することで、データの伝送誤りを検出する方法。
伝送途中のデータはノイズなどが入って破損するケースもあるため、CRCのような方法を用いて受信側の機器でデータの正常性を確認するらしい。
CRCの手順
- 送信側の機器で、送信するデータに対して特定の数値を用いて割り算を行う(この割り算とは、2進数で構成されているデータを、同じく2進数で構成されている特定の数で割るということ)。
- 割り算した余りの数値を誤りを検出する符号としてデータに付加する。これにより送信データは特定の数値で割り切れる数値となる。
- 受信側の機器で、受け取ったデータに対し送信側と同じ特定の数を用いて割り算を行う
- 結果が割り切れていればデータは正常に伝送されたと判断されるが、そうでない場合はデータは正常に伝送されなかったと判断され、破棄されたのち送信側の機器にデータの再送を要求する。
FCSとは
Frame Check Sequenceの略。イーサネットフレームに入っている、受信したデータに誤りがないかどうかをチェックするためのフィールド。
上記の手順2でいう「割り算した余りの数値」がこのFCSに入っている。多分。
参考
renderメソッドとは何か
renderメソッドとは
呼び出すビューを指定することができるメソッド。
通常Railsではコントローラのアクションが動くと同名のビューが呼び出されるが、そうでないビューを呼び出したい場合はこのrenderメソッドを使う。 コントローラでもビューでも使用可能。
ちなみに呼び出されたビューはerb(もしくはhamlかslim)からhtmlに変換された後、ブラウザに読み込まれて画面に描画される。 このhtmlファイルをブラウザが読み込んで描画するまでの流れを(ブラウザ)レンダリングと言う。
レンダリングはLoading、Scripting、Rendering、Paintingという4つの工程に分けられるとのことだが、脱線するのでここでは省略する。
コントローラでの主な使われ方
何かユーザーが入力したデータを保存したい時などに、saveメソッド、redirect_toメソッドと併せて使われる。 例えばこんな感じ。
def new @task = Task.new end def create @task = Task.new(task_params) if @task.save redirect_to tasks_url, notice: "タスク「#{task.name}」を登録しました" else render :new end end
上記のコードでは、saveによる検証結果がvalidationに引っかからずtrueであればredirect_toが動き、falseであればrenderが動く流れとなっている。
後者の場合だと、renderはコントローラのアクションを経由せずにビューをレンダリングするので、ユーザーが入力したデータを残したまま再びnewのビューが描画されることとなる。より細かくいうと、newアクションの@task = Task.newが実行されず、入力値を保持したcreateアクションの@taskがそのままnewのビューに渡されるので同じ状態の見た目が再びレンダリングされるという流れ。
ちなみに前者の場合だと、redirect_toによりtasks_url向けのHTTPリクエストが送信され、indexアクションを経由したのちindexビューがレンダリングされる。
ビューでの主な使われ方
ビューでrenderメソッドを使うのは部分テンプレートを呼び出す場合がほとんど。
部分テンプレートとは複数のビューの共通部分として作成するビュー。 これは「ソフトウェア開発全体において情報を重複させない」というRailsのDRY原則に基づいた手法。
renderメソッドを使って部分テンプレートを呼び出す際は、部分テンプレートを呼び出していることを明示的にするためにpartialオプションを使用する。こんな感じで。
<%= render partial: 'ファイル名' %>
参考
【Rails】renderメソッドの使い方を徹底解説! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト
マイグレーションでできることのまとめ
マイグレーションとは
SQLを直接使わずに、データベースのテーブルやカラムを変更できる仕組み。 変更の1つ1つをRubyのファイル(マイグレーションファイル)として実現している。
マイグレーション作成コマンド
$ rails generate migration クラス名
クラス名は何でもOKだが、基本は「すること+テーブル名」。 例えばUserテーブルのhogeカラムを変更したい場合は ChangeHogeToUser で、Userテーブルを削除したい場合は DropUser となる。
マイグレーションでできること
マイグレーションを可逆的にする(up, down)
upはマイグレーション実行時に実行され、downはロールバック実行時に実行されるメソッド。 下記のコードでは、マイグレーションを実行するとテーブルが削除され、ロールバックを実行すると元のテーブルが復元される。
class DropUser < ActiveRecord::Migration[5.0] # 変更内容 def up drop_table :users end # 変更前の状態 def down create_table :users do |t| t.string :uuid t.string :name t.timestamps end end end
テーブル名の変更
下記のコードではテーブル名をissuesからtasksに変更している。
class RenameIssueToTask < ActiveRecord::Migration def change rename_table :issues, :tasks end end
既存カラムの変更
下記のコードではUserモデルのuuidカラムをNOT NULL制約に変更している。
class ChangeColumnToUser< ActiveRecord::Migration def up change_column :users, :uuid, :string, null: false, default: 0 end def down change_column :users, :uuid, :string, null: true, default: 0 end end
既存カラムを変更する際のオプション
①NULL / NOT NULL
# NULL change_column :テーブル名, :カラム名, :型, null: true # NOT NULL change_column :テーブル名, :カラム名, :型, null: false
②インデックス
特定のカラムからデータを取得する際に、そのカラムのデータを複製して検索を行いやすくするためのもの。例えばUsersテーブルのnameカラムにインデックスを張ることで、アルファベット順にnameを並べ替えて検索しやすくしてくれる(並び替えた後にどういった検索処理が行われるのかはよく分かってない)
change_column :テーブル名, :カラム名, :型, index: true
③デフォルト値
change_column :テーブル名, :カラム名, :型, default: "piyo"
④長さ
change_column :テーブル名, :カラム名, :string, limit: 12
⑤小数部の精度を指定する
change_column :テーブル名, :カラム名, :型, precision: 6
カラム名の変更
class RenameAgeToNenrei < ActiveRecord::Migration def change rename_column :User, :age, :nenrei end end
カラムを追加/削除する
class AddColumnToUser < ActiveRecord::Migration def change # 追加 add_column :users, :piyo, :string # 削除 remove_column :users, :piyo, :string # まとめて削除 remove_columns :users, :column_1, :column_2 [, ...] # 追加する場所を指定する場合 add_column :users, :piyo, :string, :after => :uuid end end
インデックスを追加/削除する
複合ユニークの使い所は、例えばユーザAから投稿Aへのいいねなど。この組み合わせは一意にしておかないと、この例で言うと同じユーザが1つの投稿に何度もいいねできることになる。
class AddIndexToUser < ActiveRecord::Migration def change # 追加 add_index :users, :name # ユニーク追加 add_index :users, :name, :unique => true # 削除 remove_index :users, :name # 複合インデックスの場合 add_index :users, [:name, :name2] # 複合ユニークの場合 add_index :user_accounts, [:provider, :uid], :unique => true end end
外部キーを作成/削除する
外部キーとは、テーブル同士の紐づけに用いるカラムのこと。usersテーブルとpostsテーブルがあったとして、このpostはどのuserが投稿したものなんだろう?というのをpostsテーブルで確認したい時に、user_idのカラムを用いる。user_idはusersテーブルでは主キーと呼ばれる。
class CreateUserAccounts < ActiveRecord::Migration[5.0] def change create_table :user_accounts do |t| t.references :user, index: true, foreign_key: true t.timestamps end end end
モデル作成時に作成する場合 $ rails g model UserAccount user:references
class AddRefUser < ActiveRecord::Migration def change add_reference :users, :article, foreign_key: true # 外部キーの削除はこちら remove_foreign_key :users, column: :article_id # カラムも一緒に削除する場合はこちら remove_reference :users, :article, foreign_key: true end end
既存のテーブルに追加する場合 $ rails g migration AddArticleToUsers article:references
ポリモーフィックを作る
ポリモーフィックとは、複数の異なる型やオブジェクトに対して共通のインタフェース(Rubyの世界でならメソッドと言い換えてもよさそう)を提供すること。すごく単純化して言うと、同じ名前のメソッドがそれぞれ異なるクラスで規定されていること。もちろんそのメソッドの内容はそれぞれ異なる。ダックタイピングの一種。
class CreateMessages < ActiveRecord::Migration[5.0] def change create_table :messages do |t| t.references :messagable, polymorphic: true t.text :message t.timestamps end end end
モデル作成時に作成する場合:$ rails g model message messagable:references{polymorphic} message:text
実際にポリモーフィックを利用したコードは例えば以下のような感じ。 EmployerとEmployee、どちらもsender_nameという同じ名前のメソッドを所有しているが、 どちらのsender_nameを使う際もコードはMessage.find(params[:id]).messageable.sender_nameとなる。
class Message < ApplicationRecord belongs_to :messageable, polymorphic: true end class Employer < ApplicationRecord has_many :messages, as: :messageable # 共通のインターフェース def sender_name company_name end end class Employee < ApplicationRecord has_many :messages, as: :messageable # 共通のインターフェース def sender_name "#{last_name} #{first_name}" end end