defmodule Satellite do @behaviour :gen_statem alias Satellite.Request defstruct [:socket, :config, :conn, pipeline: [], request: %Request{}] @impl true def callback_mode(), do: :handle_event_function def start_link(socket, opts) do :gen_statem.start_link(__MODULE__, {socket, opts}, []) end @impl true def init({socket, config}) do {:ok, :waiting, %__MODULE__{socket: socket, config: config}, [{:next_event, :internal, :wait}]} end @impl true def handle_event(:internal, :wait, :waiting, %{socket: socket, config: %{pipeline: pipeline}} = data) do {:ok, conn} = :gen_tcp.accept(socket) :gen_tcp.controlling_process(conn, self()) n_data = %{data | conn: conn, pipeline: pipeline, request: %Request{}} set_next_step(n_data, <<>>) end def handle_event(:internal, :read, _, %{conn: conn} = data) do case :gen_tcp.recv(conn, 0, 1000) do {:ok, packet} -> {:keep_state_and_data, [{:next_event, :internal, {:parse, packet}}]} {:error, reason} -> {:next_state, :response, %{data | request: "Error: #{inspect reason}"}, [{:next_event, :internal, :send_response}]} end end def handle_event(:internal, {:parse, packet}, {type, name, fun, acc}, %{request: request} = data) do case fun.(request, packet, acc) do {:cont, n_request, n_acc} -> {:next_state, {type, name, fun, n_acc}, %{data | request: n_request}, [{:next_event, :internal, :read}]} {:done, n_request, remaining} -> set_next_step(%{data | request: n_request}, remaining) {:error, reason} -> {:next_state, :response, %{data | request: "#{inspect reason}"}, [{:next_event, :internal, :send_response}]} end end def handle_event(:internal, :send_response, :response, %{conn: conn, request: request} = data) do :gen_tcp.send(conn, make_response(request)) {:keep_state, data, [{:next_event, :internal, :close}]} end def handle_event(:internal, :close, _, %{conn: conn} = data) do :gen_tcp.close(conn) {:next_state, :waiting, %{data | conn: nil}, [{:next_event, :internal, :wait}]} end defp set_next_step(%{pipeline: [h | t]} = data, remaining) do event = case remaining do <<>> -> :read _ -> {:parse, remaining} end {:next_state, h, %{data | pipeline: t}, [{:next_event, :internal, event}]} end defp set_next_step(%{pipeline: [], request: request} = data, _remaining) do n_request = "#{inspect request}" {:next_state, :response, %{data | request: n_request}, [{:next_event, :internal, :send_response}]} end defp make_response(request) do b = :erlang.iolist_to_binary(request) :io_lib.fwrite( "HTTP/1.0 200 OK\nContent-Type: text/html\nContent-Length: ~p\n\n~s", [:erlang.size(b), b] ) end end