Skip to content

Commit cb835fb

Browse files
author
José Valim
committed
Add basic send and more req parsed data
1 parent 1ddab8a commit cb835fb

File tree

7 files changed

+136
-19
lines changed

7 files changed

+136
-19
lines changed

LICENSE

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright (c) 2013 Plataformatec.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

README.md

+40-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
11
# Plug
22

3-
** TODO: Add description **
3+
Plug is:
4+
5+
1. An specification for composable modules in between web applications
6+
2. A connection specification and adapters for different web servers in the Erlang VM
7+
8+
## Connection
9+
10+
A connection is represented by the `Plug.Conn` record:
11+
12+
```elixir
13+
Plug.Conn[
14+
host: "www.example.com",
15+
path_info: ["bar", "baz"],
16+
script_name: ["foo"],
17+
assigns: [],
18+
...
19+
]
20+
```
21+
22+
`Plug.Conn` is a record so it can be extended with protocols. Most of the data can be read directly from the record, which is useful for pattern matching. Whenever you want to manipulate the connection, you must use the functions defined in `Plug.Connection`.
23+
24+
As everything else in Elixir, **`Plug.Conn` is immutable**, so every manipulation returns a new copy of the connection:
25+
26+
```elixir
27+
conn = assign(conn, :key, value)
28+
conn = send(conn, 200, "OK!")
29+
conn
30+
```
31+
32+
Note the connection is a **direct interface to the underlying web server**. When you call `send/3` above, it will immediately send the given status and body back to the client. Furthermore, **parsing the request information is lazy**. For example, if you want to access the request headers, they need to be explicitly fetched before hand:
33+
34+
```elixir
35+
conn = fetch(conn, :req_headers)
36+
conn.req_headers["content-type"]
37+
```
38+
39+
# License
40+
41+
Plug source code is released under Apache 2 License.
42+
Check LICENSE file for more information.

lib/plug/adapters/cowboy/connection.ex

+17-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,30 @@ defmodule Plug.Adapters.Cowboy.Connection do
22
@behaviour Plug.Connection.Spec
33
@moduledoc false
44

5-
def build(req, _transport) do
5+
def build(req, transport) do
66
{ path, req } = :cowboy_req.path req
7+
{ host, req } = :cowboy_req.host req
8+
{ port, req } = :cowboy_req.port req
9+
{ meth, req } = :cowboy_req.method req
710

811
Plug.Conn[
912
adapter: { __MODULE__, req },
10-
path_info: split_path(path)
13+
host: host,
14+
port: port,
15+
method: meth,
16+
scheme: scheme(transport),
17+
path_info: split_path(path)
1118
]
1219
end
1320

21+
def send(req, status, headers, body) do
22+
{ :ok, req } = :cowboy_req.reply(status, headers, body, req)
23+
req
24+
end
25+
26+
defp scheme(:tcp), do: :http
27+
defp scheme(:ssl), do: :https
28+
1429
defp split_path(path) do
1530
segments = :binary.split(path, "/", [:global])
1631
lc segment inlist segments, segment != "", do: segment

lib/plug/connection.ex

+31-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
defrecord Plug.Conn, assigns: [], path_info: [], script_name: [], adapter: nil do
1+
defrecord Plug.Conn,
2+
assigns: [], path_info: [], script_name: [], adapter: nil,
3+
host: nil, scheme: nil, port: nil, method: nil do
4+
25
@type assigns :: Keyword.t
36
@type segments :: [binary]
47
@type adapter :: { module, term }
8+
@type host :: binary
9+
@type scheme :: :http | :https
10+
@type port :: 0..65535
11+
@type status :: non_neg_integer
12+
@type headers :: [{ binary, binary }]
13+
@type body :: binary
14+
@type method :: binary
515

6-
record_type assigns: assigns,
7-
path_info: segments,
8-
script_name: segments,
9-
adapter: adapter
16+
record_type assigns: assigns, path_info: segments, script_name: segments,
17+
adapter: adapter, host: host, scheme: scheme, port: port,
18+
method: method
1019

