Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions lib/router/backend_request_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,10 @@ defmodule OpenAperture.Router.BackendRequestServer do
hackney_options = [:async, {:stream_to, self}]
hackney_options = get_hackney_options(url) ++ hackney_options

if has_request_body do
{time, result} = :timer.tc(:hackney, :request, [method, url, request_headers, :stream, hackney_options])
else
{time, result} = :timer.tc(:hackney, :request, [method, url, request_headers, "", hackney_options])
end
{time, result} = case has_request_body do
true -> :timer.tc(:hackney, :request, [method, url, request_headers, :stream, hackney_options])
_ -> :timer.tc(:hackney, :request, [method, url, request_headers, "", hackney_options])
end

state = Map.put(state, :request_start, :os.timestamp)

Expand All @@ -75,7 +74,7 @@ defmodule OpenAperture.Router.BackendRequestServer do
def handle_call({:send_request_chunk, chunk, false}, _from, %{hackney_client: client} = state) do
{time, result} = :timer.tc(:hackney, :send_body, [client, chunk])
case result do
:ok -> {:reply, {:ok, time}, state}
:ok -> {:reply, {:ok, time}, state}
{:error, reason} -> {:reply, {:error, reason, time}, state}
end
end
Expand All @@ -89,7 +88,7 @@ defmodule OpenAperture.Router.BackendRequestServer do
:ok ->
{response_time, result} = :timer.tc(:hackney, :start_response, [client])
case result do
{:ok, client} -> {:reply, {:ok, time + response_time}, %{state | hackney_client: client}}
{:ok, client} -> {:reply, {:ok, time + response_time}, %{state | hackney_client: client}}
{:error, reason} -> {:reply, {:error, reason, time + response_time}, state}
end
{:error, reason} -> {:reply, {:error, reason, time}, state}
Expand Down Expand Up @@ -142,4 +141,4 @@ defmodule OpenAperture.Router.BackendRequestServer do
defp get_request_duration(start_timestamp) do
:timer.now_diff(:os.timestamp, start_timestamp)
end
end
end
6 changes: 3 additions & 3 deletions lib/router/http_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ defmodule OpenAperture.Router.HttpHandler do
def terminate(_reason, _req, {_transport, {start_time, req_time}}) do
total_time = :timer.now_diff(:os.timestamp(), start_time)

total_time_ms = div(total_time, 1000)
total_time_ms = div(total_time, 1000)
router_time_ms = div(total_time - req_time, 1000)

Logger.info "Total request time (time in router): #{total_time_ms}ms (#{router_time_ms}ms)."
Expand Down Expand Up @@ -132,9 +132,9 @@ defmodule OpenAperture.Router.HttpHandler do
defp handle_request(req, path, transport) do
proto = case transport do
:ssl -> :https
_ -> :http
_ -> :http
end

ReverseProxy.proxy_request(req, path, proto)
end
end
end
33 changes: 16 additions & 17 deletions lib/router/http_request_util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ defmodule OpenAperture.Router.HttpRequestUtil do
{method, req} = :cowboy_req.method(req)

