Ace
Ace copied to clipboard
Exposing connection information to the application.
Certain applications require knowledge of the connection and not just the HTTP message that was sent. For example inspecting a client ssl certificate or blacklisting particular ip addresses.
Adding this to the request is not desirable because:
1 it conflates the transport information with the HTTP protocol 2 The request struct sent by the client should be identical to the request struct handled by the server. Assuming both client and server use Raxx. 3 It is also possible to have HTTP without a connection. Mailing Requests and Responses would work if anyone wanted to do that.
Option 1
Ace could have an entry in the process dictionary of the worker. The ace module could the expose several reader functions that work only in the context of a worker.
defmodule MyApp do
def handle_request(_request, _state) do
peer_ip = Ace.peer_ip()
response(:ok)
end
end
Somewhat ugly to use the process dictionary, however as all exposed functions are for derived properties the cannot be used to cause side effects.
Option 2
handle_request/2 (and all other callbacks) can have a handle_request/3 partner that takes request, channel, state with the default behaviour to fallback to the arity 2 implementation.
The downside of this approach is raxx is understanding both HTTP and some concept of the transport layer. e.g. would the channel object be an Ace.Connection or a Raxx.Connection.
Option 3
A server specific callback for connecting. e.g.
defmodule MyApp do
use Ace.HTTP.Service, [port: 8080]
@impl Ace.HTTP.Server
def connect(channel, config) do # 1.
Logger.metadata(client_ip: channel.ip)
{:ok, config}
end
@impl Raxx.Server
def handle_request(_request, _state) do
response(:ok)
end
end
Advantages this gives people an answer to what does init look like, it's replaced with connect.
There is a choice at 1. do we pass a connection or channel object. if channel then no error case should stop the complete connection, but the callback is executed in the same process as the handle_*. If connection then its only executed once for potentially many requests. but it would be possible to use it to stop the connection. i.e. blacklisted ips
This has been partially tackled by #96
It is not at least possible to access the socket and with that access as anything about peer ip etc
Just adding a workaround that I use in the meantime, for anyone else that might stumble upon this issue report:
def get_ace_ip() do
with info <- Process.get(Ace.HTTP.Channel),
{:tcp, socket} <- info.socket,
{:ok, {ip, _}} <- :inet.peername(socket) do
ip
else
_ -> nil
end
end
You can run that in a request handler and you'll get either the requesting IP or nil as a result.