Skip to content
This repository was archived by the owner on Mar 10, 2025. It is now read-only.

Commit 139daae

Browse files
committed
Properly handle multiple missing headers.
1 parent 67184fa commit 139daae

File tree

5 files changed

+54
-11
lines changed

5 files changed

+54
-11
lines changed

README.md

+17-2
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,29 @@ end
6666
If the header is missing or set to `nil` the status code, a status code of 400
6767
(bad request) will be returned before the plug pipeline is halted. Notice that
6868
the function specified as a callback needs to be a public function as it'll be
69-
invoked from another module.
69+
invoked from another module. Also notice that the callback must return a `Plug.Conn` struct.
7070

7171
Lastly, it's possible to extract multiple headers at the same time.
72-
7372
```elixir
7473
plug PlugRequireHeader, headers: [api_key: "x-api-key", magic: "x-magic"]
7574
```
7675

76+
If extracting multiple headers _and_ specifying an `:on_missing` callback, be aware
77+
that the callback will be invoked once for each missing header. Be careful to not send
78+
a response as you can easily run into raising a `Plug.Conn.AlreadySentError`. A way of
79+
avoiding this is to have your callback function pattern match on the state of the `conn`.
80+
```elixir
81+
plug PlugRequireHeader, headers: [api_key: "x-api-key", secret: "x-secret"], on_missing: {__MODULE__, :handle_missing_header}
82+
83+
def handle_missing_header(%Plug.Conn{state: :sent} = conn, _), do: conn
84+
def handle_missing_header(conn, missing_header_key) do
85+
conn
86+
|> send_resp(Status.code(:bad_request), "Missing header: #{missing_header_key}")
87+
|> halt
88+
end
89+
```
90+
This example will only send a response for the first missing header.
91+
7792
## Planned features
7893

7994
* Make the action taken when a required header is missing more configurable.

lib/plug_require_header.ex

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule PlugRequireHeader do
22
import Plug.Conn
33
alias Plug.Conn.Status
44

5-
@vsn "0.3.1"
5+
@vsn "0.3.2"
66
@doc false
77
def version, do: @vsn
88

@@ -71,6 +71,7 @@ defmodule PlugRequireHeader do
7171
conn |> assign(key, value)
7272
end
7373

74+
defp halt_connection(%Plug.Conn{halted: true} = conn, _), do: conn
7475
defp halt_connection(conn, _) do
7576
conn
7677
|> send_resp(Status.code(:forbidden), "")

mix.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule PlugRequireHeader.Mixfile do
44
def project do
55
[
66
app: :plug_require_header,
7-
version: "0.3.1",
7+
version: "0.3.2",
88
name: "PlugRequireHeader",
99
source_url: "https://github.com/DevL/plug_require_header",
1010
elixir: "~> 1.0",

test/plug_require_header_test.exs

+20-3
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,35 @@ defmodule PlugRequireHeaderTest do
4949
connection = conn(:get, "/")
5050
|> put_req_header("x-api-key", "12345")
5151
|> put_req_header("x-secret", "handshake")
52-
response = TestAppWithCallbackAndMultipleRequiredHeaders.call(connection, [])
52+
response = TestAppWithMultipleRequiredHeaders.call(connection, [])
5353

5454
assert response.status == Status.code(:ok)
5555
assert response.resp_body == "API key: 12345 and the secret handshake"
5656
end
5757

58+
test "block request missing one of several required headers" do
59+
connection = conn(:get, "/")
60+
|> put_req_header("x-api-key", "12345")
61+
response = TestAppWithMultipleRequiredHeaders.call(connection, [])
62+
63+
assert response.status == Status.code(:forbidden)
64+
assert response.resp_body == ""
65+
end
66+
67+
test "block request missing multiple required headers" do
68+
connection = conn(:get, "/")
69+
response = TestAppWithMultipleRequiredHeaders.call(connection, [])
70+
71+
assert response.status == Status.code(:forbidden)
72+
assert response.resp_body == ""
73+
end
74+
5875
test "invoke a callback function if any of the required headers are missing" do
5976
connection = conn(:get, "/")
6077
|> put_req_header("x-api-key", "12345")
6178
response = TestAppWithCallbackAndMultipleRequiredHeaders.call(connection, [])
6279

63-
assert response.status == Status.code(:bad_request)
64-
assert response.resp_body == "Missing header: x-secret"
80+
assert response.status == Status.code(:ok)
81+
assert response.resp_body == "API key: 12345 and the secret is missing"
6582
end
6683
end

test/test_helper.exs

+14-4
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,26 @@ defmodule TestAppWithCallback do
3535
end
3636
end
3737

38+
defmodule TestAppWithMultipleRequiredHeaders do
39+
use AppMaker, headers: [api_key: "x-api-key", secret: "x-secret"]
40+
41+
get "/" do
42+
send_resp(conn, Status.code(:ok), "API key: #{conn.assigns[:api_key]} and the secret #{conn.assigns[:secret]}")
43+
end
44+
end
45+
3846
defmodule TestAppWithCallbackAndMultipleRequiredHeaders do
3947
use AppMaker, headers: [api_key: "x-api-key", secret: "x-secret"], on_missing: {__MODULE__, :callback}
4048

4149
get "/" do
4250
send_resp(conn, Status.code(:ok), "API key: #{conn.assigns[:api_key]} and the secret #{conn.assigns[:secret]}")
4351
end
4452

45-
def callback(conn, missing_header_key) do
46-
conn
47-
|> send_resp(Status.code(:bad_request), "Missing header: #{missing_header_key}")
48-
|> halt
53+
def callback(conn, "x-api-key") do
54+
conn |> assign :api_key, "not available"
55+
end
56+
57+
def callback(conn, "x-secret") do
58+
conn |> assign :secret, "is missing"
4959
end
5060
end

0 commit comments

Comments
 (0)