method = case String.upcase(method) do
"DELETE" -> :delete
"GET" -> :get
"HEAD" -> :head
"DELETE" -> :delete
"GET" -> :get
"HEAD" -> :head
"OPTIONS" -> :options
"PATCH" -> :patch
"POST" -> :post
"PUT" -> :put
_ ->
"PATCH" -> :patch
"POST" -> :post
"PUT" -> :put
_ ->
# TODO: Figure out how we want to handle non-standard verbs.
# We'll probably have to do something whitelist-based. We
# **MUST NOT** just call `String.to_atom\1`, for reasons outlined
Expand All @@ -41,7 +41,7 @@ defmodule OpenAperture.Router.HttpRequestUtil do
The key feature of the router's reverse-proxying ability is taking a request
to http://[public hostname]:[public port]/path?querystring and forwarding it
to http://[backend hostname]:[backend port]/path?querystring.
This function takes the original request URL's host and port and replaces
This function takes the original request URL's host and port and replaces
them with the backend's host and port, as well as specifying if the backend
request needs to be made via https or http.
"""
Expand All @@ -50,11 +50,10 @@ defmodule OpenAperture.Router.HttpRequestUtil do
{host_url, req} = :cowboy_req.host_url(req)
{url, req} = :cowboy_req.url(req)

proto = if https? do
"https"
else
"http"
end
proto = case https? do
true -> "https"
_ -> "http"
end

new_url = Regex.replace(~r/^#{host_url}/, url, "#{proto}://#{backend_host}:#{backend_port}")
{new_url, req}
Expand All @@ -67,7 +66,7 @@ defmodule OpenAperture.Router.HttpRequestUtil do
headers
|> Enum.any?(fn {key, value} ->
# We have to do a case-insensitive check, because although the RFC states
# that headers should be all lowercase, many servers send it as
# that headers should be all lowercase, many servers send it as
# "Transfer-Encoding".
if String.downcase(key) == "transfer-encoding" do
# If there is a transfer-encoding header, check if its value matches
Expand All @@ -94,7 +93,7 @@ defmodule OpenAperture.Router.HttpRequestUtil do
end

@doc """
Finds the first "content-length" header and returns it, Returns nil if
Finds the first "content-length" header and returns it, Returns nil if
there isn't one.
"""
@spec get_content_length_header(Types.headers) :: {String.t, String.t} | nil
Expand All @@ -113,9 +112,9 @@ defmodule OpenAperture.Router.HttpRequestUtil do
def parse_content_length_header({_, val}) when is_binary(val) do
case Integer.parse(val) do
{num, _} -> num
_ -> nil
_ -> nil
end
end

def parse_content_length_header(_), do: nil
end
end
8 changes: 4 additions & 4 deletions lib/router/response_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ defmodule OpenAperture.Router.ResponseHandler do
defp message_loop(backend_request_pid, state \\ %{}) do
receive do
{:hackney_response, _client_ref, {:status, status_code, reason}} ->
state = Map.merge(state,
state = Map.merge(state,
%{
status_code: status_code,
status_code: status_code,
status_reason: reason
})

message_loop(backend_request_pid, state)

{:hackney_response, _client_ref, {:headers, headers}} ->
Expand All @@ -52,4 +52,4 @@ defmodule OpenAperture.Router.ResponseHandler do
message_loop(backend_request_pid, state)
end
end
end
end
22 changes: 13 additions & 9 deletions lib/router/reverse_proxy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule OpenAperture.Router.ReverseProxy do
# We can't, hoever, process parts of the client request (using the
# :cowboy_req module), in that other process, as the cowboy documentation
# states:
# "It is highly discouraged to pass the Req object to another process.
# "It is highly discouraged to pass the Req object to another process.
# Doing so and calling `cowboy_req` functions from it leads to undefined
# behavior." (http://ninenines.eu/docs/en/cowboy/1.0/manual/cowboy_req/)
#
Expand All @@ -52,7 +52,7 @@ defmodule OpenAperture.Router.ReverseProxy do
{req, _reply_time} = send_reply(req, "503 Service Unavailable", [], "")
{:ok, req, 0}
{backend_host, backend_port, is_https} ->
{method, req} = get_request_method(req)
{method, req} = get_request_method(req)
{headers, req} = :cowboy_req.headers(req)

# Add any custom headers needed...
Expand All @@ -66,11 +66,7 @@ defmodule OpenAperture.Router.ReverseProxy do
def proxy_request(method, req, host, port, is_https, headers) do
{url, req} = get_backend_url(req, host, port, is_https)

has_body = Enum.any?(headers, fn {header, _value} ->
header = String.downcase(header)
header == "content-length" || header == "transfer-encoding"
end)

has_body = has_body?(headers)
# Start performing the backend request in a separate process
case start_request(method, url, headers, has_body) do
{:error, _reason, request_time} ->
Expand All @@ -92,6 +88,14 @@ defmodule OpenAperture.Router.ReverseProxy do
end
end

@spec has_body?(Types.headers) :: boolean
defp has_body?(headers) do
Enum.any?(headers, fn {header, _value} ->
header = String.downcase(header)
header == "content-length" || header == "transfer-encoding"
end)
end

@spec handle_response(Types.cowboy_req, pid) :: {:ok, Types.cowboy_req, integer} | {:error, Types.cowboy_req, integer}
defp handle_response(req, backend_request_server_pid) do
case wait_for_response(req, backend_request_server_pid) do
Expand All @@ -117,7 +121,7 @@ defmodule OpenAperture.Router.ReverseProxy do
_ ->
# Handling the streaming response is done in two parts. Here, we
# initiate the reply, with our custom request object metadata of
# `:response_type` set to `:streaming`. In
# `:response_type` set to `:streaming`. In
# `HttpHandler.onresponse/4`, we'll check that metadata, see that
# it's set to `:streaming`, and call the streaming response body
# handler.
Expand Down Expand Up @@ -173,4 +177,4 @@ defmodule OpenAperture.Router.ReverseProxy do
Integer.to_string(status_code)
end
end
end
end
11 changes: 3 additions & 8 deletions lib/router/reverse_proxy/buffered_response_body_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ defmodule OpenAperture.Router.ReverseProxy.BufferedResponseBodyHandler do
"""

