Skip to content

Commit

Permalink
Start with cowboy support
Browse files Browse the repository at this point in the history
  • Loading branch information
José Valim committed Nov 15, 2013
1 parent 7f7faec commit 1ddab8a
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 18 deletions.
18 changes: 18 additions & 0 deletions lib/plug/adapters/cowboy/connection.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule Plug.Adapters.Cowboy.Connection do
@behaviour Plug.Connection.Spec
@moduledoc false

def build(req, _transport) do
{ path, req } = :cowboy_req.path req

Plug.Conn[
adapter: { __MODULE__, req },
path_info: split_path(path)
]
end

defp split_path(path) do
segments = :binary.split(path, "/", [:global])
lc segment inlist segments, segment != "", do: segment
end
end
26 changes: 26 additions & 0 deletions lib/plug/adapters/cowboy/handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Plug.Adapters.Cowboy.Handler do
@behaviour :cowboy_http_handler
@moduledoc false

require :cowboy_req
@connection Plug.Adapters.Cowboy.Connection

# HTTP

def init({ transport, :http }, req, main) when transport in [:tcp, :ssl] do
case main.plug(@connection.build(req, transport), []) do
Plug.Conn[adapter: { @connection, req }] ->
{ :ok, req, nil }
other ->
raise "Expected a Plug.Conn with Cowboy adapter after request, got: #{inspect other}"
end
end

def handle(req, nil) do
{ :ok, req, nil }
end

def terminate(_reason, _req, nil) do
:ok
end
end
50 changes: 49 additions & 1 deletion lib/plug/connection.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
defrecord Plug.Conn, []
defrecord Plug.Conn, assigns: [], path_info: [], script_name: [], adapter: nil do
@type assigns :: Keyword.t
@type segments :: [binary]
@type adapter :: { module, term }

record_type assigns: assigns,
path_info: segments,
script_name: segments,
adapter: adapter

@moduledoc """
The connection record.
It is recommended to use the record for reading data,
all connection manipulation should be done via the functions
in `Plug.Connection` module.
## Fields
Those fields can be accessed directly by the user.
* `assigns` - store user data that is shared in the application code
* `path_info` - path info information split into segments
* `script_name` - script name information split into segments
## Private fields
Those fields are reserved for lbiraries/framework usage.
* `adapter` - holds the adapter information in a tuple
"""
end

defmodule Plug.Connection do
@moduledoc """
Functions for manipulating the connection.
"""

alias Plug.Conn

@doc """
Assigns a new key and value in the connection.
## Examples
iex> conn.assigns[:hello]
nil
iex> conn = assign(conn, :hello, :world)
"""
@spec assign(Conn.t, atom, term) :: Conn.t
def assign(Conn[assigns: assigns] = conn, key, value) when is_atom(key) do
conn.assigns(Keyword.put(assigns, key, value))
end
end
5 changes: 5 additions & 0 deletions lib/plug/connection/spec.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Plug.Connection.Spec do
use Behaviour

defcallback build(term, term) :: term
end
25 changes: 15 additions & 10 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@ defmodule Plug.Mixfile do
def project do
[ app: :plug,
version: "0.0.1",
elixir: "~> 0.11.3-dev",
deps: deps ]
elixir: "~> 0.11.2",
deps: deps(Mix.env) ]
end

# Configuration for the OTP application
def application do
[mod: { Plug, [] }]
[]
end

# Returns the list of dependencies in the format:
# { :foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1" }
#
# To specify particular versions, regardless of the tag, do:
# { :barbat, "~> 0.1", github: "elixir-lang/barbat.git" }
defp deps do
[]
def deps(:prod) do
[ { :cowboy, "~> 0.9", github: "extend/cowboy", optional: true } ]
end

def deps(:docs) do
deps(:prod) ++
[ { :ex_doc, github: "elixir-lang/ex_doc" } ]
end

def deps(_) do
deps(:prod) ++
[ { :hackney, github: "benoitc/hackney" } ]
end
end
5 changes: 5 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[ "cowboy": {:git, "git://github.com/extend/cowboy.git", "5a25c7f7f2167b8cef03129553e56f422a9890f2", []},
"cowlib": {:git, "git://github.com/extend/cowlib.git", "63298e8e160031a70efff86a1acde7e7db1fcda6", [{:ref, "0.4.0"}]},
"hackney": {:git, "git://github.com/benoitc/hackney.git", "476b4992c64873ed67bf6daa5c48f0a31c2435b8", []},
"mimetypes": {:git, "https://github.com/spawngrid/mimetypes.git", "47d37a977a7d633199822bf6b08353007483d00f", [{:ref, "master"}]},
"ranch": {:git, "git://github.com/extend/ranch.git", "5df1f222f94e08abdcab7084f5e13027143cc222", [{:ref, "0.9.0"}]} ]
60 changes: 60 additions & 0 deletions test/plug/adapters/cowboy/connection_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
defmodule Plug.Adapters.Cowboy.ConnectionTest do
use ExUnit.Case, async: true

## Cowboy setup for testing

setup_all do
dispatch = [{ :_, [ {:_, Plug.Adapters.Cowboy.Handler, __MODULE__ } ] }]
env = [dispatch: :cowboy_router.compile(dispatch)]
{ :ok, _pid } = :cowboy.start_http(__MODULE__, 100, [port: 8001], [env: env])
:ok
end

teardown_all do
:ok = :cowboy.stop_listener(__MODULE__)
:ok
end

def plug(conn, []) do
function = binary_to_atom Enum.first(conn.path_info) || "root"
apply __MODULE__, function, [conn]
# rescue
# exception ->
# conn.send(500, exception.message <> "\n" <> Exception.format_stacktrace)
end

## Tests

def root(Plug.Conn[] = conn) do
assert conn.path_info == []
assert conn.script_name == []
conn
end

def build(Plug.Conn[] = conn) do
assert { Plug.Adapters.Cowboy.Connection, _ } = conn.adapter
assert conn.path_info == ["build", "foo", "bar"]
assert conn.script_name == []
conn
end

test "builds a connection" do
assert_ok request :get, "/"
assert_ok request :get, "/build/foo/bar"
assert_ok request :get, "//build//foo//bar"
end

## Helpers

defp assert_ok({ 204, _, _ }), do: :ok
defp assert_ok({ status, _, body }) do
flunk "Expected ok response, got status #{inspect status} with body #{inspect body}"
end

defp request(verb, path, headers // [], body // "") do
{ :ok, status, headers, client } =
:hackney.request(verb, "http://127.0.0.1:8001" <> path, headers, body, [])
{ :ok, body, _ } = :hackney.body(client)
{ status, headers, body }
end
end
13 changes: 13 additions & 0 deletions test/plug/connection_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Plug.ConnectionTest do
use ExUnit.Case, async: true

alias Plug.Conn
import Plug.Connection

test "assign/3" do
conn = Conn[]
assert conn.assigns[:hello] == nil
conn = assign(conn, :hello, :world)
assert conn.assigns[:hello] == :world
end
end
7 changes: 0 additions & 7 deletions test/plug_test.exs

This file was deleted.

3 changes: 3 additions & 0 deletions test/support/spec_test.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Plug.Connection.SpecTest do

end
7 changes: 7 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
ExUnit.start

# For cowboy testing
:application.start(:hackney)
:application.start(:crypto)
:application.start(:ranch)
:application.start(:cowlib)
:application.start(:cowboy)

0 comments on commit 1ddab8a

Please sign in to comment.