Repository: phoenixframework/phoenix
Pull request #2861 (2018-04-23 12:04:57)

Define a explicit and generic socket<->transport contract
This PR is still work in progress but I am submitting it early for discussion.

The main goal of this PR is to define a contract between transports and sockets. This brings two very interesting features:

1. Developers can use Phoenix.Socket as a generic communication layer, no longer coupled to channels. For example, you can implement your own callbacks such as a simple ping/pong communication layer and Phoenix will make sure it works for both websockets AND long polling.

2. Implementing a transport is much much much easier as it only needs to communicate with the socket and is no longer required to worry about channels.

Basically, the whole channels management is only one possible implementation of the socket<->transport contract. You can replace this implementation altogether by not using Phoenix.Socket and defining your own callbacks instead.

/cc @sasa1977 

### TODO

 * [ ] Non-channel based integration tests
 * [x] Deprecate transport macro in sockets
 * [x] Phoenix.Socket and adapter docs
 * [x] Update guides (transport)
 * [ ] CHANGELOG (transport/3, Phoenix.Socket.Transport, serializers, :handler)


josevalim: Btw, the PR has an increase in the lines of code but that's because of deprecation warnings and extra documentation. Overall it should less LOC since we removed duplication in the serializers and existing transports.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c32b46dc4..9a3078e9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@
 ## 1.4.0-dev
 
 * Enhancements
-  * [Endpoint] Add `Cowboy2Handler` for HTTP2 support with cowboy2.
+  * [Endpoint] Add `Cowboy2Adapter` for HTTP2 support with cowboy2.
   * [Phoenix] Add `Phoenix.json_library/0` and replace `Poison` with `Jason` for JSON encoding in new projects.
   * [Controller] Support partial file downloads with `:offset` and `:length` options to `send_download/3`
   * [Controller] Add additional security headers to `put_secure_browser_headers` (`x-content-type-options`, `x-download-options`, and `x-permitted-cross-domain-policies`)
diff --git a/guides/adding_pages.md b/guides/adding_pages.md
index cf181680b..4ac4f68a2 100644
--- a/guides/adding_pages.md
+++ b/guides/adding_pages.md
@@ -152,7 +152,7 @@ defmodule HelloWeb.HelloController do
   use HelloWeb, :controller
 
   def index(conn, _params) do
-    render conn, "index.html"
+    render(conn, "index.html")
   end
 end
 ```
@@ -160,9 +160,9 @@ We'll save a discussion of `use HelloWeb, :controller` for the [Controllers Guid
 
 All controller actions take two arguments. The first is `conn`, a struct which holds a ton of data about the request. The second is `params`, which are the request parameters. Here, we are not using `params`, and we avoid compiler warnings by adding the leading `_`.
 
-The core of this action is `render conn, "index.html"`. This tells Phoenix to find a template called `index.html.eex` and render it. Phoenix will look for the template in a directory named after our controller, so `lib/hello_web/templates/hello`.
+The core of this action is `render(conn, "index.html")`. This tells Phoenix to find a template called `index.html.eex` and render it. Phoenix will look for the template in a directory named after our controller, so `lib/hello_web/templates/hello`.
 
-> Note: Using an atom as the template name will also work here, `render conn, :index`, but the template will be chosen based off the Accept headers, e.g. `"index.html"` or `"index.json"`.
+> Note: Using an atom as the template name will also work here, `render(conn, :index)`, but the template will be chosen based off the Accept headers, e.g. `"index.html"` or `"index.json"`.
 
 The modules responsible for rendering are views, and we'll make a new one of those next.
 
@@ -200,7 +200,7 @@ Now that we've got the route, controller, view, and template, we should be able
 
 There are a couple of interesting things to notice about what we just did. We didn't need to stop and re-start the server while we made these changes. Yes, Phoenix has hot code reloading! Also, even though our `index.html.eex` file consisted of only a single `div` tag, the page we get is a full HTML document. Our index template is rendered into the application layout - `lib/hello_web/templates/layout/app.html.eex`. If you open it, you'll see a line that looks like this:
 
-    <%= render @view_module, @view_template, assigns %>
+    <%= render(@view_module, @view_template, assigns) %>
 
 which is what renders our template into the layout before the HTML is sent off to the browser.
 
@@ -235,7 +235,7 @@ Requests to our new route will be handled by the `HelloWeb.HelloController` `sho
 
 ```elixir
 def show(conn, %{"messenger" => messenger}) do
-  render conn, "show.html", messenger: messenger
+  render(conn, "show.html", messenger: messenger)
 end
 ```
 
diff --git a/guides/channels.md b/guides/channels.md
index e60363067..2dac9ae6e 100644
--- a/guides/channels.md
+++ b/guides/channels.md
@@ -103,9 +103,13 @@ The message flow looks something like this:
 In your Phoenix app's `Endpoint` module, a `socket` declaration specifies which socket handler will receive connections on a given URL.
 
 ```elixir
-socket "/socket", HelloWeb.UserSocket
+socket "/socket", HelloWeb.UserSocket,
+  websocket: true,
+  longpoll: false
 ```
 
+Phoenix comes with two default transports: websocket and longpoll. You can configure them directly via the `socket` declaration.
+
 ### Socket Handlers
 
 Socket handlers, such as `HelloWeb.UserSocket` in the example above, are called when Phoenix is setting up a channel connection.
@@ -143,31 +147,6 @@ The `Phoenix.Socket.Message` module defines a struct with the following keys whi
 - `payload` - The message payload
 - `ref` - The unique string ref
 
-### Transports
-
-The transport layer is involved in the "last mile" - actually getting messages to and from clients.
-The `Phoenix.Channel.Transport` module handles all the message dispatching into and out of a Channel.
-
-### Transport Adapters
-
-Within a given socket handler, we can configure which transport adapter to use.
-This determines the protocol(s) by which clients may connect.
-
-For example, in the socket handler module of a freshly-generated Phoenix app, you'll find:
-
-```elixir
-transport :websocket, Phoenix.Transports.WebSocket # transport adapter
-```
-
-If clients of your application don't support WebSockets, you can use the long polling adapter instead:
-
-```elixir
-transport :longpoll, Phoenix.Transports.LongPoll
-```
-
-Other transport adapters are possible, and we can write our own if we follow the adapter contract.
-Please see `Phoenix.TransportsWebSocket` for an example.
-
 ### PubSub
 
 Typically, we don't directly use the Phoenix PubSub layer when developing Phoenix applications.
@@ -353,7 +332,7 @@ defmodule HelloWeb.RoomChannel do
   end
 
   def handle_in("new_msg", %{"body" => body}, socket) do
-    broadcast! socket, "new_msg", %{body: body}
+    broadcast!(socket, "new_msg", %{body: body})
     {:noreply, socket}
   end
 end
@@ -371,7 +350,7 @@ def handle_out("user_joined", msg, socket) do
   if Accounts.ignoring_user?(socket.assigns[:user], msg.user_id) do
     {:noreply, socket}
   else
-    push socket, "user_joined", msg
+    push(socket, "user_joined", msg)
     {:noreply, socket}
   end
 end
diff --git a/guides/controllers.md b/guides/controllers.md
index af50b0649..05def5fc9 100644
--- a/guides/controllers.md
+++ b/guides/controllers.md
@@ -11,7 +11,7 @@ defmodule HelloWeb.PageController do
   use HelloWeb, :controller
 
   def index(conn, _params) do
-    render conn, "index.html"
+    render(conn, "index.html")
   end
 end
 ```
@@ -42,7 +42,7 @@ defmodule HelloWeb.PageController do
   . . .
 
   def test(conn, _params) do
-    render conn, "index.html"
+    render(conn, "index.html")
   end
 end
 ```
@@ -68,7 +68,7 @@ defmodule HelloWeb.HelloController do
   . . .
 
   def show(conn, %{"messenger" => messenger}) do
-    render conn, "show.html", messenger: messenger
+    render(conn, "show.html", messenger: messenger)
   end
 end
 ```
@@ -126,7 +126,7 @@ Let's say we have a `show` action which receives an id from the params map, and
 
 ```elixir
 def show(conn, %{"id" => id}) do
-  text conn, "Showing id #{id}"
+  text(conn, "Showing id #{id}")
 end
 ```
 Assuming we had a route for `get "/our_path/:id"` mapped to this `show` action, going to `/our_path/15` in your browser should display `Showing id 15` as plain text without any HTML.
@@ -135,7 +135,7 @@ A step beyond this is rendering pure JSON with the `json/2` function. We need to
 
 ```elixir
 def show(conn, %{"id" => id}) do
-  json conn, %{id: id}
+  json(conn, %{id: id})
 end
 ```
 If we again visit `our_path/15` in the browser, we should see a block of JSON with the key `id` mapped to the number `15`.
@@ -147,7 +147,7 @@ Phoenix controllers can also render HTML without a template. As you may have alr
 
 ```elixir
 def show(conn, %{"id" => id}) do
-  html conn, """
+  html(conn, """
      <html>
        <head>
           <title>Passing an Id</title>
@@ -156,7 +156,7 @@ def show(conn, %{"id" => id}) do
          <p>You sent in id #{id}</p>
        </body>
      </html>
-    """
+    """)
 end
 ```
 
@@ -177,7 +177,7 @@ defmodule HelloWeb.HelloController do
   use HelloWeb, :controller
 
   def show(conn, %{"messenger" => messenger}) do
-    render conn, "show.html", messenger: messenger
+    render(conn, "show.html", messenger: messenger)
   end
 end
 ```
@@ -348,7 +348,7 @@ As an example, let's take the `PageController` index action from a newly generat
 
 ```elixir
 def index(conn, _params) do
-  render conn, "index.html"
+  render(conn, "index.html")
 end
 ```
 What it doesn't have is an alternative template for rendering text. Let's add one at `lib/hello_web/templates/page/index.text.eex`. Here is our example `index.text.eex` template.
@@ -376,7 +376,7 @@ We also need to tell the controller to render a template with the same format as
 
 ```elixir
 def index(conn, _params) do
-  render conn, :index
+  render(conn, :index)
 end
 ```
 
@@ -386,7 +386,7 @@ Of course, we can pass data into our template as well. Let's change our action t
 
 ```elixir
 def index(conn, params) do
-  render conn, "index.text", message: params["message"]
+  render(conn, "index.text", message: params["message"])
 end
 ```
 
