Combo.Conn (combo v0.10.0)

View Source

Function plugs and %Plug.Conn{} helpers in the scope of Combo.

Summary

Functions

Performs content negotiation according to the given formats.

Gets the action name as an atom, or raises if unavailable.

A plug that may convert a JSON response into a JSONP one.

Adds key-value pairs to the connection's assigns.

Clears flash.

Gets the controller module as an atom, or raises if unavailable.

Returns the current request path with its default query params.

Returns the current request path with the given query params.

Returns the current request url with its default query params.

Returns the current request URL with the given query params.

Deletes the CSRF token from the process dictionary.

Gets the endpoint module as an atom, or raises if unavailable.

Fetches the flash, and puts it into assigns.

Gets or generates a CSRF token.

Gets the request format, such as "json", "html".

Gets previous url.

Sends HTML response.

Sends JSON response.

Gets the current layout for the given format.

Merges a enumerable into the flash.

Enables CSRF protection.

Puts a message under the key into flash.

Sets the given format into the connection.

Sets the layout for rendering.

Sets the layout for rendering if one was not set yet.

Sets the view for rendering if one was not set yet.

Puts current url to the session before sending the response, and it will be available as previous url for subsequence requests.

Sets the URL string or %URI{} to be used for URL generation.

Puts headers that improve browser security.

Sets the URL string or %URI{} to be used for the URL generation of statics.

Sets the view for rendering.

Sends redirect response to the given url.

Sends redirect response to the previous location.

Render the given template or the default template specified by the current action with the given assigns.

Renders the given template and assigns based on the conn information.

Gets the router module as an atom, or raises if unavailable.

Sends the given file or binary as a download.

Generates a status message from the template name.

Sends text response.

Gets the current view for the given format.

Gets the current view for the given format, or raises if no view was found.

Returns the template name rendered in the view as a string, or nil if no template was rendered.

Returns the template name rendered in the view as a string, or raises if no template was rendered.

Types

assigns()

@type assigns() :: keyword() | map()

format()

@type format() :: atom() | binary()

layout()

@type layout() :: {module :: module(), name :: atom() | String.t()} | false

Layout can be:

  • {module, layout}, where the module is the layout module, and the layout is the function name as an atom in the layout module. For example: {DemoLayout, :app}.

  • false, which means disabling the layout.

layout_formats()

@type layout_formats() :: [{format(), layout()}]

template()

@type template() :: atom() | String.t()

view()

@type view() :: module() | false

view_formats()

@type view_formats() :: [{format(), view()}]

Functions

accepts(conn, accepted)

@spec accepts(Plug.Conn.t(), [binary()]) :: Plug.Conn.t()

Performs content negotiation according to the given formats.

It receives a connection, a list of formats that the server is capable of processing, and then performs content negotiation based on the request information:

  1. request parameter "_format". If it is present, it is considered to be the format desired by the client.

  2. request header "accept". Fallback to parse this header and find a matching format accordingly.

It is important to notice that browsers have historically sent bad accept headers. For this reason, this function will default to "html" format whenever:

  • the accepted list of arguments contains the "html" format.

  • the accept header specified more than one media type preceded or followed by the wildcard media type "*/*".

When the server cannot serve a response in any of the formats expected by the client, this function raises Combo.NotAcceptableError, which is rendered with status 406.

Examples

accepts/2 can be invoked as a function:

iex> accepts(conn, ["html", "json"])

or used as a plug:

plug :accepts, ["html", "json"]

Use put_format/2

accepts/2 is useful when you may want to serve different content-types (such as HTML and JSON) from the same routes. However, if you always have distinct routes, you can simply hardcode the format in your route pipelines:

plug :put_format, "html"

Custom media types

It is possible to add custom media types to your application.

The first step is to configure new media types in your config/config.exs:

config :mime, :types, %{
  "application/vnd.api+json" => ["json-api"]
}

The key is the media type, the value is a list of formats the media type can be identified with. For example, by using "json-api", you will be able to use templates with extension "index.json-api" or to force a particular format in a given URL by sending "?_format=json-api".

After this change, you must recompile mime:

$ mix deps.clean mime --build
$ mix deps.get

And now you can use it in accepts too:

plug :accepts, ["html", "json-api"]

action_name!(conn)

@spec action_name!(Plug.Conn.t()) :: atom()

Gets the action name as an atom, or raises if unavailable.

allow_jsonp(conn, opts \\ [])

@spec allow_jsonp(
  Plug.Conn.t(),
  keyword()
) :: Plug.Conn.t()

A plug that may convert a JSON response into a JSONP one.

In case a JSON response is returned, it will be converted to a JSONP as long as the callback field is present in the query string. The callback field itself defaults to "callback", but may be configured with the :callback option.

In case there is no callback or the response is not encoded in JSON format, it is a no-op.

Only alphanumeric characters and underscore are allowed in the callback name. Otherwise an exception is raised.

Examples

# Will convert JSON to JSONP if callback=someFunction is given
plug :allow_jsonp

# Will convert JSON to JSONP if cb=someFunction is given
plug :allow_jsonp, callback: "cb"

assign(conn, keyword_or_map_or_fun)

Adds key-value pairs to the connection's assigns.

Accepts a keyword list, a map, or a single-argument function.

When a keyword list or map is given as the second argument, it merges into the connection's assigns. It is equivalent to calling Plug.Conn.assign/3 multiple times.

When a function is given as the second argument, it takes the current assigns as an argument and merges its return value into the connection's assigns.

Examples

iex> assign(conn, name: "Combo", lang: "Elixir")
iex> assign(conn, %{name: "Combo", lang: "Elixir"})
iex> assign(conn, fn %{name: name, lang: lang} ->
...>   %{title: Enum.join([name, lang], " | ")}
...> end)

clear_flash(conn)

@spec clear_flash(Plug.Conn.t()) :: Plug.Conn.t()

Clears flash.

controller_module!(conn)

@spec controller_module!(Plug.Conn.t()) :: atom()

Gets the controller module as an atom, or raises if unavailable.

current_path(conn)

Returns the current request path with its default query params.

See current_path/2 to override the default query params.

The path is normalized based on the conn.script_name and conn.path_info. For example, "/foo//bar/" will become "/foo/bar". If you want the original path, use conn.request_path instead.

Examples

iex> current_path(conn)
"/users/123?existing=param"

current_path(conn, params)

Returns the current request path with the given query params.

You may also retrieve only the request path by passing an empty map of params.

The path is normalized based on the conn.script_name and conn.path_info. For example, "/foo//bar/" will become "/foo/bar". If you want the original path, use conn.request_path instead.

Examples

iex> current_path(conn)
"/users/123?existing=param"

iex> current_path(conn, %{new: "param"})
"/users/123?new=param"

iex> current_path(conn, %{filter: %{status: ["draft", "published"]}})
"/users/123?filter[status][]=draft&filter[status][]=published"

iex> current_path(conn, %{})
"/users/123"

current_url(conn)

Returns the current request url with its default query params.

See current_url/2 to override the default query params.

Examples

iex> current_url(conn)
"https://www.example.com/users/123?existing=param"

current_url(conn, params)

Returns the current request URL with the given query params.

The path will be retrieved from the currently requested path via current_path/1. The scheme, host and others will be received from the URL configuration in your endpoint. The reason we don't use the host and scheme information in the request is because most applications are behind proxies and the host and scheme may not actually reflect the host and scheme accessed by the client. If you want to access the url precisely as requested by the client, see Plug.Conn.request_url/1.

Examples

iex> current_url(conn)
"https://www.example.com/users/123?existing=param"

iex> current_url(conn, %{new: "param"})
"https://www.example.com/users/123?new=param"

iex> current_url(conn, %{})
"https://www.example.com/users/123"

Custom URL Generation

In some cases, you'll need to generate a request's URL, but using a different scheme, different host, etc. This can be accomplished in two ways.

If you want to do so in a case-by-case basis, you can define a custom function that gets the endpoint URI configuration and changes it accordingly. For example, to get the current URL always in HTTPS format:

def current_secure_url(conn, params \\ %{}) do
  current_uri = URI.new!(Combo.URLBuilder.url(conn, params))
  current_secure_uri = %URI{current_uri | scheme: "https"}
  URI.to_string(current_secure_uri)
end

If you want all generated URLs to always have a certain schema, host, etc, you may use put_router_url/2.

delete_csrf_token()

Deletes the CSRF token from the process dictionary.

Note: The token is deleted only after a response has been sent.

endpoint_module!(conn)

@spec endpoint_module!(Plug.Conn.t()) :: atom()

Gets the endpoint module as an atom, or raises if unavailable.

fetch_flash(conn, opts \\ [])

@spec fetch_flash(
  Plug.Conn.t(),
  keyword()
) :: Plug.Conn.t()

Fetches the flash, and puts it into assigns.

get_csrf_token()

Gets or generates a CSRF token.

If a token exists, it is returned, otherwise it is generated and stored in the process dictionary.

get_format(conn)

@spec get_format(Plug.Conn.t()) :: String.t() | nil

Gets the request format, such as "json", "html".

get_previous_url(conn)

Gets previous url.

html(conn, data)

@spec html(Plug.Conn.t(), iodata()) :: Plug.Conn.t()

Sends HTML response.

Examples

iex> html(conn, "<html><head>...")

is_assigns(assigns)

(macro)

is_format(format)

(macro)

is_layout_formats(formats)

(macro)

is_template(template)

(macro)

is_view_formats(formats)

(macro)

json(conn, data)

@spec json(Plug.Conn.t(), term()) :: Plug.Conn.t()

Sends JSON response.

Examples

iex> json(conn, %{id: 123})

layout(conn, format \\ nil)

@spec layout(Plug.Conn.t(), format() | nil) :: layout() | nil

Gets the current layout for the given format.

If no format is given, takes the current one from the connection.

merge_flash(conn, enumerable)

@spec merge_flash(Plug.Conn.t(), Enum.t()) :: Plug.Conn.t()

Merges a enumerable into the flash.

Examples

iex> conn = merge_flash(conn, info: "Welcome Back!")
iex> Combo.Flash.get(conn.assigns.flash, :info)
"Welcome Back!"

protect_from_forgery(conn, opts \\ [])

Enables CSRF protection.

Currently used as a wrapper function for Plug.CSRFProtection.

Check get_csrf_token/0 and delete_csrf_token/0 for retrieving and deleting CSRF tokens.

put_flash(conn, key, message)

@spec put_flash(Plug.Conn.t(), atom() | binary(), binary()) :: Plug.Conn.t()

Puts a message under the key into flash.

key can be any atom or binary value. Combo does not enforce which keys are stored in the flash, as long as the values are internally consistent. In general, keys like :info and :error are good ones.

Examples

iex> conn = put_flash(conn, :info, "Welcome Back!")
iex> Combo.Flash.get(conn.assigns.flash, :info)
"Welcome Back!"

put_format(conn, format)

@spec put_format(Plug.Conn.t(), format()) :: Plug.Conn.t()

Sets the given format into the connection.

This format is used when rendering a template which is specifed by an atom. For example, render(conn, :show) will render "show.<format>" where the "<format>" is the one set here.

The default format is typically set from the negotiation done in accepts/2.

put_layout(conn, formats)

@spec put_layout(Plug.Conn.t(), layout_formats()) :: Plug.Conn.t()

Sets the layout for rendering.

Raises Plug.Conn.AlreadySentError if conn is already sent.

Examples

iex> layout(conn)
false

iex> conn = put_layout(conn, html: {Demo.Web.Layouts, :app})
iex> layout(conn)
{Demo.Web.Layouts, :app}

iex> conn = put_layout(conn, html: {Demo.Web.Layouts, :print})
iex> layout(conn)
{Demo.Web.Layouts, :print}

put_new_layout(conn, formats)

@spec put_new_layout(Plug.Conn.t(), layout_formats()) :: Plug.Conn.t()

Sets the layout for rendering if one was not set yet.

Raises Plug.Conn.AlreadySentError if conn is already sent.

See put_layout/2 for more information.

put_new_view(conn, formats)

