elixir-plug/plug

Name: plug

Owner: elixir-plug

Description: A specification and conveniences for composable modules between web applications

Created: 2013-11-15 10:30:47.0

Updated: 2018-01-19 12:18:09.0

Pushed: 2018-01-10 16:21:12.0

Homepage:

Size: 1797

Language: Elixir

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Plug

Build Status Inline docs

Plug is:

  1. A specification for composable modules between web applications
  2. Connection adapters for different web servers in the Erlang VM

Documentation for Plug is available online.

Hello world
odule MyPlug do
port Plug.Conn

f init(options) do
# initialize options

options
d

f call(conn, _opts) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello world")
d

The snippet above shows a very simple example on how to use Plug. Save that snippet to a file and run it inside the plug application with:

$ iex -S mix
iex> c "path/to/file.ex"
[MyPlug]
iex> {:ok, _} = Plug.Adapters.Cowboy.http MyPlug, []
{:ok, #PID<...>}

Access “http://localhost:4000/” and we are done! For now, we have directly started the server in our terminal but, for production deployments, you likely want to start it in your supervision tree. See the “Supervised handlers” section below.

Installation

You can use plug in your projects in two steps:

  1. Add plug and your webserver of choice (currently cowboy) to your mix.exs dependencies:

    deps do
    :cowboy, "~> 1.0.0"},
    :plug, "~> 1.0"}]
    
    
  2. List both :cowboy and :plug as your application dependencies:

    application do
    pplications: [:cowboy, :plug]]
    
    
Supported Versions

| Branch | Support | | —— | ———————— | | v1.4 | Bug fixes | | v1.3 | Security patches only | | v1.2 | Security patches only | | v1.1 | Unsupported from 01/2018 | | v1.0 | Unsupported from 05/2017 |

The Plug.Conn

In the hello world example, we defined our first plug. What is a plug after all?

A plug takes two shapes. A function plug receives a connection and a set of options as arguments and returns the connection:

hello_world_plug(conn, _opts) do
nn
 put_resp_content_type("text/plain")
 send_resp(200, "Hello world")

A module plug implements an init/1 function to initialize the options and a call/2 function which receives the connection and initialized options and returns the connection:

odule MyPlug do
f init([]), do: false
f call(conn, _opts), do: conn

As per the specification above, a connection is represented by the Plug.Conn struct:

g.Conn{host: "www.example.com",
       path_info: ["bar", "baz"],
       ...}

Data can be read directly from the connection and also pattern matched on. Manipulating the connection often happens with the use of the functions defined in the Plug.Conn module. In our example, both put_resp_content_type/2 and send_resp/3 are defined in Plug.Conn.

Remember that, as everything else in Elixir, a connection is immutable, so every manipulation returns a new copy of the connection:

 = put_resp_content_type(conn, "text/plain")
 = send_resp(conn, 200, "ok")

Finally, keep in mind that a connection is a direct interface to the underlying web server. When you call send_resp/3 above, it will immediately send the given status and body back to the client. This makes features like streaming a breeze to work with.

The Plug Router

In practice, developers rarely write their own plugs. For example, Plug ships with a router that allows developers to quickly match on incoming requests and perform some action:

odule MyRouter do
e Plug.Router

ug :match
ug :dispatch

t "/hello" do
send_resp(conn, 200, "world")
d

rward "/users", to: UsersRouter

tch _ do
send_resp(conn, 404, "oops")
d

The router is a plug and, not only that, it contains its own plug pipeline too. The example above says that when the router is invoked, it will invoke the :match plug, represented by a local match/2 function, and then call the :dispatch plug which will execute the matched code.

Plug ships with many plugs that you can add to the router plug pipeline, allowing you to plug something before a route matches or before a route is dispatched to. For example, if you want to add logging to the router, just do:

 Plug.Logger
 :match
 :dispatch

Note Plug.Router compiles all of your routes into a single function and relies on the Erlang VM to optimize the underlying routes into a tree lookup, instead of a linear lookup that would instead match route-per-route. This means route lookups are extremely fast in Plug!

This also means that a catch all match is recommended to be defined, as in the example above, otherwise routing fails with a function clause error (as it would in any regular Elixir function).

Each route needs to return the connection as per the Plug specification. See Plug.Router docs for more information.

Supervised handlers

On a production system, you likely want to start your Plug application under your application's supervision tree. Plug provides the child_spec/3 function to do just that. Start a new Elixir project with the --sup flag:

x new my_app --sup

and then update lib/my_app.ex as follows:

odule MyApp do
e Application

See https://hexdocs.pm/elixir/Application.html
for more information on OTP Applications
f start(_type, _args) do
import Supervisor.Spec

children = [
  # Define workers and child supervisors to be supervised
  Plug.Adapters.Cowboy.child_spec(scheme: :http, plug: MyRouter, options: [port: 4001])
]

# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
d

Testing plugs

Plug ships with a Plug.Test module that makes testing your plugs easy. Here is how we can test the router from above (or any other plug):

odule MyPlugTest do
e ExUnit.Case, async: true
e Plug.Test

pts MyRouter.init([])

st "returns hello world" do
# Create a test connection
conn = conn(:get, "/hello")

# Invoke the plug
conn = MyRouter.call(conn, @opts)

# Assert the response and status
assert conn.state == :sent
assert conn.status == 200
assert conn.resp_body == "world"
d

Available Plugs

This project aims to ship with different plugs that can be re-used across applications:

You can go into more details about each of them in our docs.

Helper modules

Modules that can be used after you use Plug.Router or Plug.Builder to help development:

Contributing

We welcome everyone to contribute to Plug and help us tackle existing issues!

Use the issue tracker for bug reports or feature requests. You may also start a discussion on the mailing list or the #elixir-lang channel on Freenode IRC. Open a pull request when you are ready to contribute.

When submitting a pull request you should not update the CHANGELOG.md.

If you are planning to contribute documentation, please check our best practices for writing documentation.

Finally, remember all interactions in our official spaces follow our Code of Conduct.

License

Plug source code is released under Apache 2 License. Check LICENSE file for more information.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.