Packages
bandit
0.5.6
1.12.0
1.11.1
1.11.0
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
retired
1.9.0
1.8.0
1.7.0
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.0
1.2.3
1.2.2
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
1.0.0-pre.18
1.0.0-pre.17
1.0.0-pre.16
1.0.0-pre.15
1.0.0-pre.14
1.0.0-pre.13
1.0.0-pre.12
1.0.0-pre.11
1.0.0-pre.10
1.0.0-pre.9
1.0.0-pre.8
1.0.0-pre.7
1.0.0-pre.6
1.0.0-pre.5
1.0.0-pre.4
1.0.0-pre.3
1.0.0-pre.2
1.0.0-pre.1
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.2.3
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
A pure-Elixir HTTP server built for Plug & WebSock apps
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/bandit/http2/stream_task.ex
defmodule Bandit.HTTP2.StreamTask do
@moduledoc false
# This Task is where an actual Plug is executed, within the context of an HTTP/2 stream. There
# is a bit of split responsiblity between this module and the `Bandit.HTTP2.Adapter` module
# which merits explanation:
#
# Broadly, this module is responsible for the execution of a Plug and does so within a Task
# process. Task is used in preference to GenServer here because of the shape of the
# `Plug.Conn.Adapter` API (implemented by the `Bandit.HTTP2.Adapter` module). Specifically, that
# API requires blocking semantics for the `Plug.Conn.Adapter.read_req_body/2`) call and expects
# it to block until some underlying condition has been met (the body has been read, a timeout
# has occurred, etc). The events which 'unblock' these conditions typically come from within the
# Connection, and are pushed down to streams as a fundamental design decision (rather than
# having stream processes query the connection directly). As such, it is much simpler for Task
# processes to wait in an imperative fashion using `receive` calls directly.
#
# To contain these design decisions, the 'connection-facing' API for sending data to a stream
# process is expressed on this module (via the `recv_*` functions) even though the 'other half'
# of those calls exists in the `Bandit.HTTP2.Adapter` module. As a result, this module and the
# Handler module are fairly tightly coupled, but together they express clear APIs towards both
# Plug applications and the rest of Bandit.
use Task
alias Bandit.HTTP2.{Adapter, Errors, Stream}
# A stream process can be created only once we have a stream id & set of headers. Pass them in
# at creation time to ensure this invariant
@spec start_link(
pid(),
Stream.stream_id(),
Plug.Conn.headers(),
Plug.Conn.Adapter.peer_data(),
Bandit.plug()
) :: {:ok, pid()}
def start_link(connection, stream_id, headers, peer, plug) do
Task.start_link(__MODULE__, :run, [connection, stream_id, headers, peer, plug])
end
# Let the stream task know that body data has arrived from the client. The other half of this
# flow can be found in `Bandit.HTTP2.Adapter.read_req_body/2`
@spec recv_data(pid(), iodata()) :: :ok | :noconnect | :nosuspend
def recv_data(pid, data), do: Process.send(pid, {:data, data}, [])
# Let the stream task know that the client has set the end of stream flag. The other half of
# this flow can be found in `Bandit.HTTP2.Adapter.read_req_body/2`
@spec recv_end_of_stream(pid()) :: :ok | :noconnect | :nosuspend
def recv_end_of_stream(pid), do: Process.send(pid, :end_stream, [])
# Let the stream task know that the client has reset the stream. This will terminate the
# stream's handling process
@spec recv_rst_stream(pid(), Errors.error_code()) :: true
def recv_rst_stream(pid, error_code), do: Process.exit(pid, {:recv_rst_stream, error_code})
@spec run(
pid(),
Stream.stream_id(),
Plug.Conn.headers(),
Plug.Conn.Adapter.peer_data(),
Bandit.plug()
) ::
any()
def run(connection, stream_id, headers, peer, {plug, plug_opts}) do
headers = combine_cookie_crumbs(headers)
uri = uri(headers)
# Build an Adapter struct and call the actual underlying Plug module
{Adapter, %Adapter{connection: connection, peer: peer, stream_id: stream_id, uri: uri}}
|> Plug.Conn.Adapter.conn(method(headers), uri, peer.address, headers)
|> plug.call(plug_opts)
|> case do
%Plug.Conn{state: :unset} ->
raise(Plug.Conn.NotSentError)
%Plug.Conn{state: :set} = conn ->
Plug.Conn.send_resp(conn)
%Plug.Conn{state: :chunked, adapter: {adapter_mod, req}} = conn ->
adapter_mod.chunk(req, "")
conn
%Plug.Conn{} = conn ->
conn
_ = conn ->
raise("Expected #{plug}.call/2 to return %Plug.Conn{} but got: #{inspect(conn)}")
end
end
# Per RFC7540§8.1.2.5
defp combine_cookie_crumbs(headers) do
{crumbs, other_headers} = headers |> Enum.split_with(fn {header, _} -> header == "cookie" end)
combined_cookie = Enum.map_join(crumbs, "; ", fn {"cookie", crumb} -> crumb end)
[{"cookie", combined_cookie} | other_headers]
end
defp method(headers), do: get_header(headers, ":method")
defp uri(headers) do
scheme = get_header(headers, ":scheme")
authority = get_header(headers, ":authority")
path = get_header(headers, ":path")
# Parse a string to build a URI struct. This is quite a hack and isn't tolerant
# of requests proxied from an HTTP/1.1 client (RFC7540§8.1.2.3 specifies that
# :authority MUST NOT be set in this case). In general, canonicalizing URIs is
# a delicate process & rather than building a half-baked implementation here it's
# better to leave a simple and ugly hack in place so that future improvements are
# obvious. Future paths here are discussed at https://github.com/elixir-plug/plug/issues/948)
URI.parse(scheme <> "://" <> authority <> path)
end
defp get_header(headers, header, default \\ nil) do
case List.keyfind(headers, header, 0) do
{_, value} -> value
nil -> default
end
end
end