Ace icon indicating copy to clipboard operation
Ace copied to clipboard

Exposing connection information to the application.

Open CrowdHailer opened this issue 7 years ago • 2 comments

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

CrowdHailer avatar Feb 07 '18 20:02 CrowdHailer

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

CrowdHailer avatar Apr 19 '18 19:04 CrowdHailer

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.

Nicd avatar Jun 26 '18 09:06 Nicd