@spec put_new_view(Plug.Conn.t(), view_formats()) :: Plug.Conn.t()

Sets the view for rendering if one was not set yet.

Raises Plug.Conn.AlreadySentError if conn is already sent.

put_previous_url(conn, opts)

Puts current url to the session before sending the response, and it will be available as previous url for subsequence requests.

In general, it's added to the pipeline for browser.

put_router_url(conn, uri)

Sets the URL string or %URI{} to be used for URL generation.

Examples

Imagine your application is configured to run on "example.com" but after the user signs in, you want all links to use "some_user.example.com". You can do so by setting the proper router url configuration:

def put_router_url_by_user(conn) do
  # user = ...
  put_router_url(conn, user.account_name <> ".example.com")
end

Now when you call Routes.some_route_url(conn, ...), it will use the router url set above. Keep in mind that, if you want to generate routes to the current domain, it is preferred to use Routes.some_route_path helpers, as those are always relative.

put_secure_browser_headers(conn, headers \\ %{})

Puts headers that improve browser security.

It sets the following headers, if they are not already set:

  • content-security-policy - sets frame-ancestors and base-uri to self, restricting embedding and the use of <base> element to same origin respectively. It is equivalent to setting "base-uri 'self'; frame-ancestors 'self';".

  • referrer-policy - only send origin on cross origin requests.

  • x-content-type-options - sets to nosniff. This requires script and style tags to be sent with proper content type.

  • x-permitted-cross-domain-policies - sets to none to restrict Adobe Flash Player's access to data.

A custom headers map may also be given to be merged with defaults.

It is recommended for custom header keys to be in lowercase, to avoid sending duplicate keys or invalid responses.

put_static_url(conn, uri)

Sets the URL string or %URI{} to be used for the URL generation of statics.

Using this function on a %Plug.Conn{} struct tells static_url/2 to use the given information for URL generation instead of the %Plug.Conn{}'s endpoint configuration (much like put_router_url/2 but for static URLs).

put_view(conn, formats)

@spec put_view(Plug.Conn.t(), view_formats()) :: Plug.Conn.t()

Sets the view for rendering.

Raises Plug.Conn.AlreadySentError if conn is already sent.

Examples

iex> put_view(conn, html: PageHTML, json: PageJSON)

redirect(conn, opts)

@spec redirect(
  Plug.Conn.t(),
  keyword()
) :: Plug.Conn.t()

Sends redirect response to the given url.

For security, :to only accepts paths. Use the :external option to redirect to any URL.

The response will be sent with the status code defined within the connection , via Plug.Conn.put_status/2. If no status code is set, a 302 response is sent.

Examples

iex> redirect(conn, to: "/login")

iex> redirect(conn, external: "https://example.com")

redirect_back(conn, opts \\ [])

Sends redirect response to the previous location.

It attempts to use path from the following sources in sequence:

  1. the path retrieved from "Referer" request header
  2. the path retrieved by get_previous_url/1. To make it work, you need to add put_previous_url/2 to the pipeline first.
  3. the path retrieved from :fallback option
  4. the root path

Examples

iex> redirect_back(conn)

iex> redirect_back(conn, fallback: ~p"/users")

render(conn, template_or_assigns \\ [])

@spec render(Plug.Conn.t(), template() | assigns()) :: Plug.Conn.t()

Render the given template or the default template specified by the current action with the given assigns.

See render/3 for more information.

render(conn, template, assigns)

@spec render(Plug.Conn.t(), template(), assigns()) :: Plug.Conn.t()

Renders the given template and assigns based on the conn information.

Once the template is rendered, the template format is set as the response content type (for example, an HTML template will set "text/html" as response content type) and the data is sent to the client with default status of 200.

Arguments

  • conn - a %Plug.Conn{} struct.

  • template - an atom or a string. If an atom, like :index, it will render a template with the same format as the one returned by get_format/1. For example, for an HTML request, it will render the "index.html" template. If the template is a string, it must contain the extension too, like "index.html".

  • assigns - a keyword list or a map. It's merged into conn.assigns and have higher precedence than conn.assigns. The merged assigns will be used in the template.

Examples