alias OpenAperture.Router.ReverseProxy.Client
alias OpenAperture.Router.Timeouts
alias OpenAperture.Router.Types

# Read the timeouts info from the env config
@timeouts Application.get_env(:openaperture_router, :timeouts, [
connecting: 5_000,
sending_request_body: 60_000,
waiting_for_response: 60_000,
receiving_response: 60_000
])
@timeouts Timeouts.from_env

@doc """
Handles buffering the entire response body from the backend server before
Expand Down Expand Up @@ -47,4 +42,4 @@ defmodule OpenAperture.Router.ReverseProxy.BufferedResponseBodyHandler do
{:error, :timeout}
end
end
end
end
10 changes: 3 additions & 7 deletions lib/router/reverse_proxy/chunked_response_body_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@ defmodule OpenAperture.Router.ReverseProxy.ChunkedResponseBodyHandler do
"""

alias OpenAperture.Router.ReverseProxy.Client
alias OpenAperture.Router.Timeouts
alias OpenAperture.Router.Types

# Read the timeouts info from the env config
@timeouts Application.get_env(:openaperture_router, :timeouts, [
connecting: 5_000,
sending_request_body: 60_000,
waiting_for_response: 60_000,
receiving_response: 60_000
])
@timeouts Timeouts.from_env

@doc """
Handles chunks coming from the backend server and forwards them on to the
Expand Down Expand Up @@ -47,4 +43,4 @@ defmodule OpenAperture.Router.ReverseProxy.ChunkedResponseBodyHandler do
{:error, :timeout}
end
end
end
end
24 changes: 12 additions & 12 deletions lib/router/reverse_proxy/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule OpenAperture.Router.ReverseProxy.Client do
functionality.
"""
require Logger

alias OpenAperture.Router.BackendRequestServer
alias OpenAperture.Router.Types

Expand All @@ -15,7 +15,7 @@ defmodule OpenAperture.Router.ReverseProxy.Client do
waiting_for_response: 60_000,
receiving_response: 60_000
])

@doc """
Sends the request body from the client to the backend server.
"""
Expand Down Expand Up @@ -97,7 +97,7 @@ defmodule OpenAperture.Router.ReverseProxy.Client do

