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

It has a role similar to GenServer.init/2, and is called after a successful QUIC handshake.

Arguments

There are two arguments, ConnectionState and ClientIndication.

If necessary, you can check the values of the ClientIndication parameters, origin and path, and do whatever you want. In the example above, if the origin is not correct, the connection is disconnected at this point, and if the origin is as expected, the connection is maintained.

If WebTransport mode is turned off, the client indication variable will be passed as nil. See the WebTransport mode page for details.

The handling of origin is similar to that of CORS in HTTP.

path is the part of the URL path specified when this server is accessed.

If the URL is quic-transport://my-service.example.org/foobar, then the /foobar part will be included.

You may want to look at this value to see if you can branch the service if necessary.

Return value

If there are no problems, return a value like {:ok, conn, state}. It is the same as GenServer.init/1 in that you can freely create and return a state. The only difference is that the ConnectionState structure is passed around as well.

If there is a problem and you want to shut down the connection here, it will return data of the form {:stop, error_code, reason}.

The error_code and reason used here will be used in the CLOSE FRAME to be sent to the client.

  @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

This one is also somewhat understandable if you look at the example.

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

You can think of it as almost the same as GenServer.handle_info/2, except that it pulls around the ConnectionState structure for the arguments and return value. This callback is called when the process receives a message.

  @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

As with handle_info/2 above, you can think of this function as having almost the same role as the corresponding 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

This is also the same as handle_info/2 and handle_cast/2 above.

  @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

Similarly, there is a function called 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

A function that can process data received in a stream.

@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

You may be familiar with conn and state by now. The stream_id and data specified in the first two arguments are passed.

Return value

If there is no problem, return data in the form of {:ok, conn, state}. If there is a problem and you want to close the connection, return data in the form of {: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