Advanced examples
Buffering with LuaFilter
The BufferingLuaFilter in the example below buffers data from the backend in case the backend sends data in small packages. The goal is to avoid too many calls on the filter chain.
<filter>
<filter-name>BufferingLuaFilter</filter-name>
<filter-class>ch::nevis::isiweb4::filter::lua::LuaFilter</filter-class>
<init-param>
<param-name>Script.OutputFunctionName</param-name>
<param-value>outputStream</param-value>
</init-param>
<init-param>
<param-name>Script.Namespace</param-name>
<param-value>Buffering_</param-value>
</init-param>
<init-param>
<param-name>Buffering_Buffersize</param-name>
<param-value>128</param-value>
</init-param>
<init-param>
<param-name>Script</param-name>
<param-value>
local fullBody = {}
local len = 0
function outputStream(req, resp, chunk)
if chunk == nil then
return table.concat(fullBody)
elseif len > tonumber(Buffering_Buffersize) then
ret = table.concat(fullBody)
fullBody = {}
table.insert(fullBody, chunk)
len = string.len(chunk)
return ret
else
table.insert(fullBody, chunk)
len = len + string.len(chunk)
return nil
end
end
</param-value>
</init-param>
</filter>
Multipart request handling with LuaFilter
The LuaFilter configured in the following sample code contains two methods:
- The checkIfMultiPartFormData method is called on the input headers (once on every request). It decides whether the received request is a multipart request and stores the result in the is MultipartFormDataReq variable. In case of a multipart request, the function instantiates a multipart parser and registers the removeSignaturePart function into it. The removeSignaturePart function ignores the part if its name matches the given part name. For further details on the multipart parser module, see the chapter Multipart.
- The second method is called processRequest. This is an InputFunctionName function, which operates on the body of the request, removing the given part from the multipart request.
<filter>
<filter-name>RemoveSignaturePartLuaFilter</filter-name>
<filter-class>ch::nevis::isiweb4::filter::lua::LuaFilter</filter-class>
<init-param>
<param-name>Script.InputFunctionName</param-name>
<param-value>processRequest</param-value>
</init-param>
<init-param>
<param-name>Script.InputHeaderFunctionName</param-name>
<param-value>checkIfMultiPartFormData</param-value>
</init-param>
<init-param>
<param-name>Script</param-name>
<param-value>
<!-- ===================================== -->
package.path = package.path .. ";@PKG_VAR@/@PKG_INSTANCE@/lib/?.lua"
utils = require "Utils"
Multipart = require "Multipart"
local bufferSize = 8192
local isMultipartFormDataReq = false
local multipartParser = null;
<!-- ===================================== -->
function removeSignaturePart(name, value)
if name ~= "PartNameToDelete" then
return name, value
end
end
<!-- ===================================== -->
function checkIfMultiPartFormData(req, resp, chunk)
local contentTypeHeader = req:getHeader("Content-Type")
local contentTypeValue = "multipart/form-data"
isMultipartFormDataReq = contentTypeHeader and (string.sub(contentTypeHeader, 1, string.len(contentTypeValue)) == contentTypeValue)
if isMultipartFormDataReq then
req:removeHeader("Content-Length")
multipartParser = Multipart:new(contentTypeHeader, bufferSize)
multipartParser:registerFormDataFunction(removeSignaturePart)
end
end
<!-- ===================================== -->
function processRequest(req, resp, chunk)
local ret = chunk
if isMultipartFormDataReq then
ret = multipartParser:stream(chunk)
end
return ret
end
<!-- ===================================== -->
</param-value>
</init-param>
</filter
Delegating cookies received from a side call to another backend
This sample shows how to delegate cookies received from a side call to another backend:
<filter>
<filter-name>CookieCacheFilter</filter-name>
<filter-class>ch::nevis::isiweb4::filter::cookie::CookieCacheFilter</filter-class>
<init-param>
<param-name>CookieManager</param-name>
<param-value>
store:^LOGIN_CONTROL$
allow:^.*$
</param-value>
</init-param>
</filter>
<filter>
<filter-name>LoginControlFilter</filter-name>
<filter-class>ch::nevis::isiweb4::filter::lua::LuaFilter</filter-class>
<init-param>
<param-name>ModifierFlags</param-name>
<param-value>+RESET_COOKIES</param-value>
</init-param>
<init-param>
<param-name>Script.InputHeaderFunctionName</param-name>
<param-value>inputHeader</param-value>
</init-param>
<init-param>
<param-name>Script</param-name>
<param-value>
package.path = package.path .. ";@PKG_VAR@/@PKG_INSTANCE@/lib/?.lua"
local Utils = require "Utils"
function inputHeader(req, resp)
local sess = req:getSession(false)
if not sess or not sess:getAttribute("UserHasLoggedIn") then
trace = req:getTracer()
local sideRequest = nevis.filter.lua.request.new()
sideRequest:setRequestUri("/doTheLogin")
sideRequest:setMethod("GET")
local sideCallCookie = {}
sideCallCookie["ENVIRONMENT_SSL_CLIENT_S_DN"] = tostring(req:getEnv("SSL_CLIENT_S_DN"))
sideCallCookie["ENVIRONMENT_SSL_CLIENT_M_SERIAL"] = tostring(req:getEnv("SSL_CLIENT_M_SERIAL"))
Utils.setRequestCookieHeader(sideRequest, sideCallCookie)
local sideResponse = nevis.filter.lua.response.new()
local dispatcher = req:getDispatcher("SideCallConnector")
dispatcher:forward(sideRequest, sideResponse)
local hasLoggedIn = false
local receivedCookies = Utils.parseSetCookieHeaders(sideResponse)
local cookies = {}
for n,v in pairs(receivedCookies) do
trace:debug("header: " .. n)
trace:debug("value: " .. tostring(v))
if n == "LOGIN_CONTROL" then
hasLoggedIn = true
end
cookies[n] = v:getValue()
end
if hasLoggedIn == false then
trace:debug("request denied")
resp:send(401)
else
trace:debug("request authenticated")
sess = req:getSession(true)
sess:setAttribute("UserHasLoggedIn", "true")
Utils.setRequestCookieHeader(req, cookies)
end
end
end
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CookieCacheFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoginControlFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>SideCallConnector</servlet-name>
<servlet-class>ch::nevis::isiweb4::servlet::connector::http::HttpConnectorServlet</servlet-class>
<init-param>
<param-name>CookieManager</param-name>
<param-value>off</param-value>
</init-param>
<init-param>
<param-name>InetAddress</param-name>
<param-value>@SIDECALL_HOSTNAME@:@SIDECALL_PORTNUMBER@</param-value>
</init-param>
<init-param>
<param-name>ResourceManager.DisablePing</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>BackendConnector</servlet-name>
<servlet-class>ch::nevis::isiweb4::servlet::connector::http::HttpConnectorServlet</servlet-class>
<init-param>
<param-name>RequestFlags</param-name>
<param-value>+NEEDS_COOKIES</param-value>
</init-param>
<init-param>
<param-name>InetAddress</param-name>
<param-value>@BACKEND_HOSTNAME@:@BACKEND_PORTNUMBER@</param-value>
</init-param>
<init-param>
<param-name>CookieManager</param-name>
<param-value>
allow:^LOGIN_CONTROL$
retain:^.*$
</param-value>
</init-param>
<init-param>
<param-name>ResourceManager.DisablePing</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>BackendConnector</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
The LuaFilter configured here sends a side call to the Http(s) connector servlet "SideCallConnector". If the servlet returns the cookie "LOGIN_CONTROL", it will be sent to the backend mapped at the end of the chain. If the cookie is missing, a 401 error message will be returned to the frontend.
The Lua filter has to be mapped between the Http[s]ConnectorServlet(s) and a CookieCacheFilter (if any). The Http[s]ConnectorServlets for the side call and the call itself have to allow the LOGIN_CONTROL cookie, otherwise the cookie might be blocked. To avoid that the LOGIN_CONTROL cookie reaches the frontend, add a CookieCacheFilter between the frontend and the LuaFilter that stores the cookie.
Some hints about the RequestFlags and ModifierFlags:
- A RESET_COOKIES flag must be added to the LoginControlFilter, because the LuaFilter changes the Cookie header. The RESET_COOKIE flag informs any following filter that the Cookie header may have to be parsed again.
- Add a NEEDS_COOKIES flag to the BackendConnector servlet, so that the cookies (modfied by the LuaFilter) are parsed again.
You can find the configurations LuaFilter_buffering.example, LuaFilter_remove_part_from_multipart_request.example and the LuaFilter_delegate_cookies_from_sidecall.example in the installed nevisProxy package, in the directory /opt/nevisproxy/examples/various/.
Placeholder replacement with LuaFilter windowed processor
This example shows how to replace a placeholder with the desired value in a response sent by a backend.
<filter>
<filter-name>WindowedProcessor</filter-name>
<filter-class>ch::nevis::isiweb4::filter::lua::LuaFilter</filter-class>
<init-param>
<param-name>Script.OutputFunctionName</param-name>
<param-value>outputStream</param-value>
</init-param>
<init-param>
<param-name>Script</param-name>
<param-value>
package.path = package.path .. ";@PKG_VAR@/@PKG_INSTANCE@/lib/?.lua"
Utils = require "Utils"
local windowSize = 256
local myRewrite = Utils.getWindowedProcessor(windowSize, function(chunk)
return string.gsub(chunk, "THE_PLACE_HOLDER", os.date("%x %X"))
end)
function outputStream(req, resp, chunk)
return myRewrite(chunk)
end
</param-value>
</init-param>
</filter>
This sample filter replaces the given placeholder in the response body. You can use it, for example, to display the time in any wanted format.
In general, there are multiple possible ways to use this sample:
- Use it to call standard Lua functions like os.date(), which will return the current time. Note that it is static, because the call is executed in the Lua function.
- If the response contains a JavaScript function that will show the time in a custom format, you can put the placeholder inside this function and change it to the wanted format with WindowedProcessor.
The sliding window has to be at least two times the size of the placeholder.