Parallel Request Synchronization
There could be unexpected behavior when clients send multiple parallel requests to a protected resource the first time. Depending on the configuration, the session cookie could be regenerated, but do to race conditions and the parallel requests this could result in blocking certain requests, or even loosing the session on client side. To prevent this, the best solution is to trigger a login or a stepup with only one request, and only use parallel requests after the authentication has finished. A workaround could be the example below, where simultaneous requests are serialized with the help of atomic functions of the LuaFilter's localstore.
You find this example in the delivered nevisproxy package under:
/opt/nevisproxy/examples/various/LuaFilter_request_synchronization.example
Parallel Request Synchronization with the LuaFilter
<filter>
<filter-name>RequestSynchronizingFilter</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.InputHeaderFunctionName</param-name>
<param-value>inputHeader</param-value>
</init-param>
<init-param>
<param-name>Script.NotifySessionInvalidateFunctionName</param-name>
<param-value>storeCleanup</param-value>
</init-param>
<init-param>
<param-name>Script</param-name>
<param-value>
local store = nevis.localstore:instance()
local keyPrefix = "PendingRequests_"
local timer = nevis.system.timer.new()
function inputHeader(req, resp)
local tracer = req:getTracer()
local session = req:getSession(true)
local disableSerializeFlag = session:getAttribute("disableSerializeFlag")
if not disableSerializeFlag then
local key = keyPrefix .. session:getId()
local ctr = store:increment(key)
if ctr == 0 then
ctr = store:increment(key)
end
tracer:info("ENTER: ctr is " .. tostring(ctr))
while ctr ~= 1 do
store:decrement(key);
timer:sleep(10)
ctr = store:increment(key)
end
end
end
function outputStream(req, resp, chunk)
local tracer = req:getTracer()
if chunk == nil then
local session = req:getSession(true)
local key = keyPrefix .. session:getId()
local ctr = store:decrement(key);
tracer:info("EXIT ctr is " .. tostring(ctr))
end
return chunk
end
function storeCleanup(session)
store:remove(keyPrefix .. session:getId())
end
</param-value>
</init-param>
</filter>
<filter>
<filter-name>IdentityCreationFilter</filter-name>
<filter-class>ch::nevis::isiweb4::filter::auth::IdentityCreationFilter</filter-class>
<init-param>
<param-name>AuthenticationServlet</param-name>
<param-value>AuthConnector</param-value>
</init-param>
<init-param>
<param-name>EntryPointID</param-name>
<param-value>test</param-value>
</init-param>
<init-param>
<param-name>LoginRendererServlet</param-name>
<param-value>LoginRenderer</param-value>
</init-param>
<init-param>
<param-name>Realm</param-name>
<param-value>test</param-value>
</init-param>
</filter>
<filter>
<filter-name>DisableSynchronizationFilter</filter-name>
<filter-class>ch::nevis::isiweb4::filter::lua::LuaFilter</filter-class>
<init-param>
<param-name>Script.InputHeaderFunctionName</param-name>
<param-value>inputHeader</param-value>
</init-param>
<init-param>
<param-name>Script</param-name>
<param-value>
function inputHeader(req, resp)
local session = req:getSession(true)
session:setAttribute("disableSerializeFlag", true)
end
</param-value>
</init-param>
</filter>
Description
In the inputHeader function, the localstore is used to store a key per session (session ID with a prefix). Its value will be incremented as new requests reach the LuaFilter, decremented as the response pass through it back to the client. If the counter for a session is more than one, then the request processing is paused for the configured time (10 ms), then retried. To prevent filling up the localstore with the keys, the key is removed when its session has been invalidated.
The lua script which is responsible for the serialization is only enabled if the disableSerializeFlag
session attribute is not yet set. Once the login (or stepup) is finished and the request passes through the IdentityCreationFilter, the next filter in line DisableSynchronizationFilter will set this session attribute and the serialization is skipped for future requests.
Limitations
- The requests serialization with this example is only possible per instance level.
- Using LocalSessionStoreServlet as a session store.