@doc """
Adds OpenAperture Router-specific request headers, which can be used by the
backend servers to get information regarding the original request. Because
backend servers to get information regarding the original request. Because
the router proxies the request, many of the normal versions of these headers
will be set with router-specific information, which might not be especially
useful for the backend server. The added headers are:
Expand All @@ -111,16 +111,16 @@ defmodule OpenAperture.Router.ReverseProxy.Client do
@spec add_router_request_headers(Types.headers, String.t, integer, Types.cowboy_req, :http | :https) :: {Types.headers, Types.cowboy_req}
def add_router_request_headers(headers, host, port, req, proto) when is_list(headers) do
x_openaperture_request_id_header = List.keyfind(headers, "x-openaperture-request-id", 0)
x_forwarded_for_header = List.keyfind(headers, "x-forwarded-for", 0)
x_forwarded_host_header = List.keyfind(headers, "x-forwarded-host", 0)
x_forwarded_port_header = List.keyfind(headers, "x-forwarded-port", 0)
x_forwarded_for_header = List.keyfind(headers, "x-forwarded-for", 0)
x_forwarded_host_header = List.keyfind(headers, "x-forwarded-host", 0)
x_forwarded_port_header = List.keyfind(headers, "x-forwarded-port", 0)
x_forwarded_proto_header = List.keyfind(headers, "x-forwarded-proto", 0)

headers = if x_openaperture_request_id_header == nil, do: add_request_id_header(headers), else: headers
{headers, req} = if x_forwarded_for_header == nil, do: add_forwarded_for_header(headers, req), else: {headers, req}
headers = if x_forwarded_host_header == nil, do: add_forwarded_host_header(headers, host), else: headers
headers = if x_forwarded_port_header == nil, do: add_forwarded_port_header(headers, port), else: headers
headers = if x_forwarded_proto_header == nil, do: add_forwarded_proto_header(headers, proto), else: headers
headers = if x_openaperture_request_id_header == nil, do: add_request_id_header(headers), else: headers
{headers, req} = if x_forwarded_for_header == nil, do: add_forwarded_for_header(headers, req), else: {headers, req}
headers = if x_forwarded_host_header == nil, do: add_forwarded_host_header(headers, host), else: headers
headers = if x_forwarded_port_header == nil, do: add_forwarded_port_header(headers, port), else: headers
headers = if x_forwarded_proto_header == nil, do: add_forwarded_proto_header(headers, proto), else: headers

{headers, req}
end
Expand Down Expand Up @@ -162,4 +162,4 @@ defmodule OpenAperture.Router.ReverseProxy.Client do
defp add_forwarded_proto_header(headers, proto) do
[{"X-Forwarded-Proto", Atom.to_string(proto)}] ++ headers
end
end
end
12 changes: 4 additions & 8 deletions lib/router/reverse_proxy/streaming_response_body_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ defmodule OpenAperture.Router.ReverseProxy.StreamingResponseBodyHandler do
This module contains the handler for processing streaming response bodies.
"""

# Read the timeouts info from the env config
@timeouts Application.get_env(:openaperture_router, :timeouts, [
connecting: 5_000,
sending_request_body: 60_000,
waiting_for_response: 60_000,
receiving_response: 60_000
])
alias OpenAperture.Router.Timeouts

@timeouts Timeouts.from_env

#@doc handle_streaming_response_body()
def handle(socket, transport) do
Expand All @@ -35,4 +31,4 @@ defmodule OpenAperture.Router.ReverseProxy.StreamingResponseBodyHandler do
after timeout -> :timeout
end
end
end
end
4 changes: 2 additions & 2 deletions lib/router/route_cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ defmodule OpenAperture.Router.RouteCache do
Logger.debug "Routes matching #{host}:#{port}: #{inspect routes}"

case routes do
nil -> nil
nil -> nil
[route | []] -> route
routes ->
index = :random.uniform(length(routes)) - 1
Enum.at(routes, index)
end
end
end
end
Loading