Skip to content

Handler

Lyo Kato edited this page Jan 4, 2021 · 42 revisions

Callbacks

init/2

Check out the following example

@impl Requiem
def init(conn, client) do

  Logger.debug("new connection is established, connection_id: #{conn.dcid}")

  if client.origin == "my-origin.example.com" do

    my_state = %{path: client.path}
    {:ok, conn, state}

  else

    {:stop, 400, :forbidden}

  end
end

GenServer.init/2に似ている役割を持っています。QUICのハンドシェイクに成功した後に呼ばれます。

Arguments

引数が2つあり、ConnectionStateと、ClientIndicationです。

必要ならClientIndicationのパラメータであるoriginとpathの値を確認し、任意の処理を行うことが出来ます。 上の例ではoriginが正しくなければここで切断し、originが期待通りであれば接続を維持するような処理になっています。

WebTransportモードをoffにした場合は、client indicationの部分はnilが渡されます。詳しくはWebTransportのページを参照してください。

originの扱いは、HTTPにおけるCORS と同じような話だと捉えてもらってよいです。

pathはこのサーバーにアクセスされるときに指定されたURLのパスの部分が入ります。

quic-transport://my-service.example.org/foobar というURLでアクセスされたとしたら、/foobarの部分が入ります。

必要によっては、この値を見て、サービスを分岐させて見ると良いでしょう。

Return value

特に問題が無ければ、{:ok, conn, state}のように値を返してください。自由にstateを作って渡すところは、GenServer.init/1と同じです。 違うのはConnectionStateの構造体も一緒に引き回すということだけです。

問題があって接続をここで遮断したい場合は{:stop, error_code, reason}という形のデータを返します。

ここで使われたerror_codeとreasonは、クライアントに対して送信するCLOSE FRAMEに使われます。

  @callback init(conn :: Requiem.ConnectionState.t(), client :: Requiem.ClientIndication.t()) ::
              {:ok, Requiem.ConnectionState.t(), any}
              | {:ok, Requiem.ConnectionState.t(), any, timeout | :hibernate}
              | {:stop, non_neg_integer, atom}

handle_info/3

こちらも例を見れば何となく理解できるかと思います。

@impl Requiem
def handle_info({:my_event, my_param}, conn, state) do
   do_something(my_param)
  {:noreply, conn, state}
end

引数とレスポンスに、ConnectionStateの構造体を引き回す以外は、ほぼGenServer.handle_info/2と同じようなものだと思ってください。 このプロセスがメッセージを受信したときに呼ばれます。

  @callback handle_info(
              request :: term,
              conn :: Requiem.ConnectionState.t(),
              state :: any
            ) ::
              {:noreply, Requiem.ConnectionState.t(), any}
              | {:noreply, Requiem.ConnectionState.t(), any, timeout | :hibernate}
              | {:stop, non_neg_integer, atom}

handle_cast/3

こちらも上記のhandle_info/2と同じように、対応するGenServer.handle_cast/2とほぼ同じ役割の関数だと考えてください。

@impl Requiem
def handle_cast({:my_event, my_param}, conn, state) do
   do_something(my_param)
  {:noreply, conn, state}
end
  @callback handle_cast(
              request :: term,
              conn :: Requiem.ConnectionState.t(),
              state :: any
            ) ::
              {:noreply, Requiem.ConnectionState.t(), any}
              | {:noreply, Requiem.ConnectionState.t(), any, timeout | :hibernate}
              | {:stop, non_neg_integer, atom}

handle_call/4

@impl Requiem
def handle_call({:my_event, my_param}, from, conn, state) do
  {:reply, conn, state}
end

こちらも上記のhandle_info/2やhandle_cast/2と同様です。

  @callback handle_call(
              request :: term,
              from :: pid,
              conn :: Requiem.ConnectionState.t(),
              state :: any
            ) ::
              {:noreply, Requiem.ConnectionState.t(), any}
              | {:noreply, Requiem.ConnectionState.t(), any, timeout | :hibernate}
              | {:reply, any, Requiem.ConnectionState.t(), any}
              | {:reply, any, Requiem.ConnectionState.t(), any, timeout | :hibernate}
              | {:stop, non_neg_integer, atom}

terminate/3

同様にterminate/3という関数もあります。

@impl Requiem
def terminate(reason, conn, state) do
  :ok
