defmodule PlugWithCustomCall do
use Plug.Builder
plug Plug.Logger
plug Plug.Head
def call(conn, opts) do
super(conn, opts) # calls Plug.Logger and Plug.Head
assign(conn, :called_all_plugs, true)
end
end
defmodule HelloPhoenix.Router do
use HelloPhoenix.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
# Other scopes may use custom stacks.
# scope "/api", HelloPhoenix do
# pipe_through :api
# end
end
def start(_type, [plug]) do
{:ok, socket} = :gen_tcp.listen(port,
[:binary, packet: :line, active: false, reuseaddr: true])
Logger.info "Accepting connections on port #{port}"
opts = plug.init(%{})
Task.start_link(fn -> loop_acceptor(socket, {plug, opts}) end)
{:ok, self()}
end
defp loop_acceptor(socket, plug) do
{:ok, client} = :gen_tcp.accept(socket)
serve(client, %Request{})
|> respond(plug, client)
:gen_tcp.close(client)
loop_acceptor(socket, plug)
end
%Stupify.Request{
awaiting: :statusline,
headers: [],
statusline: ""
}
defp serve(socket, %Request{awaiting: :response} = req), do: req
defp serve(socket, req) do
req = socket
|> read_line()
|> parse(req)
serve(socket, req)
end
defp parse(data, %Request{awaiting: :statusline} = req) do
%{req | statusline: data, awaiting: :headers}
end
defp parse("", %Request{awaiting: :headers} = req) do
%{req | awaiting: :response}
end
defp parse(data, %Request{awaiting: :headers} = req) do
%{req | headers: [parse_header(data) | req.headers] , awaiting: :headers}
end
defp respond(req, {plug, opts}, socket) do
Request.build_conn(req, socket)
|> plug.call(opts)
end
def build_conn(%Stupify.Request{} = req, socket) do
{ verb, path, version } = parse_statusline(req.statusline)
headers = Enum.into(req.headers, %{})
IO.inspect %Plug.Conn{
host: headers["host"],
method: verb,
request_path: path,
req_headers: headers,
scheme: :http,
adapter: {Stupify, socket},
owner: self()
}
end
def send_resp(%Conn{adapter: {adapter, payload}, state: :set, owner: owner} = conn) do
adapter.send_resp(payload, conn.status, conn.resp_headers, conn.resp_body)
end
defmodule Plug.Conn do
# ...
def send_resp(socket, status, headers, body) do
reason = Plug.Conn.Status.reason_phrase(status)
write_line("HTTP/1.1 #{status} #{reason}", socket)
send_headers headers, socket
write_line("", socket)
write_line(body, socket)
{:ok, nil, socket}
end
end
defmodule Plug.Conn.Adapter do
@callback send_resp(payload, Conn.status, Conn.headers, Conn.body) ::
{:ok, sent_body :: binary | nil, payload}
@callback send_file(payload, Conn.status, Conn.headers, file :: binary,
offset :: integer, length :: integer | :all) ::
{:ok, sent_body :: binary | nil, payload}
@callback send_chunked(payload, Conn.status, Conn.headers) ::
{:ok, sent_body :: binary | nil, payload}
@callback chunk(payload, Conn.status) ::
:ok | {:ok, sent_body :: binary, payload} | {:error, term}
@callback read_req_body(payload, options :: Keyword.t) ::
{:ok, data :: binary, payload} |
{:more, data :: binary, payload} |
{:error, term}
@callback parse_req_multipart(payload, options :: Keyword.t, fun) ::
{:ok, Conn.params, payload} | {:more, Conn.params, payload}
end