はじめに
ElixirでのWebアプリケーション開発において出会う様々な用語や概念を深堀します。今回は、Phoenixフレームワークを使用した際のHTTP通信とWebソケット通信の違いに焦点を当てて解説していきます。
Phoenixフレームワークにおけるクライアントとサーバー間でのデータのやり取り
Phoenixフレームワークでは、HTTP通信とWebソケット通信の両方がサポートされています。これにより、静的なウェブページの提供からリアルタイム通信を必要とするアプリケーションまで、幅広いニーズに対応できます。
HTTP通信とWebソケット通信の特徴
HTTP通信
- 非持続的な接続: HTTPはリクエスト/レスポンスモデルに基づいており、クライアントがリクエストを送信した後にサーバーからレスポンスを受け取ると、その接続は通常終了
- ステートレス: 各HTTPリクエストは独立しており、以前のリクエストからの情報は保存されません(セッションやクッキーを使用して間接的に状態を管理する方法はある)
- リクエスト主導型: データの交換はクライアントからのリクエストによってのみ開始
Webソケット通信
- 持続的な接続: Webソケットは接続を確立した後、その接続を開いたままにしておくことで、サーバーとクライアント間でリアルタイムのデータ交換が可能
- 双方向通信: サーバーとクライアントは、接続が開いている間はいつでもお互いにデータを送信できます。これにより、リアルタイムアプリケーションの実装が容易に
- フルデュプレックス通信: サーバーとクライアントは同時にデータを送受信できるため、より効率的な通信が可能
HTTP通信の実装方法
Phoenixフレームワークでは、Plug.Connを使用してHTTPリクエストの情報を取得し、レスポンスを構築します。
defmodule MyAppWeb.MyController do
use MyAppWeb, :controller
import Plug.Conn, only: [get_req_header: 2]
def my_action(conn, _params) do
headers = conn.req_headers
user_agent = get_req_header(conn, "user-agent")
text(conn, "User-Agent: #{user_agent}")
end
end
Plugライブラリとは
ElixirでHTTPミドルウェアとアプリケーションを構築するための仕組みを提供するライブラリです。Plugを使用することで、開発者はリクエスト処理のパイプラインを簡単に定義し、リクエストの変換や特定の条件下でのレスポンスの返し方を制御できます。
Plug.Connとは
Plug.Conn
とは、HTTP接続(リクエストとレスポンス)を処理するための構造体とユーティリティ関数を提供するものです。
Plug.Conn
を使用することで開発者はリクエストの受け取りからレスポンスの生成に至るまでの処理を細かく制御できます。この構造体を使うことで、認証、データのバリデーション、レスポンスのカスタマイズなど、リクエストのライフサイクルにわたってさまざまな処理を効率的かつ柔軟に、実行できるようになります。
コード内で出てくるconnとは
先ほどの例で出てきたconn
はPlug.Conn
構造体のインスタンスです。このconn
オブジェクトを通してHTTPリクエストの処理とHTTPレスポンスの生成が行われます。
connの具体的な渡され方
Phoenixフレームワークを使用するWebアプリケーションでは、HTTPリクエストがサーバーに届くと、Plugパイプラインを通じて処理が行われ、最終的に特定のコントローラーのアクション(関数)にconn
が渡されます。
Plugパイプラインはrouter.ex
の以下のような設定
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloServerWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_user
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", PjCanolaServerWeb do
pipe_through [:browser, :require_verify_header]
live "/", PageLive
end
ファイル内で定義されているpipeline :browser do
ブロックやpipeline :api do
ブロック内にあるplug
宣言は、Plugパイプラインの一部です。
これらのplugは、Phoenixフレームワークがリクエストを処理する際に、Plug.Conn構造体を通して順番に実行される一連のミドルウェア(または操作)を定義しています。
上記のplug :fetch_current_user
の定義は以下。
@spec fetch_current_user(Plug.Conn.t(), keyword()) :: Plug.Conn.t()
def fetch_current_user(conn, _opts) do
{user_token, conn} = ensure_user_token(conn)
user = user_token && Accounts.get_user_by_session_token(user_token)
conn = assign(conn, :current_user, user)
end
リクエストに含まれるユーザートークンを使用して現在のユーザーを識別し、その情報をconnに割り当てることで、以降の処理でユーザー情報に基づいたロジックを実行できるようにしています。
基本の整理
基本が曖昧な初学者向けに(私w)上記以外のことも整理しました。
パイプラインとは
pipeline :browser do ... end
のように定義されるパイプラインは、その中に含まれる一連のplugによって構成されます。Phoenixにおいてパイプラインはplugパイプライのみなのでパイプライン=Plugパイプラインです。(PhoenixフレームワークおよびPlugライブラリにおけるパイプラインは、一連のplugを通じてHTTPリクエストを処理するためのメカニズムという意味です。)
つまり、Plug.Conn構造体を通して一連のplug処理を行うことを意味します。ここでのplug
は、リクエストを受け取り、何らかの操作を施し、次のplugへ渡す関数またはモジュールです。
それをまとめたのがパイプラインとなります。
パイプラインの役割
- コンテントタイプの受け入れ
- セッションの取得と管理
- フラッシュメッセージの取得
- CSRF保護の適用
- セキュリティヘッダーの追加
- 現在のユーザーの取得
トークンとは
トークンは、認証やセッション管理においてユーザーを一意に識別するために使用される文字列です。ウェブアプリケーションにおいて、トークンはユーザーがログインした際に生成され、クライアント(例えば、ブラウザのクッキー)に保存されることが多いです。その後のリクエストでは、このトークンをサーバーに送信してユーザーを認証します。
まとめ
パイプラインを定義することで、複数のルートやコントローラー間で共通の処理を再利用できるようになります。例えば、ウェブアプリケーションの全てのページに対する共通のセキュリティポリシーを適用する場合、それをパイプライン内で定義し、必要なルートにpipe_through
を使用して適用します。
パイプラインは、PhoenixやPlugにおいて非常に強力な概念であり、HTTPリクエストの処理を柔軟かつ効率的に構成するためのキーとなります。
最後に
PhoenixフレームワークはHTTP通信とWebソケット通信の二つの通信方法が混在しており、Plug.conn
はHTTP通信なんだという理解ができるとセッション管理のあたりが徐々にわかってきました。
誤りなどありましたらご指摘お願いします!
また、thewaggleではElixirでの開発にジョインしてくださる仲間を募集しています!
ご興味ある方、お気軽にご連絡ください。