Before rendering a template, you must configure the view modules to be used. There are multiple ways to do that:

  • use use Combo.Controller, which infers the view modules at compile-time.
  • use Combo.Conn.put_view/2, which sets the view modules at runtime.

use Combo.Controller

After the viets set, you can render in two ways, either passing a string with the template name and explicit format:

defmodule Demo.Web.UserController do
  use Combo.Controller, formats: [:html, :json]

  def show(conn, _params) do
    render(conn, "show.html", message: "Hello")
  end
end

The example above renders a template "show.html" from the Demo.Web.UserHTML and sets the response content type to "text/html".

Or, if you want the template format to be set dynamically based on the request, you can pass an atom instead:

def show(conn, _params) do
  render(conn, :show, message: "Hello")
end

Combo.Conn.put_view/2

If the formats are not known at compile-time, you can call put_view/2 at runtime:

defmodule Demo.Web.UserController do
  use Combo.Controller

  def show(conn, _params) do
    conn
    |> put_view(html: Demo.Web.UserHTML)
    |> render("show.html", message: "Hello")
  end
end

router_module!(conn)

@spec router_module!(Plug.Conn.t()) :: atom()

Gets the router module as an atom, or raises if unavailable.

send_download(conn, kind, opts \\ [])

Sends the given file or binary as a download.

The second argument must be {:binary, contents}, where contents will be sent as download, or{:file, path}, where path is the filesystem location of the file to be sent. Be careful to not interpolate the path from external parameters, as it could allow traversal of the filesystem.

The download is achieved by setting "content-disposition" to attachment. The "content-type" will also be set based on the extension of the given filename but can be customized via the :content_type and :charset options.

Options

  • :filename - the filename to be presented to the user as download
  • :content_type - the content type of the file or binary sent as download. It is automatically inferred from the filename extension
  • :disposition - specifies disposition type (:attachment or :inline). If :attachment was used, user will be prompted to save the file. If :inline was used, the browser will attempt to open the file. Defaults to :attachment.
  • :charset - the charset of the file, such as "utf-8". Defaults to none
  • :offset - the bytes to offset when reading. Defaults to 0
  • :length - the total bytes to read. Defaults to :all
  • :encode - encodes the filename using URI.encode/2. Defaults to true. When false, disables encoding. If you disable encoding, you need to guarantee there are no special characters in the filename, such as quotes, newlines, etc. Otherwise you can expose your application to security attacks

Examples

To send a file that is stored inside your application priv directory:

path = Application.app_dir(:my_app, "priv/example.pdf")
send_download(conn, {:file, path})

When using {:file, path}, the filename is inferred from the given path but may also be set explicitly.

To allow the user to download contents that are in memory as a binary or string:

send_download(conn, {:binary, "world"}, filename: "hello.txt")

See Plug.Conn.send_file/3 and Plug.Conn.send_resp/3 if you would like to access the low-level functions used to send files and responses via Plug.

status_message_from_template(template)

@spec status_message_from_template(String.t()) :: String.t()

Generates a status message from the template name.

Examples

iex> status_message_from_template("404.html")
"Not Found"

iex> status_message_from_template("whatever.html")
"Internal Server Error"

text(conn, data)

@spec text(Plug.Conn.t(), String.Chars.t()) :: Plug.Conn.t()

Sends text response.

Examples

iex> text(conn, "hello")

iex> text(conn, :implements_to_string)

view_module(conn, format \\ nil)

@spec view_module(Plug.Conn.t(), format() | nil) :: view()

Gets the current view for the given format.

If no format is given, takes the current one from the connection.

view_module!(conn, format \\ nil)

@spec view_module!(Plug.Conn.t(), format() | nil) :: view()

Gets the current view for the given format, or raises if no view was found.

If no format is given, takes the current one from the connection.

view_template(conn)

@spec view_template(Plug.Conn.t()) :: String.t() | nil

Returns the template name rendered in the view as a string, or nil if no template was rendered.

view_template!(conn)

@spec view_template!(Plug.Conn.t()) :: String.t()

Returns the template name rendered in the view as a string, or raises if no template was rendered.