The Anatomy of a Plug

Before we build our plug, let’s take a deep dive into the Plug library and learn how plugs work from the inside. There are two kinds of plugs: module plugs and function plugs. A function plug is a single function. A module plug is a module that provides two functions with some configuration details. Either way, they work the same.

We have seen both kinds of plugs in use. From the endpoint module in lib/rumbl_web/endpoint.ex, you can see an example of a module plug:

 plug Plug.RequestId

You specify a module plug by providing the module name. In the router, you can see an example of a function plug:

 plug ​:protect_from_forgery

You specify a function plug with the name of the function as an atom. Because a module is just a collection of functions, it strengthens the idea that plugs are just functions.

For our first plug, we’ll write a module plug that encapsulates all the authentication logic in one place.

Module Plugs

Sometimes you might want to share a plug across more than one module. In that case, you can use a module plug. To satisfy the Plug specification, a module plug must have two functions, named init and call.

The simplest possible module plug returns the given options on init and the given connection on call. This plug does nothing:

 defmodule​ NothingPlug ​do
 def​ init(opts) ​do
  opts
 end
 
 def​ call(conn, _opts) ​do
  conn
 end
 end

Remember, a typical plug transforms a connection. The main work of a module plug happens in call. In our NothingPlug, we simply pass the connection through without changes. The call will happen at runtime.

Sometimes, you might want to let the programmer change the behavior of a plug. We can do that work in the second argument to call, options. In our NothingPlug, we don’t need any more information to do our job, so we ignore the options.

Sometimes, you might need Phoenix to do some heavy lifting to transform options. That’s the job of the init function. Plug uses the result of init as the second argument to call. In development mode, Phoenix calls init at runtime, but in production mode, init is called only once, at compile time. This strategy makes init the perfect place to validate and transform options without slowing down every request so call can be as fast as possible. Since call is the workhorse of Plug, we want it to do as little work as possible.

For both module and function plugs, the request interface is the same. conn, the first argument, is the data we pass through every plug. It has the details for any request, and we morph it in tiny steps until we eventually send a response. All plugs take a conn and return a conn.

You’ll see piped functions using a common data structure over and over in Elixir. The trick that makes this tactic work is having the right common data structure. Since Plug works with web APIs, our data structure will specify the typical details of the web server’s domain.

In Phoenix, you’ll see connections, usually abbreviated conn, literally everywhere. At the end of the day, the conn is only a Plug.Conn struct, and it forms the foundation for Plug.

Plug.Conn Fields

You can find great online documentation for Plug.Conn.[18] This structure has the various fields that web applications need to understand about web requests and responses. Let’s look at some of the supported fields.

Request fields contain information about the inbound request. They’re parsed by the adapter for the web server you’re using. Cowboy is the default web server that Phoenix uses, but you can also choose to plug in your own. These fields contain strings, except where otherwise specified:

host

The requested host. For example, www.pragprog.com.

method

The request method. For example, GET or POST.

path_info

The path, split into a List of segments. For example, ["admin", "users"].

req_headers

A list of request headers. For example, [{"content-type", "text/plain"}].

scheme

The request protocol as an atom. For example, :https.

You can get other information as well, such as the query string, the remote IP address, the port, and the like. For Phoenix, if a web request’s information is available from the web server’s adapter, it’s in Plug.Conn.

Next comes a set of fetchable fields. A fetchable field is empty until you explicitly request it. These fields require a little time to process, so they’re left out of the connection by default until you want to explicitly fetch them:

cookies

These are the request cookies with the response cookies.

params

These are the request parameters. Some plugs help to parse these parameters from the query string, or from the request body.

Next are a series of fields that are used to process web requests and keep information about the plug pipeline. Here are some of the fields you’ll encounter:

assigns

This user-defined map contains anything you want to put in it. For instance, this is where we will keep the authenticated user for the current request.

halted

Sometimes a connection must be halted, such as a failed authorization. In this case, the halting plug sets this flag.

You can also find a secret_key_base for everything related to encryption.

Since the Plug framework handles the whole life cycle of a request, including both the request and the response, Plug.Conn provides fields for the response:

resp_body

Initially an empty string, the response body will contain the HTTP response string when it’s available.

resp_cookies

The resp_cookies has the outbound cookies for the response.

resp_headers

These headers follow the HTTP specification and contain information such as the response type and caching rules.

status

The response code generally contains 200299 for success, 300399 for redirects, 400499 for bad client requests such as not-found, and 500+ for server errors.

Finally, Plug supports some private fields reserved for the adapter and frameworks:

adapter

Information about the underlying web server is stored here.

private

This field has a map for the private use of frameworks.

Initially, a conn comes in almost blank and is filled out progressively by different plugs in the pipeline. For example, the endpoint may parse parameters, and the application developer will set fields primarily in assigns. Functions that render set the response fields such as status, change the state, and so on.

Plug.Conn also defines many functions that directly manipulate those fields, which makes abstracting the work of doing more complex operations such as managing cookies or sending files straightforward.

Now that you have a little more knowledge, we’re ready to transform the connection by writing our first plug.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.2.240