Skip to content

Commit 57acc98

Browse files
authored
Merge pull request #133 from ngeraedts/x-fwd-for-header
Handle x-forwarded-for headers
2 parents 57b1a24 + d2edb5a commit 57acc98

File tree

2 files changed

+61
-4
lines changed

2 files changed

+61
-4
lines changed

lib/reverse_proxy_plug.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ defmodule ReverseProxyPlug do
267267
headers =
268268
conn.req_headers
269269
|> normalize_headers
270+
|> add_x_fwd_for_header(conn)
270271

271272
headers =
272273
if options[:preserve_host_header],
@@ -321,6 +322,23 @@ defmodule ReverseProxyPlug do
321322
|> Enum.reject(fn {header, _} -> Enum.member?(hop_by_hop_headers, header) end)
322323
end
323324

325+
defp add_x_fwd_for_header(headers, conn) do
326+
{x_fwd_for, headers} = Enum.split_with(headers, fn {k, _v} -> k == "x-forwarded-for" end)
327+
328+
remote_ip = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
329+
330+
x_forwarded_for =
331+
case x_fwd_for do
332+
[{"x-forwarded-for", x_fwd_value}] ->
333+
"#{x_fwd_value}, #{remote_ip}"
334+
335+
_ ->
336+
remote_ip
337+
end
338+
339+
headers ++ [{"x-forwarded-for", x_forwarded_for}]
340+
end
341+
324342
defp recycle_cookies(client_opts, conn) do
325343
case get_cookies(conn) do
326344
"" ->

test/reverse_proxy_plug_test.exs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,39 @@ defmodule ReverseProxyPlugTest do
198198
assert conn.resp_body == "not found"
199199
end
200200

201+
test_stream_and_buffer "adds x-forwarded-for header if not present" do
202+
%{opts: opts, get_responder: get_responder} = test_reuse_opts
203+
204+
ReverseProxyPlug.HTTPClientMock
205+
|> expect(:request, fn %{headers: headers} = request ->
206+
send(self(), {:headers, headers})
207+
get_responder.(%{}).(request)
208+
end)
209+
210+
conn(:get, "/")
211+
|> ReverseProxyPlug.call(ReverseProxyPlug.init(opts))
212+
213+
assert_receive {:headers, headers}
214+
assert_header(headers, "x-forwarded-for", ["127.0.0.1"])
215+
end
216+
217+
test_stream_and_buffer "appends to x-forwarded-for header if present" do
218+
%{opts: opts, get_responder: get_responder} = test_reuse_opts
219+
220+
ReverseProxyPlug.HTTPClientMock
221+
|> expect(:request, fn %{headers: headers} = request ->
222+
send(self(), {:headers, headers})
223+
get_responder.(%{}).(request)
224+
end)
225+
226+
conn(:get, "/")
227+
|> Plug.Conn.put_req_header("x-forwarded-for", "127.0.0.2")
228+
|> ReverseProxyPlug.call(ReverseProxyPlug.init(opts))
229+
230+
assert_receive {:headers, headers}
231+
assert_header(headers, "x-forwarded-for", ["127.0.0.2, 127.0.0.1"])
232+
end
233+
201234
test_stream_and_buffer "removes hop-by-hop headers before forwarding request" do
202235
%{opts: opts, get_responder: get_responder} = test_reuse_opts
203236

@@ -212,7 +245,7 @@ defmodule ReverseProxyPlugTest do
212245
|> ReverseProxyPlug.call(ReverseProxyPlug.init(opts))
213246

214247
assert_receive {:headers, headers}
215-
assert @end_to_end_headers == headers
248+
for {n, v} <- @end_to_end_headers, do: assert_header(headers, n, [v])
216249
end
217250

218251
test_stream_and_buffer "removes hop-by-hop headers from response" do
@@ -470,7 +503,7 @@ defmodule ReverseProxyPlugTest do
470503
|> ReverseProxyPlug.call(ReverseProxyPlug.init(opts_with_upstream))
471504

472505
assert_receive {:headers, headers}
473-
assert [{"host", "example-custom-port.com:8081"}] == headers
506+
assert_header(headers, "host", ["example-custom-port.com:8081"])
474507
end
475508

476509
test_stream_and_buffer "don't include the port in the host header when is the default and preserve_host_header is false in opts" do
@@ -488,7 +521,7 @@ defmodule ReverseProxyPlugTest do
488521
|> ReverseProxyPlug.call(ReverseProxyPlug.init(opts_with_upstream))
489522

490523
assert_receive {:headers, headers}
491-
assert [{"host", "example-custom-port.com"}] == headers
524+
assert_header(headers, "host", ["example-custom-port.com"])
492525
end
493526

494527
test_stream_and_buffer "don't include the port in the host header when is the default for https and preserve_host_header is false in opts" do
@@ -506,7 +539,7 @@ defmodule ReverseProxyPlugTest do
506539
|> ReverseProxyPlug.call(ReverseProxyPlug.init(opts_with_upstream))
507540

508541
assert_receive {:headers, headers}
509-
assert [{"host", "example-custom-port.com"}] == headers
542+
assert_header(headers, "host", ["example-custom-port.com"])
510543
end
511544

512545
test_stream_and_buffer "returns gateway timeout on connect timeout" do
@@ -610,4 +643,10 @@ defmodule ReverseProxyPlugTest do
610643

611644
ReverseProxyPlug.call(conn, ReverseProxyPlug.init(opts))
612645
end
646+
647+
defp assert_header(headers, key, value) do
648+
header = for {^key, v} <- headers, do: v
649+
650+
assert header == value
651+
end
613652
end

0 commit comments

Comments
 (0)