@@ -434,7 +434,7 @@ end
 
 The status code we provide must be valid - [Cowboy](https://github.com/ninenines/cowboy), the web server Phoenix runs on, will throw an error on invalid codes. If we look at our development logs (which is to say, the iex session), or use our browser's web inspection network tool, we will see the status code being set as we reload the page.
 
-If the action sends a response - either renders or redirects - changing the code will not change the behavior of the response. If, for example, we set the status to 404 or 500 and then `render "index.html"`, we do not get an error page. Similarly, no 300 level code will actually redirect. (It wouldn't know where to redirect to, even if the code did affect behavior.)
+If the action sends a response - either renders or redirects - changing the code will not change the behavior of the response. If, for example, we set the status to 404 or 500 and then `render("index.html")`, we do not get an error page. Similarly, no 300 level code will actually redirect. (It wouldn't know where to redirect to, even if the code did affect behavior.)
 
 The following implementation of the `HelloWeb.PageController` `index` action, for example, will _not_ render the default `not_found` behavior as expected.
 
@@ -487,7 +487,7 @@ Then we'll change the `index` action to do nothing but redirect to our new route
 
 ```elixir
 def index(conn, _params) do
-  redirect conn, to: "/redirect_test"
+  redirect(conn, to: "/redirect_test")
 end
 ```
 
@@ -495,7 +495,7 @@ Finally, let's define in the same file the action we redirect to, which simply r
 
 ```elixir
 def redirect_test(conn, _params) do
-  text conn, "Redirect!"
+  text(conn, "Redirect!")
 end
 ```
 
@@ -507,7 +507,7 @@ Notice that the redirect function takes `conn` as well as a string representing
 
 ```elixir
 def index(conn, _params) do
-  redirect conn, external: "https://elixir-lang.org/"
+  redirect(conn, external: "https://elixir-lang.org/")
 end
 ```
 
@@ -518,7 +518,7 @@ defmodule HelloWeb.PageController do
   use HelloWeb, :controller
 
   def index(conn, _params) do
-    redirect conn, to: redirect_test_path(conn, :redirect_test)
+    redirect(conn, to: redirect_test_path(conn, :redirect_test))
   end
 end
 ```
@@ -527,7 +527,7 @@ Note that we can't use the url helper here because `redirect/2` using the atom `
 
 ```elixir
 def index(conn, _params) do
-  redirect conn, to: redirect_test_url(conn, :redirect_test)
+  redirect(conn, to: redirect_test_url(conn, :redirect_test))
 end
 ```
 
@@ -535,7 +535,7 @@ If we want to use the url helper to pass a full url to `redirect/2`, we must use
 
 ```elixir
 def index(conn, _params) do
-  redirect conn, external: redirect_test_url(conn, :redirect_test)
+  redirect(conn, external: redirect_test_url(conn, :redirect_test))
 end
 ```
 
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index f15dffb90..924166566 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -106,6 +106,24 @@ Buildpack added. Next release on mysterious-meadow-6277 will use:
 Run `git push heroku master` to create a new release using these buildpacks.
 ```
 
+This phoenix static buildpack pack can be configured to change the node version and compile options. Please refer to the [configuration section](https://github.com/gjaldon/heroku-buildpack-phoenix-static#configuration) for full details. We will override the compile options here.
+
+Create a config file named `phoenix_static_buildpack.config` in the root directory for your project with the following:
+
+```
+compile="compile"
+```
+
+And a file named `compile` with the following contents:
+
+```
+npm run deploy
+cd $phoenix_dir
+mix "${phoenix_ex}.digest"
+```
+
+This will ensure that the `deploy` script in the package.json is used instead of the static buildpack default of `brunch`.
+
 ## Making our Project ready for Heroku
 
 Every new Phoenix project ships with a config file `config/prod.secret.exs` which stores configuration that should not be committed along with our source code. By default Phoenix adds it to our `.gitignore` file.
@@ -183,7 +201,7 @@ config :hello, Hello.Repo,
   ssl: true
 ```
 
-Finally, we need to decrease the timeout for the websocket transport in `lib/hello_web/channels/user_socket.ex`:
+Finally, we need to decrease the timeout for the websocket transport in `lib/hello_web/endpoint.ex`:
 
 ```elixir
 defmodule HelloWeb.UserSocket do
@@ -192,9 +210,9 @@ defmodule HelloWeb.UserSocket do
   ...
 
   ## Transports
-  transport :websocket, Phoenix.Transports.WebSocket,
-    timeout: 45_000
-    ...
+  socket "/socket", HelloWeb.UserSocket,
+    websocket: [timeout: 45_000],
+    longpoll: false
 end
 ```
 
diff --git a/guides/errors.md b/guides/errors.md
index 477625ffc..e44a9e7a9 100644
--- a/guides/errors.md
+++ b/guides/errors.md
@@ -23,7 +23,7 @@ defmodule Hello.ErrorView do
   # In case no render clause matches or no
   # template is found, let's render it as 500
   def template_not_found(_template, assigns) do
-    render "500.html", assigns
+    render("500.html", assigns)
   end
 end
 ```
diff --git a/guides/phoenix_mix_tasks.md b/guides/phoenix_mix_tasks.md
index 9da39d2c1..8c59c2551 100644
--- a/guides/phoenix_mix_tasks.md
+++ b/guides/phoenix_mix_tasks.md
@@ -772,7 +772,7 @@ def change do
   create table(:comments) do
     add :body,       :string
     add :word_count, :integer
-    timestamps
+    timestamps()
   end
 end
 . . .
@@ -886,7 +886,7 @@ defmodule Mix.Tasks.Hello.Greeting do
   """
 
   def run(_args) do
-    Mix.shell.info "Greetings from the Hello Phoenix Application!"
+    Mix.shell.info("Greetings from the Hello Phoenix Application!")
   end
 
   # We can define other functions as needed here.
@@ -939,8 +939,8 @@ If you want to make your new mix task to use your application's infrastructure,
 ```elixir
   . . .
   def run(_args) do
-    Mix.Task.run "app.start"
-    Mix.shell.info "Now I have access to Repo and other goodies!"
+    Mix.Task.run("app.start")
+    Mix.shell.info("Now I have access to Repo and other goodies!")
   end
   . . .
 ```
diff --git a/guides/plug.md b/guides/plug.md
index 73f87c270..762518607 100644
--- a/guides/plug.md
+++ b/guides/plug.md
@@ -47,7 +47,7 @@ defmodule HelloWeb.MessageController do
           message ->
             case authorize_message(conn, params["id"]) do
               :ok ->
-                render conn, :show, page: find_message(params["id"])
+                render(conn, :show, page: find_message(params["id"]))
               :error ->
                 conn |> put_flash(:info, "You can't access that page") |> redirect(to: "/")
             end
@@ -70,7 +70,7 @@ defmodule HelloWeb.MessageController do
   plug :authorize_message
 
   def show(conn, params) do
-    render conn, :show, page: find_message(params["id"])
+    render(conn, :show, page: find_message(params["id"]))
   end
 
   defp authenticate(conn, _) do
diff --git a/guides/presence.md b/guides/presence.md
index c352652ea..d3ebb620c 100644
--- a/guides/presence.md
+++ b/guides/presence.md
@@ -86,7 +86,7 @@ defmodule HelloWeb.RoomChannel do
   end
 
   def handle_info(:after_join, socket) do
-    push socket, "presence_state", Presence.list(socket)
+    push(socket, "presence_state", Presence.list(socket))
     {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
       online_at: inspect(System.system_time(:seconds))
     })
diff --git a/guides/templates.md b/guides/templates.md
index 48cee4be6..b8380bb5b 100644
--- a/guides/templates.md
+++ b/guides/templates.md
@@ -43,7 +43,7 @@ defmodule HelloWeb.PageController do
   ...
 
   def test(conn, _params) do
-    render conn, "test.html"
+    render(conn, "test.html")
   end
 end
 ```
@@ -74,7 +74,7 @@ defmodule HelloWeb.PageView do
   use HelloWeb, :view
 
   def handler_info(conn) do
-    "Request Handled By: #{controller_module conn}.#{action_name conn}"
+    "Request Handled By: #{controller_module(conn)}.#{action_name(conn)}"
   end
 
   def connection_keys(conn) do
@@ -89,7 +89,7 @@ We have a route. We created a new controller action. We have made modifications
 
 ```html
 <div class="jumbotron">
-  <p><%= handler_info @conn %></p>
+  <p><%= handler_info(@conn) %></p>
 </div>
 ```
 
@@ -111,11 +111,11 @@ We can add a header and a list comprehension like this.
 
 ```html
 <div class="jumbotron">
-  <p><%= handler_info @conn %></p>
+  <p><%= handler_info(@conn) %></p>
 
   <h3>Keys for the conn Struct</h3>
 
-  <%= for key <- connection_keys @conn do %>
+  <%= for key <- connection_keys(@conn) do %>
     <p><%= key %></p>
   <% end %>
 </div>
@@ -148,12 +148,12 @@ Now that we have a template, we simply render it within our list comprehension i
 
 ```html
 <div class="jumbotron">
-  <p><%= handler_info @conn %></p>
+  <p><%= handler_info(@conn) %></p>
 
   <h3>Keys for the conn Struct</h3>
 
-  <%= for key <- connection_keys @conn do %>
-    <%= render "key.html", key: key %>
+  <%= for key <- connection_keys(@conn) do %>
+    <%= render("key.html", key: key) %>
   <% end %>
 </div>
 ```
@@ -172,8 +172,8 @@ Let's move our template into a shared view.
 <div class="jumbotron">
   ...
 
-  <%= for key <- connection_keys @conn do %>
-    <%= render HelloWeb.PageView, "key.html", key: key %>
+  <%= for key <- connection_keys(@conn) do %>
+    <%= render(HelloWeb.PageView, "key.html", key: key) %>
   <% end %>
 </div>
 ```
@@ -189,8 +189,8 @@ end
 Now we can move `key.html.eex` from the `lib/hello_web/templates/page` directory into the `lib/hello_web/templates/shared` directory. Once that happens, we can change the render call in `lib/hello_web/templates/page/test.html.eex` to use the new `HelloWeb.SharedView`.
 
 ```html
-<%= for key <- connection_keys @conn do %>
-  <%= render HelloWeb.SharedView, "key.html", key: key %>
+<%= for key <- connection_keys(@conn) do %>
+  <%= render(HelloWeb.SharedView, "key.html", key: key) %>
 <% end %>
 ```
 Going back to [localhost:4000/test](http://localhost:4000/test) again. The page should look exactly as it did before.
diff --git a/guides/views.md b/guides/views.md
index 190bc03cb..62af400ed 100644
--- a/guides/views.md
+++ b/guides/views.md
@@ -73,7 +73,7 @@ Let's open up the `lib/hello_web/templates/page/index.html.eex` and locate this
 
 ```html
 <div class="jumbotron">
-  <h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
+  <h2><%= gettext("Welcome to %{name}!", name: "Phoenix") %></h2>
   <p class="lead">A productive web framework that<br>does not compromise speed and maintainability.</p>
 </div>
 ```
@@ -82,9 +82,9 @@ Then let's add a line with a link back to the same page. (The objective is to se
 
 ```html
 <div class="jumbotron">
-  <h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
+  <h2><%= gettext("Welcome to %{name}!", name: "Phoenix") %></h2>
   <p class="lead">A productive web framework that<br>does not compromise speed and maintainability.</p>
-  <p><a href="<%= page_path @conn, :index %>">Link back to this page</a></p>
+  <p><a href="<%= page_path(@conn, :index) %>">Link back to this page</a></p>
 </div>
 ```
 
@@ -123,8 +123,7 @@ This is the message: <%= message() %>
 This doesn't correspond to any action in our controller, but we'll exercise it in an `iex` session. At the root of our project, we can run `iex -S mix`, and then explicitly render our template.
 
 ```console
-iex(1)> Phoenix.View.render(HelloWeb.PageView, "test.html",
-%{})
+iex(1)> Phoenix.View.render(HelloWeb.PageView, "test.html", %{})
   {:safe, [["" | "This is the message: "] | "Hello from the view!"]}
 ```
 As we can see, we're calling `render/3` with the individual view responsible for our test template, the name of our test template, and an empty map representing any data we might have wanted to pass in. The return value is a tuple beginning with the atom `:safe` and the resultant io list of the interpolated template. "Safe" here means that Phoenix has escaped the contents of our rendered template. Phoenix defines its own `Phoenix.HTML.Safe` protocol with implementations for atoms, bitstrings, lists, integers, floats, and tuples to handle this escaping for us as our templates are rendered into strings.
@@ -173,7 +172,7 @@ If we need only the rendered string, without the whole tuple, we can use the `re
 Layouts are just templates. They have a view, just like other templates. In a newly generated app, this is `lib/hello_web/views/layout_view.ex`. You may be wondering how the string resulting from a rendered view ends up inside a layout. That's a great question! If we look at `lib/hello_web/templates/layout/app.html.eex`, just about in the middle of the `<body>`, we will see this.
 
 ```html
-<%= render @view_module, @view_template, assigns %>
+<%= render(@view_module, @view_template, assigns) %>
 ```
 
 This is where the view module and its template from the controller are rendered to a string and placed in the layout.
@@ -197,7 +196,7 @@ defmodule HelloWeb.ErrorView do
   # In case no render clause matches or no
   # template is found, let's render it as 500
   def template_not_found(_template, assigns) do
-    render "500.html", assigns
+    render("500.html", assigns)
   end
 end
 ```
@@ -289,13 +288,13 @@ defmodule HelloWeb.PageController do
   def show(conn, _params) do
     page = %{title: "foo"}
 
-    render conn, "show.json", page: page
+    render(conn, "show.json", page: page)
   end
 
   def index(conn, _params) do
     pages = [%{title: "foo"}, %{title: "bar"}]
 
-    render conn, "index.json", pages: pages
+    render(conn, "index.json", pages: pages)
   end
 end
 ```
@@ -320,7 +319,7 @@ defmodule HelloWeb.PageView do
 end
 ```
 
-In the view we see our `render/2` function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. In our controller `show/2` function, `render conn, "show.json", page: page` will pattern match on the matching name and extension in the view's `render/2` functions. In other words, `render conn, "index.json", pages: pages` will call `render("index.json", %{pages: pages})`. The `render_many/3` function takes the data we want to respond with (`pages`), a `View`, and a string to pattern match on the `render/2` function defined on `View`. It will map over each item in `pages`, and pass the item to the `render/2` function in `View` matching the file string. `render_one/3` follows, the same signature, ultimately using the `render/2` matching `page.json` to specify what each `page` looks like. The `render/2` matching `"index.json"` will respond with JSON as you would expect:
+In the view we see our `render/2` function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. In our controller `show/2` function, `render(conn, "show.json", page: page)` will pattern match on the matching name and extension in the view's `render/2` functions. In other words, `render(conn, "index.json", pages: pages)` will call `render("index.json", %{pages: pages})`. The `render_many/3` function takes the data we want to respond with (`pages`), a `View`, and a string to pattern match on the `render/2` function defined on `View`. It will map over each item in `pages`, and pass the item to the `render/2` function in `View` matching the file string. `render_one/3` follows, the same signature, ultimately using the `render/2` matching `page.json` to specify what each `page` looks like. The `render/2` matching `"index.json"` will respond with JSON as you would expect:
 
 ```javascript
   {
diff --git a/installer/README.md b/installer/README.md
index 773cf2133..48c596f1d 100644
--- a/installer/README.md
+++ b/installer/README.md
@@ -3,5 +3,4 @@
 Provides `phx.new` installer as an archive. To build and install it locally:
 
     $ cd installer
-    $ MIX_ENV=prod mix archive.build
-    $ mix archive.install
+    $ MIX_ENV=prod mix do archive.build, archive.install
diff --git a/installer/templates/phx_assets/webpack/app.js b/installer/templates/phx_assets/webpack/app.js
index df229e404..e79c19b2b 100644
--- a/installer/templates/phx_assets/webpack/app.js
+++ b/installer/templates/phx_assets/webpack/app.js
@@ -1,7 +1,7 @@
 // We need to import the CSS so that webpack will load it.
 // The MiniCssExtractPlugin is used to separate it out into
 // its own CSS file.
-import css from "../css/app.css";
+import css from "../css/app.css"
 
 // webpack automatically bundles all modules in your
 // entry points. Those entry points can be configured
diff --git a/installer/templates/phx_assets/webpack/package.json b/installer/templates/phx_assets/webpack/package.json
index bb947c55c..00168881c 100644
--- a/installer/templates/phx_assets/webpack/package.json
+++ b/installer/templates/phx_assets/webpack/package.json
@@ -17,7 +17,6 @@
     "css-loader": "^0.28.10",
     "mini-css-extract-plugin": "^0.4.0",
     "optimize-css-assets-webpack-plugin": "^4.0.0",
-    "style-loader": "^0.20.2",
     "uglifyjs-webpack-plugin": "^1.2.4",
     "webpack": "4.4.0",
     "webpack-cli": "^2.0.10"
diff --git a/installer/templates/phx_ecto/repo.ex b/installer/templates/phx_ecto/repo.ex
index b687a0c1a..1e81c5bf1 100644
--- a/installer/templates/phx_ecto/repo.ex
+++ b/installer/templates/phx_ecto/repo.ex
@@ -5,7 +5,6 @@ defmodule <%= app_module %>.Repo do
   Dynamically loads the repository url from the
   DATABASE_URL environment variable.
   """
-  @impl true
   def init(_, opts) do
     if url = System.get_env("DATABASE_URL") do
       {:ok, Keyword.put(opts, :url, url)}
diff --git a/installer/templates/phx_gettext/errors.pot b/installer/templates/phx_gettext/errors.pot
index c536d273a..d630f3894 100644
--- a/installer/templates/phx_gettext/errors.pot
+++ b/installer/templates/phx_gettext/errors.pot
@@ -1,11 +1,11 @@
-## This file is a PO Template file.
+## This is a PO Template file.
 ##
 ## `msgid`s here are often extracted from source code.
 ## Add new translations manually only if they're dynamic
 ## translations that can't be statically extracted.
 ##
 ## Run `mix gettext.extract` to bring this file up to
-## date. Leave `msgstr`s empty as changing them here as no
+## date. Leave `msgstr`s empty as changing them here has no
 ## effect: edit them in PO (`.po`) files instead.
 <%= if ecto do %>
 ## From Ecto.Changeset.cast/4
diff --git a/installer/templates/phx_gettext/gettext.ex b/installer/templates/phx_gettext/gettext.ex
index f60f32478..c693406ce 100644
--- a/installer/templates/phx_gettext/gettext.ex
+++ b/installer/templates/phx_gettext/gettext.ex
@@ -8,15 +8,15 @@ defmodule <%= web_namespace %>.Gettext do
       import <%= web_namespace %>.Gettext
 
       # Simple translation
-      gettext "Here is the string to translate"
+      gettext("Here is the string to translate")
 
       # Plural translation
-      ngettext "Here is the string to translate",
+      ngettext("Here is the string to translate",
                "Here are the strings to translate",
-               3
+               3)
 
       # Domain-based translation
-      dgettext "errors", "Here is the error message to translate"
+      dgettext("errors", "Here is the error message to translate")
 
   See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
   """
diff --git a/installer/templates/phx_single/lib/app_name/application.ex b/installer/templates/phx_single/lib/app_name/application.ex
index 56d522c75..1980c5e09 100644
--- a/installer/templates/phx_single/lib/app_name/application.ex
+++ b/installer/templates/phx_single/lib/app_name/application.ex
@@ -5,7 +5,6 @@ defmodule <%= app_module %>.Application do
 
   use Application
 
-  @impl true
   def start(_type, _args) do
     # List all child processes to be supervised
     children = [<%= if ecto do %>
diff --git a/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex b/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
index c5f84d0b0..0510c47d9 100644
--- a/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
+++ b/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
@@ -5,7 +5,6 @@ defmodule <%= app_module %>.Application do
 
   use Application
 
-  @impl true
   def start(_type, _args) do<%= if ecto do %>
     Supervisor.start_link([
       <%= app_module %>.Repo,
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
index 83099d0c8..c4758c9f5 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
+++ b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
@@ -5,7 +5,6 @@ defmodule <%= web_namespace %>.Application do
 
   use Application
 
-  @impl true
   def start(_type, _args) do
     # List all child processes to be supervised
     children = [
diff --git a/installer/templates/phx_web/channels/user_socket.ex b/installer/templates/phx_web/channels/user_socket.ex
index a5b3f3991..69e043f65 100644
--- a/installer/templates/phx_web/channels/user_socket.ex
+++ b/installer/templates/phx_web/channels/user_socket.ex
@@ -4,10 +4,6 @@ defmodule <%= web_namespace %>.UserSocket do
   ## Channels
   # channel "room:*", <%= web_namespace %>.RoomChannel
 
-  ## Transports
-  transport :websocket, Phoenix.Transports.WebSocket
-  # transport :longpoll, Phoenix.Transports.LongPoll
-
   # Socket params are passed from the client and can
   # be used to verify and authenticate a user. After
   # verification, you can put default assigns into
diff --git a/installer/templates/phx_web/endpoint.ex b/installer/templates/phx_web/endpoint.ex
index a3f6469df..836894ad9 100644
--- a/installer/templates/phx_web/endpoint.ex
+++ b/installer/templates/phx_web/endpoint.ex
@@ -1,7 +1,9 @@
 defmodule <%= endpoint_module %> do
   use Phoenix.Endpoint, otp_app: :<%= web_app_name %>
 
-  socket "/socket", <%= web_namespace %>.UserSocket
+  socket "/socket", <%= web_namespace %>.UserSocket,
+    websocket: true,
+    longpoll: false
 
   # Serve at "/" the static files from "priv/static" directory.
   #
@@ -46,7 +48,6 @@ defmodule <%= endpoint_module %> do
   It receives the endpoint configuration and checks if
   configuration should be loaded from the system environment.
   """
-  @impl true
   def init(_key, config) do
     if config[:load_from_system_env] do
       port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
diff --git a/installer/templates/phx_web/templates/layout/app.html.eex b/installer/templates/phx_web/templates/layout/app.html.eex
index 6885c3267..d9bc67994 100644
--- a/installer/templates/phx_web/templates/layout/app.html.eex
+++ b/installer/templates/phx_web/templates/layout/app.html.eex
@@ -5,8 +5,7 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
     <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no, user-scalable=no"/>
     <title><%= app_module %> · Phoenix Framework</title>
-    <link rel="stylesheet" href="<%%= Routes.static_push(@conn, "/css/app.css") %>"/>
-    <script defer type="text/javascript" src="<%%= Routes.static_push(@conn, "/js/app.js") %>"></script>
+    <link rel="stylesheet" href="<%%= Routes.static_path(@conn, "/css/app.css") %>"/>
   </head>
   <body>
     <header>
@@ -26,5 +25,6 @@
       <p class="alert alert-danger" role="alert"><%%= get_flash(@conn, :error) %></p>
       <%%= render @view_module, @view_template, assigns %>
     </main>
+    <script type="text/javascript" src="<%%= Routes.static_path(@conn, "/js/app.js") %>"></script>
   </body>
 </html>
diff --git a/installer/templates/phx_web/views/error_helpers.ex b/installer/templates/phx_web/views/error_helpers.ex
index 867beacae..e813b4472 100644
--- a/installer/templates/phx_web/views/error_helpers.ex
+++ b/installer/templates/phx_web/views/error_helpers.ex
@@ -22,10 +22,10 @@ defmodule <%= web_namespace %>.ErrorHelpers do
     # to translate as a static argument:
     #
     #     # Translate "is invalid" in the "errors" domain
-    #     dgettext "errors", "is invalid"
+    #     dgettext("errors", "is invalid")
     #
     #     # Translate the number of files with plural rules
-    #     dngettext "errors", "1 file", "%{count} files", count
+    #     dngettext("errors", "1 file", "%{count} files", count)
     #
     # Because the error messages we show in our forms and APIs
     # are defined inside Ecto, we need to translate them dynamically.
diff --git a/lib/phoenix.ex b/lib/phoenix.ex
index 9537bbbdf..57af75552 100644
--- a/lib/phoenix.ex
+++ b/lib/phoenix.ex
@@ -49,7 +49,8 @@ defmodule Phoenix do
 
     children = [
       # Code reloading must be serial across all Phoenix apps
-      worker(Phoenix.CodeReloader.Server, [])
+      worker(Phoenix.CodeReloader.Server, []),
+      supervisor(Phoenix.Transports.LongPoll.Supervisor, [])
     ]
 
     Supervisor.start_link(children, strategy: :one_for_one, name: Phoenix.Supervisor)
diff --git a/lib/phoenix/channel.ex b/lib/phoenix/channel.ex
index e2ca5ff7a..4b38b09a2 100644
--- a/lib/phoenix/channel.ex
+++ b/lib/phoenix/channel.ex
@@ -52,7 +52,7 @@ defmodule Phoenix.Channel do
   and broadcasting the message to all topic subscribers for this socket.
 
       def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do
-        broadcast! socket, "new_msg", %{uid: uid, body: body}
+        broadcast!(socket, "new_msg", %{uid: uid, body: body})
         {:noreply, socket}
       end
 
@@ -60,7 +60,7 @@ defmodule Phoenix.Channel do
 
       # client asks for their current rank, push sent directly as a new event.
       def handle_in("current_rank", socket) do
-        push socket, "current_rank", %{val: Game.get_rank(socket.assigns[:user])}
+        push(socket, "current_rank", %{val: Game.get_rank(socket.assigns[:user])})
         {:noreply, socket}
       end
 
@@ -113,9 +113,9 @@ defmodule Phoenix.Channel do
       # for every socket subscribing to this topic, append an `is_editable`
       # value for client metadata.
       def handle_out("new_msg", msg, socket) do
-        push socket, "new_msg", Map.merge(msg,
+        push(socket, "new_msg", Map.merge(msg,
           %{is_editable: User.can_edit_message?(socket.assigns[:user], msg)}
-        )
+        ))
         {:noreply, socket}
       end
 
@@ -123,7 +123,7 @@ defmodule Phoenix.Channel do
       # is ignoring the user who joined.
       def handle_out("user_joined", msg, socket) do
         unless User.ignoring?(socket.assigns[:user], msg.user_id) do
-          push socket, "user_joined", msg
+          push(socket, "user_joined", msg)
         end
         {:noreply, socket}
       end
@@ -138,18 +138,18 @@ defmodule Phoenix.Channel do
       # within channel
       def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do
         ...
-        broadcast_from! socket, "new_msg", %{uid: uid, body: body}
-        MyApp.Endpoint.broadcast_from! self(), "room:superadmin",
-          "new_msg", %{uid: uid, body: body}
+        broadcast_from!(socket, "new_msg", %{uid: uid, body: body})
+        MyApp.Endpoint.broadcast_from!(self(), "room:superadmin",
+          "new_msg", %{uid: uid, body: body})
         {:noreply, socket}
       end
 
       # within controller
       def create(conn, params) do
         ...
-        MyApp.Endpoint.broadcast! "room:" <> rid, "new_msg", %{uid: uid, body: body}
-        MyApp.Endpoint.broadcast! "room:superadmin", "new_msg", %{uid: uid, body: body}
-        redirect conn, to: "/"
+        MyApp.Endpoint.broadcast!("room:" <> rid, "new_msg", %{uid: uid, body: body})
+        MyApp.Endpoint.broadcast!("room:superadmin", "new_msg", %{uid: uid, body: body})
+        redirect(conn, to: "/")
       end
 
   ## Terminate
@@ -245,7 +245,7 @@ defmodule Phoenix.Channel do
 
       alias Phoenix.Socket.Broadcast
       def handle_info(%Broadcast{topic: _, event: ev, payload: payload}, socket) do
-        push socket, ev, payload
+        push(socket, ev, payload)
         {:noreply, socket}
       end
 
@@ -259,6 +259,7 @@ defmodule Phoenix.Channel do
   incoming events:
 
       use Phoenix.Channel, log_join: :info, log_handle_in: false
+
   """
   alias Phoenix.Socket
   alias Phoenix.Channel.Server
@@ -267,10 +268,6 @@ defmodule Phoenix.Channel do
   @type socket_ref :: {transport_pid :: Pid, serializer :: module,
                        topic :: binary, ref :: binary, join_ref :: binary}
 
-  @callback code_change(old_vsn, Socket.t, extra :: term) ::
-              {:ok, Socket.t} |
-              {:error, reason :: term} when old_vsn: term | {:down, term}
-
   @callback join(topic :: binary, auth_msg :: map, Socket.t) ::
               {:ok, Socket.t} |
               {:ok, map, Socket.t} |
@@ -278,17 +275,29 @@ defmodule Phoenix.Channel do
 
   @callback handle_in(event :: String.t, msg :: map, Socket.t) ::
               {:noreply, Socket.t} |
+              {:noreply, Socket.t, timeout | :hibernate} |
               {:reply, reply, Socket.t} |
               {:stop, reason :: term, Socket.t} |
               {:stop, reason :: term, reply, Socket.t}
 
+  @callback handle_out(event :: String.t, msg :: map, Socket.t) ::
+              {:noreply, Socket.t} |
+              {:noreply, Socket.t, timeout | :hibernate} |
+              {:stop, reason :: term, Socket.t}
+
   @callback handle_info(term, Socket.t) ::
               {:noreply, Socket.t} |
               {:stop, reason :: term, Socket.t}
 
+  @callback code_change(old_vsn, Socket.t, extra :: term) ::
+              {:ok, Socket.t} |
+              {:error, reason :: term} when old_vsn: term | {:down, term}
+
   @callback terminate(reason :: :normal | :shutdown | {:shutdown, :left | :closed | term}, Socket.t) ::
               term
 
+  @optional_callbacks handle_in: 3, handle_out: 3, handle_info: 2, code_change: 3, terminate: 2
+
   defmacro __using__(opts \\ []) do
     quote do
       opts = unquote(opts)
@@ -303,23 +312,8 @@ defmodule Phoenix.Channel do
       import Phoenix.Socket, only: [assign: 3]
 
       def __socket__(:private) do
-        %{log_join: @phoenix_log_join,
-          log_handle_in: @phoenix_log_handle_in}
-      end
-
-      def code_change(_old, socket, _extra), do: {:ok, socket}
-
-      def handle_in(_event, _message, socket) do
-        {:noreply, socket}
+        %{log_join: @phoenix_log_join, log_handle_in: @phoenix_log_handle_in}
       end
-
-      def handle_info(message, state) do
-        Phoenix.Channel.Server.unhandled_handle_info(message, state)
-      end
-
-      def terminate(_reason, _socket), do: :ok
-
-      defoverridable code_change: 3, handle_info: 2, handle_in: 3, terminate: 2
     end
   end
 
@@ -346,9 +340,9 @@ defmodule Phoenix.Channel do
       intercept ["new_msg"]
 
       def handle_out("new_msg", payload, socket) do
-        push socket, "new_msg", Map.merge(payload,
+        push(socket, "new_msg", Map.merge(payload,
           is_editable: User.can_edit_message?(socket.assigns[:user], payload)
-        )
+        ))
         {:noreply, socket}
       end
 
@@ -366,7 +360,7 @@ defmodule Phoenix.Channel do
 
   @doc false
   def __on_definition__(env, :def, :handle_out, [event, _payload, _socket], _, _)
-    when is_binary(event) do
+      when is_binary(event) do
 
     unless event in Module.get_attribute(env.module, :phoenix_intercepts) do
       IO.write "#{Path.relative_to(env.file, File.cwd!)}:#{env.line}: [warning] " <>
@@ -374,7 +368,9 @@ defmodule Phoenix.Channel do
                "Add \"#{event}\" to your list of intercepted events with intercept/1"
     end
   end
+
   def __on_definition__(_env, _kind, _name, _args, _guards, _body) do
+    :ok
   end
 
   @doc """
@@ -384,7 +380,7 @@ defmodule Phoenix.Channel do
 
   ## Examples
 
-      iex> broadcast socket, "new_message", %{id: 1, content: "hello"}
+      iex> broadcast(socket, "new_message", %{id: 1, content: "hello"})
       :ok
 
   """
@@ -409,7 +405,7 @@ defmodule Phoenix.Channel do
 
   ## Examples
 
-      iex> broadcast_from socket, "new_message", %{id: 1, content: "hello"}
+      iex> broadcast_from(socket, "new_message", %{id: 1, content: "hello"})
       :ok
 
   """
@@ -433,7 +429,7 @@ defmodule Phoenix.Channel do
 
   ## Examples
 
-      iex> push socket, "new_message", %{id: 1, content: "hello"}
+      iex> push(socket, "new_message", %{id: 1, content: "hello"})
       :ok
 
   """
@@ -465,7 +461,7 @@ defmodule Phoenix.Channel do
       end
 
       def handle_info({:work_complete, result, ref}, socket) do
-        reply ref, {:ok, result}
+        reply(ref, {:ok, result})
         {:noreply, socket}
       end
 
@@ -509,7 +505,7 @@ defmodule Phoenix.Channel do
         end
 
         def handle_info(:after_join, socket) do
-          push socket, "feed", %{list: feed_items(socket)}
+          push(socket, "feed", %{list: feed_items(socket)})
           {:noreply, socket}
         end
     """
diff --git a/lib/phoenix/channel/server.ex b/lib/phoenix/channel/server.ex
index 67c8dec32..6fe5bf8ac 100644
--- a/lib/phoenix/channel/server.ex
+++ b/lib/phoenix/channel/server.ex
@@ -1,29 +1,54 @@
 defmodule Phoenix.Channel.Server do
+  @moduledoc false
   use GenServer
+
   require Logger
   require Phoenix.Endpoint
+
   alias Phoenix.PubSub
   alias Phoenix.Socket
-  alias Phoenix.Socket.{Broadcast, Message, Reply}
-
-  @moduledoc false
+  alias Phoenix.Socket.{Broadcast, Message, Reply, PoolSupervisor}
 
   ## Transport API
 
+  @doc """
+  Starts a channel server.
+  """
+  def start_link(socket, payload, pid, ref) do
+    GenServer.start_link(__MODULE__, {socket, payload, pid, ref})
+  end
+
   @doc """
   Joins the channel in socket with authentication payload.
   """
-  @spec join(Socket.t, map) :: {:ok, map, pid} | {:error, map}
-  def join(socket, auth_payload) do
-    Phoenix.Endpoint.instrument socket, :phoenix_channel_join,
-      %{params: auth_payload, socket: socket}, fn ->
+  @spec join(Socket.t, module, Message.t, keyword) :: {:ok, map, pid, Socket.t} | {:error, map}
+  def join(socket, channel, message, opts) do
+    %{topic: topic, payload: payload, ref: join_ref} = message
+    assigns = Keyword.get(opts, :assigns, %{})
+
+    socket =
+      %Socket{
+        socket
+        | topic: topic,
+          channel: channel,
+          join_ref: join_ref,
+          assigns: Map.merge(socket.assigns, assigns),
+          private: channel.__socket__(:private)
+      }
+
+    instrument = %{params: payload, socket: socket}
+
+    Phoenix.Endpoint.instrument socket, :phoenix_channel_join, instrument, fn ->
       ref = make_ref()
+      key = {self(), ref}
 
-      case GenServer.start_link(__MODULE__, {socket, auth_payload, self(), ref}) do
+      case PoolSupervisor.start_child(socket.handler, key, [socket, payload, self(), ref]) do
+        {:ok, :undefined} ->
+          log_join socket, topic, fn -> "Replied #{topic} :error" end
+          receive do: ({^ref, reply} -> {:error, reply})
         {:ok, pid} ->
+          log_join socket, topic, fn -> "Replied #{topic} :ok" end
           receive do: ({^ref, reply} -> {:ok, reply, pid})
-        :ignore ->
-          receive do: ({^ref, reply} -> {:error, reply})
         {:error, reason} ->
           Logger.error fn -> Exception.format_exit(reason) end
           {:error, %{reason: "join crashed"}}
@@ -31,34 +56,68 @@ defmodule Phoenix.Channel.Server do
     end
   end
 
+  defp log_join(_, "phoenix" <> _, _func), do: :noop
+  defp log_join(%{private: %{log_join: false}}, _topic, _func), do: :noop
+  defp log_join(%{private: %{log_join: level}}, _topic, func), do: Logger.log(level, func)
+
+  @doc """
+  Gets the socket from the channel.
+  """
+  @spec socket(pid) :: Socket.t
+  def socket(pid) do
+    GenServer.call(pid, :socket)
+  end
+
   @doc """
-  Notifies the channel the client closed.
+  Notifies the channels the clients closed.
 
   This event is synchronous as we want to guarantee
-  proper termination of the channel.
+  proper termination of the channels.
   """
-  def close(pid, timeout \\ 5000) do
+  @spec close([pid], timeout) :: :ok
+  def close(pids, timeout \\ 5000)
+
+  def close([], _timeout) do
+    :ok
+  end
+
+  def close(pids, timeout) do
     # We need to guarantee that the channel has been closed
-    # otherwise the link in the transport will trigger it to
-    # crash.
-    ref = Process.monitor(pid)
-    GenServer.cast(pid, :close)
+    # otherwise the link in the transport will trigger it to crash.
+    pids_and_refs =
+      for pid <- pids do
+        ref = Process.monitor(pid)
+        GenServer.cast(pid, :close)
+        {pid, ref}
+      end
+
+    timeout_ref = make_ref()
+    timer_ref = Process.send_after(self(), {:timeout, timeout_ref}, timeout)
+
+    Enum.reduce(pids_and_refs, :infinity, fn {pid, ref}, timeout ->
+      receive do
+        {:DOWN, ^ref, _, _, _} -> timeout
+        {:timeout, ^timeout_ref} -> kill(pid, ref)
+      after
+        timeout -> kill(pid, ref)
+      end
+    end)
+
+    Process.cancel_timer(timer_ref)
+
     receive do
-      {:DOWN, ^ref, _, _, _} -> :ok
+      {:timeout, ^timeout_ref} -> :ok
     after
-      timeout ->
-        Process.exit(pid, :kill)
-        receive do
-          {:DOWN, ^ref, _, _, _} -> :ok
-        end
+      0 -> :ok
     end
   end
 
-  @doc """
-  Gets the socket from the channel.
-  """
-  def socket(pid) do
-    GenServer.call(pid, :socket)
+  defp kill(pid, ref) do
+    Process.exit(pid, :kill)
+
+    receive do
+      {:DOWN, ^ref, _, _, _} -> 0
+    end
   end
 
   ## Channel API
@@ -99,7 +158,6 @@ defmodule Phoenix.Channel.Server do
     raise_invalid_message(topic, event, payload)
   end
 
-
   @doc """
   Broadcasts on the given pubsub server with the given
   `from`, `topic`, `event` and `payload`.
@@ -142,11 +200,8 @@ defmodule Phoenix.Channel.Server do
   """
   def push(pid, topic, event, payload, serializer)
       when is_binary(topic) and is_binary(event) and is_map(payload) do
-
-    encoded_msg = serializer.encode!(%Message{topic: topic,
-                                              event: event,
-                                              payload: payload})
-    send pid, encoded_msg
+    message = %Message{topic: topic, event: event, payload: payload}
+    send pid, serializer.encode!(message)
     :ok
   end
   def push(_, topic, event, payload, _) do
@@ -158,10 +213,8 @@ defmodule Phoenix.Channel.Server do
   """
   def reply(pid, join_ref, ref, topic, {status, payload}, serializer)
       when is_binary(topic) and is_map(payload) do
-
-    send pid, serializer.encode!(
-      %Reply{topic: topic, join_ref: join_ref, ref: ref, status: status, payload: payload}
-    )
+    reply = %Reply{topic: topic, join_ref: join_ref, ref: ref, status: status, payload: payload}
+    send pid, serializer.encode!(reply)
     :ok
   end
   def reply(_, _, _, topic, {_status, payload}, _) do
@@ -183,13 +236,15 @@ defmodule Phoenix.Channel.Server do
 
   @doc false
   def init({socket, auth_payload, parent, ref}) do
+    _ = Process.monitor(socket.transport_pid)
+    %{channel: channel, topic: topic} = socket
     socket = %{socket | channel_pid: self()}
 
-    case socket.channel.join(socket.topic, auth_payload, socket) do
+    case channel.join(topic, auth_payload, socket) do
       {:ok, socket} ->
-        join(socket, %{}, parent, ref)
+        init(socket, %{}, parent, ref)
       {:ok, reply, socket} ->
-        join(socket, reply, parent, ref)
+        init(socket, reply, parent, ref)
       {:error, reply} ->
         send(parent, {ref, reply})
         :ignore
@@ -206,18 +261,9 @@ defmodule Phoenix.Channel.Server do
     end
   end
 
-  @doc false
-  def code_change(old, socket, extra) do
-    socket.channel.code_change(old, socket, extra)
-  end
-
-  defp join(socket, reply, parent, ref) do
-    PubSub.subscribe(socket.pubsub_server, socket.topic,
-      link: true,
-      fastlane: {socket.transport_pid,
-                 socket.serializer,
-                 socket.channel.__intercepts__()})
-
+  defp init(socket, reply, parent, ref) do
+    fastlane = {socket.transport_pid, socket.serializer, socket.channel.__intercepts__()}
+    PubSub.subscribe(socket.pubsub_server, socket.topic, link: true, fastlane: fastlane)
     send(parent, {ref, reply})
     {:ok, %{socket | joined: true}}
   end
@@ -229,11 +275,12 @@ defmodule Phoenix.Channel.Server do
 
   @doc false
   def handle_cast(:close, socket) do
-    handle_result({:stop, {:shutdown, :closed}, socket}, :handle_in)
+    handle_result({:stop, {:shutdown, :closed}, socket}, :handle_cast)
   end
 
+  @doc false
   def handle_info(%Message{topic: topic, event: "phx_leave", ref: ref}, %{topic: topic} = socket) do
-    handle_result({:stop, {:shutdown, :left}, :ok, put_in(socket.ref, ref)}, :handle_in)
+    handle_in({:stop, {:shutdown, :left}, :ok, put_in(socket.ref, ref)})
   end
 
   def handle_info(%Message{topic: topic, event: event, payload: payload, ref: ref},
@@ -242,7 +289,7 @@ defmodule Phoenix.Channel.Server do
       %{ref: ref, event: event, params: payload, socket: socket}, fn ->
       event
       |> socket.channel.handle_in(payload, put_in(socket.ref, ref))
-      |> handle_result(:handle_in)
+      |> handle_in()
     end
   end
 
@@ -253,15 +300,44 @@ defmodule Phoenix.Channel.Server do
     |> handle_result(:handle_out)
   end
 
-  def handle_info(msg, socket) do
-    msg
-    |> socket.channel.handle_info(socket)
-    |> handle_result(:handle_info)
+  def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do
+    handle_result({:stop, reason, socket}, :handle_info)
+  end
+
+  def handle_info(msg, %{channel: channel} = socket) do
+    if function_exported?(channel, :handle_info, 2) do
+      msg
+      |> socket.channel.handle_info(socket)
+      |> handle_result(:handle_info)
+    else
+      proc =
+        case Process.info(self(), :registered_name) do
+          {_, []}   -> self()
+          {_, name} -> name
+        end
+
+      :error_logger.warning_msg('~p ~p received unexpected message in handle_info/2: ~p~n',
+                                [__MODULE__, proc, msg])
+      {:noreply, socket}
+    end
   end
 
   @doc false
-  def terminate(reason, socket) do
-    socket.channel.terminate(reason, socket)
+  def code_change(old, %{channel: channel} = socket, extra) do
+    if function_exported?(channel, :code_change, 3) do
+      channel.code_change(old, socket, extra)
+    else
+      {:ok, socket}
+    end
+  end
+
+  @doc false
+  def terminate(reason, %{channel: channel} = socket) do
+    if function_exported?(channel, :terminate, 2) do
+      channel.terminate(reason, socket)
+    else
+      :ok
+    end
   end
 
   @doc false
@@ -299,31 +375,8 @@ defmodule Phoenix.Channel.Server do
     end)
   end
 
-  @doc false
-  # TODO: Revisit in future GenServer releases
-  def unhandled_handle_info(msg, state) do
-    proc =
-      case Process.info(self(), :registered_name) do
-        {_, []}   -> self()
-        {_, name} -> name
-      end
-    :error_logger.warning_msg('~p ~p received unexpected message in handle_info/2: ~p~n',
-                              [__MODULE__, proc, msg])
-    {:noreply, state}
-  end
-
   ## Handle results
 
-  defp handle_result({:reply, reply, %Socket{} = socket}, callback) do
-    handle_reply(socket, reply, callback)
-    {:noreply, put_in(socket.ref, nil)}
-  end
-
-  defp handle_result({:stop, reason, reply, socket}, callback) do
-    handle_reply(socket, reply, callback)
-    handle_result({:stop, reason, socket}, callback)
-  end
-
   defp handle_result({:stop, reason, socket}, _callback) do
     case reason do
       :normal -> notify_transport_of_graceful_exit(socket)
@@ -370,20 +423,39 @@ defmodule Phoenix.Channel.Server do
     """
   end
 
-  ## Handle replies
+  defp notify_transport_of_graceful_exit(socket) do
+    %{topic: topic, join_ref: ref, transport_pid: transport_pid} = socket
+    close_msg = %Message{join_ref: ref, ref: ref, topic: topic, event: "phx_close", payload: %{}}
+    send(transport_pid, {:graceful_exit, self(), close_msg})
+    :ok
+  end
 
-  defp handle_reply(socket, {status, payload}, :handle_in)
-       when is_atom(status) and is_map(payload) do
+  ## Handle in/replies
+
+  defp handle_in({:reply, reply, %Socket{} = socket}) do
+    handle_reply(socket, reply)
+    {:noreply, put_in(socket.ref, nil)}
+  end
+
+  defp handle_in({:stop, reason, reply, socket}) do
+    handle_reply(socket, reply)
+    handle_result({:stop, reason, socket}, :handle_in)
+  end
 
+  defp handle_in(other) do
+    handle_result(other, :handle_in)
+  end
+
+  defp handle_reply(socket, {status, payload}) when is_atom(status) and is_map(payload) do
     reply(socket.transport_pid, socket.join_ref, socket.ref, socket.topic, {status, payload},
           socket.serializer)
   end
 
-  defp handle_reply(socket, status, :handle_in) when is_atom(status) do
-    handle_reply(socket, {status, %{}}, :handle_in)
+  defp handle_reply(socket, status) when is_atom(status) do
+    handle_reply(socket, {status, %{}})
   end
 
-  defp handle_reply(_socket, reply, :handle_in) do
+  defp handle_reply(_socket, reply) do
     raise """
     Channel replies from `handle_in/3` are expected to be one of:
 
@@ -399,17 +471,4 @@ defmodule Phoenix.Channel.Server do
     got #{inspect reply}
     """
   end
-
-  defp handle_reply(_socket, _reply, _other) do
-    raise """
-    Channel replies can only be sent from a `handle_in/3` callback.
-    Use `push/3` to send an out-of-band message down the socket
-    """
-  end
-
-  defp notify_transport_of_graceful_exit(socket) do
-    Phoenix.Socket.Transport.notify_graceful_exit(socket)
-    Process.unlink(socket.transport_pid)
-    :ok
-  end
 end
diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex
index 6c18d5b83..63bb144e3 100644
--- a/lib/phoenix/controller.ex
+++ b/lib/phoenix/controller.ex
@@ -22,7 +22,7 @@ defmodule Phoenix.Controller do
 
         def show(conn, %{"id" => id}) do
           user = Repo.get(User, id)
-          render conn, "show.html", user: user
+          render(conn, "show.html", user: user)
         end
       end
 
@@ -271,7 +271,7 @@ defmodule Phoenix.Controller do
 
   ## Examples
 
-      iex> json conn, %{id: 123}
+      iex> json(conn, %{id: 123})
 
   """
   @spec json(Plug.Conn.t, term) :: Plug.Conn.t
@@ -353,9 +353,9 @@ defmodule Phoenix.Controller do
 
   ## Examples
 
-      iex> text conn, "hello"
+      iex> text(conn, "hello")
 
-      iex> text conn, :implements_to_string
+      iex> text(conn, :implements_to_string)
 
   """
   @spec text(Plug.Conn.t, String.Chars.t) :: Plug.Conn.t
@@ -368,7 +368,7 @@ defmodule Phoenix.Controller do
 
   ## Examples
 
-      iex> html conn, "<html><head>..."
+      iex> html(conn, "<html><head>...")
 
   """
   @spec html(Plug.Conn.t, iodata) :: Plug.Conn.t
@@ -384,9 +384,9 @@ defmodule Phoenix.Controller do
 
   ## Examples
 
-      iex> redirect conn, to: "/login"
+      iex> redirect(conn, to: "/login")
 
-      iex> redirect conn, external: "http://elixir-lang.org"
+      iex> redirect(conn, external: "http://elixir-lang.org")
 
   """
   def redirect(conn, opts) when is_list(opts) do
@@ -531,11 +531,11 @@ defmodule Phoenix.Controller do
 
   ## Examples
 
-      iex> layout_formats conn
+      iex> layout_formats(conn)
       ["html"]
 
-      iex> put_layout_formats conn, ["html", "mobile"]
-      iex> layout_formats conn
+      iex> put_layout_formats(conn, ["html", "mobile"])
+      iex> layout_formats(conn)
       ["html", "mobile"]
 
   Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
@@ -606,7 +606,7 @@ defmodule Phoenix.Controller do
         use Phoenix.Controller
 
         def show(conn, _params) do
-          render conn, "show.html", message: "Hello"
+          render(conn, "show.html", message: "Hello")
         end
       end
 
@@ -618,7 +618,7 @@ defmodule Phoenix.Controller do
   the extension):
 
       def show(conn, _params) do
-        render conn, :show, message: "Hello"
+        render(conn, :show, message: "Hello")
       end
 
   In order for the example above to work, we need to do content negotiation with
@@ -648,7 +648,7 @@ defmodule Phoenix.Controller do
         plug :put_view, MyAppWeb.SpecialView
 
         def show(conn, _params) do
-          render conn, :show, message: "Hello"
+          render(conn, :show, message: "Hello")
         end
       end
 
@@ -661,7 +661,7 @@ defmodule Phoenix.Controller do
         use Phoenix.Controller
 
         def show(conn, _params) do
-          render conn, "show.html", message: "Hello"
+          render(conn, "show.html", message: "Hello")
         end
       end
 
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index 4ea010d6c..f159d1cbe 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -584,7 +584,10 @@ defmodule Phoenix.Endpoint do
 
   defp server() do
     quote location: :keep, unquote: false do
-      @doc false
+      @doc """
+      Returns the child specification to start the endpoint
+      under a supervision tree.
+      """
       def child_spec(opts) do
         %{
           id: __MODULE__,
@@ -593,8 +596,6 @@ defmodule Phoenix.Endpoint do
         }
       end
 
-      defoverridable child_spec: 1
-
       @doc """
       Starts the endpoint supervision tree.
       """
@@ -715,10 +716,17 @@ defmodule Phoenix.Endpoint do
   end
 
   @doc false
-  defmacro __before_compile__(env) do
-    sockets = Module.get_attribute(env.module, :phoenix_sockets)
-    otp_app = Module.get_attribute(env.module, :otp_app)
-    instrumentation = Phoenix.Endpoint.Instrument.definstrument(otp_app, env.module)
+  defmacro __before_compile__(%{module: module}) do
+    sockets = Module.get_attribute(module, :phoenix_sockets)
+    otp_app = Module.get_attribute(module, :otp_app)
+    instrumentation = Phoenix.Endpoint.Instrument.definstrument(otp_app, module)
+
+    dispatches =
+      for {_, _, _, paths} <- sockets, {path, return} <- paths do
+        quote do
+          def __dispatch__(unquote(path), _opts), do: unquote(Macro.escape(return))
+        end
+      end
 
     quote do
       defoverridable [call: 2]
@@ -737,10 +745,13 @@ defmodule Phoenix.Endpoint do
         end
       end
 
-      @doc """
-      Returns all sockets configured in this endpoint.
-      """
-      def __sockets__, do: unquote(sockets)
+      @doc false
+      def __sockets__, do: unquote(Macro.escape(sockets))
+
+      @doc false
+      def __dispatch__(path, opts)
+      unquote(dispatches)
+      def __dispatch__(_, opts), do: {:plug, __MODULE__, opts}
 
       unquote(instrumentation)
     end
@@ -749,19 +760,85 @@ defmodule Phoenix.Endpoint do
   ## API
 
   @doc """
-  Defines a mount-point for a Socket module to handle channel definitions.
+  Defines a websocket/longpoll mount-point for a socket.
+
+  Note: the `:websocket` and `:longpoll` options only have an
+  effect if the socket given as argument has no `transport`
+  declarations in it.
+
+  ## Options
+
+    * `:websocket` - controls the websocket configuration.
+      Defaults to `true`. May be false or a keyword list
+      of options. see "WebSocket configuration" for more info.
+
+    * `:longpoll` - controls the longpoll configuration.
+      Defaults to `false`. May be true or a keyword list
+      of options. see "Longpoll configuration" for more info.
+
+    * `:shutdown` - the maximum shutdown time of each channel
+      when the endpoint is shutting down. Applies only to channel-
+      based sockets.
 
   ## Examples
 
       socket "/ws", MyApp.UserSocket
-      socket "/ws/admin", MyApp.AdminUserSocket
 
-  By default, the given path is a websocket upgrade endpoint,
-  with long-polling fallback. The transports can be configured
-  within the Socket handler. See `Phoenix.Socket` for more information
-  on defining socket handlers.
+      socket "/ws/admin", MyApp.AdminUserSocket,
+        longpoll: true,
+        websocket: [compress: true]
+
+  ## Websocket configuration
+
+    * `:timeout` - the timeout for keeping websocket connections
+      open after it last received data, defaults to 60_000ms
+
+    * `:transport_log` - if the transport layer itself should log and,
+      if so, the level
+
+    * `:serializer` - a list of serializers for messages. See
+      `Phoenix.Socket` for more information.
+
+    * `:check_origin` - if we should check the origin of requests when the
+      origin header is present. It defaults to true and, in such cases,
+      it will check against the host value in `YourApp.Endpoint.config(:url)[:host]`.
+      It may be set to `false` (not recommended) or to a list of explicitly
+      allowed origins.
+
+          check_origin: ["https://example.com",
+                         "//another.com:888", "//other.com"]
+
+      Note: To connect from a native app be sure to either have the native app
+      set an origin or allow any origin via `check_origin: false`
+
+    * `:code_reloader` - enable or disable the code reloader. Defaults to your
+      endpoint configuration
+
+  ## Longpoll configuration
+
+    * `:window_ms` - how long the client can wait for new messages
+      in its poll request
+
+    * `:pubsub_timeout_ms` - how long a request can wait for the
+      pubsub layer to respond
+
+    * `:crypto` - options for verifying and signing the token, accepted
+      by `Phoenix.Token`. By default tokens are valid for 2 weeks
+
+    * `:transport_log` - if the transport layer itself should log and, if so,
+      the level
+
+    * `:check_origin` - if we should check the origin of requests when the
+      origin header is present. It defaults to true and, in such cases,
+      it will check against the host value in `YourApp.Endpoint.config(:url)[:host]`.
+      It may be set to `false` (not recommended) or to a list of explicitly
+      allowed origins.
+
+    * `:code_reloader` - enable or disable the code reloader. Defaults to your
+      endpoint configuration
+
   """
-  defmacro socket(path, module) do
+  defmacro socket(path, module, opts \\ []) do
     # Tear the alias to simply store the root in the AST.
     # This will make Elixir unable to track the dependency
     # between endpoint <-> socket and avoid recompiling the
@@ -770,10 +847,44 @@ defmodule Phoenix.Endpoint do
     module = tear_alias(module)
 
     quote do
-      @phoenix_sockets {unquote(path), unquote(module)}
+      @phoenix_sockets Phoenix.Endpoint.__socket__(__MODULE__, unquote(path), unquote(module), unquote(opts))
     end
   end
 
+  @doc false
+  # TODO: Move default configs here once the transport is deprecated
+  # TODO: Return only socket and opts once Cowboy1 transport handling is removed
+  def __socket__(endpoint, path, socket, opts) do
+    paths = []
+    {websocket, opts} = Keyword.pop(opts, :websocket, true)
+    {longpoll, opts} = Keyword.pop(opts, :longpoll, false)
+
+    paths =
+      if websocket do
+        triplet = {:websocket, socket, socket_config(websocket, Phoenix.Transports.WebSocket)}
+        [{socket_path(path, :websocket), triplet} | paths]
+      else
+        paths
+      end
+
+    paths =
+      if longpoll do
+        plug_init = {endpoint, socket, socket_config(longpoll, Phoenix.Transports.LongPoll)}
+        [{socket_path(path, :longpoll), {:plug, Phoenix.Transports.LongPoll, plug_init}} | paths]
+      else
+        paths
+      end
+
+    {path, socket, opts, paths}
+  end
+
+  defp socket_path(path, key) do
+    Plug.Router.Utils.split(path) ++ [Atom.to_string(key)]
+  end
+
+  defp socket_config(true, module), do: module.default_config()
+  defp socket_config(config, module), do: Keyword.merge(module.default_config(), config)
+
   @doc """
   Instruments the given function using the instrumentation provided by
   the given endpoint.
diff --git a/lib/phoenix/endpoint/cowboy2_adapter.ex b/lib/phoenix/endpoint/cowboy2_adapter.ex
new file mode 100644
index 000000000..de47d056f
--- /dev/null
+++ b/lib/phoenix/endpoint/cowboy2_adapter.ex
@@ -0,0 +1,71 @@
+defmodule Phoenix.Endpoint.Cowboy2Adapter do
+  @moduledoc """
+  The Cowboy2 adapter for Phoenix.
+
+  It implements the required `child_spec/3` function as well
+  as WebSocket transport functionality.
+
+  ## Custom dispatch options
+
+  You can provide custom dispatch options in order to use Phoenix's
+  builtin Cowboy server with custom handlers. For example, to handle
+  raw WebSockets [as shown in Cowboy's docs](https://github.com/ninenines/cowboy/tree/2.0.x/examples)).
+
+  The options are passed to both `:http` and `:https` keys in the
+  endpoint configuration. However, once you pass your custom dispatch
+  options, you will need to manually wire the Phoenix endpoint by
+  adding the following rule:
+
+      {:_, Phoenix.Endpoint.Cowboy2Handler, {MyAppWeb.Endpoint, []}}
+
+  For example:
+
+      config :myapp, MyAppWeb.Endpoint,
+        http: [dispatch: [
+                {:_, [
+                    {"/foo", MyAppWeb.CustomHandler, []},
+                    {:_, Phoenix.Endpoint.Cowboy2Handler, {MyAppWeb.Endpoint, []}}
+                  ]}]]
+
+  It is also important to specify your handlers first, otherwise
+  Phoenix will intercept the requests before they get to your handler.
+  """
+
+  require Logger
+
+  @doc false
+  def child_spec(scheme, endpoint, config) do
+    if scheme == :https do
+      Application.ensure_all_started(:ssl)
+    end
+
+    dispatches = [{:_, Phoenix.Endpoint.Cowboy2Handler, {endpoint, endpoint.init([])}}]
+    config = Keyword.put_new(config, :dispatch, [{:_, dispatches}])
+    spec = Plug.Adapters.Cowboy2.child_spec(scheme: scheme, plug: {endpoint, []}, options: config)
+    update_in spec.start, &{__MODULE__, :start_link, [scheme, endpoint, &1]}
+  end
+
+  @doc false
+  def start_link(scheme, endpoint, {m, f, [ref | _] = a}) do
+    # ref is used by Ranch to identify its listeners, defaulting
+    # to plug.HTTP and plug.HTTPS and overridable by users.
+    case apply(m, f, a) do
+      {:ok, pid} ->
+        Logger.info info(scheme, endpoint, ref)
+        {:ok, pid}
+
+      {:error, {:shutdown, {_, _, {{_, {:error, :eaddrinuse}}, _}}}} = error ->
+        Logger.error [info(scheme, endpoint, ref), " failed, port already in use"]
+        error
+
+      {:error, _} = error ->
+        error
+    end
+  end
+
+  defp info(scheme, endpoint, ref) do
+    {addr, port} = :ranch.get_addr(ref)
+    addr_str = :inet.ntoa(addr)
+    "Running #{inspect endpoint} with Cowboy2 using #{scheme}://#{addr_str}:#{port}"
+  end
+end
diff --git a/lib/phoenix/endpoint/cowboy2_handler.ex b/lib/phoenix/endpoint/cowboy2_handler.ex
index 9116e588d..f8d2e97e6 100644
--- a/lib/phoenix/endpoint/cowboy2_handler.ex
+++ b/lib/phoenix/endpoint/cowboy2_handler.ex
@@ -1,145 +1,116 @@
 defmodule Phoenix.Endpoint.Cowboy2Handler do
-  @moduledoc """
-  The Cowboy2 adapter for Phoenix.
+  @moduledoc false
 
-  It implements the required `child_spec/3` function as well
-  as the handler for the WebSocket transport.
-
-  ## Custom dispatch options
-
-  You can provide custom dispatch options in order to use Phoenix's
-  builtin Cowboy server with custom handlers. For example, to handle
-  raw WebSockets [as shown in Cowboy's docs](https://github.com/ninenines/cowboy/tree/2.0.x/examples)).
-
-  The options are passed to both `:http` and `:https` keys in the
-  endpoint configuration. However, once you pass your custom dispatch
-  options, you will need to manually wire all Phoenix endpoints,
-  including the socket transports.
-
-  You will need the following rules:
-
-    * Per websocket transport:
-
-      ```
-      {"/socket/websocket", Phoenix.Endpoint.Cowboy2WebSocket,
-        {Phoenix.Transports.WebSocket,
-          {MyAppWeb.Endpoint, MyAppWeb.UserSocket, :websocket}}}
-      ```
-
-    * Per longpoll transport:
-
-      ```
-      {"/socket/long_poll", Plug.Adapters.Cowboy2.Handler,
-        {Phoenix.Transports.LongPoll,
-          {MyAppWeb.Endpoint, MyAppWeb.UserSocket, :longpoll}}}
-      ```
+  if Code.ensure_loaded?(:cowboy_websocket) do
+    @behaviour :cowboy_websocket
+  end
 
-    * For the live-reload websocket:
+  @connection Plug.Adapters.Cowboy2.Conn
+  @already_sent {:plug_conn, :sent}
+
+  # Note we keep the websocket state as [handler | state]
+  # to avoid conflicts with {endpoint, opts}.
+  def init(req, {endpoint, opts}) do
+    %{path_info: path_info} = conn = @connection.conn(req)
+
+    try do
+      case endpoint.__dispatch__(path_info, opts) do
+        {:websocket, handler, opts} ->
+          case Phoenix.Transports.WebSocket.connect(conn, endpoint, handler, opts) do
+            {:ok, %{adapter: {@connection, req}}, state} ->
+              timeout = Keyword.fetch!(opts, :timeout)
+              compress = Keyword.fetch!(opts, :compress)
+              cowboy_opts = %{idle_timeout: timeout, compress: compress}
+              {:cowboy_websocket, req, [handler | state], cowboy_opts}
+
+            {:error, %{adapter: {@connection, req}}} ->
+              {:error, req}
+          end
+
+        {:plug, handler, opts} ->
+          %{adapter: {@connection, req}} =
+            conn
+            |> handler.call(opts)
+            |> maybe_send(handler)
+
+          {:ok, req, {handler, opts}}
+      end
+    catch
+      :error, value ->
+        stack = System.stacktrace()
+        exception = Exception.normalize(:error, value, stack)
+        exit({{exception, stack}, {endpoint, :call, [conn, opts]}})
+
+      :throw, value ->
+        stack = System.stacktrace()
+        exit({{{:nocatch, value}, stack}, {endpoint, :call, [conn, opts]}})
+
+      :exit, value ->
+        exit({value, {endpoint, :call, [conn, opts]}})
+    after
+      receive do
+        @already_sent -> :ok
+      after
+        0 -> :ok
+      end
+    end
+  end
 
-      ```
-      {"/phoenix/live_reload/socket/websocket", Phoenix.Endpoint.Cowboy2WebSocket,
-        {Phoenix.Transports.WebSocket,
-          {MyAppWeb.Endpoint, Phoenix.LiveReloader.Socket, :websocket}}}
-      ```
+  defp maybe_send(%Plug.Conn{state: :unset}, _plug), do: raise(Plug.Conn.NotSentError)
+  defp maybe_send(%Plug.Conn{state: :set} = conn, _plug), do: Plug.Conn.send_resp(conn)
+  defp maybe_send(%Plug.Conn{} = conn, _plug), do: conn
 
-      If you decide to include the live-reload websocket, you should
-      disable it when building for production.
+  defp maybe_send(other, plug) do
+    raise "Cowboy2 adapter expected #{inspect(plug)} to return Plug.Conn but got: " <>
+            inspect(other)
+  end
 
-    * For the endpoint:
+  ## Websocket callbacks
 
-      ```
-      {:_, Plug.Adapters.Cowboy2.Handler, {MyAppWeb.Endpoint, []}}
-      ```
+  def websocket_init([handler | state]) do
+    {:ok, state} = handler.init(state)
+    {:ok, [handler | state]}
+  end
 
-  For example:
+  def websocket_handle({opcode, payload}, [handler | state]) when opcode in [:text, :binary] do
+    handle_reply(handler, handler.handle_in({payload, opcode: opcode}, state))
+  end
 
-      config :myapp, MyAppWeb.Endpoint,
-        http: [dispatch: [
-                {:_, [
-                    {"/foo", MyAppWeb.CustomHandler, []},
-                    {"/bar", MyAppWeb.AnotherHandler, []},
-                    {"/phoenix/live_reload/socket/websocket", Phoenix.Endpoint.Cowboy2WebSocket,
-                      {Phoenix.Transports.WebSocket,
-                        {MyAppWeb.Endpoint, Phoenix.LiveReloader.Socket, :websocket}}},
-                    {:_, Plug.Adapters.Cowboy2.Handler, {MyAppWeb.Endpoint, []}}
-                  ]}]]
+  def websocket_handle(_other, handler_state) do
+    {:ok, handler_state}
+  end
 
-  Note: if you reconfigure HTTP options in `MyAppWeb.Endpoint.init/1`,
-  your dispatch options set in mix config will be overwritten.
+  def websocket_info(message, [handler | state]) do
+    handle_reply(handler, handler.handle_info(message, state))
+  end
 
-  It is also important to specify your handlers first, otherwise
-  Phoenix will intercept the requests before they get to your handler.
-  """
+  def terminate(_reason, _req, {_handler, _state}) do
+    :ok
+  end
 
-  @behaviour Phoenix.Endpoint.Handler
-  require Logger
+  def terminate({:error, :closed}, _req, [handler | state]) do
+    handler.terminate(:closed, state)
+  end
 
-  @doc """
-  Generates a childspec to be used in the supervision tree.
-  """
-  def child_spec(scheme, endpoint, config) do
-    if scheme == :https do
-      Application.ensure_all_started(:ssl)
-    end
+  def terminate({:remote, :closed}, _req, [handler | state]) do
+    handler.terminate(:closed, state)
+  end
 
-    dispatches =
-      for {path, socket} <- endpoint.__sockets__,
-          {transport, {module, config}} <- socket.__transports__,
-          # Allow handlers to be configured at the transport level
-          handler = config[:cowboy] || default_for(module),
-          do: {Path.join(path, Atom.to_string(transport)),
-               handler,
-               {module, {endpoint, socket, transport}}}
-
-    dispatches =
-      dispatches ++ [{:_, Plug.Adapters.Cowboy2.Handler, {endpoint, []}}]
-
-    # Use put_new to allow custom dispatches
-    config = Keyword.put_new(config, :dispatch, [{:_, dispatches}])
-
-    cowboy_supervisor =
-      Plug.Adapters.Cowboy2.child_spec(scheme: scheme, plug: {endpoint, []}, options: config)
-
-    %{
-      start: start,
-      id: id,
-      type: type,
-      shutdown: shutdown,
-      restart: restart,
-      modules: modules,
-    } = cowboy_supervisor
-
-    start = {__MODULE__, :start_link, [scheme, endpoint, start]}
-    {id, start, restart, shutdown, type, modules}
+  def terminate({:remote, code, _}, _req, [handler | state])
+      when code in 1000..1003 or code in 1005..1011 or code == 1015 do
+    handler.terminate(:closed, state)
   end
 
-  defp default_for(Phoenix.Transports.LongPoll), do: Plug.Adapters.Cowboy2.Handler
-  defp default_for(Phoenix.Transports.WebSocket), do: Phoenix.Endpoint.Cowboy2WebSocket
-  defp default_for(_), do: nil
-
-  @doc """
-  Callback to start the Cowboy2 endpoint.
-  """
-  def start_link(scheme, endpoint, {m, f, [ref | _] = a}) do
-    # ref is used by Ranch to identify its listeners, defaulting
-    # to plug.HTTP and plug.HTTPS and overridable by users.
-    case apply(m, f, a) do
-      {:ok, pid} ->
-        Logger.info info(scheme, endpoint, ref)
-        {:ok, pid}
-
-      {:error, {:shutdown, {_, _, {{_, {:error, :eaddrinuse}}, _}}}} = error ->
-        Logger.error [info(scheme, endpoint, ref), " failed, port already in use"]
-        error
-
-      {:error, _} = error ->
-        error
-    end
+  def terminate(:remote, _req, [handler | state]) do
+    handler.terminate(:closed, state)
   end
 
-  defp info(scheme, endpoint, ref) do
-    {addr, port} = :ranch.get_addr(ref)
-    addr_str = :inet.ntoa(addr)
-    "Running #{inspect endpoint} with Cowboy2 using #{scheme}://#{addr_str}:#{port}"
+  def terminate(reason, _req, [handler | state]) do
+    handler.terminate(reason, state)
   end
+
+  defp handle_reply(handler, {:ok, state}), do: {:ok, [handler | state]}
+  defp handle_reply(handler, {:push, data, state}), do: {:reply, data, [handler | state]}
+  defp handle_reply(handler, {:reply, _status, data, state}), do: {:reply, data, [handler | state]}
+  defp handle_reply(handler, {:stop, _reason, state}), do: {:stop, [handler | state]}
 end
diff --git a/lib/phoenix/endpoint/cowboy2_websocket.ex b/lib/phoenix/endpoint/cowboy2_websocket.ex
deleted file mode 100644
index 9f8e097b8..000000000
--- a/lib/phoenix/endpoint/cowboy2_websocket.ex
+++ /dev/null
@@ -1,89 +0,0 @@
-defmodule Phoenix.Endpoint.Cowboy2WebSocket do
-  # Implementation of the WebSocket transport for Cowboy.
-  @moduledoc false
-
-  if Code.ensure_loaded?(:cowboy_websocket) do
-    @behaviour :cowboy_websocket
-  end
-
-  @connection Plug.Adapters.Cowboy2.Conn
-  @already_sent {:plug_conn, :sent}
-
-  def init(req, {module, opts}) do
-    conn = @connection.conn(req)
-    opts = Tuple.append(opts, req.pid)
-    try do
-      case module.init(conn, opts) do
-        {:ok, %{adapter: {@connection, req}}, {_module, {_socket, opts} = args}} ->
-          timeout = Keyword.fetch!(opts, :timeout)
-          compress = Keyword.fetch!(opts, :compress)
-          {:cowboy_websocket, req, {module, args}, %{idle_timeout: timeout, compress: compress}}
-        {:error, %{adapter: {@connection, req}}} ->
-          {:error, req}
-      end
-    catch
-      _kind, _reason ->
-        {:error, req}
-    after
-      receive do
-        @already_sent -> :ok
-      after
-        0 -> :ok
-      end
-    end
-  end
-
-  ## Websocket callbacks
-
-  def websocket_init({module, args}) do
-    {:ok, state, _timeout} = module.ws_init(args)
-    {:ok, {module, state}}
-  end
-
-  def websocket_handle({opcode = :text, payload}, {handler, state}) do
-    handle_reply handler, handler.ws_handle(opcode, payload, state)
-  end
-  def websocket_handle({opcode = :binary, payload}, {handler, state}) do
-    handle_reply handler, handler.ws_handle(opcode, payload, state)
-  end
-  def websocket_handle(_other, {handler, state}) do
-    {:ok, {handler, state}}
-  end
-
-  def websocket_info(message, {handler, state}) do
-    handle_reply handler, handler.ws_info(message, state)
-  end
-
-  def terminate({:error, :closed}, _req, {handler, state}) do
-    handler.ws_close(state)
-    :ok
-  end
-  def terminate({:remote, :closed}, _req, {handler, state}) do
-    handler.ws_close(state)
-    :ok
-  end
-  def terminate({:remote, code, _}, _req, {handler, state})
-      when code in 1000..1003 or code in 1005..1011 or code == 1015 do
-    handler.ws_close(state)
-    :ok
-  end
-  def terminate(:remote, _req, {handler, state}) do
-    handler.ws_close(state)
-    :ok
-  end
-  def terminate(reason, _req, {handler, state}) do
-    handler.ws_close(state)
-    handler.ws_terminate(reason, state)
-    :ok
-  end
-
-  defp handle_reply(handler, {:shutdown, new_state}) do
-    {:stop, {handler, new_state}}
-  end
-  defp handle_reply(handler, {:ok, new_state}) do
-    {:ok, {handler, new_state}}
-  end
-  defp handle_reply(handler, {:reply, {opcode, payload}, new_state}) do
-    {:reply, {opcode, payload}, {handler, new_state}}
-  end
-end
diff --git a/lib/phoenix/endpoint/cowboy_handler.ex b/lib/phoenix/endpoint/cowboy_adapter.ex
similarity index 69%
rename from lib/phoenix/endpoint/cowboy_handler.ex
rename to lib/phoenix/endpoint/cowboy_adapter.ex
index 1ea06a1e0..0915dd503 100644
--- a/lib/phoenix/endpoint/cowboy_handler.ex
+++ b/lib/phoenix/endpoint/cowboy_adapter.ex
@@ -1,4 +1,4 @@
-defmodule Phoenix.Endpoint.CowboyHandler do
+defmodule Phoenix.Endpoint.CowboyAdapter do
   @moduledoc """
   The Cowboy adapter for Phoenix.
 
@@ -9,7 +9,7 @@ defmodule Phoenix.Endpoint.CowboyHandler do
 
   *NOTE*: This feature depends on the internals of Cowboy 1.0 API
   and how it integrates with Phoenix. Those may change at *any time*,
-  without backwards compatibility, specifically when Cowboy 2.0 is released.
+  without backwards compatibility.
 
   You can provide custom dispatch options in order to use Phoenix's
   builtin Cowboy server with custom handlers. For example, to handle
@@ -27,7 +27,7 @@ defmodule Phoenix.Endpoint.CowboyHandler do
       ```
       {"/socket/websocket", Phoenix.Endpoint.CowboyWebSocket,
         {Phoenix.Transports.WebSocket,
-          {MyAppWeb.Endpoint, MyAppWeb.UserSocket, :websocket}}}
+          {MyAppWeb.Endpoint, MyAppWeb.UserSocket, websocket_config}}}
       ```
 
     * Per longpoll transport:
@@ -35,7 +35,7 @@ defmodule Phoenix.Endpoint.CowboyHandler do
       ```
       {"/socket/long_poll", Plug.Adapters.Cowboy.Handler,
         {Phoenix.Transports.LongPoll,
-          {MyAppWeb.Endpoint, MyAppWeb.UserSocket, :longpoll}}}
+          {MyAppWeb.Endpoint, MyAppWeb.UserSocket, longpoll_config}}}
       ```
 
     * For the live-reload websocket:
@@ -43,7 +43,7 @@ defmodule Phoenix.Endpoint.CowboyHandler do
       ```
       {"/phoenix/live_reload/socket/websocket", Phoenix.Endpoint.CowboyWebSocket,
         {Phoenix.Transports.WebSocket,
-          {MyAppWeb.Endpoint, Phoenix.LiveReloader.Socket, :websocket}}}
+          {MyAppWeb.Endpoint, Phoenix.LiveReloader.Socket, websocket_config}}}
       ```
 
       If you decide to include the live-reload websocket, you should
@@ -64,7 +64,7 @@ defmodule Phoenix.Endpoint.CowboyHandler do
                     {"/bar", MyAppWeb.AnotherHandler, []},
                     {"/phoenix/live_reload/socket/websocket", Phoenix.Endpoint.CowboyWebSocket,
                       {Phoenix.Transports.WebSocket,
-                        {MyAppWeb.Endpoint, Phoenix.LiveReloader.Socket, :websocket}}},
+                        {MyAppWeb.Endpoint, Phoenix.LiveReloader.Socket, websocket_config}}},
                     {:_, Plug.Adapters.Cowboy.Handler, {MyAppWeb.Endpoint, []}}
                   ]}]]
 
@@ -74,47 +74,55 @@ defmodule Phoenix.Endpoint.CowboyHandler do
   It is also important to specify your handlers first, otherwise
   Phoenix will intercept the requests before they get to your handler.
   """
-  @behaviour Phoenix.Endpoint.Handler
+
   require Logger
 
-  @doc """
-  Generates a childspec to be used in the supervision tree.
-  """
+  @doc false
   def child_spec(scheme, endpoint, config) do
     if scheme == :https do
       Application.ensure_all_started(:ssl)
     end
 
     dispatches =
-      for {path, socket} <- endpoint.__sockets__,
-          {transport, {module, config}} <- socket.__transports__,
-          # Allow handlers to be configured at the transport level
-          handler = config[:cowboy] || default_for(module),
-          do: {Path.join(path, Atom.to_string(transport)),
-               handler,
-               {module, {endpoint, socket, transport}}}
+      for {path, socket, _, transports} <- endpoint.__sockets__,
+          transport <- transports(endpoint, path, socket, transports),
+          do: transport
 
     dispatches =
       dispatches ++ [{:_, Plug.Adapters.Cowboy.Handler, {endpoint, []}}]
 
-    # Use put_new to allow custom dispatches
     config = Keyword.put_new(config, :dispatch, [{:_, dispatches}])
+    spec = Plug.Adapters.Cowboy.child_spec(scheme: scheme, plug: {endpoint, []}, options: config)
+    update_in spec.start, &{__MODULE__, :start_link, [scheme, endpoint, &1]}
+  end
+
+  defp transports(endpoint, path, socket, socket_transports) do
+    case socket.__transports__ do
+      transports when transports == %{} ->
+        for {path, transport} <- socket_transports,
+            {handler, init} = handler_for_transport(transport, endpoint, socket),
+            do: {"/" <> Path.join(path), handler, init}
+
+      transports ->
+        for {transport, {module, config}} <- transports,
+            handler = config[:cowboy] || default_for(module),
+            do: {Path.join(path, Atom.to_string(transport)), handler, {module, {endpoint, socket, config}}}
+    end
+  end
 
-    {ref, mfa, type, timeout, kind, modules} =
-      Plug.Adapters.Cowboy.child_spec(scheme, endpoint, [], config)
+  defp handler_for_transport({:websocket, socket, config}, endpoint, socket) do
+    {Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, {endpoint, socket, config}}}
+  end
 
-    # Rewrite MFA for proper error reporting
-    mfa = {__MODULE__, :start_link, [scheme, endpoint, mfa]}
-    {ref, mfa, type, timeout, kind, modules}
+  defp handler_for_transport({:plug, module, config}, _endpoint, _socket) do
+    {Plug.Adapters.Cowboy.Handler, {module, config}}
   end
 
   defp default_for(Phoenix.Transports.LongPoll), do: Plug.Adapters.Cowboy.Handler
   defp default_for(Phoenix.Transports.WebSocket), do: Phoenix.Endpoint.CowboyWebSocket
   defp default_for(_), do: nil
 
-  @doc """
-  Callback to start the Cowboy endpoint.
-  """
+  @doc false
   def start_link(scheme, endpoint, {m, f, [ref | _] = a}) do
     # ref is used by Ranch to identify its listeners, defaulting
     # to plug.HTTP and plug.HTTPS and overridable by users.
diff --git a/lib/phoenix/endpoint/cowboy_websocket.ex b/lib/phoenix/endpoint/cowboy_websocket.ex
index 47cf4f827..2f0eff9f5 100644
--- a/lib/phoenix/endpoint/cowboy_websocket.ex
+++ b/lib/phoenix/endpoint/cowboy_websocket.ex
@@ -1,5 +1,4 @@
 defmodule Phoenix.Endpoint.CowboyWebSocket do
-  # Implementation of the WebSocket transport for Cowboy.
   @moduledoc false
 
   if Code.ensure_loaded?(:cowboy_websocket_handler) do
@@ -9,12 +8,16 @@ defmodule Phoenix.Endpoint.CowboyWebSocket do
   @connection Plug.Adapters.Cowboy.Conn
   @already_sent {:plug_conn, :sent}
 
-  def init({transport, :http}, req, {module, opts}) when transport in [:tcp, :ssl] do
+  def init({transport, :http}, req, {module, args}) when transport in [:tcp, :ssl] do
+    {endpoint, handler, opts} = args
     conn = @connection.conn(req, transport)
+
     try do
-      case module.init(conn, opts) do
+      case Phoenix.Transports.WebSocket.connect(conn, endpoint, handler, opts) do
         {:ok, %{adapter: {@connection, req}}, args} ->
-          {:upgrade, :protocol, __MODULE__, req, args}
+          timeout = Keyword.fetch!(opts, :timeout)
+          {:upgrade, :protocol, __MODULE__, req, {handler, args, timeout}}
+
         {:error, %{adapter: {@connection, req}}} ->
           {:shutdown, req, :no_state}
       end
@@ -22,8 +25,8 @@ defmodule Phoenix.Endpoint.CowboyWebSocket do
       kind, reason ->
         # Although we are not performing a call, we are using the call
         # function for now so it is properly handled in error reports.
-        mfa = {module, :call, [conn, opts]}
-        {:upgrade, :protocol, __MODULE__, req, {:error, mfa, kind, reason, System.stacktrace}}
+        mfa = {module, :call, [conn, args]}
+        {:upgrade, :protocol, __MODULE__, req, {:error, mfa, kind, reason, System.stacktrace()}}
     after
       receive do
         @already_sent -> :ok
@@ -33,17 +36,16 @@ defmodule Phoenix.Endpoint.CowboyWebSocket do
     end
   end
 
-  def upgrade(req, env, __MODULE__, {handler, opts}) do
-    args = [req, env, __MODULE__, {handler, opts}]
-    resume(:cowboy_websocket, :upgrade, args)
-  end
-
   def upgrade(_req, _env, __MODULE__, {:error, mfa, kind, reason, stack}) do
     reason = format_reason(kind, reason, stack)
     exit({reason, mfa})
   end
 
-  def terminate(_reason,  _req, _state) do
+  def upgrade(req, env, __MODULE__, {_, _, _} = args) do
+    resume(:cowboy_websocket, :upgrade, [req, env, __MODULE__, args])
+  end
+
+  def terminate(_reason, _req, _state) do
     :ok
   end
 
@@ -57,6 +59,7 @@ defmodule Phoenix.Endpoint.CowboyWebSocket do
     else
       {:suspend, module, fun, args} ->
         {:suspend, __MODULE__, :resume, [module, fun, args]}
+
       _ ->
         # We are forcing a shutdown exit because we want to make
         # sure all transports exits with reason shutdown to guarantee
@@ -71,50 +74,54 @@ defmodule Phoenix.Endpoint.CowboyWebSocket do
 
   ## Websocket callbacks
 
-  def websocket_init(_transport, req, {handler, args}) do
-    {:ok, state, timeout} = handler.ws_init(args)
+  def websocket_init(_transport, req, {handler, args, timeout}) do
+    {:ok, state} = handler.init(args)
     {:ok, :cowboy_req.compact(req), {handler, state}, timeout}
   end
 
-  def websocket_handle({opcode = :text, payload}, req, {handler, state}) do
-    handle_reply req, handler, handler.ws_handle(opcode, payload, state)
+  def websocket_handle({opcode, payload}, req, {handler, state})
+      when opcode in [:text, :binary] do
+    handle_reply(req, handler, handler.handle_in({payload, opcode: opcode}, state))
   end
-  def websocket_handle({opcode = :binary, payload}, req, {handler, state}) do
-    handle_reply req, handler, handler.ws_handle(opcode, payload, state)
-  end
-  def websocket_handle(_other, req, {handler, state}) do
-    {:ok, req, {handler, state}}
+
+  def websocket_handle(_other, req, state) do
+    {:ok, req, state}
   end
 
   def websocket_info(message, req, {handler, state}) do
-    handle_reply req, handler, handler.ws_info(message, state)
+    handle_reply(req, handler, handler.handle_info(message, state))
   end
 
   def websocket_terminate({:error, :closed}, _req, {handler, state}) do
-    handler.ws_close(state)
+    handler.terminate(:closed, state)
     :ok
   end
+
   def websocket_terminate({:remote, :closed}, _req, {handler, state}) do
-    handler.ws_close(state)
+    handler.terminate(:closed, state)
     :ok
   end
+
   def websocket_terminate({:remote, code, _}, _req, {handler, state})
       when code in 1000..1003 or code in 1005..1011 or code == 1015 do
-    handler.ws_close(state)
+    handler.terminate(:closed, state)
     :ok
   end
+
   def websocket_terminate(reason, _req, {handler, state}) do
-    handler.ws_terminate(reason, state)
+    handler.terminate(reason, state)
     :ok
   end
 
-  defp handle_reply(req, handler, {:shutdown, new_state}) do
-    {:shutdown, req, {handler, new_state}}
-  end
-  defp handle_reply(req, handler, {:ok, new_state}) do
-    {:ok, req, {handler, new_state}}
-  end
-  defp handle_reply(req, handler, {:reply, {opcode, payload}, new_state}) do
-    {:reply, {opcode, payload}, req, {handler, new_state}}
-  end
+  defp handle_reply(req, handler, {:ok, state}),
+    do: {:ok, req, {handler, state}}
+
+  defp handle_reply(req, handler, {:push, data, state}),
+    do: {:reply, data, req, {handler, state}}
+
+  defp handle_reply(req, handler, {:reply, _status, data, state}),
+    do: {:reply, data, req, {handler, state}}
+
+  defp handle_reply(req, handler, {:stop, _reason, state}),
+    do: {:shutdown, req, {handler, state}}
 end
diff --git a/lib/phoenix/endpoint/handler.ex b/lib/phoenix/endpoint/handler.ex
deleted file mode 100644
index 915001523..000000000
--- a/lib/phoenix/endpoint/handler.ex
+++ /dev/null
@@ -1,94 +0,0 @@
-defmodule Phoenix.Endpoint.Handler do
-  @moduledoc """
-  API for exporting a webserver.
-
-  A handler will need to implement a `child_spec/3` function which takes:
-
-    * the scheme of the endpoint :http or :https
-    * phoenix top-most endpoint module
-    * phoenix app configuration for the specified scheme
-
-  It has to return a supervisor child specification.
-  """
-
-  @doc """
-  Provides a server child specification to be started under the endpoint.
-  """
-  @callback child_spec(scheme :: atom, endpoint :: module, config :: Keyword.t) :: Supervisor.Spec.spec
-
-  alias Phoenix.Endpoint.{CowboyHandler, Cowboy2Handler}
-
-  use Supervisor
-  require Logger
-
-  @doc false
-  def start_link(otp_app, endpoint, opts \\ []) do
-    Supervisor.start_link(__MODULE__, {otp_app, endpoint}, opts)
-  end
-
-  @doc false
-  def init({otp_app, endpoint}) do
-    user_handler = endpoint.config(:handler)
-    autodetected_handler = cowboy_version_handler()
-    warn_on_different_handler_version(user_handler, autodetected_handler, endpoint)
-    handler = user_handler || autodetected_handler
-
-    children =
-      for {scheme, port} <- [http: 4000, https: 4040],
-          config = endpoint.config(scheme) do
-        handler.child_spec(scheme, endpoint, default(config, otp_app, port))
-      end
-    supervise(children, strategy: :one_for_one)
-  end
-
-  defp default(config, otp_app, port) when is_list(config) do
-    {config_keywords, config_other} = Enum.split_with(config, &keyword_item?/1)
-
-    config_keywords =
-      config_keywords
-      |> Keyword.put_new(:otp_app, otp_app)
-      |> Keyword.put_new(:port, port)
-
-    config_keywords
-    |> Keyword.put(:port, to_port(config_keywords[:port]))
-    |> Kernel.++(config_other)
-  end
-
-  defp keyword_item?({key, _}) when is_atom(key), do: true
-  defp keyword_item?(_), do: false
-
-  # TODO v1.4: Deprecate {:system, env_var}
-  defp to_port(nil), do: raise "server can't start because :port in config is nil, please use a valid port number"
-  defp to_port(binary)  when is_binary(binary), do: String.to_integer(binary)
-  defp to_port(integer) when is_integer(integer), do: integer
-  defp to_port({:system, env_var}), do: to_port(System.get_env(env_var))
-
-  defp cowboy_version_handler() do
-    case Application.spec(:cowboy, :vsn) do
-      [?1 | _] -> CowboyHandler
-      _ -> Cowboy2Handler
-    end
-  end
-
-  defp warn_on_different_handler_version(CowboyHandler, Cowboy2Handler, endpoint) do
-    Logger.warn("""
-    You have specified #{inspect CowboyHandler} for Cowboy v1.x \
-    in the :handler configuration of your Phoenix endpoint #{inspect endpoint} \
-    but your mix.exs has fetched Cowboy v2.x.
-
-    If you wish to use Cowboy 1, please update mix.exs to point to the \
-    correct Cowboy version:
-
-        {:cowboy, "~> 1.0"}
-
-    If you want to use Cowboy 2, then please remove the :handler option \
-    in your config.exs file or set it to:
-
-        handler: Phoenix.Endpoint.Cowboy2Handler
-
-    """)
-
-    raise "aborting due to handler mismatch"
-  end
-  defp warn_on_different_handler_version(_user, _autodetected, _endpoint), do: nil
-end
diff --git a/lib/phoenix/endpoint/instrument.ex b/lib/phoenix/endpoint/instrument.ex
index 8b7c5694e..5b289612a 100644
--- a/lib/phoenix/endpoint/instrument.ex
+++ b/lib/phoenix/endpoint/instrument.ex
@@ -23,7 +23,7 @@ defmodule Phoenix.Endpoint.Instrument do
       ## Examples
 
           instrument :render_view, %{view: "index.html"}, fn ->
-            render conn, "index.html"
+            render(conn, "index.html")
           end
 
       """
@@ -143,7 +143,7 @@ defmodule Phoenix.Endpoint.Instrument do
             unquote(inst).unquote(event)(:start, var!(compile), var!(runtime))
           catch
             kind, error ->
-              Logger.error unquote(error_prefix) <> Exception.format(kind, error)
+              Logger.error [unquote(error_prefix), Exception.format(kind, error, System.stacktrace())]
           end
       end
     end
diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex
index 37229fb6f..fd274bd48 100644
--- a/lib/phoenix/endpoint/supervisor.ex
+++ b/lib/phoenix/endpoint/supervisor.ex
@@ -1,11 +1,11 @@
 defmodule Phoenix.Endpoint.Supervisor do
-  # This module contains the logic used by most functions
-  # in Phoenix.Endpoint as well the supervisor for starting
-  # the adapters/handlers.
+  # This module contains the logic used by most functions in Phoenix.Endpoint
+  # as well the supervisor for sockets, adapters, watchers, etc.
   @moduledoc false
 
   require Logger
   use Supervisor
+  alias Phoenix.Endpoint.{CowboyAdapter, Cowboy2Adapter}
 
   @doc """
   Starts the endpoint supervision tree.
@@ -38,16 +38,13 @@ defmodule Phoenix.Endpoint.Supervisor do
 
     children =
       pubsub_children(mod, conf) ++
+      socket_children(mod) ++
       config_children(mod, conf, otp_app) ++
-      server_children(mod, conf, server?) ++
+      server_children(mod, conf, otp_app, server?) ++
       watcher_children(mod, conf, server?)
 
-    supervise(children, strategy: :one_for_one)
-  end
-
-  defp config_children(mod, conf, otp_app) do
-    args = [mod, conf, defaults(otp_app, mod), [name: Module.concat(mod, "Config")]]
-    [worker(Phoenix.Config, args)]
+    # Supervisor.init(children, strategy: :one_for_one)
+    {:ok, {{:one_for_one, 3, 5}, children}}
   end
 
   defp pubsub_children(mod, conf) do
@@ -61,19 +58,84 @@ defmodule Phoenix.Endpoint.Supervisor do
     end
   end
 
-  # TODO v1.4: Handlers -> Phoenix.Server
-  # TODO v1.4: Each transport should have its own tree
-  defp server_children(mod, conf, server?) do
+  defp socket_children(mod) do
+    mod.__sockets__
+    |> Enum.uniq_by(&elem(&1, 1))
+    |> Enum.map(fn {_, mod, opts, _} -> mod.child_spec(opts) end)
+  end
+
+  defp config_children(mod, conf, otp_app) do
+    args = [mod, conf, defaults(otp_app, mod), [name: Module.concat(mod, "Config")]]
+    [worker(Phoenix.Config, args)]
+  end
+
+  defp server_children(mod, config, otp_app, server?) do
     if server? do
-      server = Module.concat(mod, "Server")
-      long_poll = Module.concat(mod, "LongPoll.Supervisor")
-      [supervisor(Phoenix.Endpoint.Handler, [conf[:otp_app], mod, [name: server]]),
-       supervisor(Phoenix.Transports.LongPoll.Supervisor, [[name: long_poll]])]
+      user_adapter = user_adapter(mod, config)
+      autodetected_adapter = cowboy_version_adapter()
+      warn_on_different_adapter_version(user_adapter, autodetected_adapter, mod)
+      adapter = user_adapter || autodetected_adapter
+
+      for {scheme, port} <- [http: 4000, https: 4040], opts = config[scheme] do
+        port = :proplists.get_value(:port, opts, port)
+
+        unless port do
+          raise "server can't start because :port in #{scheme} config is nil, " <>
+                  "please use a valid port number"
+        end
+
+        opts = [port: port_to_integer(port), otp_app: otp_app] ++ :proplists.delete(:port, opts)
+        adapter.child_spec(scheme, mod, opts)
+      end
     else
       []
     end
   end
 
+  defp user_adapter(endpoint, config) do
+    case config[:handler] do
+      nil ->
+        config[:adapter]
+
+      Phoenix.Endpoint.CowboyHandler ->
+        Logger.warn "Phoenix.Endpoint.CowboyHandler is deprecated, please use Phoenix.Endpoint.CowboyAdapter instead"
+        CowboyAdapter
+
+      other ->
+        Logger.warn "The :handler option in #{inspect endpoint} is deprecated, please use :adapter instead"
+        other
+    end
+  end
+
+  defp cowboy_version_adapter() do
+    case Application.spec(:cowboy, :vsn) do
+      [?1 | _] -> CowboyAdapter
+      _ -> Cowboy2Adapter
+    end
+  end
+
+  defp warn_on_different_adapter_version(CowboyAdapter, Cowboy2Adapter, endpoint) do
+    Logger.warn("""
+    You have specified #{inspect CowboyAdapter} for Cowboy v1.x \
+    in the :adapter configuration of your Phoenix endpoint #{inspect endpoint} \
+    but your mix.exs has fetched Cowboy v2.x.
+
+    If you wish to use Cowboy 1, please update mix.exs to point to the \
+    correct Cowboy version:
+
+        {:cowboy, "~> 1.0"}
+
+    If you want to use Cowboy 2, then please remove the :adapter option \
+    in your config.exs file or set it to:
+
+        adapter: Phoenix.Endpoint.Cowboy2Adapter
+
+    """)
+
+    raise "aborting due to adapter mismatch"
+  end
+  defp warn_on_different_adapter_version(_user, _autodetected, _endpoint), do: :ok
+
   defp watcher_children(_mod, conf, server?) do
     if server? do
       Enum.map(conf[:watchers], fn {cmd, args} ->
@@ -269,11 +331,11 @@ defmodule Phoenix.Endpoint.Supervisor do
     raise ArgumentError, "expected a path starting with a single / but got #{inspect path}"
   end
 
-  # TODO v1.4: Deprecate {:system, env_var}
+  # TODO: Deprecate {:system, env_var} once we require Elixir v1.7+
   defp host_to_binary({:system, env_var}), do: host_to_binary(System.get_env(env_var))
   defp host_to_binary(host), do: host
 
-  # TODO v1.4: Deprecate {:system, env_var}
+  # TODO: Deprecate {:system, env_var} once we require Elixir v1.7+
   defp port_to_integer({:system, env_var}), do: port_to_integer(System.get_env(env_var))
   defp port_to_integer(port) when is_binary(port), do: String.to_integer(port)
   defp port_to_integer(port) when is_integer(port), do: port
diff --git a/lib/phoenix/logger.ex b/lib/phoenix/logger.ex
index 6d4a1cd6c..4e3215b6e 100644
--- a/lib/phoenix/logger.ex
+++ b/lib/phoenix/logger.ex
@@ -28,9 +28,6 @@ defmodule Phoenix.Logger do
   require Logger
   import Phoenix.Controller
 
-  alias Phoenix.Socket
-
-
   def phoenix_controller_call(:start, _compile, %{log_level: false}), do: :ok
   def phoenix_controller_call(:start, %{module: module}, %{log_level: level, conn: conn}) do
     Logger.log level, fn ->
@@ -104,7 +101,6 @@ defmodule Phoenix.Logger do
   defp log_receive(topic, event, socket, params) do
     channel_log(:log_handle_in, socket, fn ->
       "INCOMING #{inspect event} on #{inspect topic} to #{inspect(socket.channel)}\n" <>
-      "  Transport:  #{inspect socket.transport}\n" <>
       "  Parameters: #{inspect filter_values(params)}"
     end)
   end
@@ -113,17 +109,18 @@ defmodule Phoenix.Logger do
   defp log_join(topic, socket, params) do
     channel_log(:log_join, socket, fn ->
       "JOIN #{inspect topic} to #{inspect(socket.channel)}\n" <>
-      "  Transport:  #{inspect socket.transport} (#{socket.vsn})\n" <>
-      "  Serializer:  #{inspect socket.serializer}\n" <>
+      "  Transport:  #{inspect socket.transport}\n" <>
+      "  Serializer: #{inspect socket.serializer}\n" <>
       "  Parameters: #{inspect filter_values(params)}"
     end)
   end
 
-  defp channel_log(log_option, %Socket{private: private}, message_or_func) do
+  defp channel_log(log_option, %Phoenix.Socket{private: private}, message_or_func) do
     case Map.fetch(private, log_option) do
       {:ok, false} -> :ok
       {:ok, level} -> Logger.log(level, message_or_func)
     end
+
     :ok
   end
 end
diff --git a/lib/phoenix/presence.ex b/lib/phoenix/presence.ex
index 5d49d3dde..61f9cd1b1 100644
--- a/lib/phoenix/presence.ex
+++ b/lib/phoenix/presence.ex
@@ -43,7 +43,7 @@ defmodule Phoenix.Presence do
         end
 
         def handle_info(:after_join, socket) do
-          push socket, "presence_state", Presence.list(socket)
+          push(socket, "presence_state", Presence.list(socket))
           {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
             online_at: inspect(System.system_time(:seconds))
           })
diff --git a/lib/phoenix/router/helpers.ex b/lib/phoenix/router/helpers.ex
index 3a305b20d..be789e866 100644
--- a/lib/phoenix/router/helpers.ex
+++ b/lib/phoenix/router/helpers.ex
@@ -152,19 +152,6 @@ defmodule Phoenix.Router.Helpers do
         endpoint.static_path(path)
       end
 
-      @doc """
-      Performs a server push and returns the path to a static asset given its file path.
-
-      The server push will only happen if using HTTP/2 with a supported plug adapter.
-      If not supported, this function will simply return the path to the static
-      asset.
-      """
-      def static_push(%Conn{} = conn, path) do
-        path = static_path(conn, path)
-        Plug.Conn.push(conn, path)
-        path
-      end
-
       @doc """
       Generates url to a static asset given its file path.
       """
diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex
index d751e5840..fe8ea2c93 100644
--- a/lib/phoenix/socket.ex
+++ b/lib/phoenix/socket.ex
@@ -1,6 +1,6 @@
 defmodule Phoenix.Socket do
   @moduledoc ~S"""
-  Defines a socket and its state.
+  A socket implementation that multiplexes messages over channels.
 
   `Phoenix.Socket` is used as a module for establishing and maintaining
   the socket state via the `Phoenix.Socket` struct.
@@ -10,18 +10,17 @@ defmodule Phoenix.Socket do
   It is the responsibility of the socket to tie transports and channels
   together.
 
-  By default, Phoenix supports both websockets and longpoll transports.
-  For example:
+  By default, Phoenix supports both websockets and longpoll when invoking
+  `Phoenix.Endpoint.socket/3` in your endpoint:
 
-      transport :websocket, Phoenix.Transports.WebSocket
+      socket "/socket", MyApp.Socket, websocket: true, longpoll: false
 
   The command above means incoming socket connections can be made via
-  the WebSocket transport. Events are routed by topic to channels:
+  a WebSocket connection. Events are routed by topic to channels:
 
       channel "room:lobby", MyApp.LobbyChannel
 
-  See `Phoenix.Channel` for more information on channels. Check each
-  transport module to find the options specific to each transport.
+  See `Phoenix.Channel` for more information on channels.
 
   ## Socket Behaviour
 
@@ -39,7 +38,6 @@ defmodule Phoenix.Socket do
       defmodule MyApp.UserSocket do
         use Phoenix.Socket
 
-        transport :websocket, Phoenix.Transports.WebSocket
         channel "room:*", MyApp.RoomChannel
 
         def connect(params, socket) do
@@ -52,7 +50,7 @@ defmodule Phoenix.Socket do
       # Disconnect all user's socket connections and their multiplexed channels
       MyApp.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
 
-  ## Socket Fields
+  ## Socket fields
 
     * `id` - The string id of the socket
     * `assigns` - The map of socket assigns, default: `%{}`
@@ -61,24 +59,68 @@ defmodule Phoenix.Socket do
     * `endpoint` - The endpoint module where this socket originated, for example: `MyApp.Endpoint`
     * `handler` - The socket module where this socket originated, for example: `MyApp.UserSocket`
     * `joined` - If the socket has effectively joined the channel
-    * `pubsub_server` - The registered name of the socket's pubsub server
     * `join_ref` - The ref sent by the client when joining
     * `ref` - The latest ref sent by the client
+    * `pubsub_server` - The registered name of the socket's pubsub server
     * `topic` - The string topic, for example `"room:123"`
-    * `transport` - The socket's transport, for example: `Phoenix.Transports.WebSocket`
+    * `transport` - An identifier for the transport, used for logging
     * `transport_pid` - The pid of the socket's transport process
-    * `transport_name` - The socket's transport, for example: `:websocket`
-    * `serializer` - The serializer for socket messages,
-      for example: `Phoenix.Transports.WebSocketSerializer`
-    * `vsn` - The protocol version of the client, for example: "2.0.0"
+    * `serializer` - The serializer for socket messages
+
+  ## Client-server communication
+
+  The encoding of server data and the decoding of client data is done
+  according to a serializer, defined in `Phoenix.Socket.Serializer`.
+  By default, JSON encoding is used to broker messages to and from
+  clients with `Phoenix.Socket.V2.JSONSerializer`.
+
+  The serializer `encode!/1` and `fastlane!/1` functions must return
+  a tuple in the format `{:text | :binary, iodata}`.
+
+  The serializer `decode!` function must return a `Phoenix.Socket.Message`
+  which is forwarded to channels except:
+
+    * "heartbeat" events in the "phoenix" topic - should just emit an OK reply
+    * "phx_join" on any topic - should join the topic
+    * "phx_leave" on any topic - should leave the topic
+
+  Each message also has a `ref` field which is used to track responses.
+
+  The server may send messages or replies back. For messages, the
+  ref uniquely identifies the message. For replies, the ref matches
+  the original message. Both data-types also include a join_ref that
+  uniquely identifes the currently joined channel.
+
+  The `Phoenix.Socket` implementation may also sent special messages
+  and replies:
+
+    * "phx_error" - in case of errors, such as a channel process
+      crashing, or when attempting to join an already joined channel
 
-  ## Custom transports
+    * "phx_close" - the channel was gracefully closed
+
+  Phoenix ships with a JavaScript implementation of both websocket
+  and long polling that interacts with Phoenix.Socket and can be
+  used as reference for those interested in implementing custom clients.
+
+  ## Custom sockets and transports
 
   See the `Phoenix.Socket.Transport` documentation for more information on
-  writing your own transports.
+  writing your own socket that does not leverage channels or for writing
+  your own transports that interacts with other sockets.
+
+  ## Garbage collection
+
+  It's possible to force garbage collection in the transport process after
+  processing large messages. For example, to trigger such from your channels,
+  run:
+
+      send(socket.transport_pid, :garbage_collect)
   """
 
+  require Logger
   alias Phoenix.Socket
+  alias Phoenix.Socket.{Broadcast, Message, Reply}
 
   @doc """
   Receives the socket params and authenticates the connection.
@@ -122,92 +164,77 @@ defmodule Phoenix.Socket do
     defexception [:message]
   end
 
-  @type t :: %Socket{id: nil,
-                     assigns: map,
-                     channel: atom,
-                     channel_pid: pid,
-                     endpoint: atom,
-                     handler: atom,
-                     joined: boolean,
-                     pubsub_server: atom,
-                     ref: term,
-                     topic: String.t,
-                     transport: atom,
-                     transport_name: atom,
-                     serializer: atom,
-                     transport_pid: pid,
-                     private: %{}}
-
-  defstruct id: nil,
-            assigns: %{},
+  defstruct assigns: %{},
             channel: nil,
             channel_pid: nil,
             endpoint: nil,
             handler: nil,
+            id: nil,
             joined: false,
+            join_ref: nil,
+            private: %{},
             pubsub_server: nil,
             ref: nil,
-            join_ref: nil,
+            serializer: nil,
             topic: nil,
             transport: nil,
-            transport_pid: nil,
-            transport_name: nil,
-            serializer: nil,
-            private: %{},
-            vsn: nil
-
-  defmacro __using__(_) do
+            transport_pid: nil
+
+  @type t :: %Socket{
+          assigns: map,
+          channel: atom,
+          channel_pid: pid,
+          endpoint: atom,
+          handler: atom,
+          id: nil,
+          joined: boolean,
+          ref: term,
+          private: %{},
+          pubsub_server: atom,
+          serializer: atom,
+          topic: String.t,
+          transport: atom,
+          transport_pid: pid,
+        }
+
+  defmacro __using__(_opts) do
     quote do
+      ## User API
+
+      import Phoenix.Socket
       @behaviour Phoenix.Socket
-      import unquote(__MODULE__)
+      @before_compile Phoenix.Socket
       Module.register_attribute(__MODULE__, :phoenix_channels, accumulate: true)
       @phoenix_transports %{}
-      @before_compile unquote(__MODULE__)
-    end
-  end
 
-  defmacro __before_compile__(env) do
-    transports = Module.get_attribute(env.module, :phoenix_transports)
-    channels   = Module.get_attribute(env.module, :phoenix_channels)
+      ## Callbacks
 
-    transport_defs =
-      for {name, {mod, conf}} <- transports do
-        quote do
-          def __transport__(unquote(name)) do
-            {unquote(mod), unquote(Macro.escape(conf))}
-          end
-        end
-      end
+      @behaviour Phoenix.Socket.Transport
 
-    channel_defs =
-      for {topic_pattern, module, opts} <- channels do
-        topic_pattern
-        |> to_topic_match()
-        |> defchannel(module, opts)
+      @doc false
+      def child_spec(opts) do
+        Phoenix.Socket.__child_spec__(__MODULE__, opts)
       end
 
-    quote do
-      def __transports__, do: unquote(Macro.escape(transports))
-      unquote(transport_defs)
-      unquote(channel_defs)
-      def __channel__(_topic, _transport), do: nil
-    end
-  end
+      @doc false
+      def connect(map), do: Phoenix.Socket.__connect__(__MODULE__, map)
 
-  defp to_topic_match(topic_pattern) do
-    case String.split(topic_pattern, "*") do
-      [prefix, ""] -> quote do: <<unquote(prefix) <> _rest>>
-      [bare_topic] -> bare_topic
-      _            -> raise ArgumentError, "channels using splat patterns must end with *"
-    end
-  end
+      @doc false
+      def init(state), do: Phoenix.Socket.__init__(state)
 
-  defp defchannel(topic_match, channel_module, opts) do
-    quote do
-      def __channel__(unquote(topic_match)), do: unquote({channel_module, Macro.escape(opts)})
+      @doc false
+      def handle_in(message, state), do: Phoenix.Socket.__in__(message, state)
+
+      @doc false
+      def handle_info(message, state), do: Phoenix.Socket.__info__(message, state)
+
+      @doc false
+      def terminate(reason, state), do: Phoenix.Socket.__terminate__(reason, state)
     end
   end
 
+  ## USER API
+
   @doc """
   Adds key/value pair to socket assigns.
 
@@ -251,10 +278,9 @@ defmodule Phoenix.Socket do
   """
   defmacro channel(topic_pattern, module, opts \\ []) do
     # Tear the alias to simply store the root in the AST.
-    # This will make Elixir unable to track the dependency
-    # between endpoint <-> socket and avoid recompiling the
-    # endpoint (alongside the whole project) whenever the
-    # socket changes.
+    # This will make Elixir unable to track the dependency between
+    # endpoint <-> socket and avoid recompiling the endpoint
+    # (alongside the whole project) whenever the socket changes.
     module = tear_alias(module)
 
     quote do
@@ -270,20 +296,11 @@ defmodule Phoenix.Socket do
   end
   defp tear_alias(other), do: other
 
-  @doc """
-  Defines a transport with configuration.
-
-  ## Examples
+  # TODO: Remove the transport/3 implementation on v1.5
+  # but we should keep the warning and always define
+  # __transports__ for backwards compatibility.
 
-      # customize default `:websocket` transport options
-      transport :websocket, Phoenix.Transports.WebSocket,
-        timeout: 10_000
-
-      # define separate transport, using websocket handler
-      transport :websocket_slow_clients, Phoenix.Transports.WebSocket,
-        timeout: 60_000
-
-  """
+  @doc false
   defmacro transport(name, module, config \\ []) do
     quote do
       @phoenix_transports Phoenix.Socket.__transport__(
@@ -293,6 +310,27 @@ defmodule Phoenix.Socket do
 
   @doc false
   def __transport__(transports, name, module, user_conf) do
+    IO.warn """
+    transport/3 in Phoenix.Socket is deprecated.
+
+    Instead of defining transports in your socket.ex file:
+
+        transport :websocket, Phoenix.Transport.Websocket,
+          key1: value1, key2: value2, key3: value3
+
+        transport :longpoll, Phoenix.Transport.LongPoll,
+          key1: value1, key2: value2, key3: value3
+
+    You should configure websocket/longpoll in your endpoint.ex:
+
+        socket "/socket", MyApp.UserSocket,
+          websocket: [key1: value1, key2: value2, key3: value3],
+          longpoll: [key1: value1, key2: value2, key3: value3]
+
+    Note if you have explicitly upgraded to Cowboy 2, any transport
+    defined with the `transport/3` macro will be ignored.
+    """
+
     defaults = module.default_config()
 
     conf =
@@ -302,7 +340,7 @@ defmodule Phoenix.Socket do
 
     Map.update(transports, name, {module, conf}, fn {dup_module, _} ->
       raise ArgumentError,
-        "duplicate transports (#{inspect dup_module} and #{inspect module}) defined for #{inspect name}."
+        "duplicate transports (#{inspect dup_module} and #{inspect module}) defined for #{inspect name}"
     end)
   end
   defp merge_defaults(conf, defaults), do: Keyword.merge(defaults, conf)
@@ -342,9 +380,280 @@ defmodule Phoenix.Socket do
   defp precompile_serializers(serializers) do
     for {module, requirement} <- serializers do
       case Version.parse_requirement(requirement) do
-        {:ok, requirement} -> {module, requirement}
+        {:ok, requirement} -> {rewrite_serializer(module), requirement}
         :error -> Version.match?("1.0.0", requirement)
       end
     end
   end
+
+  defp rewrite_serializer(Phoenix.Transports.V2.WebSocketSerializer), do: Phoenix.Socket.V2.JSONSerializer
+  defp rewrite_serializer(Phoenix.Transports.V2.LongPollSerializer), do: Phoenix.Socket.V2.JSONSerializer
+  defp rewrite_serializer(Phoenix.Transports.WebSocketSerializer), do: Phoenix.Socket.V1.JSONSerializer
+  defp rewrite_serializer(Phoenix.Transports.LongPollSerializer), do: Phoenix.Socket.V1.JSONSerializer
+  defp rewrite_serializer(module), do: module
+
+  defmacro __before_compile__(env) do
+    transports = Module.get_attribute(env.module, :phoenix_transports)
+    channels   = Module.get_attribute(env.module, :phoenix_channels)
+
+    channel_defs =
+      for {topic_pattern, module, opts} <- channels do
+        topic_pattern
+        |> to_topic_match()
+        |> defchannel(module, opts)
+      end
+
+    quote do
+      def __transports__, do: unquote(Macro.escape(transports))
+      unquote(channel_defs)
+      def __channel__(_topic, _transport), do: nil
+    end
+  end
+
+  defp to_topic_match(topic_pattern) do
+    case String.split(topic_pattern, "*") do
+      [prefix, ""] -> quote do: <<unquote(prefix) <> _rest>>
+      [bare_topic] -> bare_topic
+      _            -> raise ArgumentError, "channels using splat patterns must end with *"
+    end
+  end
+
+  defp defchannel(topic_match, channel_module, opts) do
+    quote do
+      def __channel__(unquote(topic_match)), do: unquote({channel_module, Macro.escape(opts)})
+    end
+  end
+
+  ## CALLBACKS IMPLEMENTATION
+
+  def __child_spec__(handler, opts) do
+    import Supervisor.Spec
+    partitions = Keyword.get(opts, :partitions) || System.schedulers_online()
+
+    worker_opts = [shutdown: Keyword.get(opts, :shutdown, 5_000), restart: :temporary]
+    worker = worker(Phoenix.Channel.Server, [], worker_opts)
+    supervisor(Phoenix.Socket.PoolSupervisor, [{handler, partitions, worker}], id: handler)
+  end
+
+  def __connect__(handler, map) do
+    %{
+      endpoint: endpoint,
+      options: options,
+      transport: transport,
+      params: params
+    } = map
+
+    vsn = params["vsn"] || "1.0.0"
+
+    case negotiate_serializer(Keyword.fetch!(options, :serializer), vsn) do
+      {:ok, serializer} -> user_connect(handler, endpoint, transport, serializer, params)
+      :error -> :error
+    end
+  end
+
+  def __init__({state, %{id: id, endpoint: endpoint} = socket}) do
+    _ = id && endpoint.subscribe(id, link: true)
+    {:ok, {state, %{socket | transport_pid: self()}}}
+  end
+
+  def __in__({payload, opts}, {state, socket}) do
+    %{topic: topic} = message = socket.serializer.decode!(payload, opts)
+    handle_in(Map.get(state.channels, topic), message, state, socket)
+  end
+
+  def __info__({:DOWN, ref, _, pid, reason}, {state, socket}) do
+    case state.channels_inverse do
+      %{^pid => {topic, join_ref}} ->
+        state = delete_channel(state, pid, topic, ref)
+        {:push, encode_on_exit(socket, topic, join_ref, reason), {state, socket}}
+
+      %{} ->
+        {:ok, {state, socket}}
+    end
+  end
+
+  def __info__({:graceful_exit, pid, %Phoenix.Socket.Message{} = message}, {state, socket}) do
+    state =
+      case state.channels_inverse do
+        %{^pid => {topic, _join_ref}} ->
+          {^pid, monitor_ref} = Map.fetch!(state.channels, topic)
+          delete_channel(state, pid, topic, monitor_ref)
+
+        %{} ->
+          state
+      end
+
+    {:push, encode_reply(socket, message), {state, socket}}
+  end
+
+  def __info__(%Broadcast{event: "disconnect"}, state) do
+    {:stop, {:shutdown, :disconnected}, state}
+  end
+
+  def __info__({:socket_push, opcode, payload}, state) do
+    {:push, {opcode, payload}, state}
+  end
+
+  def __info__(:garbage_collect, state) do
+    :erlang.garbage_collect(self())
+    {:ok, state}
+  end
+
+  def __info__(_, state) do
+    {:ok, state}
+  end
+
+  def __terminate__(_reason, {%{channels_inverse: channels_inverse}, _socket}) do
+    Phoenix.Channel.Server.close(Map.keys(channels_inverse))
+    :ok
+  end
+
+  defp negotiate_serializer(serializers, vsn) when is_list(serializers) do
+    case Version.parse(vsn) do
+      {:ok, vsn} ->
+        serializers
+        |> Enum.find(:error, fn {_serializer, vsn_req} -> Version.match?(vsn, vsn_req) end)
+        |> case do
+          {serializer, _vsn_req} ->
+            {:ok, serializer}
+
+          :error ->
+            Logger.error "The client's requested transport version \"#{vsn}\" " <>
+                          "does not match server's version requirements of #{inspect serializers}"
+            :error
+        end
+
+      :error ->
+        Logger.error "Client sent invalid transport version \"#{vsn}\""
+        :error
+    end
+  end
+
+  defp user_connect(handler, endpoint, transport, serializer, params) do
+    # The information in the Phoenix.Socket goes to userland and channels.
+    socket = %Socket{
+      handler: handler,
+      endpoint: endpoint,
+      pubsub_server: endpoint.__pubsub_server__,
+      serializer: serializer,
+      transport: transport
+    }
+
+    # The information in the state is kept only inside the socket process.
+    state = %{
+      channels: %{},
+      channels_inverse: %{}
+    }
+
+    case handler.connect(params, socket) do
+      {:ok, %Socket{} = socket} ->
+        case handler.id(socket) do
+          nil ->
+            {:ok, {state, socket}}
+
+          id when is_binary(id) ->
+            {:ok, {state, %{socket | id: id}}}
+
+          invalid ->
+            Logger.error "#{inspect handler}.id/1 returned invalid identifier " <>
+                           "#{inspect invalid}. Expected nil or a string."
+            :error
+        end
+
+      :error ->
+        :error
+
+      invalid ->
+        Logger.error "#{inspect handler}.connect/2 returned invalid value #{inspect invalid}. " <>
+                     "Expected {:ok, socket} or :error"
+        :error
+    end
+  end
+
+  defp handle_in(_, %{ref: ref, topic: "phoenix", event: "heartbeat"}, state, socket) do
+    reply = %Reply{
+      ref: ref,
+      topic: "phoenix",
+      status: :ok,
+      payload: %{}
+    }
+
+    {:reply, :ok, encode_reply(socket, reply), {state, socket}}
+  end
+
+  defp handle_in(nil, %{event: "phx_join", topic: topic, ref: ref} = message, state, socket) do
+    case socket.handler.__channel__(topic) do
+      {channel, opts} ->
+        case Phoenix.Channel.Server.join(socket, channel, message, opts) do
+          {:ok, reply, pid} ->
+            reply = %Reply{join_ref: ref, ref: ref, topic: topic, status: :ok, payload: reply}
+            state = put_channel(state, pid, topic, ref)
+            {:reply, :ok, encode_reply(socket, reply), {state, socket}}
+
+          {:error, reply} ->
+            reply = %Reply{join_ref: ref, ref: ref, topic: topic, status: :error, payload: reply}
+            {:reply, :error, encode_reply(socket, reply), {state, socket}}
+        end
+
+      _ ->
+        {:reply, :error, encode_ignore(socket, message), {state, socket}}
+    end
+  end
+
+  defp handle_in({pid, ref}, %{event: "phx_join", topic: topic} = message, state, socket) do
+    Logger.debug fn ->
+      "Duplicate channel join for topic \"#{topic}\" in #{inspect(socket.handler)}. " <>
+        "Closing existing channel for new join."
+    end
+
+    :ok = Phoenix.Channel.Server.close([pid])
+    handle_in(nil, message, delete_channel(state, pid, topic, ref), socket)
+  end
+
+  defp handle_in({pid, _ref}, message, state, socket) do
+    send(pid, message)
+    {:ok, {state, socket}}
+  end
+
+  defp handle_in(nil, message, state, socket) do
+    {:reply, :error, encode_ignore(socket, message), {state, socket}}
+  end
+
+  defp put_channel(state, pid, topic, join_ref) do
+    %{channels: channels, channels_inverse: channels_inverse} = state
+    monitor_ref = Process.monitor(pid)
+
+    %{
+      state |
+        channels: Map.put(channels, topic, {pid, monitor_ref}),
+        channels_inverse: Map.put(channels_inverse, pid, {topic, join_ref})
+    }
+  end
+
+  defp delete_channel(state, pid, topic, monitor_ref) do
+    %{channels: channels, channels_inverse: channels_inverse} = state
+    Process.demonitor(monitor_ref, [:flush])
+
+    %{
+      state |
+        channels: Map.delete(channels, topic),
+        channels_inverse: Map.delete(channels_inverse, pid)
+    }
+  end
+
+  defp encode_on_exit(socket, topic, ref, _reason) do
+    message = %Message{join_ref: ref, ref: ref, topic: topic, event: "phx_error", payload: %{}}
+    encode_reply(socket, message)
+  end
+
+  defp encode_ignore(%{handler: handler} = socket, %{ref: ref, topic: topic}) do
+    Logger.warn fn -> "Ignoring unmatched topic \"#{topic}\" in #{inspect(handler)}" end
+    reply = %Reply{ref: ref, topic: topic, status: :error, payload: %{reason: "unmatched topic"}}
+    encode_reply(socket, reply)
+  end
+
+  defp encode_reply(%{serializer: serializer}, message) do
+    {:socket_push, opcode, payload} = serializer.encode!(message)
+    {opcode, payload}
+  end
 end
diff --git a/lib/phoenix/socket/message.ex b/lib/phoenix/socket/message.ex
index 9caeb8498..5c6410e89 100644
--- a/lib/phoenix/socket/message.ex
+++ b/lib/phoenix/socket/message.ex
@@ -9,6 +9,7 @@ defmodule Phoenix.Socket.Message do
     * `event`- The string event name, for example "phx_join"
     * `payload` - The message payload
     * `ref` - The unique string ref
+    * `join_ref` - The unique string ref when joining
 
   """
 
@@ -46,6 +47,7 @@ defmodule Phoenix.Socket.Reply do
     * `status` - The reply status as an atom
     * `payload` - The reply payload
     * `ref` - The unique string ref
+    * `join_ref` - The unique string ref when joining
 
   """
 
diff --git a/lib/phoenix/socket/pool_supervisor.ex b/lib/phoenix/socket/pool_supervisor.ex
new file mode 100644
index 000000000..074fdec1d
--- /dev/null
+++ b/lib/phoenix/socket/pool_supervisor.ex
@@ -0,0 +1,32 @@
+defmodule Phoenix.Socket.PoolSupervisor do
+  @moduledoc false
+  use Supervisor
+
+  def start_link({name, _partitions, _worker} = triplet) do
+    Supervisor.start_link(__MODULE__, triplet, name: name)
+  end
+
+  def start_child(name, key, args) do
+    partitions = :ets.lookup_element(name, :partitions, 2)
+    sup = :ets.lookup_element(name, :erlang.phash2(key, partitions), 2)
+    Supervisor.start_child(sup, args)
+  end
+
+  @doc false
+  def init({name, partitions, worker}) do
+    import Supervisor.Spec
+
+    ref = :ets.new(name, [:named_table, :public, read_concurrency: true])
+    :ets.insert(ref, {:partitions, partitions - 1})
+
+    children =
+      for i <- 0..(partitions - 1) do
+        name = :"#{name}#{i}"
+        :ets.insert(ref, {i, name})
+        supervisor_opts = [strategy: :simple_one_for_one, name: name]
+        supervisor(Supervisor, [[worker], supervisor_opts], id: name)
+      end
+
+    supervise(children, strategy: :one_for_one)
+  end
+end
diff --git a/lib/phoenix/socket/serializers/v1_json_serializer.ex b/lib/phoenix/socket/serializers/v1_json_serializer.ex
new file mode 100644
index 000000000..61187374f
--- /dev/null
+++ b/lib/phoenix/socket/serializers/v1_json_serializer.ex
@@ -0,0 +1,38 @@
+defmodule Phoenix.Socket.V1.JSONSerializer do
+  @moduledoc false
+  @behaviour Phoenix.Socket.Serializer
+
+  alias Phoenix.Socket.{Broadcast, Message, Reply}
+
+  def fastlane!(%Broadcast{} = msg) do
+    map = %Message{topic: msg.topic, event: msg.event, payload: msg.payload}
+    {:socket_push, :text, encode_v1_fields_only(map)}
+  end
+
+  def encode!(%Reply{} = reply) do
+    map = %Message{
+      topic: reply.topic,
+      event: "phx_reply",
+      ref: reply.ref,
+      payload: %{status: reply.status, response: reply.payload}
+    }
+
+    {:socket_push, :text, encode_v1_fields_only(map)}
+  end
+
+  def encode!(%Message{} = map) do
+    {:socket_push, :text, encode_v1_fields_only(map)}
+  end
+
+  def decode!(message, _opts) do
+    message
+    |> Phoenix.json_library().decode!()
+    |> Phoenix.Socket.Message.from_map!()
+  end
+
+  defp encode_v1_fields_only(%Message{} = msg) do
+    msg
+    |> Map.take([:topic, :event, :payload, :ref])
+    |> Phoenix.json_library().encode_to_iodata!()
+  end
+end
diff --git a/lib/phoenix/socket/serializers/v2_json_serializer.ex b/lib/phoenix/socket/serializers/v2_json_serializer.ex
index 0f71929d9..4c951eb90 100644
--- a/lib/phoenix/socket/serializers/v2_json_serializer.ex
+++ b/lib/phoenix/socket/serializers/v2_json_serializer.ex
@@ -2,7 +2,7 @@ defmodule Phoenix.Socket.V2.JSONSerializer do
   @moduledoc false
   @behaviour Phoenix.Socket.Serializer
 
-  alias Phoenix.Socket.{Reply, Message, Broadcast}
+  alias Phoenix.Socket.{Broadcast, Message, Reply}
 
   def fastlane!(%Broadcast{} = msg) do
     data = Phoenix.json_library().encode_to_iodata!([nil, nil, msg.topic, msg.event, msg.payload])
diff --git a/lib/phoenix/socket/transport.ex b/lib/phoenix/socket/transport.ex
index c461c09e2..f84771c05 100644
--- a/lib/phoenix/socket/transport.ex
+++ b/lib/phoenix/socket/transport.ex
@@ -1,91 +1,40 @@
 defmodule Phoenix.Socket.Transport do
   @moduledoc """
-  API for building transports.
+  Outlines the Socket <-> Transport communication.
 
-  This module describes what is required to build a Phoenix transport.
-  The transport sits between the socket and channels, forwarding client
-  messages to channels and vice-versa.
+  This module specifies a behaviour that all sockets must implement.
+  `Phoenix.Socket` is just one possible implementation of a socket
+  that multiplexes events over multiple channels. Developers can
+  implement their own sockets as long as they implement the behaviour
+  outlined here.
 
-  A transport is responsible for:
+  Developers interested in implementing custom transports must invoke
+  the socket API defined in this module. This module also provides
+  many conveniences to make it easier to build custom transports.
 
-    * Implementing the transport behaviour
-    * Establishing the socket connection
-    * Handling of incoming messages
-    * Handling of outgoing messages
-    * Managing channels
-    * Providing secure defaults
+  ## Workflow
 
-  ## The transport behaviour
+  Whenever your endpoint starts, it will automatically invoke the
+  `child_spec/1` on each listed socket and start that specification
+  under the endpoint supervisor. For this reason, custom transports
+  that are manually started in the supervision tree must be listed
+  after the endpoint.
 
-  The transport requires one function:
+  Whenever the transport receives a connection, it should invoke the
+  `c:connect/1` callback with a map of metadata. Different sockets may
+  require different metadatas.
 
-    * `default_config/0` - returns the default transport configuration
-      to be merged when the transport is declared in the socket module
+  If the connection is accepted, the transport can move the connection
+  to another process, if so desires, or keep using the same process. The
+  process responsible for managing the socket should then call `c:init/1`.
 
-  ## Socket connections
+  For each message received from the client, the transport must call
+  `c:handle_in/2` on the socket. For each informational message the
+  transport receives, it should call `c:handle_info/2` on the socket.
 
-  Once a connection is established, the transport is responsible
-  for invoking the `Phoenix.Socket.connect/2` callback and acting
-  accordingly. Once connected, the transport should request the
-  `Phoenix.Socket.id/1` and subscribe to the topic if one exists.
-  On subscribed, the transport must be able to handle "disconnect"
-  broadcasts on the given id topic.
-
-  The `connect/6` function in this module can be used as a
-  convenience or a documentation on such steps.
-
-  ## Incoming messages
-
-  Incoming messages are encoded in whatever way the transport
-  chooses. Those messages must be decoded in the transport into a
-  `Phoenix.Socket.Message` before being forwarded to a channel.
-
-  Most of those messages are user messages except:
-
-    * "heartbeat" events in the "phoenix" topic - should just emit
-      an OK reply
-    * "phx_join" on any topic - should join the topic
-    * "phx_leave" on any topic - should leave the topic
-
-  The function `dispatch/3` can be used to handle these messages.
-
-  ## Outgoing messages
-
-  Channels can send two types of message back to a transport:
-  `Phoenix.Socket.Message` and `Phoenix.Socket.Reply`. Those
-  messages are encoded in the channel into a format defined by
-  the transport. That's why transports are required to pass a
-  serializer that abides to the behaviour described in
-  `Phoenix.Transports.Serializer`.
-
-  ## Managing channel exits
-
-  Because channels are spawned from the transport process, transports
-  must trap exits and correctly handle the `{:EXIT, _, _}` messages
-  arriving from channels, relaying the proper response to the client.
-
-  The `"phx_error"` event is sent by the transport when a channel exits,
-  and represents the channel terminating against its will. The
-  `on_exit_message/3` function aids in constructing the `"phx_error"` message.
-
-  For graceful exits, the channel will notify the transort it is
-  gracefully terminating via the following message:
-
-      {:graceful_exit, channel_pid, %Phoenix.Socket.Message{}}
-
-  The `%Phoenix.Socket.Message{}` is the leave message for the transport
-  to relay to the client.
-
-  ## Duplicate Join Subscriptions
-
-  For a given topic, the client may only establish a single channel
-  subscription. When attempting to create a duplicate subscription,
-  `dispatch/3` will close the existing channel, log a warning, and
-  spawn a new channel for the topic. When sending the `"phx_close"`
-  event from the closed channel, the message will contain the `ref` the
-  client sent when joining. This allows the client to uniquely identify
-  `"phx_close"` and `"phx_error"` messages when force-closing a channel
-  on duplicate joins.
+  On termination, `c:terminate/2` must be called. A special atom with
+  reason `:closed` can be used to specify that the client terminated
+  the connection.
 
   ## Security
 
@@ -94,196 +43,170 @@ defmodule Phoenix.Socket.Transport do
 
   The functionality provided by this module helps in performing "origin"
   header checks and ensuring only SSL connections are allowed.
+  """
 
-  ## Remote Client
+  @type state :: term()
 
-  Channels can reply, synchronously, to any `handle_in/3` event. To match
-  pushes with replies, clients must include a unique `ref` with every
-  message and the channel server will reply with a matching ref where
-  the client can pick up the callback for the matching reply.
+  @doc """
+  Returns a child specification for socket management.
+
+  This is invoked only once per socket regardless of
+  the number of transports and should be responsible
+  for setting up any process structure used exclusively
+  by the socket regardless of transports.
 
-  Phoenix includes a JavaScript client for WebSocket and Longpolling
-  support using JSON encodings.
+  Each socket connection is started by the transport
+  and the process that controls the socket likely
+  belongs to the transport. However, some sockets spawn
+  new processes, such as `Phoenix.Socket` which spawns
+  channels, and this gives the ability to start a
+  supervision tree associated to the socket.
 
-  Clients can be implemented for other protocols and encodings by
-  abiding by the `Phoenix.Socket.Message` format.
+  It receives the socket options from the endpoint,
+  for example:
 
-  ## Protocol Versioning
+      socket "/my_app", MyApp.Socket, shutdown: 5000
 
-  Clients are expected to send the Channel Transport protocol version that they
-  expect to be talking to. The version can be retrieved on the server from
-  `Phoenix.Channel.Transport.protocol_version/0`. If no version is provided, the
-  Transport adapters should default to assume a `"1.0.0"` version number.
-  See `web/static/js/phoenix.js` for an example transport client
-  implementation.
+  means `child_spec([shutdown: 5000])` will be invoked.
   """
+  @callback child_spec(keyword) :: Supervisor.child_spec
 
-  require Logger
-  alias Phoenix.Socket
-  alias Phoenix.Socket.Message
-  alias Phoenix.Socket.Reply
+  @doc """
+  Connects to the socket.
 
-  @protocol_version "2.0.0"
+  The transport passes a map of metadata and the socket
+  returns `{:ok, state}` or `:error`. The state must be
+  stored by the transport and returned in all future
+  operations.
 
-  @doc """
-  Provides a keyword list of default configuration for socket transports.
-  """
-  @callback default_config() :: Keyword.t
+  This function is used for authorization purposes and it
+  may be invoked outside of the process that effectively
+  runs the socket.
+
+  In the default `Phoenix.Socket` implementation, the
+  metadata expects the following keys:
+
+    * endpoint - the application endpoint
+    * transport - the transport name
+    * params - the connection parameters
+    * options - a keyword list of transport options, often
+      given by developers when configuring the transport.
+      It must include a `:serializer` field with the list of
+      serializers and their requirements
 
-  @doc """
-  Returns the Channel Transport protocol version.
   """
-  def protocol_version, do: @protocol_version
+  @callback connect(transport_info :: map) :: {:ok, state} | :error
 
   @doc """
-  Handles the socket connection.
-
-  It builds a new `Phoenix.Socket`, invokes the handler
-  `connect/2` callback and returns the result.
+  Initializes the socket state.
 
-  If the connection was successful, generates `Phoenix.PubSub`
-  topic from the `id/1` callback.
+  This must be executed from the process that will effectively
+  operate the socket.
   """
-  def connect(endpoint, handler, transport_name, transport, serializer_config, params, pid \\ self()) do
-    vsn = params["vsn"] || "1.0.0"
-
-    case serializer_for_vsn(vsn, serializer_config) do
-      {:ok, serializer} ->
-        do_connect(vsn, endpoint, handler, transport_name, transport, serializer, params, pid)
-      {:error, reason} ->
-        Logger.error(reason)
-        :error
-    end
-  end
-  defp serializer_for_vsn(vsn, serializer) when is_atom(serializer) do
-    if Version.match?(vsn, "~> 1.0.0") do
-      {:ok, serializer}
-    else
-      {:error, "The client's requested channel transport version \"#{vsn}\" " <>
-               "does not match server's version requirements of \"~> 1.0.0\""}
-    end
-  rescue
-    Version.InvalidVersionError ->
-      {:error, "The client's requested channel transport version \"#{inspect vsn}\" " <>
-               "is invalid"}
-  end
-  defp serializer_for_vsn(vsn, serializers) when is_list(serializers) do
-    serializers
-    |> Enum.find(fn {_serializer, vsn_req} -> Version.match?(vsn, vsn_req) end)
-    |> case do
-      {serializer, _vsn_req} -> {:ok, serializer}
-      nil ->
-        {:error, "The client's requested channel transport version \"#{vsn}\" " <>
-                 "does not match server's version requirements of #{inspect serializers}"}
-    end
-  end
+  @callback init(state) :: {:ok, state}
 
-  defp do_connect(vsn, endpoint, handler, transport_name, transport, serializer, params, pid) do
-    socket = %Socket{endpoint: endpoint,
-                     transport: transport,
-                     transport_pid: pid,
-                     transport_name: transport_name,
-                     handler: handler,
-                     vsn: vsn,
-                     pubsub_server: endpoint.__pubsub_server__,
-                     serializer: serializer}
-
-    case handler.connect(params, socket) do
-      {:ok, socket} ->
-        case handler.id(socket) do
-          nil                   -> {:ok, socket}
-          id when is_binary(id) -> {:ok, %Socket{socket | id: id}}
-          invalid               ->
-            Logger.error "#{inspect handler}.id/1 returned invalid identifier #{inspect invalid}. " <>
-                         "Expected nil or a string."
-            :error
-        end
+  @doc """
+  Handles incoming socket messages.
 
-      :error ->
-        :error
+  The message is represented as `{payload, options}`. It must
+  return one of:
 
-      invalid ->
-        Logger.error "#{inspect handler}.connect/2 returned invalid value #{inspect invalid}. " <>
-                     "Expected {:ok, socket} or :error"
-        :error
-    end
-  end
+    * `{:ok, state}` - continues the socket with no reply
+    * `{:reply, status, reply, state}` - continues the socket with reply
+    * `{:stop, reason, state}` - stops the socket
+
+  The `reply` is a tuple contain an `opcode` atom and a message that can
+  be any term. The built-in websocket transport supports both `:text` and
+  `:binary` opcode and the message must be always iodata. Long polling only
+  supports text opcode.
+  """
+  @callback handle_in({message :: term, opts :: keyword}, state) ::
+              {:ok, state}
+              | {:reply, :ok | :error, {opcode :: atom, message :: term}, state}
+              | {:stop, reason :: term, state}
 
   @doc """
-  Dispatches `Phoenix.Socket.Message` to a channel.
+  Handles info messages.
 
-  All serialized, remote client messages should be deserialized and
-  forwarded through this function by adapters.
+  The message is a term. It must return one of:
 
-  The following returns must be handled by transports:
+    * `{:ok, state}` - continues the socket with no reply
+    * `{:push, reply, state}` - continues the socket with reply
+    * `{:stop, reason, state}` - stops the socket
 
-    * `:noreply` - Nothing to be done by the transport
-    * `{:reply, reply}` - The reply to be sent to the client
-    * `{:joined, channel_pid, reply}` - The channel was joined
-      and the reply must be sent as result
-    * `{:error, reason, reply}` - An error occurred and the reply
-      must be sent as result
+  The `reply` is a tuple contain an `opcode` atom and a message that can
+  be any term. The built-in websocket transport supports both `:text` and
+  `:binary` opcode and the message must be always iodata. Long polling only
+  supports text opcode.
+  """
+  @callback handle_info(message :: term, state) ::
+              {:ok, state}
+              | {:push, {opcode :: atom, message :: term}, state}
+              | {:stop, reason :: term, state}
 
-  ## Parameter filtering on join
+  @doc """
+  Invoked on termination.
 
-  When logging parameters, Phoenix can filter out sensitive parameters
-  such as passwords and tokens. Parameters to be filtered can be added
-  via the `:filter_parameters` option:
+  If `reason` is `:closed`, it means the client closed the socket.
+  """
+  @callback terminate(reason :: term, state) :: :ok
 
-      config :phoenix, :filter_parameters, ["password", "secret"]
+  require Logger
+  alias Phoenix.Socket.{Reply, Message}
 
-  With the configuration above, Phoenix will filter any parameter
-  that contains the terms `password` or `secret`. The match is
-  case sensitive.
+  @doc false
+  def protocol_version do
+    IO.warn "Phoenix.Socket.Transport.protocol_version/0 is deprecated"
+    "2.0.0"
+  end
 
-  Phoenix's default is `["password"]`.
+  @doc false
+  def connect(endpoint, handler, _transport_name, transport, serializers, params, _pid \\ self()) do
+    IO.warn "Phoenix.Socket.Transport.connect/7 is deprecated"
+
+    handler.connect(%{
+      endpoint: endpoint,
+      transport: transport,
+      options: [serializer: serializers],
+      params: params
+    })
+  end
 
-  """
+  @doc false
   def dispatch(msg, channels, socket)
 
   def dispatch(%{ref: ref, topic: "phoenix", event: "heartbeat"}, _channels, socket) do
+    IO.warn "Phoenix.Socket.Transport.dispatch/3 is deprecated"
     {:reply, %Reply{join_ref: socket.join_ref, ref: ref, topic: "phoenix", status: :ok, payload: %{}}}
   end
 
   def dispatch(%Message{} = msg, channels, socket) do
+    IO.warn "Phoenix.Socket.Transport.dispatch/3 is deprecated"
     channels
     |> Map.get(msg.topic)
     |> do_dispatch(msg, socket)
   end
 
-  @doc false
-  def build_channel_socket(%Socket{} = socket, channel, topic, join_ref, opts) do
-    %Socket{socket |
-            topic: topic,
-            channel: channel,
-            join_ref: join_ref,
-            assigns: Map.merge(socket.assigns, opts[:assigns] || %{}),
-            private: channel.__socket__(:private)}
-  end
-
-  defp do_dispatch(nil, %{event: "phx_join", topic: topic} = msg, base_socket) do
-    case base_socket.handler.__channel__(topic) do
+  defp do_dispatch(nil, %{event: "phx_join", topic: topic, ref: ref} = msg, socket) do
+    case socket.handler.__channel__(topic) do
       {channel, opts} ->
-        socket = build_channel_socket(base_socket, channel, topic, msg.ref, opts)
+        case Phoenix.Channel.Server.join(socket, channel, msg, opts) do
+          {:ok, reply, pid} ->
+            {:joined, pid, %Reply{join_ref: ref, ref: ref, topic: topic, status: :ok, payload: reply}}
 
-        case Phoenix.Channel.Server.join(socket, msg.payload) do
-          {:ok, response, pid} ->
-            log socket, topic, fn -> "Replied #{topic} :ok" end
-            {:joined, pid, %Reply{join_ref: socket.join_ref, ref: msg.ref, topic: topic, status: :ok, payload: response}}
-
-          {:error, reason} ->
-            log socket, topic, fn -> "Replied #{topic} :error" end
-            {:error, reason, %Reply{join_ref: socket.join_ref, ref: msg.ref, topic: topic, status: :error, payload: reason}}
+          {:error, reply} ->
+            {:error, reply, %Reply{join_ref: ref, ref: ref, topic: topic, status: :error, payload: reply}}
         end
 
-      nil -> reply_ignore(msg, base_socket)
+      nil ->
+        reply_ignore(msg, socket)
     end
   end
 
-  defp do_dispatch(pid, %{event: "phx_join"} = msg, socket) when is_pid(pid) do
+  defp do_dispatch({pid, _ref}, %{event: "phx_join"} = msg, socket) when is_pid(pid) do
     Logger.debug "Duplicate channel join for topic \"#{msg.topic}\" in #{inspect(socket.handler)}. " <>
                  "Closing existing channel for new join."
-    :ok = Phoenix.Channel.Server.close(pid)
+    :ok = Phoenix.Channel.Server.close([pid])
     do_dispatch(nil, msg, socket)
   end
 
@@ -291,39 +214,36 @@ defmodule Phoenix.Socket.Transport do
     reply_ignore(msg, socket)
   end
 
-  defp do_dispatch(channel_pid, msg, _socket) do
+  defp do_dispatch({channel_pid, _ref}, msg, _socket) do
     send(channel_pid, msg)
     :noreply
   end
 
-  defp log(_, "phoenix" <> _, _func), do: :noop
-  defp log(%{private: %{log_join: false}}, _topic, _func), do: :noop
-  defp log(%{private: %{log_join: level}}, _topic, func), do: Logger.log(level, func)
-
   defp reply_ignore(msg, socket) do
     Logger.warn fn -> "Ignoring unmatched topic \"#{msg.topic}\" in #{inspect(socket.handler)}" end
     {:error, :unmatched_topic, %Reply{join_ref: socket.join_ref, ref: msg.ref, topic: msg.topic, status: :error,
                                       payload: %{reason: "unmatched topic"}}}
   end
 
-  @doc """
-  Returns the message to be relayed when a channel exits.
-  """
+  @doc false
   def on_exit_message(topic, join_ref, _reason) do
+    IO.warn "Phoenix.Socket.Transport.on_exit_mesage/3 is deprecated"
     %Message{join_ref: join_ref, ref: join_ref, topic: topic, event: "phx_error", payload: %{}}
   end
 
-  # TODO v2: Remove 2-arity
   @doc false
   def on_exit_message(topic, reason) do
-    IO.warn "Phoenix.Transport.on_exit_message/2 is deprecated. Use on_exit_message/3 instead."
+    IO.warn "Phoenix.Transport.on_exit_message/2 is deprecated"
     on_exit_message(topic, nil, reason)
   end
 
-  @doc false
-  def notify_graceful_exit(%Socket{topic: topic, join_ref: ref} = socket) do
-    close_msg = %Message{join_ref: ref, ref: ref, topic: topic, event: "phx_close", payload: %{}}
-    send(socket.transport_pid, {:graceful_exit, self(), close_msg})
+  @doc """
+  Runs the code reloader if enabled.
+  """
+  def code_reload(conn, endpoint, opts) do
+    reload? = Keyword.get(opts, :code_reloader, endpoint.config(:code_reloader))
+    reload? && Phoenix.CodeReloader.reload!(endpoint)
+    conn
   end
 
   @doc """
@@ -332,7 +252,7 @@ defmodule Phoenix.Socket.Transport do
   Uses the endpoint configuration to decide so. It is a
   noop if the connection has been halted.
   """
-  def force_ssl(%Plug.Conn{halted: true} = conn, _socket, _endpoint, _opts) do
+  def force_ssl(%{halted: true} = conn, _socket, _endpoint, _opts) do
     conn
   end
 
@@ -430,6 +350,7 @@ defmodule Phoenix.Socket.Transport do
           boolean when is_boolean(boolean) ->
             boolean
         end
+
       {:cache, check_origin}
     end)
   end
@@ -441,6 +362,7 @@ defmodule Phoenix.Socket.Transport do
           "invalid check_origin: #{inspect origin}. " <>
           "Expected an origin with a host that is parsable by URI.parse/1. For example: " <>
           "[\"https://example.com\", \"//another.com:888\", \"//other.com\"]"
+
       %{scheme: scheme, port: port, host: host} ->
         {scheme, host, port}
     end
@@ -474,7 +396,7 @@ defmodule Phoenix.Socket.Transport do
   defp compare_host?(request_host, allowed_host),
     do: request_host == allowed_host
 
-  # TODO v1.4: Deprecate {:system, env_var}
+  # TODO: Deprecate {:system, env_var} once we require Elixir v1.7+
   defp host_to_binary({:system, env_var}), do: host_to_binary(System.get_env(env_var))
   defp host_to_binary(host), do: host
 end
diff --git a/lib/phoenix/token.ex b/lib/phoenix/token.ex
index 2ec024a0a..078954a21 100644
--- a/lib/phoenix/token.ex
+++ b/lib/phoenix/token.ex
@@ -58,8 +58,8 @@ defmodule Phoenix.Token do
 
       def create(conn, params) do
         user = User.create(params)
-        render conn, "user.json",
-               %{token: Phoenix.Token.sign(conn, "user salt", user.id), user: user}
+        render(conn, "user.json",
+               %{token: Phoenix.Token.sign(conn, "user salt", user.id), user: user})
       end
 
   Once the token is sent, the client may now send it back to the server
diff --git a/lib/phoenix/transports/long_poll.ex b/lib/phoenix/transports/long_poll.ex
index 2730cf975..f76b66bd8 100644
--- a/lib/phoenix/transports/long_poll.ex
+++ b/lib/phoenix/transports/long_poll.ex
@@ -1,87 +1,40 @@
 defmodule Phoenix.Transports.LongPoll do
   @moduledoc false
+  @behaviour Plug
 
-  _ = """
-  Socket transport for long poll clients.
-
-  ## Configuration
-
-  The long poll is configurable in your socket:
-
-      transport :longpoll, Phoenix.Transports.LongPoll,
-        window_ms: 10_000,
-        pubsub_timeout_ms: 2_000,
-        transport_log: false,
-        crypto: [max_age: 1_209_600]
-
-    * `:window_ms` - how long the client can wait for new messages
-      in its poll request
-
-    * `:pubsub_timeout_ms` - how long a request can wait for the
-      pubsub layer to respond
-
-    * `:crypto` - options for verifying and signing the token, accepted
-      by `Phoenix.Token`. By default tokens are valid for 2 weeks
-
-    * `:transport_log` - if the transport layer itself should log and, if so, the level
-
-    * `:check_origin` - if we should check the origin of requests when the
-      origin header is present. It defaults to true and, in such cases,
-      it will check against the host value in `YourApp.Endpoint.config(:url)[:host]`.
-      It may be set to `false` (not recommended) or to a list of explicitly
-      allowed origins
-
-    * `:code_reloader` - optionally override the default `:code_reloader` value
-      from the socket's endpoint
-  """
-
-  ## Transport callbacks
-
-  @behaviour Phoenix.Socket.Transport
-  alias Phoenix.Transports.LongPollSerializer
-  alias Phoenix.Socket.V2
+  import Plug.Conn
+  alias Phoenix.Socket.{V1, V2, Transport}
 
   def default_config() do
-    [window_ms: 10_000,
-     pubsub_timeout_ms: 2_000,
-     serializer: [{Phoenix.Transports.LongPollSerializer, "~> 1.0.0"},
-                  {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}],
-     transport_log: false,
-     crypto: [max_age: 1_209_600]]
+    [
+      window_ms: 10_000,
+      pubsub_timeout_ms: 2_000,
+      serializer: [{V1.JSONSerializer, "~> 1.0.0"}, {V2.JSONSerializer, "~> 2.0.0"}],
+      transport_log: false,
+      crypto: [max_age: 1_209_600]
+    ]
   end
 
-  ## Plug callbacks
-
-  @behaviour Plug
-
-  import Plug.Conn
-  alias Phoenix.Socket.Transport
-
-  @doc false
   def init(opts), do: opts
 
-  @doc false
-  def call(conn, {endpoint, handler, transport}) do
-    {_, opts} = handler.__transport__(transport)
-
+  def call(conn, {endpoint, handler, opts}) do
     conn
-    |> code_reload(opts, endpoint)
-    |> fetch_query_params
+    |> fetch_query_params()
     |> put_resp_header("access-control-allow-origin", "*")
-    |> Plug.Conn.fetch_query_params
+    |> Transport.code_reload(endpoint, opts)
     |> Transport.transport_log(opts[:transport_log])
     |> Transport.force_ssl(handler, endpoint, opts)
-    |> Transport.check_origin(handler, endpoint, opts, &status_json(&1, %{}))
-    |> dispatch(endpoint, handler, transport, opts)
+    |> Transport.check_origin(handler, endpoint, opts, &status_json/1)
+    |> dispatch(endpoint, handler, opts)
   end
 
-  defp dispatch(%{halted: true} = conn, _, _, _, _) do
+  defp dispatch(%{halted: true} = conn, _, _, _) do
     conn
   end
 
   # Responds to pre-flight CORS requests with Allow-Origin-* headers.
   # We allow cross-origin requests as we always validate the Origin header.
-  defp dispatch(%{method: "OPTIONS"} = conn, _, _, _, _) do
+  defp dispatch(%{method: "OPTIONS"} = conn, _, _, _) do
     headers = get_req_header(conn, "access-control-request-headers") |> Enum.join(", ")
 
     conn
@@ -92,75 +45,76 @@ defmodule Phoenix.Transports.LongPoll do
   end
 
   # Starts a new session or listen to a message if one already exists.
-  defp dispatch(%{method: "GET"} = conn, endpoint, handler, transport, opts) do
+  defp dispatch(%{method: "GET"} = conn, endpoint, handler, opts) do
     case resume_session(conn.params, endpoint, opts) do
       {:ok, server_ref} ->
         listen(conn, server_ref, endpoint, opts)
       :error ->
-        new_session(conn, endpoint, handler, transport, opts)
+        new_session(conn, endpoint, handler, opts)
     end
   end
 
   # Publish the message encoded as a JSON body.
-  defp dispatch(%{method: "POST"} = conn, endpoint, _, _, opts) do
+  defp dispatch(%{method: "POST"} = conn, endpoint, _, opts) do
     case resume_session(conn.params, endpoint, opts) do
       {:ok, server_ref} ->
-        conn |> parse_json() |> publish(server_ref, endpoint, opts)
+        publish(conn, server_ref, endpoint, opts)
       :error ->
-        conn |> put_status(:gone) |> status_json(%{})
+        conn |> put_status(:gone) |> status_json()
     end
   end
 
   # All other requests should fail.
-  defp dispatch(conn, _, _, _, _) do
+  defp dispatch(conn, _, _, _) do
     send_resp(conn, :bad_request, "")
   end
 
-  ## Connection helpers
+  defp publish(conn, server_ref, endpoint, opts) do
+    case read_body(conn, []) do
+      {:ok, body, conn} ->
+        status = transport_dispatch(endpoint, server_ref, body, opts)
+        conn |> put_status(status) |> status_json()
 
-  # force application/json for xdomain clients
-  defp parse_json(conn) do
-    conn
-    |> read_body([])
-    |> decode(serializer(conn.params["vsn"]))
-  end
-  defp decode({:ok, body, conn}, serializer) do
-    assign(conn, :message, serializer.decode!(body, []))
-  rescue
-    Phoenix.Socket.InvalidMessageError -> raise Plug.Parsers.ParseError
+      _ ->
+        raise Plug.BadRequestError
+    end
   end
-  defp decode(_bad_request, _serializr), do: raise Plug.BadRequestError
 
-  defp serializer("1." <> _ = _vsn), do: LongPollSerializer
-  defp serializer("2." <> _ = _vsn), do: V2.JSONSerializer
-  defp serializer(nil), do: LongPollSerializer
+  defp transport_dispatch(endpoint, server_ref, body, opts) do
+    ref = make_ref()
+    broadcast_from!(endpoint, server_ref, {:dispatch, client_ref(server_ref), body, ref})
+
+    receive do
+      {:ok, ^ref} -> :ok
+      {:error, ^ref} -> :unauthorized
+    after
+      opts[:window_ms] -> :request_timeout
+    end
+  end
 
-  defp new_session(conn, endpoint, handler, transport, opts) do
-    serializer = opts[:serializer]
+  ## Session handling
 
+  defp new_session(conn, endpoint, handler, opts) do
     priv_topic =
       "phx:lp:"
       <> Base.encode64(:crypto.strong_rand_bytes(16))
       <> (System.system_time(:milliseconds) |> Integer.to_string)
 
-    args = [endpoint, handler, transport, __MODULE__, serializer,
-            conn.params, opts[:window_ms], priv_topic]
-
-    supervisor = Module.concat(endpoint, "LongPoll.Supervisor")
+    arg = {endpoint, handler, opts, conn.params, priv_topic}
 
-    case Supervisor.start_child(supervisor, args) do
+    case Supervisor.start_child(Phoenix.Transports.LongPoll.Supervisor, [arg]) do
       {:ok, :undefined} ->
-        conn |> put_status(:forbidden) |> status_json(%{})
+        conn |> put_status(:forbidden) |> status_json()
+
       {:ok, server_pid} ->
         data  = {:v1, endpoint.config(:endpoint_id), server_pid, priv_topic}
         token = sign_token(endpoint, data, opts)
-        conn |> put_status(:gone) |> status_json(%{token: token})
+        conn |> put_status(:gone) |> status_token_messages_json(token, [])
     end
   end
 
   defp listen(conn, server_ref, endpoint, opts) do
     ref = make_ref()
-
     broadcast_from!(endpoint, server_ref, {:flush, client_ref(server_ref), ref})
 
     {status, messages} =
@@ -182,20 +136,9 @@ defmodule Phoenix.Transports.LongPoll do
 
     conn
     |> put_status(status)
-    |> status_json(%{token: conn.params["token"], messages: messages})
-  end
-
-  defp publish(conn, server_ref, endpoint, opts) do
-    msg = conn.assigns.message
-
-    case transport_dispatch(endpoint, server_ref, msg, opts) do
-      :ok               -> conn |> put_status(:ok) |> status_json(%{})
-      {:error, _reason} -> conn |> put_status(:unauthorized) |> status_json(%{})
-    end
+    |> status_token_messages_json(conn.params["token"], messages)
   end
 
-  ## Endpoint helpers
-
   # Retrieves the serialized `Phoenix.LongPoll.Server` pid
   # by publishing a message in the encrypted private topic.
   defp resume_session(%{"token" => token}, endpoint, opts) do
@@ -217,27 +160,13 @@ defmodule Phoenix.Transports.LongPoll do
         :error
     end
   end
-  defp resume_session(_params, _endpoint, _opts), do: :error
 
-  # Publishes a message to the pubsub system.
-  defp transport_dispatch(endpoint, server_ref, msg, opts) do
-    ref = make_ref()
-    broadcast_from!(endpoint, server_ref, {:dispatch, client_ref(server_ref), msg, ref})
+  defp resume_session(_params, _endpoint, _opts), do: :error
 
-    receive do
-      {:dispatch, ^ref}      -> :ok
-      {:error, reason, ^ref} -> {:error, reason}
-    after
-      opts[:window_ms] -> {:error, :timeout}
-    end
-  end
+  ## Helpers
 
   defp server_ref(endpoint_id, id, pid, topic) do
-    if endpoint_id == id and Process.alive?(pid) do
-      pid
-    else
-      topic
-    end
+    if endpoint_id == id and Process.alive?(pid), do: pid, else: topic
   end
 
   defp client_ref(topic) when is_binary(topic), do: topic
@@ -261,18 +190,24 @@ defmodule Phoenix.Transports.LongPoll do
     Phoenix.Token.verify(endpoint, Atom.to_string(endpoint.__pubsub_server__), signed, opts[:crypto])
   end
 
-  defp status_json(conn, data) do
-    status = Plug.Conn.Status.code(conn.status || 200)
-    data   = Map.put(data, :status, status)
-    conn
-    |> put_status(200)
-    |> Phoenix.Controller.json(data)
+  defp status_json(conn) do
+    status = conn.status || 200
+    send_json(conn, [{"status", Integer.to_string(status)}])
+  end
+
+  defp status_token_messages_json(conn, token, messages) do
+    status = conn.status || 200
+    messages = [?[, Enum.intersperse(messages, ?,), ?]]
+    token = Phoenix.json_library().encode_to_iodata!(token)
+    data = [{"status", Integer.to_string(status)}, {"token", token}, {"messages", messages}]
+    send_json(conn, data)
   end
 
-  defp code_reload(conn, opts, endpoint) do
-    reload? = Keyword.get(opts, :code_reloader, endpoint.config(:code_reloader))
-    if reload?, do: Phoenix.CodeReloader.reload!(endpoint)
+  defp send_json(conn, json) do
+    data = for {k, v} <- json, do: [?", k, ?", ?:, v]
 
     conn
+    |> put_resp_header("content-type", "application/json; charset=utf-8")
+    |> send_resp(200, [?{, Enum.intersperse(data, ?,), ?}])
   end
 end
diff --git a/lib/phoenix/transports/long_poll_serializer.ex b/lib/phoenix/transports/long_poll_serializer.ex
deleted file mode 100644
index 88ac4d89e..000000000
--- a/lib/phoenix/transports/long_poll_serializer.ex
+++ /dev/null
@@ -1,48 +0,0 @@
-defmodule Phoenix.Transports.LongPollSerializer do
-  @moduledoc false
-  @behaviour Phoenix.Transports.Serializer
-
-  alias Phoenix.Socket.{Reply, Message, Broadcast}
-
-  def fastlane!(%Broadcast{} = broadcast) do
-    {:socket_push, :text, to_map(broadcast)}
-  end
-
-  def encode!(message) do
-    {:socket_push, :text, to_map(message)}
-  end
-
-  defp to_map(%Reply{} = reply) do
-    %{
-      topic: reply.topic,
-      event: "phx_reply",
-      ref: reply.ref,
-      join_ref: reply.ref,
-      payload: %{status: reply.status, response: reply.payload}
-    }
-  end
-  defp to_map(%Message{} = msg) do
-    %{
-      topic: msg.topic,
-      event: msg.event,
-      payload: msg.payload,
-      ref: msg.ref,
-      join_ref: msg.join_ref
-    }
-  end
-  defp to_map(%Broadcast{} = bcast) do
-    %{
-      topic: bcast.topic,
-      event: bcast.event,
-      payload: bcast.payload,
-      ref: nil,
-      join_ref: nil
-    }
-  end
-
-  def decode!(message, _opts) do
-    message
-    |> Phoenix.json_library().decode!()
-    |> Phoenix.Socket.Message.from_map!()
-  end
-end
diff --git a/lib/phoenix/transports/long_poll_server.ex b/lib/phoenix/transports/long_poll_server.ex
index 3cc2f9943..3e79b7127 100644
--- a/lib/phoenix/transports/long_poll_server.ex
+++ b/lib/phoenix/transports/long_poll_server.ex
@@ -1,16 +1,16 @@
 defmodule Phoenix.Transports.LongPoll.Supervisor do
   @moduledoc false
-
   use Supervisor
 
-  def start_link(options) do
-    Supervisor.start_link(__MODULE__, [], options)
+  def start_link() do
+    Supervisor.start_link(__MODULE__, [], name: __MODULE__)
   end
 
   def init([]) do
     children = [
       worker(Phoenix.Transports.LongPoll.Server, [], restart: :temporary)
     ]
+
     supervise(children, strategy: :simple_one_for_one)
   end
 end
@@ -19,103 +19,59 @@ defmodule Phoenix.Transports.LongPoll.Server do
   @moduledoc false
 
   use GenServer
-
   alias Phoenix.PubSub
-  alias Phoenix.Socket.{Transport, Broadcast}
-
-  @doc """
-  Starts the Server.
-
-    * `socket` - The `Phoenix.Socket` struct returned from `connect/2`
-      of the socket handler.
-    * `window_ms` - The longpoll session timeout, in milliseconds
-
-  If the server receives no message within `window_ms`, it terminates
-  and clients are responsible for opening a new session.
-  """
-  def start_link(endpoint, handler, transport_name, transport,
-                 serializer, params, window_ms, priv_topic) do
-    GenServer.start_link(__MODULE__, [endpoint, handler, transport_name, transport,
-                                      serializer, params, window_ms, priv_topic])
+
+  def start_link(arg) do
+    GenServer.start_link(__MODULE__, arg)
   end
 
-  ## Callbacks
-
-  def init([endpoint, handler, transport_name, transport,
-            serializer, params, window_ms, priv_topic]) do
-    Process.flag(:trap_exit, true)
-
-    case Transport.connect(endpoint, handler, transport_name, transport, serializer, params) do
-      {:ok, socket} ->
-        state = %{buffer: [],
-                  socket: socket,
-                  channels: %{},
-                  channels_inverse: %{},
-                  window_ms: trunc(window_ms * 1.5),
-                  pubsub_server: socket.endpoint.__pubsub_server__(),
-                  priv_topic: priv_topic,
-                  last_client_poll: now_ms(),
-                  serializer: socket.serializer,
-                  client_ref: nil}
-
-        if socket.id, do: PubSub.subscribe(state.pubsub_server, socket.id, link: true)
-        :ok = PubSub.subscribe(state.pubsub_server, priv_topic, link: true)
+  def init({endpoint, handler, options, params, priv_topic}) do
+    config = %{endpoint: endpoint, transport: :long_polling, options: options, params: params}
+    window_ms = Keyword.fetch!(options, :window_ms)
 
-        schedule_inactive_shutdown(state.window_ms)
+    case handler.connect(config) do
+      {:ok, handler_state} ->
+        {:ok, handler_state} = handler.init(handler_state)
 
-        {:ok, state}
-      :error ->
-        :ignore
-    end
-  end
+        state = %{
+          buffer: [],
+          handler: {handler, handler_state},
+          window_ms: trunc(window_ms * 1.5),
+          pubsub_server: endpoint.__pubsub_server__(),
+          priv_topic: priv_topic,
+          last_client_poll: now_ms(),
+          client_ref: nil
+        }
 
-  def handle_call(:stop, _from, state), do: {:stop, :shutdown, :ok, state}
-
-  # Handle client dispatches
-  def handle_info({:dispatch, client_ref, msg, ref}, state) do
-    msg
-    |> Transport.dispatch(state.channels, state.socket)
-    |> case do
-      {:joined, channel_pid, reply_msg} ->
-        broadcast_from!(state, client_ref, {:dispatch, ref})
-        new_state = %{state | channels: Map.put(state.channels, msg.topic, channel_pid),
-                              channels_inverse: Map.put(state.channels_inverse, channel_pid, {msg.topic, msg.ref})}
-        publish_reply(reply_msg, new_state)
-
-      {:reply, reply_msg} ->
-        broadcast_from!(state, client_ref, {:dispatch, ref})
-        publish_reply(reply_msg, state)
-
-      :noreply ->
-        broadcast_from!(state, client_ref, {:dispatch, ref})
-        {:noreply, state}
+        :ok = PubSub.subscribe(state.pubsub_server, priv_topic, link: true)
+        schedule_inactive_shutdown(state.window_ms)
+        {:ok, state}
 
-      {:error, reason, error_reply_msg} ->
-        broadcast_from!(state, client_ref, {:error, reason, ref})
-        publish_reply(error_reply_msg, state)
+    :error ->
+      :ignore
     end
   end
 
-  # Detects disconnect broadcasts and shuts down
-  def handle_info(%Broadcast{event: "disconnect"}, state) do
-    {:stop, {:shutdown, :disconnected}, state}
-  end
+  def handle_info({:dispatch, client_ref, body, ref}, state) do
+    %{handler: {handler, handler_state}} = state
 
-  def handle_info({:EXIT, channel_pid, reason}, state) do
-    case Map.get(state.channels_inverse, channel_pid) do
-      nil ->
-        {:stop, {:shutdown, :pubsub_server_terminated}, state}
-      {topic, join_ref} ->
-        new_state = delete(state, topic, channel_pid)
-        msg = Transport.on_exit_message(topic, join_ref, reason)
+    case handler.handle_in({body, []}, handler_state) do
+      {:reply, status, {_, reply}, handler_state} ->
+        state = %{state | handler: {handler, handler_state}}
+        status = if status == :ok, do: :ok, else: :error
+        broadcast_from!(state, client_ref, {status, ref})
+        publish_reply(state, reply)
 
-        publish_reply(msg, new_state)
-    end
-  end
+      {:ok, handler_state} ->
+        state = %{state | handler: {handler, handler_state}}
+        broadcast_from!(state, client_ref, {:ok, ref})
+        {:noreply, state}
 
-  def handle_info({:graceful_exit, channel_pid, %Phoenix.Socket.Message{} = msg}, state) do
-    new_state = delete(state, msg.topic, channel_pid)
-    publish_reply(msg, new_state)
+      {:stop, reason, handler_state} ->
+        state = %{state | handler: {handler, handler_state}}
+        broadcast_from!(state, client_ref, {:error, ref})
+        {:stop, reason, state}
+    end
   end
 
   def handle_info({:subscribe, client_ref, ref}, state) do
@@ -142,12 +98,27 @@ defmodule Phoenix.Transports.LongPoll.Server do
     end
   end
 
-  def handle_info({:socket_push, :text, encoded}, state) do
-    notify_client_now_available(state)
-    {:noreply, %{state | buffer: [encoded | state.buffer]}}
+  def handle_info(message, state) do
+    %{handler: {handler, handler_state}} = state
+
+    case handler.handle_info(message, handler_state) do
+      {:push, {_, reply}, handler_state} ->
+        state = %{state | handler: {handler, handler_state}}
+        publish_reply(state, reply)
+
+      {:ok, handler_state} ->
+        state = %{state | handler: {handler, handler_state}}
+        {:noreply, state}
+
+      {:stop, reason, handler_state} ->
+        state = %{state | handler: {handler, handler_state}}
+        {:stop, reason, state}
+    end
   end
 
-  def terminate(_reason, _state) do
+  def terminate(reason, state) do
+    %{handler: {handler, handler_state}} = state
+    handler.terminate(reason, handler_state)
     :ok
   end
 
@@ -156,19 +127,21 @@ defmodule Phoenix.Transports.LongPoll.Server do
   defp broadcast_from!(_state, client_ref, msg) when is_pid(client_ref),
     do: send(client_ref, msg)
 
-  defp publish_reply(msg, state) do
-    notify_client_now_available(state)
-    {:socket_push, :text, encoded} = state.serializer.encode!(msg)
+  defp publish_reply(state, reply) when is_map(reply) do
+    IO.warn "Returning a map from the LongPolling serializer is deprecated. " <>
+            "Please return JSON encoded data instead (see Phoenix.Socket.Serializer)"
+    publish_reply(state, Phoenix.json_library().encode_to_iodata!(reply))
+  end
 
-    {:noreply, %{state | buffer: [encoded | state.buffer]}}
+  defp publish_reply(state, reply) do
+    notify_client_now_available(state)
+    {:noreply, update_in(state.buffer, &[reply | &1])}
   end
 
   defp notify_client_now_available(state) do
     case state.client_ref do
-      {client_ref, ref} ->
-        broadcast_from!(state, client_ref, {:now_available, ref})
-      nil ->
-        :ok
+      {client_ref, ref} -> broadcast_from!(state, client_ref, {:now_available, ref})
+      nil -> :ok
     end
   end
 
@@ -177,14 +150,4 @@ defmodule Phoenix.Transports.LongPoll.Server do
   defp schedule_inactive_shutdown(window_ms) do
     Process.send_after(self(), :shutdown_if_inactive, window_ms)
   end
-
-  defp delete(state, topic, channel_pid) do
-    case Map.fetch(state.channels, topic) do
-      {:ok, ^channel_pid} ->
-        %{state | channels: Map.delete(state.channels, topic),
-                  channels_inverse: Map.delete(state.channels_inverse, channel_pid)}
-      {:ok, _newer_pid} ->
-        %{state | channels_inverse: Map.delete(state.channels_inverse, channel_pid)}
-    end
-  end
 end
diff --git a/lib/phoenix/transports/serializer.ex b/lib/phoenix/transports/serializer.ex
deleted file mode 100644
index 948738cf7..000000000
--- a/lib/phoenix/transports/serializer.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defmodule Phoenix.Transports.Serializer do
-  @moduledoc false
-
-  @doc "Translates a `Phoenix.Socket.Broadcast` struct to fastlane format"
-  @callback fastlane!(Phoenix.Socket.Broadcast.t) :: term
-
-  @doc "Encodes `Phoenix.Socket.Message` struct to transport representation"
-  @callback encode!(Phoenix.Socket.Message.t | Phoenix.Socket.Reply.t) :: term
-
-  @doc "Decodes iodata into `Phoenix.Socket.Message` struct"
-  @callback decode!(iodata, options :: Keyword.t) :: Phoenix.Socket.Message.t
-end
diff --git a/lib/phoenix/transports/websocket.ex b/lib/phoenix/transports/websocket.ex
index ce2b30bf9..b54b90d46 100644
--- a/lib/phoenix/transports/websocket.ex
+++ b/lib/phoenix/transports/websocket.ex
@@ -1,215 +1,38 @@
 defmodule Phoenix.Transports.WebSocket do
   @moduledoc false
-
-  _ = """
-  Socket transport for websocket clients.
-
-  ## Configuration
-
-  The websocket is configurable in your socket:
-
-      transport :websocket, Phoenix.Transports.WebSocket,
-        timeout: :infinity,
-        transport_log: false
-
-    * `:timeout` - the timeout for keeping websocket connections
-      open after it last received data, defaults to 60_000ms
-
-    * `:transport_log` - if the transport layer itself should log and, if so, the level
-
-    * `:serializer` - the serializer for websocket messages
-
-    * `:check_origin` - if we should check the origin of requests when the
-      origin header is present. It defaults to true and, in such cases,
-      it will check against the host value in `YourApp.Endpoint.config(:url)[:host]`.
-      It may be set to `false` (not recommended) or to a list of explicitly
-      allowed origins.
-
-      check_origin: ["https://example.com",
-                     "//another.com:888", "//other.com"]
-
-      Note: To connect from a native app be sure to either have the native app
-      set an origin or allow any origin via `check_origin: false`
-
-    * `:code_reloader` - optionally override the default `:code_reloader` value
-      from the socket's endpoint
-
-  ## Serializer
-
-  By default, JSON encoding is used to broker messages to and from clients.
-  A custom serializer may be given as a module which implements the `encode!/1`
-  and `decode!/2` functions defined by the `Phoenix.Transports.Serializer`
-  behaviour.
-
-  The `encode!/1` function must return a tuple in the format
-  `{:socket_push, :text | :binary, String.t | binary}`.
-
-  ## Garbage collection
-
-  It's possible to force garbage collection in the transport process after
-  processing large messages.
-
-  Send `:garbage_collect` clause to the transport process:
-
-      send socket.transport_pid, :garbage_collect
-  """
-
-  @behaviour Phoenix.Socket.Transport
+  alias Phoenix.Socket.{V1, V2, Transport}
 
   def default_config() do
-    [serializer: [{Phoenix.Transports.WebSocketSerializer, "~> 1.0.0"},
-                  {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}],
-     timeout: 60_000,
-     transport_log: false,
-     compress: false]
-  end
-
-  ## Callbacks
-
-  import Plug.Conn, only: [fetch_query_params: 1, send_resp: 3]
-
-  alias Phoenix.Socket.Broadcast
-  alias Phoenix.Socket.Transport
-
-  @doc false
-  def init(conn, {endpoint, handler, transport}) do
-    init(conn, {endpoint, handler, transport, self()})
+    [
+      serializer: [{V1.JSONSerializer, "~> 1.0.0"}, {V2.JSONSerializer, "~> 2.0.0"}],
+      timeout: 60_000,
+      transport_log: false,
+      compress: false
+    ]
   end
-  def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport, pid}) do
-    {_, opts} = handler.__transport__(transport)
-
-    conn =
-      conn
-      |> code_reload(opts, endpoint)
-      |> fetch_query_params()
-      |> Transport.transport_log(opts[:transport_log])
-      |> Transport.force_ssl(handler, endpoint, opts)
-      |> Transport.check_origin(handler, endpoint, opts)
 
-    case conn do
-      %{halted: false} = conn ->
-        params     = conn.params
-        serializer = Keyword.fetch!(opts, :serializer)
-
-        case Transport.connect(endpoint, handler, transport, __MODULE__, serializer, params, pid) do
-          {:ok, socket} ->
-            {:ok, conn, {__MODULE__, {socket, opts}}}
-          :error ->
-            conn = send_resp(conn, 403, "")
-            {:error, conn}
-        end
+  def connect(%{method: "GET"} = conn, endpoint, handler, opts) do
+    conn
+    |> Plug.Conn.fetch_query_params()
+    |> Transport.code_reload(endpoint, opts)
+    |> Transport.transport_log(opts[:transport_log])
+    |> Transport.force_ssl(handler, endpoint, opts)
+    |> Transport.check_origin(handler, endpoint, opts)
+    |> case do
       %{halted: true} = conn ->
         {:error, conn}
-    end
-  end
-
-  def init(conn, _) do
-    conn = send_resp(conn, 400, "")
-    {:error, conn}
-  end
-
-  @doc false
-  def ws_init({socket, config}) do
-    Process.flag(:trap_exit, true)
-    timeout = Keyword.fetch!(config, :timeout)
 
-    if socket.id, do: socket.endpoint.subscribe(socket.id, link: true)
+      %{params: params} = conn ->
+        config = %{endpoint: endpoint, transport: :websocket, options: opts, params: params}
 
-    {:ok, %{socket: socket,
-            channels: %{},
-            channels_inverse: %{},
-            serializer: socket.serializer}, timeout}
-  end
-
-  @doc false
-  def ws_handle(opcode, payload, state) do
-    msg = state.serializer.decode!(payload, opcode: opcode)
-
-    case Transport.dispatch(msg, state.channels, state.socket) do
-      :noreply ->
-        {:ok, state}
-      {:reply, reply_msg} ->
-        encode_reply(reply_msg, state)
-      {:joined, channel_pid, reply_msg} ->
-        encode_reply(reply_msg, put(state, msg.topic, msg.ref, channel_pid))
-      {:error, _reason, error_reply_msg} ->
-        encode_reply(error_reply_msg, state)
-    end
-  end
-
-  @doc false
-  def ws_info({:EXIT, channel_pid, reason}, state) do
-    case Map.get(state.channels_inverse, channel_pid) do
-      nil   -> {:ok, state}
-      {topic, join_ref} ->
-        new_state = delete(state, topic, channel_pid)
-        encode_reply(Transport.on_exit_message(topic, join_ref, reason), new_state)
-    end
-  end
-
-  def ws_info({:graceful_exit, channel_pid, %Phoenix.Socket.Message{} = msg}, state) do
-    new_state = delete(state, msg.topic, channel_pid)
-    encode_reply(msg, new_state)
-  end
-
-  @doc false
-  def ws_info(%Broadcast{event: "disconnect"}, state) do
-    {:shutdown, state}
-  end
-
-  def ws_info({:socket_push, _, _encoded_payload} = msg, state) do
-    format_reply(msg, state)
-  end
-
-  @doc false
-  def ws_info(:garbage_collect, state) do
-    :erlang.garbage_collect(self())
-    {:ok, state}
-  end
-
-  def ws_info(_, state) do
-    {:ok, state}
-  end
-
-  @doc false
-  def ws_terminate(_reason, _state) do
-    :ok
-  end
-
-  @doc false
-  def ws_close(state) do
-    for {pid, _} <- state.channels_inverse do
-      Phoenix.Channel.Server.close(pid)
-    end
-  end
-
-  defp put(state, topic, join_ref, channel_pid) do
-    %{state | channels: Map.put(state.channels, topic, channel_pid),
-              channels_inverse: Map.put(state.channels_inverse, channel_pid, {topic, join_ref})}
-  end
-
-  defp delete(state, topic, channel_pid) do
-    case Map.fetch(state.channels, topic) do
-      {:ok, ^channel_pid} ->
-        %{state | channels: Map.delete(state.channels, topic),
-                  channels_inverse: Map.delete(state.channels_inverse, channel_pid)}
-      {:ok, _newer_pid} ->
-        %{state | channels_inverse: Map.delete(state.channels_inverse, channel_pid)}
+        case handler.connect(config) do
+          {:ok, state} -> {:ok, conn, state}
+          :error -> {:error, Plug.Conn.send_resp(conn, 403, "")}
+        end
     end
   end
 
-  defp encode_reply(reply, state) do
-    format_reply(state.serializer.encode!(reply), state)
-  end
-
-  defp format_reply({:socket_push, encoding, encoded_payload}, state) do
-    {:reply, {encoding, encoded_payload}, state}
-  end
-
-  defp code_reload(conn, opts, endpoint) do
-    reload? = Keyword.get(opts, :code_reloader, endpoint.config(:code_reloader))
-    if reload?, do: Phoenix.CodeReloader.reload!(endpoint)
-
-    conn
+  def connect(conn, _, _, _) do
+    {:error, Plug.Conn.send_resp(conn, 400, "")}
   end
 end
diff --git a/lib/phoenix/transports/websocket_serializer.ex b/lib/phoenix/transports/websocket_serializer.ex
deleted file mode 100644
index c664fdcf0..000000000
--- a/lib/phoenix/transports/websocket_serializer.ex
+++ /dev/null
@@ -1,50 +0,0 @@
-defmodule Phoenix.Transports.WebSocketSerializer do
-  @moduledoc false
-
-  @behaviour Phoenix.Transports.Serializer
-
-  alias Phoenix.Socket.Reply
-  alias Phoenix.Socket.Message
-  alias Phoenix.Socket.Broadcast
-
-  @doc """
-  Translates a `Phoenix.Socket.Broadcast` into a `Phoenix.Socket.Message`.
-  """
-  def fastlane!(%Broadcast{} = msg) do
-    msg = %Message{topic: msg.topic, event: msg.event, payload: msg.payload}
-
-    {:socket_push, :text, encode_v1_fields_only(msg)}
-  end
-
-  @doc """
-  Encodes a `Phoenix.Socket.Message` struct to JSON string.
-  """
-  def encode!(%Reply{} = reply) do
-    msg = %Message{
-      topic: reply.topic,
-      event: "phx_reply",
-      ref: reply.ref,
-      payload: %{status: reply.status, response: reply.payload}
-    }
-
-    {:socket_push, :text, encode_v1_fields_only(msg)}
-  end
-  def encode!(%Message{} = msg) do
-    {:socket_push, :text, encode_v1_fields_only(msg)}
-  end
-
-  @doc """
-  Decodes JSON String into `Phoenix.Socket.Message` struct.
-  """
-  def decode!(message, _opts) do
-    message
-    |> Phoenix.json_library().decode!()
-    |> Phoenix.Socket.Message.from_map!()
-  end
-
-  defp encode_v1_fields_only(%Message{} = msg) do
-    msg
-    |> Map.take([:topic, :event, :payload, :ref])
-    |> Phoenix.json_library().encode_to_iodata!()
-  end
-end
diff --git a/lib/phoenix/view.ex b/lib/phoenix/view.ex
index 4e2dfc1f6..8d181a493 100644
--- a/lib/phoenix/view.ex
+++ b/lib/phoenix/view.ex
@@ -125,9 +125,19 @@ defmodule Phoenix.View do
   the template to also be looked up at `Path.join(root, "user")`.
   """
   defmacro __using__(opts) do
+    %{module: module} = __CALLER__
+
+    if Module.get_attribute(module, :view_resource) do
+      raise ArgumentError,
+            "use Phoenix.View is being called twice in the module #{module}. " <>
+              "Make sure to call it only per module"
+    else
+      view_resource = String.to_atom(Phoenix.Naming.resource_name(module, "View"))
+      Module.put_attribute(module, :view_resource, view_resource)
+    end
+
     quote do
       import Phoenix.View
-
       use Phoenix.Template, Phoenix.View.__template_options__(__MODULE__, unquote(opts))
 
       @before_compile Phoenix.View
@@ -213,7 +223,7 @@ defmodule Phoenix.View do
   To render the template within the layout, simply call `render/3`
   using the `@view_module` and `@view_template` assign:
 
-      <%= render @view_module, @view_template, assigns %>
+      <%= render(@view_module, @view_template, assigns) %>
 
   """
   def render(module, template, assigns) do
diff --git a/mix.exs b/mix.exs
index 410404838..7e09f42a5 100644
--- a/mix.exs
+++ b/mix.exs
@@ -14,19 +14,8 @@ defmodule Phoenix.MixProject do
       preferred_cli_env: [docs: :docs],
       consolidate_protocols: Mix.env != :test,
       xref: [exclude: [Ecto.Type, :ranch, {:cowboy_req, :compact, 1}]],
-
       name: "Phoenix",
-      docs: [
-        source_ref: "v#{@version}",
-        main: "overview",
-        logo: "logo.png",
-        extra_section: "GUIDES",
-        assets: "guides/assets",
-        formatters: ["html", "epub"],
-        groups_for_modules: groups_for_modules(),
-        extras: extras(),
-        groups_for_extras: groups_for_extras()
-      ],
+      docs: docs(),
       aliases: aliases(),
       source_url: "https://github.com/phoenixframework/phoenix",
       homepage_url: "http://www.phoenixframework.org",
@@ -90,6 +79,20 @@ defmodule Phoenix.MixProject do
     ]
   end
 
+  defp docs do
+    [
+      source_ref: "v#{@version}",
+      main: "overview",
+      logo: "logo.png",
+      extra_section: "GUIDES",
+      assets: "guides/assets",
+      formatters: ["html", "epub"],
+      groups_for_modules: groups_for_modules(),
+      extras: extras(),
+      groups_for_extras: groups_for_extras()
+    ]
+  end
+
   defp extras do
     [
       "guides/introduction/overview.md",
@@ -148,11 +151,16 @@ defmodule Phoenix.MixProject do
       "Endpoint And Plugs": [
         Phoenix.CodeReloader,
         Phoenix.Endpoint,
-        Phoenix.Endpoint.CowboyHandler,
-        Phoenix.Endpoint.Handler,
+        Phoenix.Endpoint.CowboyAdapter,
+        Phoenix.Endpoint.Cowboy2Adapter,
         Phoenix.Logger,
       ],
 
+      "Testing": [
+        Phoenix.ChannelTest,
+        Phoenix.ConnTest,
+      ],
+
       "Socket And Transport": [
         Phoenix.Socket,
         Phoenix.Socket.Broadcast,
@@ -169,11 +177,6 @@ defmodule Phoenix.MixProject do
         Phoenix.Template.ExsEngine,
         Phoenix.Template.HTML,
       ],
-
-      "Testing": [
-        Phoenix.ChannelTest,
-        Phoenix.ConnTest,
-      ],
     ]
   end
 
diff --git a/priv/templates/phx.gen.presence/presence.ex b/priv/templates/phx.gen.presence/presence.ex
index 57b90311e..b100c0b53 100644
--- a/priv/templates/phx.gen.presence/presence.ex
+++ b/priv/templates/phx.gen.presence/presence.ex
@@ -19,7 +19,7 @@ defmodule <%= module %> do
         end
 
         def handle_info(:after_join, socket) do
-          push socket, "presence_state", Presence.list(socket)
+          push(socket, "presence_state", Presence.list(socket))
           {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
             online_at: inspect(System.system_time(:seconds))
           })


Test patch:
diff --git a/guides/testing/testing.md b/guides/testing/testing.md
index 36af6a247..82a50223b 100644
--- a/guides/testing/testing.md
+++ b/guides/testing/testing.md
@@ -58,7 +58,7 @@ defmodule HelloWeb.PageControllerTest do
   use HelloWeb.ConnCase
 
   test "GET /", %{conn: conn} do
-    conn = get conn, "/"
+    conn = get(conn, "/")
     assert html_response(conn, 200) =~ "Welcome to Phoenix!"
   end
 end
diff --git a/guides/testing/testing_channels.md b/guides/testing/testing_channels.md
index 2b89dd521..17b947ef1 100644
--- a/guides/testing/testing_channels.md
+++ b/guides/testing/testing_channels.md
@@ -105,7 +105,7 @@ The first test block in our generated channel test looks like:
 
 ```elixir
 test "ping replies with status ok", %{socket: socket} do
