Vladimir Lebedev Vladimir Lebedev - 1 month ago 5
HTTP Question

How to get client ip address in HttpHandler (Julia Language)?

I need to get client's IP address in regular HttpHandler like this:

http = HttpHandler() do req::Request, res::Response
Response( ismatch(r"^/hello/",req.resource) ? string("Hello ", split(req.resource,'/')[3], "!") : 404 )

contain this information.


The Approach

This can be done, if you know the internals of Julia a little. It turns out that Julia uses the library libuv for low level system processing and that library has a function called uv_tcp_getpeername. This function is not exported by Julia.Base, but you can gain access to it via ccall. Also, the module HttpServer allows for way define a callback for various events, including the connect event.

Example Module

module HTTPUtil
   export get_server
   using HttpServer

   function handle_connect(client)
         buffer = Array(Uint8,32)
         bufflen::Int64 = 32


         peername::IPv4 = IPv4(buffer[5:8]...)
      catch e
         println("Error ... $e")

   function get_server()
      http = HttpHandler() do req::Request, res::Response
         ip = task_local_storage(:ip)
         println("Connection received from from $ip")
         Response(ismatch(r"^/hello/",req.resource)?string("Hello ",split(req.resource,'/')[3], " $(ip)!") : 404 )

      server = Server(http)


Each time a connection request is made, a peer socket is created by the server, and the connect handler is called which is defined to be handle_connect. It takes one parameter, client, of type Client. A Client type has a field called sock of type TcpSocket, and a TcpSocket has a field handle which is used by libuv. The object, then is each time a connection request is made, the connect handler is called, which calls uv_tcp_getpeername with the data contained in the TcpSocket handle. A byte array is declared to act as a buffer, which then is cast back to Base.IPv4. The module HTTPServer creates exactly 1 task for each client using @async, so the ip address can be stored local to the client using task_local_storage; thus there is no race condition.

Using it

julia> using HTTPUtil
julia> server = get_server()
Server(HttpHandler((anonymous function),TcpServer(init),Dict{ASCIIString,Function} with 3 entries:
  "error" => (anonymous function)
  "listen" => (anonymous function)
  "connect" => (anonymous function)),nothing)

julia> @async run(server,8000)
Listening on 8000...
Task (queued) @0x000000000767e7a0

julia> Connection received from from
Connection received from from
... etc


  1. For illustration, the output is modified so that the server will respond to each browser "Hello ipaddr"
  2. This should be included in Base and/or HttpServer, but currently is not, so you'll need to use this workaround until it is.
  3. The typical looping structure is used in get_server to illustrate there is no requirement for it to change, except to add in the ip address.
  4. Assumes IPv4, but can be improved to allow both IPv4 and IPv6 straightforwardly as libuv supports both.