plug

A specification and conveniences for composable modules between web applications

Subscribe to updates I use plug


Statistics on plug

Number of watchers on Github 1692
Number of open issues 6
Average time to close an issue about 9 hours
Main language Elixir
Average time to merge a PR 4 days
Open pull requests 4+
Closed pull requests 12+
Last commit 5 months ago
Repo Created almost 5 years ago
Repo Last Updated 5 months ago
Size 1.88 MB
Organization / Authorelixir-plug
Contributors145
Page Updated
Do you use plug? Leave a review!
View open issues (6)
View on github
Latest Open Source Launches
Trendy new open source projects in your inbox! View examples

Subscribe to our mailing list

Evaluating plug for your project? Score Explanation
Commits Score (?)
Issues & PR Score (?)

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

defmodule MyPlug do
  import Plug.Conn

  def init(options) do
    # initialize options

    options
  end

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

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:

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

    def application do
      [applications: [:cowboy, :plug]]
    end
    

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 struct

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:

def hello_world_plug(conn, _opts) do
  conn
  |> put_resp_content_type("text/plain")
  |> send_resp(200, "Hello world")
end

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:

defmodule MyPlug do
  def init([]), do: false
  def call(conn, _opts), do: conn
end

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

%Plug.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:

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

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.

Plug.Router

To write a router plug that dispatches based on the path and method of incoming requests, Plug provides Plug.Router:

defmodule MyRouter do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/hello" do
    send_resp(conn, 200, "world")
  end

  forward "/users", to: UsersRouter

  match _ do
    send_resp(conn, 404, "oops")
  end
end

The router is a plug. 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 (imported) 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 Plug.Logger
plug :match
plug :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 block 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 the Plug.Router docs for more information.

Supervised handlers

On a production system, you likely want to start your Plug pipeline 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:

$ mix new my_app --sup

and then update lib/my_app.ex as follows:

defmodule MyApp do
  use Application

  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    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)
  end
end

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):

defmodule MyPlugTest do
  use ExUnit.Case, async: true
  use Plug.Test

  @opts MyRouter.init([])

  test "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"
  end
end

Available plugs

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

  • Plug.CSRFProtection - adds Cross-Site Request Forgery protection to your application. Typically required if you are using Plug.Session;
  • Plug.Head - converts HEAD requests to GET requests;
  • Plug.Logger - logs requests;
  • Plug.MethodOverride - overrides a request method with one specified in headers;
  • Plug.Parsers - responsible for parsing the request body given its content-type;
  • Plug.RequestId - sets up a request ID to be used in logs;
  • Plug.Session - handles session management and storage;
  • Plug.SSL - enforce requests through SSL;
  • Plug.Static - serves static files;

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:

  • Plug.Debugger - shows a helpful debugging page every time there is a failure in a request;
  • Plug.ErrorHandler - allows developers to customize error pages in case of crashes instead of sending a blank one;

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.

plug open issues Ask a question     (View All Issues)
  • about 1 year How to parse multipart form with JSON field
  • over 1 year Add support for streaming response body
  • about 3 years HTTP/2 support
plug open pull requests (View All Pulls)
  • Elli adapter
  • WIP: How to parse multipart form with JSON field #570
  • Eliminate nil -> nil case
  • Add a prepend_resp_headers/3 to Plug.Conn
plug questions on Stackoverflow (View All Questions)
  • How to add TestNG plug-in into Eclipse IDE 3.4.0?
  • Allow- hot plug in for dhcp
  • How to resolve spring security core plug in?
  • CKEditor Plug-In: Getting text of drop-down item
  • Icon won't appear in CKEditor plug-in
  • Using logic when working with Sikuli plug-in in Java (NetBeans)
  • Integration of Django based plug-ins into wagtail
  • Does Jenkins Pipeline Plug-in support Docker Compose?
  • Java Plug-in is not supported by this browser on Apache Pivot web-site
  • Able to create a "generic" plug?
  • what is the URL for JetBrains IDE plug-in repository?
  • Google Earth Plug-in ver 7.0.3.8542 Prompts for username/password credentials
  • How to receive Plug & Play device notifications without a windows form
  • Is VMware Plug-in 6.5 support 6.x version?
  • How to Contribute for GEF editor context menu from other plug-in
  • I need image upload plug in like ebay produt image upload?
  • Turning off a Accessible Modal Window plug in
  • Notebook Runs As Long As You Plug The Laptop Monitor
  • Plug dataframe into VAR model in R
  • Creating Dlink Smart Plug Custom Interface
  • How do I begin using an Eclipse plug-in after downloading it from the Eclipse marketplace?
  • GNAT GPS plug-in: Action object has no attribute button
  • What's the difference between Extended Choice Parameter Plug-In and Extensible Choice Parameter plugin in Jenkins
  • Problems occurred when invoking code from plug-in: "org.eclipse.jface"
  • How to implement Post-Build stage using Jenkins Pipeline plug-in?
  • Why $cordovaImagePicker plug in is crashing on Marshmallow?
  • Filter API connections with Plug
  • How to detect cwd in an Eclipse Plug-in?
  • How to install last version of ADT plug-in for eclipse
  • Where is "imageUri" declared in this PhoneGap camera plug-in, Javascript example?
plug list of languages used
Other projects in Elixir