1120
@moduledoc """
1221
The connection record.
@@ -22,10 +31,14 @@ defrecord Plug.Conn, assigns: [], path_info: [], script_name: [], adapter: nil d
2231
* `assigns` - store user data that is shared in the application code
2332
* `path_info` - path info information split into segments
2433
* `script_name` - script name information split into segments
34+
* `host` - the requested host
35+
* `port` - the requested port
36+
* `scheme` - the request scheme
37+
* `method` - the request method
2538
2639
## Private fields
2740
28-
Those fields are reserved for lbiraries/framework usage.
41+
Those fields are reserved for libraries/framework usage.
2942
3043
* `adapter` - holds the adapter information in a tuple
3144
"""
@@ -46,10 +59,22 @@ defmodule Plug.Connection do
4659
iex> conn.assigns[:hello]
4760
nil
4861
iex> conn = assign(conn, :hello, :world)
62+
iex> conn.assigns[:hello]
63+
:world
4964
5065
"""
5166
@spec assign(Conn.t, atom, term) :: Conn.t
5267
def assign(Conn[assigns: assigns] = conn, key, value) when is_atom(key) do
5368
conn.assigns(Keyword.put(assigns, key, value))
5469
end
70+
71+
@doc """
72+
Sends to the client the given status and body.
73+
"""
74+
@spec send(Conn.t, Conn.status, Conn.body) :: Conn.t
75+
def send(Conn[adapter: { adapter, payload }] = conn, status, body) when
76+
is_integer(status) and is_binary(body) do
77+
payload = adapter.send(payload, status, [], body)
78+
conn.adapter({ adapter, payload })
79+
end
5580
end

lib/plug/connection/spec.ex

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
defmodule Plug.Connection.Spec do
22
use Behaviour
33

4-
defcallback build(term, term) :: term
4+
alias Plug.Conn
5+
@typep payload :: term
6+
7+
@doc """
8+
Sends the given status, headers and body as a response
9+
back to the client.
10+
"""
11+
defcallback send(payload, Conn.status, Conn.headers, Conn.body) :: payload
512
end

test/plug/adapters/cowboy/connection_test.exs

+27-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
defmodule Plug.Adapters.Cowboy.ConnectionTest do
22
use ExUnit.Case, async: true
33

4+
alias Plug.Conn
5+
import Plug.Connection
6+
47
## Cowboy setup for testing
58

69
setup_all do
@@ -18,32 +21,50 @@ defmodule Plug.Adapters.Cowboy.ConnectionTest do
1821
def plug(conn, []) do
1922
function = binary_to_atom Enum.first(conn.path_info) || "root"
2023
apply __MODULE__, function, [conn]
21-
# rescue
22-
# exception ->
23-
# conn.send(500, exception.message <> "\n" <> Exception.format_stacktrace)
24+
rescue
25+
exception ->
26+
send(conn, 500, exception.message <> "\n" <> Exception.format_stacktrace)
2427
end
2528

2629
## Tests
2730

28-
def root(Plug.Conn[] = conn) do
31+
def root(Conn[] = conn) do
32+
assert conn.method == "HEAD"
2933
assert conn.path_info == []
3034
assert conn.script_name == []
3135
conn
3236
end
3337

34-
def build(Plug.Conn[] = conn) do
38+
def build(Conn[] = conn) do
3539
assert { Plug.Adapters.Cowboy.Connection, _ } = conn.adapter
3640
assert conn.path_info == ["build", "foo", "bar"]
3741
assert conn.script_name == []
42+
assert conn.scheme == :http
43+
assert conn.host == "127.0.0.1"
44+
assert conn.port == 8001
45+
assert conn.method == "GET"
3846
conn
3947
end
4048

4149
test "builds a connection" do
42-
assert_ok request :get, "/"
50+
assert_ok request :head, "/"
4351
assert_ok request :get, "/build/foo/bar"
4452
assert_ok request :get, "//build//foo//bar"
4553
end
4654

55+
def send_200(conn) do
56+
send(conn, 200, "OK")
57+
end
58+
59+
def send_500(conn) do
60+
send(conn, 500, "ERROR")
61+
end
62+
63+
test "sends a response" do
64+
assert { 200, _, "OK" } = request :get, "/send_200"
65+
assert { 500, _, "ERROR" } = request :get, "/send_500"
66+
end
67+
4768
## Helpers
4869

4970
defp assert_ok({ 204, _, _ }), do: :ok

test/support/spec_test.ex

-3
This file was deleted.

0 commit comments

Comments
 (0)