-  ref = push socket, "ping", %{"hello" => "there"}
+  ref = push(socket, "ping", %{"hello" => "there"})
   assert_reply ref, :ok, %{"hello" => "there"}
 end
 ```
@@ -130,7 +130,7 @@ In the `test "ping replies with status ok", %{socket: socket} do` line, we see t
 map `%{socket: socket}`. This gives us access to the `socket` in the setup block.
 
 We emulate the client pushing a message to the channel with `push/3`. In the line
-`ref = push socket, "ping", %{"hello" => "there"}`, we push the event `"ping"` with the payload
+`ref = push(socket, "ping", %{"hello" => "there"})`, we push the event `"ping"` with the payload
 `%{"hello" => "there"}` to the channel. This triggers the `handle_in/3` callback we have for the
 `"ping"` event in our channel. Note that we store the `ref` since we need that on the next line for
 asserting the reply. With `assert_reply ref, :ok, %{"hello" => "there"}`, we assert that the
@@ -146,7 +146,7 @@ current topic. This common pattern is simple to express in Phoenix and is one of
 
 ```elixir
 def handle_in("shout", payload, socket) do
-  broadcast socket, "shout", payload
+  broadcast(socket, "shout", payload)
   {:noreply, socket}
 end
 ```
@@ -155,7 +155,7 @@ Its corresponding test looks like:
 
 ```elixir
 test "shout broadcasts to room:lobby", %{socket: socket} do