end
  @type terminate_reason :: :normal | :shutdown | {:shutdown, term} | term

  @callback terminate(
              reason :: terminate_reason,
              conn :: Requiem.ConnectionState.t(),
              state :: any
            ) :: any

handle_stream/4

ストリームに受信したデータを処理することが出来る関数です

@impl Requiem
def handle_stream(stream_id, data, conn, state) do

  case do_something(stream_id, data) do
    {:ok, good_result} -> {:ok, conn, state}
    {:error, :bad_result} -> {:stop, 400, :bad_request}
  end

end

Arguments

connとstateはもう見慣れたと思います。最初の2つの引数に指定されたstream_idとデータが渡されます。

Return value

問題がなければ{:ok, conn, state}という形でデータを返してください。 問題があり、接続を閉じたければ{:stop, error_code, reason}という形でデータを返してください。

  @callback handle_stream(
              stream_id :: non_neg_integer,
              data :: binary,
              conn :: Requiem.ConnectionState.t(),
              state :: any
            ) ::
              {:ok, Requiem.ConnectionState.t(), any}
              | {:ok, Requiem.ConnectionState.t(), any, timeout | :hibernate}
              | {:stop, non_neg_integer, atom}

handle_dgram/3

データグラムとして送信されたデータを扱えます。 stream_idのようなものが存在しないだけで、それ以外はhandle_stream/4と同じです

@impl Requiem
def handle_dgram(data, conn, state) do

  case do_something(stream_id, data) do
    {:ok, good_result} -> {:ok, conn, state}
    {:error, :bad_result} -> {:stop, 400, :bad_request}
  end

end

enable_dgramのconfigが指定されている必要があります。

See Configuration for details.

  @callback handle_dgram(
              data :: binary,
              conn :: Requiem.ConnectionState.t(),
              state :: any
            ) ::
              {:ok, Requiem.ConnectionState.t(), any}
              | {:ok, Requiem.ConnectionState.t(), any, timeout | :hibernate}
              | {:stop, non_neg_integer, atom}

Methods

ハンドラ内で以下の関数が利用できるようになっています。


close/0

各コールバックの戻り値で{:stop, error_code, reason}を返すことでコネクションの切断は可能ではありますが、 コールバックのタイミングとは別の非同期の処理の中で切断をしたいときがあるでしょう。

そういう場合にはこのcloseを呼んでください。


close/2

error_code, reasonを渡せるバージョンのcloseもあります。上のバージョンは正常終了として扱われますが、こちらはアプリケーションエラーを表現できます。

close(error_code, reason)

stream_send/2

ストリームを通して、クライアントにデータを送信したいときに使います

stream_send(stream_id, data)

このように使えますが、stream_idの値には注意してください。

See Stream for details.


dgram_send/1

ストリームではなくデータグラムを送信したいときに使えます。

enable_dgramのconfigが指定されている必要があります。

See Configuration for details.

dgram_send(data)

ConnectionState

各コールバックで渡されるconnの実態はRequiem.ConnectionStateモジュールで定義されている構造体です。

以下のようなフィールドを持っています

  • conn.scid
  • conn.dcid
  • conn.odcid

これらはQUICのフレームで利用される接続IDで、クライアントが接続開始の際に指定したものです。 (サーバー側がクライアントに対して指定したものではありません) odcidは、retryフレームを利用したトークンによるバリデーションを行う前に指定されたものです。

また、相手のAddressを持っています

  • conn.address

これはRequiem.Addressモジュールの構造体です。

  • conn.address.host
  • conn.address.port

のように相手の接続に関する情報にアクセスできます。


ClientIndication

ConnectionState以外にも知っておかなければならないものが一つあります。 init/2の第二引数として渡されるClientIndicationです。

@impl Requiem
def init(conn, client) do
  my_state = %{}
  {:ok, conn, my_state}
end

これは、WebTransportモードのときだけ利用されます。 WebTransportモードではない場合はここにはnilが渡されます。 (WebTransportモードについて詳しくは、ConfigurationWebTransportを参照してください。)

WebTransportモードの場合は、クライアントからstream_idが2のstreamを通して受け取ったデータを自動的にパースし、init/2に渡します。

ここで、originpathの値をチェックして、問題があれば切断することが出来ます。

@impl Requiem
def init(conn, client) do
  if client.origin == "example.org"  do
    my_state = %{path: client.path}
    {:ok, conn, my_state}
  else
    {:stop, 400, :shutdown}
  end
end
Clone this wiki locally