diff --git a/README.md b/README.md index 2419118..b8b0d69 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ From [Wikipedia](https://wikipedia.org/wiki/Reverse_proxy): Upstream servers can be listed per-domain in the following forms: -- List of remote nodes, e.g. `["host:4000", "host:4001"]` +- List of remote nodes, e.g. `["http://host:4000", "http://host:4001"]` - A `{plug, options}` tuple, useful for umbrella applications > Note: This structure may change in the future as the project progresses. @@ -35,10 +35,14 @@ Upstream servers can be listed per-domain in the following forms: ```elixir config :reverse_proxy, # ... - upstreams: %{ "api." => ["localhost:4000"], - "slogsdon.com" => ["localhost:4001"] } + upstreams: %{ "foobar.localhost" => ["http://www.example.com"], + "api." => ["http://localhost:4000"], + "slogsdon.com" => ["http://localhost:4001"] } ``` +You might need to create `foobar.localhost in `/etc/hosts` and replace +example.com with an actual site. + ### `:cache` Enables the caching of the responses from the upstream server. diff --git a/config/config.exs b/config/config.exs index f6734bc..135c0fa 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,6 +1,9 @@ use Mix.Config -config :reverse_proxy, - upstreams: %{ "api." => {ReverseProxyTest.SuccessPlug, []}, - "example.com" => {ReverseProxyTest.SuccessPlug, []}, - "badgateway.com" => ["localhost:1"] } +config(:reverse_proxy, + upstreams: %{ + # You could add foobar.localhost to /etc/hosts to test this + "foobar.localhost" => ["http://www.example.com"], + "api." => {ReverseProxyTest.SuccessPlug, []}, + "example.com" => {ReverseProxyTest.SuccessPlug, []}, + "badgateway.com" => ["http://localhost:1"] }) diff --git a/lib/reverse_proxy/runner.ex b/lib/reverse_proxy/runner.ex index 7729632..f357a9c 100644 --- a/lib/reverse_proxy/runner.ex +++ b/lib/reverse_proxy/runner.ex @@ -22,7 +22,7 @@ defmodule ReverseProxy.Runner do method |> client.request(url, body, headers, timeout: 5_000) - |> process_response(conn) + |> process_response(conn, server) end @spec prepare_request(String.t, Conn.t) :: {Atom.t, @@ -40,7 +40,7 @@ defmodule ReverseProxy.Runner do "transfer-encoding" ) method = conn.method |> String.downcase |> String.to_atom - url = "#{conn.scheme}://#{server}#{conn.request_path}?#{conn.query_string}" + url = "#{server}#{conn.request_path}?#{conn.query_string}" headers = conn.req_headers body = case Conn.read_body(conn) do {:ok, body, _conn} -> @@ -70,20 +70,33 @@ defmodule ReverseProxy.Runner do {method, url, body, headers} end - @spec process_response({Atom.t, Map.t}, Conn.t) :: Conn.t - defp process_response({:error, _}, conn) do - conn |> Conn.send_resp(502, "Bad Gateway") + @spec process_response({Atom.t, Map.t}, Plug.Conn.t, String.t) :: Plug.Conn.t + defp process_response({:error, _}, conn, _server) do + conn |> Plug.Conn.send_resp(502, "Bad Gateway") end - defp process_response({:ok, response}, conn) do + defp process_response({:ok, response}, conn, server) do + conn = conn |> put_resp_headers(response.headers) + conn - |> put_resp_headers(response.headers) - |> Conn.delete_resp_header("transfer-encoding") - |> Conn.send_resp(response.status_code, response.body) + |> put_resp_headers(response.headers) + |> Conn.delete_resp_header("transfer-encoding") + |> Conn.send_resp(response.status_code, response.body |> process_body(conn, server)) end @spec put_resp_headers(Conn.t, [{String.t, String.t}]) :: Conn.t defp put_resp_headers(conn, []), do: conn - defp put_resp_headers(conn, [{header, value} | rest]) do + defp put_resp_headers(conn, [{header = "Location", value}|rest]) do + [host, port] = conn |> get_host() |> String.split(":") + value = URI.parse(value) + |> Map.put(:host, host) + |> Map.put(:port, port |> String.to_integer) + |> URI.to_string + + conn + |> Plug.Conn.put_resp_header(header |> String.downcase, value) + |> put_resp_headers(rest) + end + defp put_resp_headers(conn, [{header, value}|rest]) do conn |> Conn.put_resp_header(header |> String.downcase, value) |> put_resp_headers(rest) @@ -92,4 +105,15 @@ defmodule ReverseProxy.Runner do defp upstream_select(servers) do servers |> hd end + + defp process_body(body, conn, server) do + body |> String.replace(server, conn |> get_host()) + end + + defp get_host(conn) do + case conn |> Plug.Conn.get_req_header("host") do + [head | _] -> head + _ -> "" + end + end end diff --git a/test/reverse_proxy/runner_test.exs b/test/reverse_proxy/runner_test.exs index 11fc33a..4aeace6 100644 --- a/test/reverse_proxy/runner_test.exs +++ b/test/reverse_proxy/runner_test.exs @@ -95,6 +95,26 @@ defmodule ReverseProxy.RunnerTest do assert conn.resp_headers == headers end + test "retreive/3 - http - location header rewrite" do + conn = conn(:get, "/") + |> put_req_header("host", "localhost:4000") + + conn = ReverseProxy.Runner.retreive( + conn, + ["localhost:5001"], + ReverseProxyTest.RedirectHTTP + ) + + assert conn.status == 301 + assert conn.resp_body == """ + You are being redirected. + """ + assert conn.resp_headers == [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"location", "http://localhost:4000/foo"}, + ] + end + test "retreive/3 - http - failure" do conn = conn(:get, "/") diff --git a/test/support/redirect_http.ex b/test/support/redirect_http.ex new file mode 100644 index 0000000..1eac28d --- /dev/null +++ b/test/support/redirect_http.ex @@ -0,0 +1,17 @@ +defmodule ReverseProxyTest.RedirectHTTP do + def request(_method, _url, _body, _headers, _opts \\ []) do + {:ok, %{ + headers: headers, + status_code: 301, + body: """ + You are being redirected. + """ + }} + end + def headers do + [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"Location", "http://localhost:5001/foo"}, + ] + end +end