-  push socket, "shout", %{"hello" => "all"}
+  push(socket, "shout", %{"hello" => "all"})
   assert_broadcast "shout", %{"hello" => "all"}
 end
 ```
@@ -177,7 +177,7 @@ to the client. Unlike the previous tests discussed, we are indirectly testing th
 
 ```elixir
 def handle_out(event, payload, socket) do
-  push socket, event, payload
+  push(socket, event, payload)
   {:noreply, socket}
 end
 ```
@@ -187,7 +187,7 @@ we will need to emulate that in our test. We do that by calling `broadcast_from`
 `broadcast_from!`. Both serve the same purpose with the only difference of `broadcast_from!`
 raising an error when broadcast fails.
 
-The line `broadcast_from! socket, "broadcast", %{"some" => "data"}` will trigger our `handle_out/3`
+The line `broadcast_from!(socket, "broadcast", %{"some" => "data"})` will trigger our `handle_out/3`
 callback above which pushes the same event and payload back to the client. To test this, we do
 `assert_push "broadcast", %{"some" => "data"}`.
 
diff --git a/lib/phoenix/test/channel_test.ex b/lib/phoenix/test/channel_test.ex
index 477685104..665223a33 100644
--- a/lib/phoenix/test/channel_test.ex
+++ b/lib/phoenix/test/channel_test.ex
@@ -40,13 +40,13 @@ defmodule Phoenix.ChannelTest do
   For example, we can use the `push/3` function in the test
   to push messages to the channel (it will invoke `handle_in/3`):
 
-      push socket, "my_event", %{"some" => "data"}
+      push(socket, "my_event", %{"some" => "data"})
 
   Similarly, we can broadcast messages from the test itself
   on the topic that both test and channel are subscribed to,
   triggering `handle_out/3` on the channel:
 
-      broadcast_from socket, "my_event", %{"some" => "data"}
+      broadcast_from(socket, "my_event", %{"some" => "data"})
 
   > Note only `broadcast_from/3` and `broadcast_from!/3` are
   available in tests to avoid broadcast messages to be resent
@@ -66,7 +66,7 @@ defmodule Phoenix.ChannelTest do
   a reference is returned. We can use this reference to
   assert a particular reply was sent from the server:
 
-      ref = push socket, "counter", %{}
+      ref = push(socket, "counter", %{})
       assert_reply ref, :ok, %{"counter" => 1}
 
   ## Checking side-effects
@@ -85,7 +85,7 @@ defmodule Phoenix.ChannelTest do
   Because the whole communication is asynchronous, the
   following test would be very brittle:
 
-      push socket, "publish", %{"id" => 3}
+      push(socket, "publish", %{"id" => 3})
       assert Repo.get_by(Post, id: 3, published: true)
 
   The issue is that we have no guarantees the channel has
@@ -101,7 +101,7 @@ defmodule Phoenix.ChannelTest do
 
   Then expect them in the test:
 
-      ref = push socket, "publish", %{"id" => 3}
+      ref = push(socket, "publish", %{"id" => 3})
       assert_reply ref, :ok
       assert Repo.get_by(Post, id: 3, published: true)
 
@@ -152,14 +152,11 @@ defmodule Phoenix.ChannelTest do
   """
 
   alias Phoenix.Socket
-  alias Phoenix.Socket.Message
-  alias Phoenix.Socket.Broadcast
-  alias Phoenix.Socket.Reply
-  alias Phoenix.Socket.Transport
+  alias Phoenix.Socket.{Broadcast, Message, Reply}
   alias Phoenix.Channel.Server
 
   defmodule NoopSerializer do
-    @behaviour Phoenix.Transports.Serializer
+    @behaviour Phoenix.Socket.Serializer
     @moduledoc false
 
     def fastlane!(%Broadcast{} = msg) do
@@ -183,54 +180,69 @@ defmodule Phoenix.ChannelTest do
   end
 
   @doc """
-  Builds a socket.
+  Builds a socket for the given `socket` module.
 
   The socket is then used to subscribe and join channels.
   Use this function when you want to create a blank socket
   to pass to functions like `UserSocket.connect/2`.
 
-  Otherwise, use `socket/2` if you want to build a socket with
+  Otherwise, use `socket/3` if you want to build a socket with
   id and assigns.
 
   The socket endpoint is read from the `@endpoint` variable.
   """
-  defmacro socket() do
-    if endpoint = Module.get_attribute(__CALLER__.module, :endpoint) do
-      quote do
-        %Socket{serializer: NoopSerializer,
-                transport_pid: self(),
-                endpoint: unquote(endpoint),
-                pubsub_server: unquote(endpoint).__pubsub_server__(),
-                transport: unquote(__MODULE__),
-                transport_name: :channel_test}
-      end
-    else
-      raise "module attribute @endpoint not set for socket/0"
-    end
+  defmacro socket(socket) do
+    build_socket(socket, nil, [], __CALLER__)
   end
 
   @doc """
-  Builds a socket with given id and assigns.
+  Builds a socket for the given `socket` module with given id and assigns.
 
   The socket endpoint is read from the `@endpoint` variable.
   """
-  defmacro socket(id, assigns) do
-    if endpoint = Module.get_attribute(__CALLER__.module, :endpoint) do
+  defmacro socket(socket, id, assigns) do
+    build_socket(socket, id, assigns, __CALLER__)
+  end
+
+  defp build_socket(socket, id, assigns, caller) do
+    if endpoint = Module.get_attribute(caller.module, :endpoint) do
       quote do
-        %Socket{serializer: NoopSerializer,
-                transport_pid: self(),
-                endpoint: unquote(endpoint),
-                pubsub_server: unquote(endpoint).__pubsub_server__(),
-                id: unquote(id),
-                assigns: Enum.into(unquote(assigns), %{}),
-                transport: unquote(__MODULE__),
-                transport_name: :channel_test}
+        %Socket{
+          assigns: Enum.into(unquote(assigns), %{}),
+          endpoint: unquote(endpoint),
+          handler: unquote(socket || first_socket!(endpoint)),
+          id: unquote(id),
+          pubsub_server: unquote(endpoint).__pubsub_server__(),
+          serializer: NoopSerializer,
+          transport: :channel_test,
+          transport_pid: self()
+        }
       end
     else
       raise "module attribute @endpoint not set for socket/2"
     end
   end
 
+  @doc false
+  defmacro socket() do
+    IO.warn "Phoenix.ChannelTest.socket/0 is deprecated, please call socket/1 instead"
+    build_socket(nil, nil, [], __CALLER__)
+  end
+
+  @doc false
+  defmacro socket(id, assigns) do
+    IO.warn "Phoenix.ChannelTest.socket/2 is deprecated, please call socket/3 instead"
+    build_socket(nil, id, assigns, __CALLER__)
+  end
+
+  # TODO v2: Remove this alongside the deprecations above.
+  defp first_socket!(endpoint) do
+    case endpoint.__sockets__ do
+      [] -> raise ArgumentError, "#{inspect endpoint} has no socket declaration"
+      [{_, socket} | _] -> socket
+    end
+  end
+
   @doc """
   Initiates a transport connection for the socket handler.
 
@@ -249,32 +261,29 @@ defmodule Phoenix.ChannelTest do
 
   @doc false
   def __connect__(endpoint, handler, params) do
-    endpoint
-    |> Transport.connect(handler, :channel_test, __MODULE__, NoopSerializer, __stringify__(params))
-    |> subscribe_to_socket_id(endpoint, handler)
-  end
-  defp subscribe_to_socket_id({:ok, socket}, endpoint, handler) do
-    if topic = handler.id(socket) do
-      :ok = endpoint.subscribe(topic)
-    end
-    {:ok, socket}
+    map = %{
+      endpoint: endpoint,
+      transport: :channel_test,
+      options: [serializer: [{NoopSerializer, "~> 1.0.0"}]],
+      params: __stringify__(params)
+    }
+
+    with {:ok, state} <- handler.connect(map),
+         {:ok, {_, socket}} = handler.init(state),
+         do: {:ok, socket}
   end
-  defp subscribe_to_socket_id(error, _endpoint, _handler), do: error
 
   @doc "See `subscribe_and_join!/4`."
   def subscribe_and_join!(%Socket{} = socket, topic) when is_binary(topic) do
-    subscribe_and_join!(socket, topic, %{})
+    subscribe_and_join!(socket, nil, topic, %{})
   end
+
   @doc "See `subscribe_and_join!/4`."
   def subscribe_and_join!(%Socket{} = socket, topic, payload)
       when is_binary(topic) and is_map(payload) do
-
-    {channel, opts} = match_topic_to_channel!(socket, topic)
-
-    socket
-    |> with_opts(opts)
-    |> subscribe_and_join!(channel, topic, payload)
+    subscribe_and_join!(socket, nil, topic, payload)
   end
+
   @doc """
   Same as `subscribe_and_join/4`, but returns either the socket
   or throws an error.
@@ -292,18 +301,15 @@ defmodule Phoenix.ChannelTest do
 
   @doc "See `subscribe_and_join/4`."
   def subscribe_and_join(%Socket{} = socket, topic) when is_binary(topic) do
-    subscribe_and_join(socket, topic, %{})
+    subscribe_and_join(socket, nil, topic, %{})
   end
+
   @doc "See `subscribe_and_join/4`."
   def subscribe_and_join(%Socket{} = socket, topic, payload)
       when is_binary(topic) and is_map(payload) do
-
-    {channel, opts} = match_topic_to_channel!(socket, topic)
-
-    socket
-    |> with_opts(opts)
-    |> subscribe_and_join(channel, topic, payload)
+    subscribe_and_join(socket, nil, topic, payload)
   end
+
   @doc """
   Subscribes to the given topic and joins the channel
   under the given topic and payload.
@@ -328,17 +334,12 @@ defmodule Phoenix.ChannelTest do
 
   @doc "See `join/4`."
   def join(%Socket{} = socket, topic) when is_binary(topic) do
-    join(socket, topic, %{})
+    join(socket, nil, topic, %{})
   end
-  @doc "See `join/4`."
-  def join(%Socket{} = socket, topic, payload)
-      when is_binary(topic) and is_map(payload) do
 
-    {channel, opts} = match_topic_to_channel!(socket, topic)
-
-    socket
-    |> with_opts(opts)
-    |> join(channel, topic, payload)
+  @doc "See `join/4`."
+  def join(%Socket{} = socket, topic, payload) when is_binary(topic) and is_map(payload) do
+    join(socket, nil, topic, payload)
   end
 
   @doc """
@@ -351,12 +352,23 @@ defmodule Phoenix.ChannelTest do
   """
   def join(%Socket{} = socket, channel, topic, payload \\ %{})
       when is_atom(channel) and is_binary(topic) and is_map(payload) do
+    message = %Message{
+      event: "phx_join",
+      payload: payload,
+      topic: topic,
+      ref: System.unique_integer([:positive])
+    }
+
+    {channel, opts} =
+      if channel do
+        {channel, []}
+      else
+        match_topic_to_channel!(socket, topic)
+      end
 
-    ref = System.unique_integer([:positive])
-    socket = Transport.build_channel_socket(socket, channel, topic, ref, [])
-
-    case Server.join(socket, payload) do
+    case Server.join(socket, channel, message, opts) do
       {:ok, reply, pid} ->
+        Process.link(pid)
         {:ok, reply, Server.socket(pid)}
       {:error, _} = error ->
         error
@@ -370,7 +382,7 @@ defmodule Phoenix.ChannelTest do
 
   ## Examples
 
-      iex> push socket, "new_message", %{id: 1, content: "hello"}
+      iex> push(socket, "new_message", %{id: 1, content: "hello"})
       reference
 
   """
@@ -391,13 +403,13 @@ defmodule Phoenix.ChannelTest do
   end
 
   @doc """
-  Emulates the client closing the channel.
+  Emulates the client closing the socket.
 
-  Closing channels is synchronous and has a default timeout
+  Closing socket is synchronous and has a default timeout
   of 5000 milliseconds.
   """
   def close(socket, timeout \\ 5000) do
-    Server.close(socket.channel_pid, timeout)
+    Server.close([socket.channel_pid], timeout)
   end
 
   @doc """
@@ -408,7 +420,7 @@ defmodule Phoenix.ChannelTest do
 
   ## Examples
 
-      iex> broadcast_from socket, "new_message", %{id: 1, content: "hello"}
+      iex> broadcast_from(socket, "new_message", %{id: 1, content: "hello"})
       :ok
 
   """
@@ -490,7 +502,7 @@ defmodule Phoenix.ChannelTest do
 
   Notice status and payload are patterns. This means one can write:
 
-      ref = push channel, "some_event"
+      ref = push(channel, "some_event")
       assert_reply ref, :ok, %{"data" => _}
 
   In the assertion above, we don't particularly care about
@@ -601,8 +613,4 @@ defmodule Phoenix.ChannelTest do
 
   defp stringify_kv({k, v}),
     do: {to_string(k), __stringify__(v)}
-
-  defp with_opts(%Socket{} = socket, opts) do
-    %Socket{socket | assigns: Map.merge(socket.assigns, opts[:assigns] || %{})}
-  end
 end
diff --git a/lib/phoenix/test/conn_test.ex b/lib/phoenix/test/conn_test.ex
index 636e1f250..a5ea4dedb 100644
--- a/lib/phoenix/test/conn_test.ex
+++ b/lib/phoenix/test/conn_test.ex
@@ -14,10 +14,10 @@ defmodule Phoenix.ConnTest do
   the preferred way to test anything that your router dispatches
   to.
 
-      conn = get build_conn(), "/"
+      conn = get(build_conn(), "/")
       assert conn.resp_body =~ "Welcome!"
 
-      conn = post build_conn(), "/login", [username: "john", password: "doe"]
+      conn = post(build_conn(), "/login", [username: "john", password: "doe"])
       assert conn.resp_body =~ "Logged in!"
 
   As in your application, the connection is also the main abstraction
@@ -46,7 +46,7 @@ defmodule Phoenix.ConnTest do
   For such cases, just pass an atom representing the action
   to dispatch:
 
-      conn = get build_conn(), :index
+      conn = get(build_conn(), :index)
       assert conn.resp_body =~ "Welcome!"
 
   ## Views testing
@@ -56,8 +56,8 @@ defmodule Phoenix.ConnTest do
   For such cases, a connection can be created using the
   `conn/3` helper:
 
-      MyApp.UserView.render "hello.html",
-                             conn: conn(:get, "/")
+      MyApp.UserView.render("hello.html",
+                             conn: conn(:get, "/"))
 
   ## Recycling
 
@@ -79,10 +79,10 @@ defmodule Phoenix.ConnTest do
   before the next dispatch:
 
       # No recycling as the connection is fresh
-      conn = get build_conn(), "/"
+      conn = get(build_conn(), "/")
 
       # The connection is recycled, creating a new one behind the scenes
-      conn = post conn, "/login"
+      conn = post(conn, "/login")
 
       # We can also recycle manually in case we want custom headers
       conn =
@@ -91,7 +91,7 @@ defmodule Phoenix.ConnTest do
         |> put_req_header("x-special", "nice")
 
       # No recycling as we did it explicitly
-      conn = delete conn, "/logout"
+      conn = delete(conn, "/logout")
 
   Recycling also recycles the "accept" header.
   """
@@ -120,10 +120,7 @@ defmodule Phoenix.ConnTest do
   """
   @spec conn() :: Conn.t
   def conn() do
-    IO.write :stderr, """
-    warning: using conn/0 to build a connection is deprecated. Use build_conn/0 instead.
-    #{Exception.format_stacktrace}
-    """
+    IO.warn "using conn/0 to build a connection is deprecated. Use build_conn/0 instead"
     build_conn()
   end
 
@@ -190,8 +187,8 @@ defmodule Phoenix.ConnTest do
   This function, as well as `get/3`, `post/3` and friends, accepts the
   request body or parameters as last argument:
 
-        get build_conn(), "/", some: "param"
-        get build_conn(), "/", "some=param&url=encoded"
+        get(build_conn(), "/", some: "param")
+        get(build_conn(), "/", "some=param&url=encoded")
 
   The allowed values are:
 
@@ -338,7 +335,7 @@ defmodule Phoenix.ConnTest do
 
   ## Examples
 
-      conn = get build_conn(), "/"
+      conn = get(build_conn(), "/")
       assert response(conn, 200) =~ "hello world"
 
   """
@@ -348,7 +345,7 @@ defmodule Phoenix.ConnTest do
     expected connection to have a response but no response was set/sent.
     Please verify that you assign to "conn" after a request:
 
-        conn = get conn, "/"
+        conn = get(conn, "/")
         assert html_response(conn) =~ "Hello"
     """
   end
@@ -584,11 +581,11 @@ defmodule Phoenix.ConnTest do
   ## Examples
 
       assert_error_sent :not_found, fn ->
-        get build_conn(), "/users/not-found"
+        get(build_conn(), "/users/not-found")
       end
 
       response = assert_error_sent 404, fn ->
-        get build_conn(), "/users/not-found"
+        get(build_conn(), "/users/not-found")
       end
       assert {404, [_h | _t], "Page not found"} = response
   """
diff --git a/test/mix/tasks/phx.new_test.exs b/test/mix/tasks/phx.new_test.exs
index 97cf197a8..a13003586 100644
--- a/test/mix/tasks/phx.new_test.exs
+++ b/test/mix/tasks/phx.new_test.exs
@@ -6,6 +6,13 @@ for pattern <- ["../../../installer/lib/phx_new/project.ex",
     file <- [_|_] = Path.wildcard(Path.expand(pattern, __DIR__)),
     do: Code.require_file(file, __DIR__)
 
+# Define a fake live reload socket.
+defmodule Phoenix.LiveReloader.Socket do
+  def child_spec(_) do
+    Supervisor.Spec.worker(Task, [fn -> :ok end], restart: :temporary)
+  end
+end
+
 # Here we test the installer is up to date.
 defmodule Mix.Tasks.Phx.NewTest do
   use ExUnit.Case
diff --git a/test/phoenix/channel_test.exs b/test/phoenix/channel_test.exs
index c18294779..f995167cb 100644
--- a/test/phoenix/channel_test.exs
+++ b/test/phoenix/channel_test.exs
@@ -12,47 +12,87 @@ defmodule Phoenix.Channel.ChannelTest do
   test "broadcasts from self" do
     Phoenix.PubSub.subscribe(@pubsub, "sometopic")
 
-    socket = %Phoenix.Socket{pubsub_server: @pubsub, topic: "sometopic",
-                             channel_pid: self(), joined: true}
+    socket = %Phoenix.Socket{
+      pubsub_server: @pubsub,
+      topic: "sometopic",
+      channel_pid: self(),
+      joined: true
+    }
 
     broadcast_from(socket, "event1", %{key: :val})
+
     refute_received %Phoenix.Socket.Broadcast{
-      event: "event1", payload: %{key: :val}, topic: "sometopic"}
+      event: "event1",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
 
     broadcast_from!(socket, "event2", %{key: :val})
+
     refute_received %Phoenix.Socket.Broadcast{
-      event: "event2", payload: %{key: :val}, topic: "sometopic"}
+      event: "event2",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
 
     broadcast(socket, "event3", %{key: :val})
+
     assert_receive %Phoenix.Socket.Broadcast{
-      event: "event3", payload: %{key: :val}, topic: "sometopic"}
+      event: "event3",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
 
     broadcast!(socket, "event4", %{key: :val})
+
     assert_receive %Phoenix.Socket.Broadcast{
-      event: "event4", payload: %{key: :val}, topic: "sometopic"}
+      event: "event4",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
   end
 
   test "broadcasts from other" do
     Phoenix.PubSub.subscribe(@pubsub, "sometopic")
 
-    socket = %Phoenix.Socket{pubsub_server: @pubsub, topic: "sometopic",
-                             channel_pid: spawn_link(fn -> :ok end), joined: true}
+    socket = %Phoenix.Socket{
+      pubsub_server: @pubsub,
+      topic: "sometopic",
+      channel_pid: spawn_link(fn -> :ok end),
+      joined: true
+    }
 
     broadcast_from(socket, "event1", %{key: :val})
+
     assert_receive %Phoenix.Socket.Broadcast{
-      event: "event1", payload: %{key: :val}, topic: "sometopic"}
+      event: "event1",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
 
     broadcast_from!(socket, "event2", %{key: :val})
+
     assert_receive %Phoenix.Socket.Broadcast{
-      event: "event2", payload: %{key: :val}, topic: "sometopic"}
+      event: "event2",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
 
     broadcast(socket, "event3", %{key: :val})
+
     assert_receive %Phoenix.Socket.Broadcast{
-      event: "event3", payload: %{key: :val}, topic: "sometopic"}
+      event: "event3",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
 
     broadcast!(socket, "event4", %{key: :val})
+
     assert_receive %Phoenix.Socket.Broadcast{
-      event: "event4", payload: %{key: :val}, topic: "sometopic"}
+      event: "event4",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
   end
 
   test "broadcasts when not joined" do
@@ -76,11 +116,20 @@ defmodule Phoenix.Channel.ChannelTest do
   end
 
   test "pushing to transport" do
-    socket = %Phoenix.Socket{serializer: Phoenix.ChannelTest.NoopSerializer,
-                             topic: "sometopic", transport_pid: self(), joined: true}
+    socket = %Phoenix.Socket{
+      serializer: Phoenix.ChannelTest.NoopSerializer,
+      topic: "sometopic",
+      transport_pid: self(),
+      joined: true
+    }
+
     push(socket, "event1", %{key: :val})
+
     assert_receive %Phoenix.Socket.Message{
-      event: "event1", payload: %{key: :val}, topic: "sometopic"}
+      event: "event1",
+      payload: %{key: :val},
+      topic: "sometopic"
+    }
   end
 
   test "pushing when not joined" do
@@ -92,27 +141,50 @@ defmodule Phoenix.Channel.ChannelTest do
   end
 
   test "replying to transport" do
-    socket = %Phoenix.Socket{serializer: Phoenix.ChannelTest.NoopSerializer, ref: "123",
-                             topic: "sometopic", transport_pid: self(), joined: true,}
+    socket = %Phoenix.Socket{
+      serializer: Phoenix.ChannelTest.NoopSerializer,
+      ref: "123",
+      topic: "sometopic",
+      transport_pid: self(),
+      joined: true
+    }
+
     ref = socket_ref(socket)
     reply(ref, {:ok, %{key: :val}})
+
     assert_receive %Phoenix.Socket.Reply{
-      payload: %{key: :val}, ref: "123", status: :ok, topic: "sometopic"}
+      payload: %{key: :val},
+      ref: "123",
+      status: :ok,
+      topic: "sometopic"
+    }
   end
 
   test "replying just status to transport" do
-    socket = %Phoenix.Socket{serializer: Phoenix.ChannelTest.NoopSerializer, ref: "123",
-                             topic: "sometopic", transport_pid: self(), joined: true,}
+    socket = %Phoenix.Socket{
+      serializer: Phoenix.ChannelTest.NoopSerializer,
+      ref: "123",
+      topic: "sometopic",
+      transport_pid: self(),
+      joined: true
+    }
+
     ref = socket_ref(socket)
     reply(ref, :ok)
+
     assert_receive %Phoenix.Socket.Reply{
-      payload: %{}, ref: "123", status: :ok, topic: "sometopic"}
+      payload: %{},
+      ref: "123",
+      status: :ok,
+      topic: "sometopic"
+    }
   end
 
   test "socket_ref raises ArgumentError when socket is not joined or has no ref" do
     assert_raise ArgumentError, ~r"join", fn ->
       socket_ref(%Phoenix.Socket{joined: false})
     end
+
     assert_raise ArgumentError, ~r"ref", fn ->
       socket_ref(%Phoenix.Socket{joined: true})
     end
diff --git a/test/phoenix/controller/render_test.exs b/test/phoenix/controller/render_test.exs
index ec83d3d95..ba3e84ac8 100644
--- a/test/phoenix/controller/render_test.exs
+++ b/test/phoenix/controller/render_test.exs
@@ -146,16 +146,6 @@ defmodule Phoenix.Controller.RenderTest do
     assert conn.resp_body == ~s({"foo":"bar"})
   end
 
-  test "render/4 renders with View and Template" do
-    assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
-      conn = put_format(conn(), "html")
-      conn = put_in conn.private[:phoenix_action], :index
-      conn = put_view(conn, nil)
-      conn = render(conn, MyApp.UserView, "index.html", title: "Hello")
-      assert conn.resp_body == "Hello"
-    end) =~ "render/4 with a view is deprecated"
-  end
-
   test "errors when rendering without format" do
     assert_raise RuntimeError, ~r/cannot render template :index because conn.params/, fn ->
       render(conn(), :index)
diff --git a/test/phoenix/endpoint/render_errors_test.exs b/test/phoenix/endpoint/render_errors_test.exs
index 0883f6d6a..5e1cd05ad 100644
--- a/test/phoenix/endpoint/render_errors_test.exs
+++ b/test/phoenix/endpoint/render_errors_test.exs
@@ -1,8 +1,8 @@
 defmodule Phoenix.Endpoint.RenderErrorsTest do
   use ExUnit.Case, async: true
   use RouterHelper
-  import ExUnit.CaptureLog
 
+  import ExUnit.CaptureLog
   view = __MODULE__
 
   def render("app.html", %{view_template: view_template} = assigns) do
@@ -230,14 +230,6 @@ defmodule Phoenix.Endpoint.RenderErrorsTest do
     end) =~ "Could not render errors due to unknown format \"unknown\""
   end
 
-  test "does not capture warning when format does match ErrorView" do
-    assert capture_log(fn ->
-      conn(:get, "/")
-      |> put_req_header("accept", "text/html")
-      |> render([], fn -> throw :hello end)
-    end) == ""
-  end
-
   test "exception page for NoRouteError with plug_status 404" do
     conn = render(conn(:get, "/"), [], fn ->
       raise Phoenix.Router.NoRouteError, conn: conn(:get, "/"), router: nil, plug_status: 404
diff --git a/test/phoenix/integration/endpoint_test.exs b/test/phoenix/integration/endpoint_test.exs
index 2397d9832..c89e3f667 100644
--- a/test/phoenix/integration/endpoint_test.exs
+++ b/test/phoenix/integration/endpoint_test.exs
@@ -1,14 +1,12 @@
 Code.require_file "../../support/http_client.exs", __DIR__
 
 defmodule Phoenix.Integration.EndpointTest do
-  # Cannot run async because of serve endpoints
   use ExUnit.Case
   import ExUnit.CaptureLog
 
   alias Phoenix.Integration.AdapterTest.ProdEndpoint
   alias Phoenix.Integration.AdapterTest.DevEndpoint
   alias Phoenix.Integration.AdapterTest.ProdInet6Endpoint
-  alias Phoenix.Integration.AdapterTest.InvalidHandlerEndpoint
 
   Application.put_env(:endpoint_int, ProdEndpoint,
     http: [port: "4807"], url: [host: "example.com"], server: true,
@@ -18,9 +16,6 @@ defmodule Phoenix.Integration.EndpointTest do
   Application.put_env(:endpoint_int, ProdInet6Endpoint,
     http: [{:port, "4809"}, :inet6],
     url: [host: "example.com"], server: true)
-  Application.put_env(:endpoint_int, InvalidHandlerEndpoint,
-    http: [{:port, "4810"}, :inet6], handler: Phoenix.Endpoint.CowboyHandler,
-    url: [host: "example.com"], server: true)
 
   defmodule Router do
     @moduledoc """
@@ -78,7 +73,7 @@ defmodule Phoenix.Integration.EndpointTest do
     end
   end
 
-  for mod <- [ProdEndpoint, DevEndpoint, ProdInet6Endpoint, InvalidHandlerEndpoint] do
+  for mod <- [ProdEndpoint, DevEndpoint, ProdInet6Endpoint] do
     defmodule mod do
       use Phoenix.Endpoint, otp_app: :endpoint_int
       @before_compile Wrapper
@@ -126,15 +121,14 @@ defmodule Phoenix.Integration.EndpointTest do
         {:ok, resp} = HTTPClient.request(:get, "http://127.0.0.1:#{@prod}/oops", %{})
         assert resp.status == 500
         assert resp.body == "500.html from Phoenix.ErrorView"
-      end) =~ "** (RuntimeError) oops"
 
-      assert capture_log(fn ->
         {:ok, resp} = HTTPClient.request(:get, "http://127.0.0.1:#{@prod}/router/oops", %{})
         assert resp.status == 500
         assert resp.body == "500.html from Phoenix.ErrorView"
+
+        Supervisor.stop(ProdEndpoint)
       end) =~ "** (RuntimeError) oops"
 
-      Supervisor.stop(ProdEndpoint)
       {:error, _reason} = HTTPClient.request(:get, "http://127.0.0.1:#{@prod}", %{})
     end
   end
@@ -161,15 +155,14 @@ defmodule Phoenix.Integration.EndpointTest do
         {:ok, resp} = HTTPClient.request(:get, "http://127.0.0.1:#{@dev}/oops", %{})
         assert resp.status == 500
         assert resp.body =~ "RuntimeError at GET /oops"
-      end) =~ "** (RuntimeError) oops"
 
-      assert capture_log(fn ->
         {:ok, resp} = HTTPClient.request(:get, "http://127.0.0.1:#{@dev}/router/oops", %{})
         assert resp.status == 500
         assert resp.body =~ "RuntimeError at GET /router/oops"
+
+        Supervisor.stop(DevEndpoint)
       end) =~ "** (RuntimeError) oops"
 
-      Supervisor.stop(DevEndpoint)
       {:error, _reason} = HTTPClient.request(:get, "http://127.0.0.1:#{@dev}", %{})
     end
   end
diff --git a/test/phoenix/integration/long_poll_test.exs b/test/phoenix/integration/long_poll_test.exs
index a09178084..d32d05830 100644
--- a/test/phoenix/integration/long_poll_test.exs
+++ b/test/phoenix/integration/long_poll_test.exs
@@ -6,8 +6,7 @@ defmodule Phoenix.Integration.LongPollTest do
   import ExUnit.CaptureLog
 
   alias Phoenix.Integration.HTTPClient
-  alias Phoenix.Socket.{V2, Broadcast}
-  alias Phoenix.Transports.LongPollSerializer
+  alias Phoenix.Socket.{Broadcast, Message, V1, V2}
   alias Phoenix.PubSub.Local
   alias __MODULE__.Endpoint
 
@@ -15,16 +14,9 @@ defmodule Phoenix.Integration.LongPollTest do
   @port 5808
   @pool_size 1
 
-  handler =
-    case Application.spec(:cowboy, :vsn) do
-      [?2 | _] -> Phoenix.Endpoint.Cowboy2Handler
-      _ -> Phoenix.Endpoint.CowboyHandler
-    end
-
   Application.put_env(:phoenix, Endpoint, [
     https: false,
     http: [port: @port],
-    handler: handler,
     secret_key_base: String.duplicate("abcdefgh", 8),
     server: true,
     pubsub: [adapter: Phoenix.PubSub.PG2, name: __MODULE__, pool_size: @pool_size]
@@ -67,39 +59,12 @@ defmodule Phoenix.Integration.LongPollTest do
 
     channel "room:*", RoomChannel
 
-    transport :longpoll, Phoenix.Transports.LongPoll,
-      window_ms: 200,
-      pubsub_timeout_ms: 200,
-      check_origin: ["//example.com"]
-
-    def connect(%{"reject" => "true"}, _socket) do
-      :error
-    end
-    def connect(params, socket) do
-      Logger.disable(self())
-      {:ok, assign(socket, :user_id, params["user_id"])}
-    end
-
-    def id(socket) do
-      if id = socket.assigns.user_id, do: "user_sockets:#{id}"
-    end
-  end
-
-  defmodule LoggingSocket do
-    use Phoenix.Socket
-
-    channel "room:*", RoomChannel
-
-    transport :longpoll, Phoenix.Transports.LongPoll,
-      window_ms: 200,
-      pubsub_timeout_ms: 200,
-      check_origin: ["//example.com"]
-
     def connect(%{"reject" => "true"}, _socket) do
       :error
     end
 
     def connect(params, socket) do
+      unless params["logging"] == "enabled", do: Logger.disable(self())
       {:ok, assign(socket, :user_id, params["user_id"])}
     end
 
@@ -111,9 +76,11 @@ defmodule Phoenix.Integration.LongPollTest do
   defmodule Endpoint do
     use Phoenix.Endpoint, otp_app: :phoenix
 
-    socket "/ws", UserSocket
-    socket "/ws/admin", UserSocket
-    socket "/ws/logging", LoggingSocket
+    socket "/ws", UserSocket,
+      longpoll: [window_ms: 200, pubsub_timeout_ms: 200, check_origin: ["//example.com"]]
+
+    socket "/ws/admin", UserSocket,
+      longpoll: [window_ms: 200, pubsub_timeout_ms: 200, check_origin: ["//example.com"]]
   end
 
   setup_all do
@@ -122,10 +89,10 @@ defmodule Phoenix.Integration.LongPollTest do
   end
 
   setup config do
-    supervisor = Module.concat(Endpoint, "LongPoll.Supervisor")
-    for {_, pid, _, _} <- Supervisor.which_children(supervisor) do
-      Supervisor.terminate_child(supervisor, pid)
+    for {_, pid, _, _} <- Supervisor.which_children(Phoenix.Transports.LongPoll.Supervisor) do
+      Supervisor.terminate_child(Phoenix.Transports.LongPoll.Supervisor, pid)
     end
+
     {:ok, topic: "room:" <> to_string(config.test)}
   end
 
@@ -140,49 +107,39 @@ defmodule Phoenix.Integration.LongPollTest do
   Returns a response with body decoded into JSON map.
   """
   def poll(method, path, vsn, params, json \\ nil, headers \\ %{}) do
+    serializer = serializer(vsn)
     headers = Map.merge(%{"content-type" => "application/json"}, headers)
-    body = encode(vsn, json)
-    query_str = params |> Map.put("vsn", vsn) |> URI.encode_query()
-    url = "http://127.0.0.1:#{@port}#{path}/longpoll?" <> query_str
-
+    body = encode(serializer, json)
+    query_string = params |> Map.put("vsn", vsn) |> URI.encode_query()
+    url = "http://127.0.0.1:#{@port}#{path}/longpoll?" <> query_string
     {:ok, resp} = HTTPClient.request(method, url, headers, body)
-
-    decode_body(vsn, resp)
+    decode_body(serializer, resp)
   end
-  defp decode_body(_vsn, %{body: ""} = resp), do: resp
-  defp decode_body("1." <> _, %{} = resp) do
-    put_in(resp, [:body], Phoenix.json_library().decode!(resp.body))
-  end
-  defp decode_body("2." <> _, %{} = resp) do
+
+  defp serializer("2." <> _), do: V2.JSONSerializer
+  defp serializer(_), do: V1.JSONSerializer
+
+  defp decode_body(serializer, %{} = resp) do
     resp
     |> put_in([:body], Phoenix.json_library().decode!(resp.body))
-    |> update_in([:body, "messages"], fn
-      nil -> []
-      messages ->
-        for msg <- messages do
-          msg
-          |> V2.JSONSerializer.decode!([])
-          |> stringify()
-        end
+    |> update_in([:body, "messages"], fn messages ->
+      for msg <- messages || [] do
+        msg
+        |> Phoenix.json_library().encode!()
+        |> serializer.decode!([])
+      end
     end)
   end
-  defp decode_body(_invalid, %{} = resp) do
-    put_in(resp, [:body], Phoenix.json_library().decode!(resp.body))
-  end
-  def stringify(struct) do
-    struct
-    |> Map.from_struct()
-    |> Phoenix.json_library().encode!()
-    |> Phoenix.json_library().decode!()
-  end
 
   defp encode(_vsn, nil), do: ""
-  defp encode("1." <> _ = _vsn, map), do: Phoenix.json_library().encode!(map)
-  defp encode("2." <> _ = _vsn, map) do
+
+  defp encode(V2.JSONSerializer, map) do
     Phoenix.json_library().encode!(
-      [map["join_ref"], map["ref"], map["topic"], map["event"], map["payload"]])
+      [map["join_ref"], map["ref"], map["topic"], map["event"], map["payload"]]
+    )
   end
-  defp encode(_, map), do: encode("1.0.0", map)
+
+  defp encode(V1.JSONSerializer, map), do: Phoenix.json_library().encode!(map)
 
   @doc """
   Joins a long poll socket.
@@ -193,32 +150,34 @@ defmodule Phoenix.Integration.LongPollTest do
   process. If the mode is pubsub, the session will use the
   pubsub system.
   """
-  def join(path, topic, vsn, mode \\ :local, payload \\ %{})
+  def join(path, topic, vsn, mode \\ :local, payload \\ %{}, params \\ %{})
 
-  def join(path, topic, vsn, :local, payload) do
-    resp = poll :get, path, vsn, %{}, %{}
+  def join(path, topic, vsn, :local, payload, params) do
+    resp = poll :get, path, vsn, params, %{}
     assert resp.body["token"]
     assert resp.body["status"] == 410
     assert resp.status == 200
 
-    session = Map.take(resp.body, ["token"])
+    session = resp.body |> Map.take(["token"]) |> Map.merge(params)
 
     resp = poll :post, path, vsn, session, %{
       "topic" => topic,
       "event" => "phx_join",
-      "ref" => "123",
-      "join_ref" => "123",
+      "ref" => "1",
+      "join_ref" => "1",
       "payload" => payload
     }
-    assert resp.body["status"] == 200
 
+    assert resp.body["status"] == 200
     session
   end
 
-  def join(path, topic, vsn, :pubsub, payload) do
-    session = join(path, topic, vsn, :local, payload)
+  def join(path, topic, vsn, :pubsub, payload, params) do
+    session = join(path, topic, vsn, :local, payload, params)
+
     {:ok, {:v1, _id, pid, topic}} =
       Phoenix.Token.verify(Endpoint, Atom.to_string(__MODULE__), session["token"])
+
     %{"token" =>
       Phoenix.Token.sign(Endpoint, Atom.to_string(__MODULE__), {:v1, "unknown", pid, topic})}
   end
@@ -236,18 +195,28 @@ defmodule Phoenix.Integration.LongPollTest do
 
       [phx_reply, user_entered, status_msg] = resp.body["messages"]
 
-      assert phx_reply ==
-        %{"event" => "phx_reply",
-          "payload" => %{"response" => %{}, "status" => "ok"},
-          "ref" => "123", "topic" => "room:lobby", "join_ref" => "123"}
-      assert status_msg ==
-        %{"event" => "joined",
-          "payload" => %{"status" => "connected", "user_id" => nil},
-          "ref" => nil, "join_ref" => nil, "topic" => "room:lobby"}
-      assert user_entered ==
-        %{"event" => "user_entered",
-          "payload" => %{"user" => nil},
-          "ref" => nil, "join_ref" => nil, "topic" => "room:lobby"}
+      assert phx_reply == %Message{
+        event: "phx_reply",
+        payload: %{"response" => %{}, "status" => "ok"},
+        ref: "1",
+        topic: "room:lobby"
+      }
+
+      assert status_msg == %Message{
+        event: "joined",
+        payload: %{"status" => "connected", "user_id" => nil},
+        ref: nil,
+        join_ref: nil,
+        topic: "room:lobby"
+      }
+
+      assert user_entered == %Message{
+        event: "user_entered",
+        payload: %{"user" => nil},
+        ref: nil,
+        join_ref: nil,
+        topic: "room:lobby"
+      }
 
       # poll without messages sends 204 no_content
       resp = poll(:get, "/ws", @vsn, session)
@@ -262,7 +231,7 @@ defmodule Phoenix.Integration.LongPollTest do
       resp = poll :post, "/ws", @vsn, session, %{
         "topic" => "room:lobby",
         "event" => "new_msg",
-        "ref" => "123",
+        "ref" => "1",
         "payload" => %{"body" => "hi!"}
       }
       assert resp.body["status"] == 200
@@ -271,12 +240,13 @@ defmodule Phoenix.Integration.LongPollTest do
       # Get published message
       resp = poll(:get, "/ws", @vsn, session)
       assert resp.body["status"] == 200
-      assert List.last(resp.body["messages"]) ==
-        %{"event" => "new_msg",
-          "payload" => %{"transport" => "Phoenix.Transports.LongPoll", "body" => "hi!"},
-          "ref" => nil,
-          "join_ref" => nil,
-          "topic" => "room:lobby"}
+      assert List.last(resp.body["messages"]) == %Message{
+        event: "new_msg",
+        payload: %{"transport" => ":long_polling", "body" => "hi!"},
+        ref: nil,
+        join_ref: nil,
+        topic: "room:lobby"
+      }
 
       # Publish unauthorized event
       capture_log fn ->
@@ -299,16 +269,15 @@ defmodule Phoenix.Integration.LongPollTest do
       assert channel
       Process.monitor(channel)
 
-      assert_receive({:DOWN, _, :process, ^channel, {:shutdown, :inactive}}, 5000)
+      assert_receive({:DOWN, _, :process, ^channel, {:shutdown, :closed}}, 5000)
       resp = poll(:post, "/ws", @vsn, session)
       assert resp.body["status"] == 410
     end
   end
 
-  for {serializer, vsn} <- [{LongPollSerializer, "1.0.0"},
-                            {V2.JSONSerializer, "2.0.0"}] do
+  for {serializer, vsn, join_ref} <- [{V1.JSONSerializer, "1.0.0", nil}, {V2.JSONSerializer, "2.0.0", "1"}] do
     @vsn vsn
-    @join_ref "123"
+    @join_ref join_ref
 
     describe "with #{vsn} serializer #{inspect serializer}" do
       test "refuses connects that error with 403 response" do
@@ -345,7 +314,7 @@ defmodule Phoenix.Integration.LongPollTest do
         resp = poll :post, "/ws", @vsn, session, %{
           "topic" => "room:lobby",
           "event" => "new_msg",
-          "ref" => "123",
+          "ref" => "1",
           "payload" => %{"body" => "hi!"}
         }
         assert resp.body["status"] == 410
@@ -354,7 +323,7 @@ defmodule Phoenix.Integration.LongPollTest do
 
       test "filter params on join" do
         log = capture_log fn ->
-          join("/ws/logging", "room:lobby", @vsn, :local, %{"foo" => "bar", "password" => "shouldnotshow"})
+          join("/ws", "room:lobby", @vsn, :local, %{"foo" => "bar", "password" => "shouldnotshow"}, %{"logging" => "enabled"})
         end
         assert log =~ "Parameters: %{\"foo\" => \"bar\", \"password\" => \"[FILTERED]\"}"
       end
@@ -378,9 +347,13 @@ defmodule Phoenix.Integration.LongPollTest do
         resp = poll(:get, "/ws", @vsn, session)
         [_phx_reply, _user_entered, _joined, chan_error] = resp.body["messages"]
 
-        assert chan_error ==
-          %{"event" => "phx_error", "payload" => %{}, "topic" => topic,
-            "ref" => "123", "join_ref" => @join_ref}
+        assert chan_error == %Message{
+          event: "phx_error",
+          payload: %{},
+          topic: topic,
+          ref: "1",
+          join_ref: @join_ref
+        }
       end
 
       test "sends phx_close if a channel server normally exits" do
@@ -396,14 +369,15 @@ defmodule Phoenix.Integration.LongPollTest do
         assert resp.status == 200
 
         resp = poll(:get, "/ws", @vsn, session)
-
         [_phx_reply, _joined, _user_entered, _leave_reply, phx_close] = resp.body["messages"]
 
-        assert phx_close ==
-          %{"event" => "phx_close", "payload" => %{},
-            "ref" => "123",
-            "join_ref" => @join_ref,
-            "topic" => "room:lobby"}
+        assert phx_close == %Message{
+          event: "phx_close",
+          payload: %{},
+          ref: "1",
+          join_ref: @join_ref,
+          topic: "room:lobby"
+        }
       end
 
       test "shuts down when receiving disconnect broadcasts on socket's id" do
@@ -428,8 +402,8 @@ defmodule Phoenix.Integration.LongPollTest do
 
         Endpoint.broadcast("user_sockets:456", "disconnect", %{})
 
-        assert_receive {:DOWN, _, :process, ^chan1, {:shutdown, :disconnected}}
-        assert_receive {:DOWN, _, :process, ^chan2, {:shutdown, :disconnected}}
+        assert_receive {:DOWN, _, :process, ^chan1, {:shutdown, :closed}}
+        assert_receive {:DOWN, _, :process, ^chan2, {:shutdown, :closed}}
 
         poll(:get, "/ws", @vsn, session)
         assert resp.body["status"] == 410
@@ -440,7 +414,7 @@ defmodule Phoenix.Integration.LongPollTest do
           resp = poll(:get, "/ws", "123.1.1", %{}, nil, %{"origin" => "https://example.com"})
           assert resp.body["status"] == 403
         end
-        assert log =~ "The client's requested channel transport version \"123.1.1\" does not match server's version"
+        assert log =~ "The client's requested transport version \"123.1.1\" does not match server's version"
       end
 
       test "forces application/json content-type" do
diff --git a/test/phoenix/integration/websocket_test.exs b/test/phoenix/integration/websocket_test.exs
index 3c7e5dd6e..c68a1abb0 100644
--- a/test/phoenix/integration/websocket_test.exs
+++ b/test/phoenix/integration/websocket_test.exs
@@ -6,26 +6,18 @@ defmodule Phoenix.Integration.WebSocketTest do
   import ExUnit.CaptureLog
 
   alias Phoenix.Integration.WebsocketClient
-  alias Phoenix.Socket.{V2, Message}
-  alias Phoenix.Transports.WebSocketSerializer
+  alias Phoenix.Socket.{V1, V2, Message}
   alias __MODULE__.Endpoint
 
   @moduletag :capture_log
   @port 5807
 
-  handler =
-    case Application.spec(:cowboy, :vsn) do
-      [?2 | _] -> Phoenix.Endpoint.Cowboy2Handler
-      _ -> Phoenix.Endpoint.CowboyHandler
-    end
-
   Application.put_env(:phoenix, Endpoint, [
     https: false,
     http: [port: @port],
     secret_key_base: String.duplicate("abcdefgh", 8),
     debug_errors: false,
     server: true,
-    handler: handler,
     pubsub: [adapter: Phoenix.PubSub.PG2, name: __MODULE__]
   ])
 
@@ -71,36 +63,12 @@ defmodule Phoenix.Integration.WebSocketTest do
 
     channel "room:*", RoomChannel
 
-    transport :websocket, Phoenix.Transports.WebSocket,
-      check_origin: ["//example.com"], timeout: 200
-
-    def connect(%{"reject" => "true"}, _socket) do
-      :error
-    end
-
-    def connect(params, socket) do
-      Logger.disable(self())
-      {:ok, assign(socket, :user_id, params["user_id"])}
-    end
-
-    def id(socket) do
-      if id = socket.assigns.user_id, do: "user_sockets:#{id}"
-    end
-  end
-
-  defmodule LoggingSocket do
-    use Phoenix.Socket
-
-    channel "room:*", RoomChannel
-
-    transport :websocket, Phoenix.Transports.WebSocket,
-      check_origin: ["//example.com"], timeout: 200
-
     def connect(%{"reject" => "true"}, _socket) do
       :error
     end
 
     def connect(params, socket) do
+      unless params["logging"] == "enabled", do: Logger.disable(self())
       {:ok, assign(socket, :user_id, params["user_id"])}
     end
 
@@ -112,9 +80,11 @@ defmodule Phoenix.Integration.WebSocketTest do
   defmodule Endpoint do
     use Phoenix.Endpoint, otp_app: :phoenix
 
-    socket "/ws", UserSocket
-    socket "/ws/admin", UserSocket
-    socket "/ws/logging", LoggingSocket
+    socket "/ws", UserSocket,
+      websocket: [check_origin: ["//example.com"], timeout: 200]
+
+    socket "/ws/admin", UserSocket,
+      websocket: [check_origin: ["//example.com"], timeout: 200]
   end
 
   setup_all do
@@ -122,7 +92,7 @@ defmodule Phoenix.Integration.WebSocketTest do
     :ok
   end
 
-  for {serializer, vsn, join_ref} <- [{WebSocketSerializer, "1.0.0", nil}, {V2.JSONSerializer, "2.0.0", "1"}] do
+  for {serializer, vsn, join_ref} <- [{V1.JSONSerializer, "1.0.0", nil}, {V2.JSONSerializer, "2.0.0", "1"}] do
     @serializer serializer
     @vsn vsn
     @join_ref join_ref
@@ -157,7 +127,7 @@ defmodule Phoenix.Integration.WebSocketTest do
         assert Process.alive?(channel_pid)
 
         WebsocketClient.send_event(sock, "room:lobby1", "new_msg", %{body: "hi!"})
-        assert_receive %Message{event: "new_msg", payload: %{"transport" => "Phoenix.Transports.WebSocket", "body" => "hi!"}}
+        assert_receive %Message{event: "new_msg", payload: %{"transport" => ":websocket", "body" => "hi!"}}
 
         WebsocketClient.leave(sock, "room:lobby1", %{})
         assert_receive %Message{event: "you_left", payload: %{"message" => "bye!"}}
@@ -175,7 +145,7 @@ defmodule Phoenix.Integration.WebSocketTest do
 
       test "logs and filter params on join and handle_in" do
         topic = "room:admin-lobby2"
-        {:ok, sock} = WebsocketClient.start_link(self(), "ws://127.0.0.1:#{@port}/ws/logging/websocket?vsn=#{@vsn}", @serializer)
+        {:ok, sock} = WebsocketClient.start_link(self(), "ws://127.0.0.1:#{@port}/ws/websocket?vsn=#{@vsn}&logging=enabled", @serializer)
         log = capture_log fn ->
           WebsocketClient.join(sock, topic, %{"join" => "yes", "password" => "no"})
           assert_receive %Message{event: "phx_reply",
@@ -295,7 +265,7 @@ defmodule Phoenix.Integration.WebSocketTest do
           assert WebsocketClient.start_link(self(), url,  @serializer) ==
                 {:error, {403, "Forbidden"}}
         end
-        assert log =~ "The client's requested channel transport version \"123.1.1\" does not match server's version"
+        assert log =~ "The client's requested transport version \"123.1.1\" does not match server's version"
       end
 
       test "shuts down if client goes quiet" do
diff --git a/test/phoenix/router/helpers_test.exs b/test/phoenix/router/helpers_test.exs
index 33529fcc8..c3729d1ea 100644
--- a/test/phoenix/router/helpers_test.exs
+++ b/test/phoenix/router/helpers_test.exs
@@ -415,12 +415,6 @@ defmodule Phoenix.Router.HelpersTest do
     assert Helpers.static_path(socket_with_endpoint(), "/images/foo.png") == "/images/foo.png"
   end
 
-  test "helpers module generates a static_push helper" do
-    conn = conn_with_endpoint()
-    assert Helpers.static_push(conn, "/images/foo.png") == "/images/foo.png"
-    assert {"/images/foo.png", [{"accept", "image/png"}]} in sent_pushes(conn)
-  end
-
   test "helpers module generates a static_url helper" do
     url = "https://static.example.com/images/foo.png"
     assert Helpers.static_url(__MODULE__, "/images/foo.png") == url
diff --git a/test/phoenix/socket/transport_test.exs b/test/phoenix/socket/transport_test.exs
new file mode 100644
index 000000000..315545de6
--- /dev/null
+++ b/test/phoenix/socket/transport_test.exs
@@ -0,0 +1,146 @@
+System.put_env("TRANSPORT_TEST_HOST", "host.com")
+
+defmodule Phoenix.Socket.TransportTest do
+  use ExUnit.Case, async: true
+  use RouterHelper
+
+  alias Phoenix.Socket.Transport
+
+  Application.put_env :phoenix, __MODULE__.Endpoint,
+    force_ssl: [],
+    url: [host: {:system, "TRANSPORT_TEST_HOST"}],
+    check_origin: ["//endpoint.com"]
+
+  defmodule Endpoint do
+    use Phoenix.Endpoint, otp_app: :phoenix
+  end
+
+  setup_all do
+    Endpoint.start_link
+    :ok
+  end
+
+  setup do
+    Logger.disable(self())
+  end
+
+  ## Check origin
+
+  describe "check_origin/4" do
+    defp check_origin(origin, opts) do
+      conn = conn(:get, "/") |> put_req_header("origin", origin)
+      Transport.check_origin(conn, make_ref(), Endpoint, opts)
+    end
+
+    test "does not check origin if disabled" do
+      refute check_origin("/", check_origin: false).halted
+    end
+
+    test "checks origin against host" do
+      refute check_origin("https://host.com/", check_origin: true).halted
+      conn = check_origin("https://another.com/", check_origin: true)
+      assert conn.halted
+      assert conn.status == 403
+    end
+
+    test "checks origin from endpoint config" do
+      refute check_origin("https://endpoint.com/", []).halted
+      conn = check_origin("https://another.com/", [])
+      assert conn.halted
+      assert conn.status == 403
+    end
+
+    test "can get the host from system variables" do
+      refute check_origin("https://host.com", check_origin: true).halted
+    end
+
+    test "wildcard subdomains" do
+      origins = ["https://*.ex.com", "http://*.ex.com"]
+
+      conn = check_origin("http://org1.ex.com", check_origin: origins)
+      refute conn.halted
+      conn = check_origin("https://org1.ex.com", check_origin: origins)
+      refute conn.halted
+    end
+
+    test "nested wildcard subdomains" do
+      origins = ["http://*.foo.example.com"]
+
+      conn = check_origin("http://org1.foo.example.com", check_origin: origins)
+      refute conn.halted
+
+      conn = check_origin("http://org1.bar.example.com", check_origin: origins)
+      assert conn.halted
+      assert conn.status == 403
+    end
+
+    test "subdomains do not match without a wildcard" do
+      conn = check_origin("http://org1.ex.com", check_origin: ["//ex.com"])
+      assert conn.halted
+    end
+
+    test "allows invalid URIs" do
+      origins = ["//example.com", "http://scheme.com", "//port.com:81"]
+
+      for config <- [origins, false, true] do
+        conn = check_origin("file://", check_origin: config)
+        refute conn.halted
+        conn = check_origin("", check_origin: config)
+        refute conn.halted
+      end
+    end
+
+    test "checks the origin of requests against allowed origins" do
+      origins = ["//example.com", "http://scheme.com", "//port.com:81"]
+
+      # not allowed host
+      conn = check_origin("http://notallowed.com/", check_origin: origins)
+      assert conn.halted
+      assert conn.status == 403
+
+      # Only host match
+      refute check_origin("http://example.com/", check_origin: origins).halted
+      refute check_origin("https://example.com/", check_origin: origins).halted
+
+      # Scheme + host match (checks port due to scheme)
+      refute check_origin("http://scheme.com/", check_origin: origins).halted
+
+      conn = check_origin("https://scheme.com/", check_origin: origins)
+      assert conn.halted
+      assert conn.status == 403
+
+      conn = check_origin("http://scheme.com:8080/", check_origin: origins)
+      assert conn.halted
+      assert conn.status == 403
+
+      # Scheme + host + port match
+      refute check_origin("http://port.com:81/", check_origin: origins).halted
+
+      conn = check_origin("http://port.com:82/", check_origin: origins)
+      assert conn.halted
+      assert conn.status == 403
+    end
+  end
+
+  describe "force_ssl/4" do
+    test "forces SSL" do
+      # Halts
+      conn = Transport.force_ssl(conn(:get, "http://foo.com/"), make_ref(), Endpoint, [])
+      assert conn.halted
+      assert get_resp_header(conn, "location") == ["https://host.com/"]
+
+      # Disabled
+      conn = Transport.force_ssl(conn(:get, "http://foo.com/"), make_ref(), Endpoint, force_ssl: false)
+      refute conn.halted
+
+      # No-op when already halted
+      conn = Transport.force_ssl(conn(:get, "http://foo.com/") |> halt(), make_ref(), Endpoint, [])
+      assert conn.halted
+      assert get_resp_header(conn, "location") == []
+
+      # Valid
+      conn = Transport.force_ssl(conn(:get, "https://foo.com/"), make_ref(), Endpoint, [])
+      refute conn.halted
+    end
+  end
+end
diff --git a/test/phoenix/transports/websocket_serializer_test.exs b/test/phoenix/socket/v1_json_serializer_test.exs
similarity index 57%
rename from test/phoenix/transports/websocket_serializer_test.exs
rename to test/phoenix/socket/v1_json_serializer_test.exs
index ae6f66ce7..9e25aabaf 100644
--- a/test/phoenix/transports/websocket_serializer_test.exs
+++ b/test/phoenix/socket/v1_json_serializer_test.exs
@@ -1,28 +1,26 @@
-defmodule Phoenix.Tranports.WebSocketSerializerTest do
+defmodule Phoenix.Socket.V1.JSONSerializerTest do
   use ExUnit.Case, async: true
 
-  alias Phoenix.Transports.WebSocketSerializer
-  alias Phoenix.Socket.{Broadcast, Message, Reply}
+  alias Phoenix.Socket.{Broadcast, Message, Reply, V1}
 
   # v1 responses must not contain join_ref
-  @v1_msg_json ["{\"", "event", "\":", 34, "e", 34, ",\"", "payload", "\":", 34, "m", 34, ",\"", "ref", "\":", "null", ",\"", "topic", "\":", 34, "t", 34, 125]
-  @v1_reply_json ["{\"", "event", "\":", 34, "phx_reply", 34, ",\"", "payload", "\":", "{\"", "response", "\":", "null", ",\"", "status", "\":", "null", 125, ",\"", "ref", "\":", 34, "null", 34, ",\"", "topic", "\":", 34, "t", 34, 125]
-  @v1_fastlane_json ["{\"", "event", "\":", 34, "e", 34, ",\"", "payload", "\":", 34, "m", 34, ",\"", "ref", "\":", "null", ",\"", "topic", "\":", 34, "t", 34, 125]
+  @serializer V1.JSONSerializer
+  @v1_msg_json "{\"event\":\"e\",\"payload\":\"m\",\"ref\":null,\"topic\":\"t\"}"
+  @v1_reply_json "{\"event\":\"phx_reply\",\"payload\":{\"response\":null,\"status\":null},\"ref\":\"null\",\"topic\":\"t\"}"
+  @v1_fastlane_json "{\"event\":\"e\",\"payload\":\"m\",\"ref\":null,\"topic\":\"t\"}"
 
   def encode!(serializer, msg) do
     {:socket_push, :text, encoded} = serializer.encode!(msg)
-    List.flatten(encoded)
+    IO.iodata_to_binary(encoded)
   end
 
   def decode!(serializer, msg, opts), do: serializer.decode!(msg, opts)
 
   def fastlane!(serializer, msg) do
     {:socket_push, :text, encoded} = serializer.fastlane!(msg)
-    List.flatten(encoded)
+    IO.iodata_to_binary(encoded)
   end
 
-  @serializer WebSocketSerializer
-
   test "encode!/1 encodes `Phoenix.Socket.Message` as JSON" do
     msg = %Message{topic: "t", event: "e", payload: "m"}
     assert encode!(@serializer, msg) == @v1_msg_json
diff --git a/test/phoenix/test/channel_test.exs b/test/phoenix/test/channel_test.exs
index 30c31bd9c..d249495bb 100644
--- a/test/phoenix/test/channel_test.exs
+++ b/test/phoenix/test/channel_test.exs
@@ -1,12 +1,17 @@
 defmodule Phoenix.Test.ChannelTest do
   use ExUnit.Case, async: true
 
-  config = [pubsub: [adapter: Phoenix.PubSub.PG2,
-                     name: Phoenix.Test.ChannelTest.PubSub], server: false]
-  Application.put_env(:phoenix, __MODULE__.Endpoint, config)
-
   alias Phoenix.Socket
   alias Phoenix.Socket.{Broadcast, Message}
+  alias __MODULE__.Endpoint
+
+  Application.put_env(:phoenix, Endpoint, [
+    pubsub: [
+      adapter: Phoenix.PubSub.PG2,
+      name: Phoenix.Test.ChannelTest.PubSub
+    ],
+    server: false
+  ])
 
   @moduletag :capture_log
 
@@ -14,10 +19,6 @@ defmodule Phoenix.Test.ChannelTest do
     assert_receive {:graceful_exit, ^pid, %Message{event: "phx_close"}}
   end
 
-  defmodule Endpoint do
-    use Phoenix.Endpoint, otp_app: :phoenix
-  end
-
   defmodule EmptyChannel do
     use Phoenix.Channel
 
@@ -148,8 +149,6 @@ defmodule Phoenix.Test.ChannelTest do
 
     channel "foo:*", Channel, assigns: %{user_socket_assigns: true}
 
-    transport :websocket, Phoenix.Transports.WebSocket
-
     def connect(params, socket) do
       if params["reject"] == true do
         :error
@@ -161,6 +160,11 @@ defmodule Phoenix.Test.ChannelTest do
     def id(_), do: "123"
   end
 
+  defmodule Endpoint do
+    use Phoenix.Endpoint, otp_app: :phoenix
+
+    socket "/socket", UserSocket
+  end
 
   @endpoint Endpoint
   use Phoenix.ChannelTest
@@ -172,39 +176,39 @@ defmodule Phoenix.Test.ChannelTest do
 
   ## socket
 
-  test "socket/0" do
-    assert socket() == %Socket{
+  test "socket/1" do
+    assert socket(UserSocket) == %Socket{
       endpoint: @endpoint,
+      handler: UserSocket,
       pubsub_server: Phoenix.Test.ChannelTest.PubSub,
-      transport: Phoenix.ChannelTest,
-      transport_name: :channel_test,
+      transport: :channel_test,
       transport_pid: self(),
       serializer: Phoenix.ChannelTest.NoopSerializer
     }
   end
 
   test "socket/2" do
-    assert socket("user:id", %{hello: :world}) == %Socket{
+    assert socket(UserSocket, "user:id", %{hello: :world}) == %Socket{
       id: "user:id",
       assigns: %{hello: :world},
       endpoint: @endpoint,
       pubsub_server: Phoenix.Test.ChannelTest.PubSub,
-      transport: Phoenix.ChannelTest,
-      transport_name: :channel_test,
+      transport: :channel_test,
       transport_pid: self(),
-      serializer: Phoenix.ChannelTest.NoopSerializer
+      serializer: Phoenix.ChannelTest.NoopSerializer,
+      handler: UserSocket
     }
   end
 
   ## join
 
   test "join/3 with success" do
-    assert {:ok, socket, client} = join(socket("id", original: :assign), Channel, "foo:socket")
+    assert {:ok, socket, client} = join(socket(UserSocket, "id", original: :assign), Channel, "foo:socket")
     assert socket.channel == Channel
     assert socket.endpoint == @endpoint
     assert socket.pubsub_server == Phoenix.Test.ChannelTest.PubSub
     assert socket.topic == "foo:socket"
-    assert socket.transport == Phoenix.ChannelTest
+    assert socket.transport == :channel_test
     assert socket.transport_pid == self()
     assert socket.serializer == Phoenix.ChannelTest.NoopSerializer
     assert socket.assigns == %{hello: :world, original: :assign}
@@ -216,27 +220,26 @@ defmodule Phoenix.Test.ChannelTest do
 
   test "join/3 with error reply" do
     assert {:error, %{reason: "mybad"}} =
-             join(socket(), Channel, "foo:error", %{"error" => "mybad"})
+             join(socket(UserSocket), Channel, "foo:error", %{"error" => "mybad"})
   end
 
   test "join/3 with crash" do
     Process.flag(:trap_exit, true)
     Logger.disable(self())
-    assert {:error, %{reason: "join crashed"}} = join(socket(), Channel, "foo:crash")
-    assert_receive {:EXIT, _, _}
+    assert {:error, %{reason: "join crashed"}} = join(socket(UserSocket), Channel, "foo:crash")
   end
 
   ## handle_in
 
   test "pushes and receives pushed messages" do
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     ref = push socket, "noreply", %{"req" => "foo"}
     assert_push "noreply", %{"resp" => "foo"}
     refute_reply ref, _status
   end
 
   test "pushes and receives replies" do
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
 
     ref = push socket, "reply", %{}
     assert_reply ref, :ok
@@ -247,7 +250,7 @@ defmodule Phoenix.Test.ChannelTest do
   end
 
   test "receives async replies" do
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
 
     ref = push socket, "async_reply", %{"req" => "foo"}
     assert_reply ref, :ok, %{"async_resp" => "foo"}
@@ -255,7 +258,7 @@ defmodule Phoenix.Test.ChannelTest do
 
   test "crashed channel propagates exit" do
     Process.flag(:trap_exit, true)
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     push socket, "crash", %{}
     pid = socket.channel_pid
     assert_receive {:terminate, _}
@@ -265,7 +268,7 @@ defmodule Phoenix.Test.ChannelTest do
 
   test "pushes on stop" do
     Process.flag(:trap_exit, true)
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     push socket, "stop", %{"reason" => :normal}
     pid = socket.channel_pid
     assert_receive {:terminate, :normal}
@@ -279,14 +282,14 @@ defmodule Phoenix.Test.ChannelTest do
   test "pushes and receives replies on stop" do
     Process.flag(:trap_exit, true)
 
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     ref = push socket, "stop_and_reply", %{}
     assert_reply ref, :ok
     pid = socket.channel_pid
     assert_receive {:terminate, :shutdown}
     assert_graceful_exit(pid)
 
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     ref = push socket, "stop_and_reply", %{"req" => "foo"}
     assert_reply ref, :ok, %{"resp" => "foo"}
     pid = socket.channel_pid
@@ -295,7 +298,7 @@ defmodule Phoenix.Test.ChannelTest do
   end
 
   test "pushes and broadcast messages" do
-    socket = subscribe_and_join!(socket(), Channel, "foo:ok")
+    socket = subscribe_and_join!(socket(UserSocket), Channel, "foo:ok")
     refute_broadcast "broadcast", _params
     push socket, "broadcast", %{"foo" => "bar"}
     assert_broadcast "broadcast", %{"foo" => "bar"}
@@ -316,14 +319,14 @@ defmodule Phoenix.Test.ChannelTest do
   end
 
   test "pushes atom parameter keys as strings" do
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
 
     ref = push socket, "reply", %{req: %{parameter: 1}}
     assert_reply ref, :ok, %{"resp" => %{"parameter" => 1}}
   end
 
   test "pushes structs without modifying them" do
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     date = ~D[2010-04-17]
 
     ref = push socket, "reply", %{req: date}
@@ -337,14 +340,14 @@ defmodule Phoenix.Test.ChannelTest do
   ## handle_out
 
   test "push broadcasts by default" do
-    socket = subscribe_and_join!(socket(), Channel, "foo:ok")
+    socket = subscribe_and_join!(socket(UserSocket), Channel, "foo:ok")
     broadcast_from! socket, "default", %{"foo" => "bar"}
     assert_push "default", %{"foo" => "bar"}
   end
 
   test "handles broadcasts and stops" do
     Process.flag(:trap_exit, true)
-    {:ok, _, socket} = subscribe_and_join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = subscribe_and_join(socket(UserSocket), Channel, "foo:ok")
     broadcast_from! socket, "stop", %{"foo" => "bar"}
     pid = socket.channel_pid
     assert_receive {:terminate, :shutdown}
@@ -355,7 +358,7 @@ defmodule Phoenix.Test.ChannelTest do
 
   test "handles messages and stops" do
     Process.flag(:trap_exit, true)
-    socket = subscribe_and_join!(socket(), Channel, "foo:ok")
+    socket = subscribe_and_join!(socket(UserSocket), Channel, "foo:ok")
     pid = socket.channel_pid
     send pid, :stop
     assert_receive {:terminate, :shutdown}
@@ -363,13 +366,13 @@ defmodule Phoenix.Test.ChannelTest do
   end
 
   test "handles messages and pushes" do
-    socket = subscribe_and_join!(socket(), Channel, "foo:ok")
+    socket = subscribe_and_join!(socket(UserSocket), Channel, "foo:ok")
     send socket.channel_pid, :push
     assert_push "info", %{"reason" => "push"}
   end
 
   test "handles messages and broadcasts" do
-    socket = subscribe_and_join!(socket(), Channel, "foo:ok")
+    socket = subscribe_and_join!(socket(UserSocket), Channel, "foo:ok")
     send socket.channel_pid, :broadcast
     assert_broadcast "info", %{"reason" => "broadcast"}
   end
@@ -378,7 +381,7 @@ defmodule Phoenix.Test.ChannelTest do
 
   test "leaves the channel" do
     Process.flag(:trap_exit, true)
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     ref = leave(socket)
     assert_reply ref, :ok
 
@@ -392,7 +395,7 @@ defmodule Phoenix.Test.ChannelTest do
 
   test "closes the channel" do
     Process.flag(:trap_exit, true)
-    {:ok, _, socket} = join(socket(), Channel, "foo:ok")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
     close(socket)
 
     pid = socket.channel_pid
@@ -405,7 +408,7 @@ defmodule Phoenix.Test.ChannelTest do
 
   test "kills the channel when we reach timeout on close" do
     Process.flag(:trap_exit, true)
-    {:ok, _, socket} = join(socket(), Channel, "foo:timeout")
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:timeout")
     close(socket, 0)
 
     pid = socket.channel_pid
@@ -429,7 +432,7 @@ defmodule Phoenix.Test.ChannelTest do
   end
 
   test "external subscriptions" do
-    socket = subscribe_and_join!(socket(), Channel, "foo:external")
+    socket = subscribe_and_join!(socket(UserSocket), Channel, "foo:external")
     socket.endpoint.broadcast!("external:topic", "external_event", %{one: 1})
     assert_receive %Message{topic: "foo:external",
                             event: "external_event",
@@ -438,7 +441,7 @@ defmodule Phoenix.Test.ChannelTest do
   end
 
   test "warns on unhandled handle_info/2 messages" do
-    socket = subscribe_and_join!(socket(), EmptyChannel, "topic")
+    socket = subscribe_and_join!(socket(UserSocket), EmptyChannel, "topic")
     assert ExUnit.CaptureLog.capture_log(fn ->
       send socket.channel_pid, :unhandled
       ref = push socket, "hello", %{}
diff --git a/test/phoenix/transports/long_poll_serializer_test.exs b/test/phoenix/transports/long_poll_serializer_test.exs
deleted file mode 100644
index 8447eb86a..000000000
--- a/test/phoenix/transports/long_poll_serializer_test.exs
+++ /dev/null
@@ -1,30 +0,0 @@
-defmodule Phoenix.Tranports.LongPollSerializerTest do
-  use ExUnit.Case, async: true
-
-  alias Phoenix.Transports.LongPollSerializer
-  alias Phoenix.Socket.{Message, Broadcast, Reply}
-
-  @msg_json [123, [[34, ["topic"], 34], 58, [34, ["t"], 34], 44, [34, ["ref"], 34], 58, "null", 44, [34, ["payload"], 34], 58, [34, ["m"], 34], 44, [34, ["event"], 34], 58, [34, ["e"], 34]], 125]
-
-  test "fastlane!/1 translates `Phoenix.Socket.Broadcast` into 'Phoenix.Socket.Message'" do
-    broadcast = %Broadcast{topic: "t", event: "e", payload: "m"}
-    assert LongPollSerializer.fastlane!(broadcast) ==
-      {:socket_push, :text,
-        %{topic: broadcast.topic, event: broadcast.event, payload: broadcast.payload, ref: nil, join_ref: nil}}
-  end
-
-  test "encode!/1 encodes `Phoenix.Socket.Message` as JSON" do
-    reply = %Reply{topic: "t", payload: "m", ref: "foo", status: "bar"}
-    assert LongPollSerializer.encode!(reply) ==
-      {:socket_push, :text,
-        %{topic: reply.topic,
-          event: "phx_reply",
-          join_ref: "foo",
-          ref: reply.ref, payload: %{status: reply.status, response: reply.payload}}}
-  end
-
-  test "decode!/2 decodes `Phoenix.Socket.Message` from JSON" do
-    assert %Message{topic: "t", event: "e", payload: "m"} ==
-      LongPollSerializer.decode!(@msg_json, opcode: :text)
-  end
-end
diff --git a/test/phoenix/transports/socket_test.exs b/test/phoenix/transports/socket_test.exs
deleted file mode 100644
index cafd63a91..000000000
--- a/test/phoenix/transports/socket_test.exs
+++ /dev/null
@@ -1,55 +0,0 @@
-defmodule Phoenix.Transports.SocketTest do
-  use ExUnit.Case, async: true
-
-  defmodule UserSocket do
-    use Phoenix.Socket
-
-    transport :websocket, Phoenix.Transports.WebSocket, timeout: 1234
-    transport :longpoll, Phoenix.Transports.LongPoll
-
-    def connect(_, socket), do: {:ok, socket}
-    def id(_), do: nil
-  end
-
-  defmodule SpdyTransport do
-    def default_config(), do: []
-  end
-
-  test "duplicate transports raises" do
-    assert_raise ArgumentError, ~r/duplicate transports/, fn ->
-      defmodule MySocket do
-        use Phoenix.Socket
-        transport :websocket, Phoenix.Transports.WebSocket
-        transport :websocket, SpdyTransport
-        def connect(_, socket), do: {:ok, socket}
-        def id(_), do: nil
-      end
-    end
-  end
-
-  test "__transports__" do
-    assert %{longpoll: {Phoenix.Transports.LongPoll, _},
-             websocket: {Phoenix.Transports.WebSocket, _}} = UserSocket.__transports__()
-  end
-
-  test "transport config is exposted and merged with prior registrations" do
-    {Phoenix.Transports.WebSocket, opts} = UserSocket.__transport__(:websocket)
-    assert Enum.sort(opts) ==
-      [compress: false,
-       serializer: [{Phoenix.Transports.WebSocketSerializer, parse_requirement!("~> 1.0.0")},
-                    {Phoenix.Socket.V2.JSONSerializer, parse_requirement!("~> 2.0.0")}],
-       timeout: 1234, transport_log: false]
-
-    {Phoenix.Transports.LongPoll, opts} = UserSocket.__transport__(:longpoll)
-    assert Enum.sort(opts) ==
-      [crypto: [max_age: 1209600], pubsub_timeout_ms: 2000,
-       serializer: [{Phoenix.Transports.LongPollSerializer, parse_requirement!("~> 1.0.0")},
-                    {Phoenix.Socket.V2.JSONSerializer, parse_requirement!("~> 2.0.0")}],
-       transport_log: false, window_ms: 10000]
-  end
-
-  defp parse_requirement!(requirement) do
-    {:ok, req} = Version.parse_requirement(requirement)
-    req
-  end
-end
diff --git a/test/phoenix/transports/transport_test.exs b/test/phoenix/transports/transport_test.exs
deleted file mode 100644
index 6b4bdf295..000000000
--- a/test/phoenix/transports/transport_test.exs
+++ /dev/null
@@ -1,156 +0,0 @@
-System.put_env("TRANSPORT_TEST_HOST", "host.com")
-
-defmodule Phoenix.Transports.TransportTest do
-  use ExUnit.Case, async: true
-  use RouterHelper
-
-  alias Phoenix.Socket.Transport
-  alias Phoenix.Socket.Message
-
-  Application.put_env :phoenix, __MODULE__.Endpoint,
-    force_ssl: [],
-    url: [host: {:system, "TRANSPORT_TEST_HOST"}],
-    check_origin: ["//endpoint.com"]
-
-  defmodule Endpoint do
-    use Phoenix.Endpoint, otp_app: :phoenix
-  end
-
-  setup_all do
-    Endpoint.start_link
-    :ok
-  end
-
-  setup do
-    Logger.disable(self())
-  end
-
-  ## on_exit_message
-
-  test "on_exit_message/3" do
-    assert Transport.on_exit_message("foo", "1", :oops) ==
-           %Message{ref: "1", event: "phx_error", payload: %{}, topic: "foo", join_ref: "1"}
-  end
-
-  ## Check origin
-
-  defp check_origin(origin, opts) do
-    conn = conn(:get, "/") |> put_req_header("origin", origin)
-    Transport.check_origin(conn, make_ref(), Endpoint, opts)
-  end
-
-  test "does not check origin if disabled" do
-    refute check_origin("/", check_origin: false).halted
-  end
-
-  test "checks origin against host" do
-    refute check_origin("https://host.com/", check_origin: true).halted
-    conn = check_origin("https://another.com/", check_origin: true)
-    assert conn.halted
-    assert conn.status == 403
-  end
-
-  test "checks origin from endpoint config" do
-    refute check_origin("https://endpoint.com/", []).halted
-    conn = check_origin("https://another.com/", [])
-    assert conn.halted
-    assert conn.status == 403
-  end
-
-  test "can get the host from system variables" do
-    refute check_origin("https://host.com", check_origin: true).halted
-  end
-
-  test "wildcard subdomains" do
-    origins = ["https://*.ex.com", "http://*.ex.com"]
-
-    conn = check_origin("http://org1.ex.com", check_origin: origins)
-    refute conn.halted
-    conn = check_origin("https://org1.ex.com", check_origin: origins)
-    refute conn.halted
-  end
-
-  test "nested wildcard subdomains" do
-    origins = ["http://*.foo.example.com"]
-
-    conn = check_origin("http://org1.foo.example.com", check_origin: origins)
-    refute conn.halted
-
-    conn = check_origin("http://org1.bar.example.com", check_origin: origins)
-    assert conn.halted
-    assert conn.status == 403
-  end
-
-  test "subdomains do not match without a wildcard" do
-    conn = check_origin("http://org1.ex.com", check_origin: ["//ex.com"])
-    assert conn.halted
-  end
-
-  test "allows invalid URIs" do
-    origins = ["//example.com", "http://scheme.com", "//port.com:81"]
-
-    for config <- [origins, false, true] do
-      conn = check_origin("file://", check_origin: config)
-      refute conn.halted
-      conn = check_origin("", check_origin: config)
-      refute conn.halted
-    end
-  end
-
-  test "checks the origin of requests against allowed origins" do
-    origins = ["//example.com", "http://scheme.com", "//port.com:81"]
-
-    # not allowed host
-    conn = check_origin("http://notallowed.com/", check_origin: origins)
-    assert conn.halted
-    assert conn.status == 403
-
-    # Only host match
-    refute check_origin("http://example.com/", check_origin: origins).halted
-    refute check_origin("https://example.com/", check_origin: origins).halted
-
-    # Scheme + host match (checks port due to scheme)
-    refute check_origin("http://scheme.com/", check_origin: origins).halted
-
-    conn = check_origin("https://scheme.com/", check_origin: origins)
-    assert conn.halted
-    assert conn.status == 403
-
-    conn = check_origin("http://scheme.com:8080/", check_origin: origins)
-    assert conn.halted
-    assert conn.status == 403
-
-    # Scheme + host + port match
-    refute check_origin("http://port.com:81/", check_origin: origins).halted
-
-    conn = check_origin("http://port.com:82/", check_origin: origins)
-    assert conn.halted
-    assert conn.status == 403
-  end
-
-  ## force_ssl
-
-  test "forces SSL" do
-    # Halts
-    conn = Transport.force_ssl(conn(:get, "http://foo.com/"), make_ref(), Endpoint, [])
-    assert conn.halted
-    assert get_resp_header(conn, "location") == ["https://host.com/"]
-
-    # Disabled
-    conn = Transport.force_ssl(conn(:get, "http://foo.com/"), make_ref(), Endpoint, force_ssl: false)
-    refute conn.halted
-
-    # No-op when already halted
-    conn = Transport.force_ssl(conn(:get, "http://foo.com/") |> halt(), make_ref(), Endpoint, [])
-    assert conn.halted
-    assert get_resp_header(conn, "location") == []
-
-    # Valid
-    conn = Transport.force_ssl(conn(:get, "https://foo.com/"), make_ref(), Endpoint, [])
-    refute conn.halted
-  end
-
-  test "provides the protocol version" do
-    assert Version.match?(Transport.protocol_version(), "~> 2.0")
-  end
-end