daurnimator / lua-http

HTTP Library for Lua. Supports HTTP(S) 1.0, 1.1 and 2.0; client and server.
https://daurnimator.github.io/lua-http/
MIT License
807 stars 82 forks source link

How to get clientIP without any 3rdParty lib or trick. #229

Open dotsinspace opened 1 week ago

dotsinspace commented 1 week ago

I have built Lua-HTTP server, and it is running perfectly fine. but when I try to get client Ip then all of my tricks failed. Even I have seen many solutions for implementing reverse proxy and all which I don't want.

Following is my server snipped.

--
-- IMPORTS
--
local HttpServer = require "http.server"
local HttpHeaders = require "http.headers"
local CJson = require "cjson"

--
-- PACKAGES
--
local Context = require "packages.context"
local RateLimiter = require "packages.dyna_modules.RateLimiter"
local Router = require "packages.routes"

--
-- GLOBALS
--
local HOST = '0.0.0.0'
local PORT = 8080

--
-- FUNCTIONS
--
local function ResponseError(ServerInformation, context, op, err)
  -- Local assignment.
  local msg = op .. " on " .. tostring(context) .. " failed"

  -- Style guide.
  Context.Debug:info(string.format("Error caught total number of active connections: %s", ServerInformation
    .n_connections))

  -- Check if error is available or not.
  if err then
    -- Update error message.
    msg = msg .. ": " .. tostring(err)
  end

  -- Style guide.
  Context.Debug:info(string.format("Error: %s", msg))

  -- Return message.
  assert(io.stderr:write(msg, "\n"))
end
local function ResponseBuilder(ServerInformation, stream)
  -- Variable assignment.
  local requestHeaders = assert(stream:get_headers())
  local requestMethod = requestHeaders:get ":method"
  local requestPath = requestHeaders:get(":path")

  print('-x-x-x-x-x-x-x', stream:get_peer())

  -- Run the netstat command to get information on active TCP connections
  local handle = io.popen("netstat -tn 2>/dev/null | grep ':8080'")   -- Filter by port 8080
  local output = handle:read("*a")
  handle:close()

  print('vwbwbwbwb', output)

  -- Parse the output to get the client IP address
  local clientIp = output:match("tcp%s+([%d%.]+):8080")

  print(clientIp)

  for index, value in pairs(stream) do
    print(index, value)
  end
  -- Check rate limit for the client IP
  local isAllowed = RateLimiter.get(Context, clientIp)

  -- Style guide.
  Context.Debug:info(string.format("Request recivied total number of active connections are %s",
    ServerInformation.n_connections))

  -- Check if rate limit exceeded.
  if not isAllowed then
    -- If rate-limited, return 429 for too many requests.
    local ResponseHeaders = HttpHeaders.new()

    -- Update response headers.
    ResponseHeaders:append(":status", "429")
    ResponseHeaders:append("content-type", "text/plain")

    -- Send response.
    assert(stream:write_headers(ResponseHeaders, false))
    assert(stream:write_chunk("Rate limit exceeded. Please try again later.", true))
    return
  end

  -- Style guide.
  Context.Debug:info(string.format("Request recivied total number of active connections are %s",
    ServerInformation.n_connections))
  Context.Debug:info(string.format("Request recivied with method: %s and path : %s", requestMethod, requestPath))

  -- Build response headers
  local ResponseHeaders = HttpHeaders.new()

  -- Object assignment.
  local NotFoundHandler = function(__requestMethod)
    -- Style guide.
    Context.Debug:info(string.format("Route Not found: %s %s", __requestMethod, requestPath))

    -- Return response.
    return "404 Not Found"
  end
  local Handler = Router[requestPath] or NotFoundHandler

  -- Send response
  ResponseHeaders:append(":status", "200")
  ResponseHeaders:append("content-type", "text/plain")

  -- Lookup the appropriate handler from the routes table
  local responseBody = Handler(Context, requestMethod)

  -- Send headers to client; end the stream immediately if this was a HEAD request
  assert(stream:write_headers(ResponseHeaders, requestMethod == "HEAD"))

  -- If the method is not HEAD, send the response body
  if requestMethod ~= "HEAD" then
    -- Style guide.
    Context.Debug:info(string.format("Sending Response: %s", responseBody))

    -- Encode the response body as JSON
    local encodedResponseBody = CJson.encode(responseBody)

    -- Assert the response body.
    assert(stream:write_chunk(encodedResponseBody, true))
  end
end

--
-- SERVER
--
local Server = assert(HttpServer.listen {
  host = HOST,
  port = PORT,
  onstream = ResponseBuilder,
  onerror = ResponseError
})

-- Manually call :listen() so that we are bound before calling :localname()
assert(Server:listen())
do
  -- Bound server to given port.
  local boundPort = select(3, Server:localname())

  -- Style guide.
  Context.Debug:info(string.format("Server is listening on %s:%d", HOST, boundPort))
end

-- Start the main server loop
assert(Server:loop())

Please help..i have only this small server and i want to implement Ratelimiter but without clip ip how will i going to do it.

Regards

daurnimator commented 1 day ago

https://daurnimator.github.io/lua-http/0.4/#stream:peername

e.g.

local function ResponseBuilder(ServerInformation, stream)
   print("Peer's IP is:", stream:peername())