26.07.2023, 01:25
Hi,
Im using the followig code to create a reverse proxy to authenticate with a Camera NVR and attempt to display the MJPEG stream in an iframe. This is to display the nvr camera feed in the visu without the user needing to fill the auth each time.
Currently i can display a snapshot ( the /path 6 url ) but i would like display the MJPEG stream ( the /path5 url ).
The MJPEG stream works in the iframe without the proxy if i set the src as the nvr url directly and manually fill the auth form when it pops up.
The Auth is successfully prefilled using the proxy when displaying the snapshot but i have only been successful in display a snapshot and not the MJPEG stream url.
Also open to any alterations as im unsure if the current method will drain memory with the proxy handling the MJPEG stream.
current code below:
Im using the followig code to create a reverse proxy to authenticate with a Camera NVR and attempt to display the MJPEG stream in an iframe. This is to display the nvr camera feed in the visu without the user needing to fill the auth each time.
Currently i can display a snapshot ( the /path 6 url ) but i would like display the MJPEG stream ( the /path5 url ).
The MJPEG stream works in the iframe without the proxy if i set the src as the nvr url directly and manually fill the auth form when it pops up.
The Auth is successfully prefilled using the proxy when displaying the snapshot but i have only been successful in display a snapshot and not the MJPEG stream url.
Also open to any alterations as im unsure if the current method will drain memory with the proxy handling the MJPEG stream.
current code below:
Code:
if not server then
local http = require("socket.http")
local ltn12 = require("ltn12")
local url = require("socket.url")
local md5 = require("encdec").md5 -- Using the md5 library for hash computation
local port = 7673
local urlMappings = {
["/path1"] = "http://nodejs.org", -- Replace with the target URL for path1 -- test proxy redirect
["/path5"] = "https://<NVR IP>/cgi-bin/mjpg/video.cgi?channel=3&subtype=1", -- Replace with the target URL for path5
["/path6"] = "https://<NVR IP>/cgi-bin/snapshot.cgi?channel=1", -- Replace with the target URL for path6
-- Add more mappings as needed
}
-- Set up the login credentials for digest authentication
local username = "user"
local password = "password"
-- Function to generate the Digest authentication header with the correct nonce
local function generateDigestHeader(uri, method, realm, nonce, qop, opaque)
local ha1 = md5(username .. ":" .. realm .. ":" .. password)
local ha2 = md5(method .. ":" .. uri)
local nc = "00000001"
local cnonce = "0a4f113b"
local response = md5(ha1 .. ":" .. nonce .. ":" .. nc .. ":" .. cnonce .. ":" .. qop .. ":" .. ha2)
return string.format('Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s", nc=%s, cnonce="%s", qop="%s", opaque="%s"',
username, realm, nonce, uri, response, nc, cnonce, qop, opaque)
end
-- Function to handle incoming queries and perform digest authentication
function handleQuery(query)
local method, path, _ = query:match("(%u+)%s+([^%s]+)")
path = path:gsub("?.*", "")
local targetURL = urlMappings[path]
if targetURL then
-- Perform digest authentication before sending the response
local response_body = {}
local response_headers = {}
local res, status_code, response_headers, status_string = http.request {
url = targetURL,
headers = {
["Accept"] = "*/*", -- Accept all content types
},
method = "HEAD",
create = function()
local conn = socket.tcp()
conn:settimeout(5) -- Set a timeout for the initial connection
return conn
end,
sink = ltn12.sink.table(response_body),
headers = response_headers, -- Store the response headers in the table
}
if res and response_headers["www-authenticate"] then
local realm = response_headers["www-authenticate"]:match('realm="(.-)"')
local nonce = response_headers["www-authenticate"]:match('nonce="(.-)"')
local qop = response_headers["www-authenticate"]:match('qop="(.-)"')
local opaque = response_headers["www-authenticate"]:match('opaque="(.-)"')
-- Perform the actual HTTPS request with digest authentication
local auth_response_body = {}
local auth_res, auth_status_code, auth_response_headers, auth_status_string = http.request {
url = targetURL,
headers = {
["Authorization"] = generateDigestHeader(targetURL, "GET", realm, nonce, qop, opaque),
["Accept"] = "*/*", -- Accept all content types
},
method = "GET",
sink = ltn12.sink.table(auth_response_body),
create = function()
local conn = socket.tcp()
conn:settimeout(10) -- Set a timeout for the actual request
return conn
end,
}
log(auth_response_headers["content-type"])
if auth_res and auth_status_code == 200 then
-- Check if the response is an image (e.g., for snapshot requests)
local content_type = auth_response_headers["content-type"] or ""
local image_types = { ["image/jpeg"] = true, ["image/png"] = true, ["image/gif"] = true }
if image_types[content_type] then
-- If it's an image, set the appropriate Content-Type header
local response = "HTTP/1.1 200 OK\r\nContent-Type: " .. content_type .. "\r\n\r\n" .. table.concat(auth_response_body)
log(query, "Success - Image")
return response
else
-- For other types of responses, pass them through as they are
local response = "HTTP/1.1 200 OK\r\n\r\n" .. table.concat(auth_response_body)
log(query, "Success - Other")
return response
end
else
-- If the request failed, log and return a 500 error response
log(query, "Error: Digest authentication request failed with status code " .. auth_status_code)
return "HTTP/1.1 500 Internal Server Error\r\n\r\n"
end
else
-- If the initial connection failed or no digest headers were received, log and return a 500 error response
log(query, "Error: Failed to establish a connection with the server or no digest headers received.")
return "HTTP/1.1 500 Internal Server Error\r\n\r\n"
end
else
return "HTTP/1.1 404 Not Found\r\n\r\n"
end
end
-- Create a server socket to listen for incoming queries
local server = assert(socket.bind("*", port))
log("Proxy server is listening on localhost:" .. port .. "\n")
while true do
local client = server:accept()
local query = client:receive()
local response = handleQuery(query)
client:send(response)
client:close()
end
end