Intercepting Invocations
CORBA brokers provide support for interception of GIOP requests and replies both in the server and client sides. Although the interception model is heavily influenced by the CORBA Portable Interceptors specification, OiL does not stick to it. Like other aspects of the OiL's API, it priories a simpler model more adequate to Lua than the defined by the CORBA standard.
Interceptors
Invocation interception is done by interceptor objects, which can be register in the broker as a client interceptor or a server interceptor. Client interceptors intercept invocations issued to remote servants, while server interceptors intercept invocations received from remote applications.
A client interceptor is an object that provides either methods sendrequest
or receivereply
.
The first method is called before an invocation request is sent to a remote servant, and the second is called after a reply is received from a remote servant.
On the other hand, a server interceptor is an object that provides either methods receiverequest
or sendreply
, which are called before an invocation request is processed by the server, and after a reply is sent to the corresponding invoker, respectively.
Each one of these methods receives a single parameter, which is a table representing the invocation.
The structure and information contained in this table varies accordingly to the method being called.
However, for a single invocation issued or received, the table received by method sendrequest
and receivereply
are always the same.
The same is true for methods receiverequest
and sendreply
, which means that the parameter received by the former method is the same parameter passed to the corresponding invocation of the later.
This also means that any field stored in this table during the execution of the first method will be available during the execution of the other for a single invocation.
For an example, consider the code below that measures the time each invocation takes to conclude.
local socket = require "socket" TimeProfiler = {} function TimeProfiler:receiverequest(request) request.start = socket.gettime() end function TimeProfiler:sendreply(reply) local time = socket.gettime() - reply.start print(string.format("opreation '%s' lasted %d seconds", reply.operation, time)) end
Client Interceptors
The fields provided by table request
of operation sendrequest
of the client interceptor are:
request_id |
number | [read-only] | Number that identifies the pending request over the connection used to communicate with the remote ORB. Note that this identifier is not unique across different connections, and it may be reused by other requests after this one is completed (i.e. replied). |
response_expected |
boolean | Flag that indicates if the server must send a response for this request. | |
reference |
table | [read-only] | The complete IOR structure (see CORBA::IOP::IOR in CORBA specs.) that identifies the servant the request is sent to. |
object_key |
string | [occasional] | Key of the object the request is addressed to. | If the ORB is not able to decode an IOR profile, this field is
profile_tag |
number | [occasional, read-only] | The identification of the kind of the IOR profile used to establish a connection with the servant's ORB.
If the ORB is not able to establish a connection, this field is nil . |
profile_data |
table | [occasional, read-only] | The decoded profile data used to establish a connection with the servant's ORB.
If the ORB is not able to establish a connection, this field is nil .
The structure of this profile depends on the value of the profile_tag .
For IIOP profiles (profile_tag == 0 ), this table follows the structure of CORBA::IIOP::ProfileBody_1_0 . |
interface |
object | [read-only] | Complete description of the interface of the servant being invoked. |
interface_name |
string | [read-only] | Name of the the interface of the servant being invoked. |
operation |
object | Description of the operation being invoked.
To change the operation being invoked on the remote servant, the application must set a new operation description to this field, which can be obtained from the local Interface Repository (see field types ) |
|
operation_name |
string | [read-only] | Name of the operation being invoked. |
parameters |
table | List of the parameters passed to the operation being invoked. | |
service_context |
table | [optional] | Set this value to define the list of service context data to be marshaled in the request.
See CORBA::IOP::ServiceContextList in CORBA specs. |
success |
boolean | [optional] | Set this value to cancel request:
true cancels the request and indicates to the invocation was successful;
false cancels the request and indicates to the invocation raised an exception. |
results |
table | [optional] | Set this value to provide the result values of a canceled request. This value must be a list of the values to be returned (in case of a successful invocation) or a list with a single value indicating the raised exception or error (in case of a failed invocation). |
reply_service_context |
table | [optional] | Set this value to define the list of service context to be added to the reply of a cancelled request.
See CORBA::IOP::ServiceContextList in CORBA specs. |
forward_reference |
table | [optional] | Set this value to forward this request to another reference.
This value must be a complete IOR structure (see CORBA::IOP::IOR in CORBA specs.). |
The fields added to table request
of operation receivereply
of the client interceptor are (all other fields provided on the corresponding sendrequest
operation are still available):
success |
boolean | Indication of success of the invocation.
true indicates the invocation was successful;
false indicates the invocation raised an exception. |
|
results |
table | List of the values to be returned (in case of a successful invocation) or a list with a single value indicating the raised exception or error (in case of a failed invocation). | |
reply_service_context |
table | The list of service context received with the reply of the request.
See CORBA::IOP::ServiceContextList in CORBA specs. |
Server Interceptors
The fields provided by table request
of operation receiverequest
of the client interceptor are:
request_id |
number | [read-only] | Number that identifies the pending request over the connection used to communicate with the remote ORB. Note that this identifier is not unique across different connections, and it may be reused by other requests after this one is completed (i.e. replied). |
response_expected |
boolean | [read-only] | Flag that indicates if the server must send a response for this request. |
servant |
table | [occasional, read-only] | The implementation of the object request is addressed to.
If the ORB cannot find a servant with the provided object_key then this field is nil . |
object_key |
string | Key of the object the request is addressed to. | |
interface |
object | [read-only] | Complete description of the interface of the servant being invoked.
If the ORB cannot find a servant with the provided object_key then this field is nil . |
interface_name |
string | [occasional, read-only] | Name of the the interface of the servant being invoked.
If the ORB cannot find a servant with the provided object_key then this field is nil . |
operation |
object | [occasional] | Description of the operation being invoked.
If the ORB cannot find a servant with the provided object_key or the operation requested is not defined in the servant's interface then this field is nil .
To change the operation being invoked on the remote servant, the application must set a new operation description to this field, which can be obtained from the local Interface Repository (see field types ) |
operation_name |
string | [read-only] | Name of the operation being invoked. |
parameters |
table | [occasional] | List of the parameters passed to the operation being invoked.
If the ORB cannot find a servant with the provided object_key or the operation requested is not defined in the servant's interface then this field is nil . |
service_context |
table | [optional] | Set this value to define the list of service context data to be marshaled in the request.
See CORBA::IOP::ServiceContextList in CORBA specs. |
success |
boolean | [optional] | Set this value to cancel request:
true cancels the request and indicates to the invocation was successful;
false cancels the request and indicates to the invocation raised an exception. |
results |
table | [optional] | Set this value to provide the result values of a canceled request. This value must be a list of the values to be returned (in case of a successful invocation) or a list with a single value indicating the raised exception or error (in case of a failed invocation). |
reply_service_context |
table | [optional] | Set this value to define the list of service context to be added to the reply of a cancelled request.
See CORBA::IOP::ServiceContextList in CORBA specs. |
forward_reference |
table | [optional] | Set this value to forward this request to another reference.
This value must be a complete IOR structure (see CORBA::IOP::IOR in CORBA specs.). |
The fields added to table request
of operation sendreply
of the client interceptor are (all other fields provided on the corresponding receiverequest
operation are still available):
success |
boolean | Indication of success of the invocation.
true indicates the invocation was successful;
false indicates the invocation raised an exception. |
|
results |
table | List of the values to be returned (in case of a successful invocation) or a list with a single value indicating the raised exception or error (in case of a failed invocation). | |
reply_service_context |
table | The list of service context received with the reply of the request.
See CORBA::IOP::ServiceContextList in CORBA specs. |
|
forward_reference |
table | [optional] | Set this value to forward this request to another reference.
This value must be a complete IOR structure (see CORBA::IOP::IOR in CORBA specs.). |
Adding Information to Invocations
GIOP messages for requests and replies has the special field service_context
, which can be used to store additional information to be send with the invocation.
Basically, the service_context
is a sequence of structures, each containing a tag number and a octet sequence that is used to store arbitrary data.
Interceptors can define the value of field service_context
in the parameter of methods sendrequest
or sendreply
.
Similarly, the value of field service_context
can be accessed through the parameter of methods receiverequest
or receivereply
.
To promote interoperability with other CORBA ORBs, the information contained in field service_context
is usually encoded using CORBA's CDR.
To help this, OiL brokers provide methods newencoder
()
and newdecoder
(stream)
.
The example below, illustrates how to use interceptors to add authentication information in an invocation.
require "oil" oil.main(function() local broker = oil.init() print("Type the your username") local Username = io.read() print("Type the password") local Password = io.read() local AuthID = broker:loadidl"struct AuthID { string user; string pswd; };" local encoder = broker:newencoder() encoder:put({user=UserName,pswd=Password}, AuthID) local AuthData = encoder:getdata() local Authenticator = {} function Authenticator:sendrequest(request) request.service_context[#request.service_context + 1] = { context_id = 404, context_data = AuthData, } end broker:setinterceptor(Authenticator, "client") ... end)Server
require "oil" oil.main(function() local broker = oil.init() local AuthID = broker:loadidl[[ struct AuthID { string user; string pswd; }; ]] local Validator = {} function Validator:receiverequest(request) local valid for _, context in ipairs(request.service_context) do if context.context_id == 404 then local decoder = broker:newdecoder(context.context_data) local authid = decoder:get(AuthID) valid = (authid.user == "admin") and (authid.pswd == "admin") end end if not valid then -- cancel request with an exception request.success = false request[1] = broker:newexcept{ "CORBA::NO_PERMISSION", minor_code_value = 0, } end end broker:setinterceptor(Validator, "server") ... end)
Multithreading Issues
OiL guarantees that for each single invocation both methods of the interceptor is invoked in the same thread.
Moreover, this thread is the same that initiated the invocation (in case of a client interceptor) or the thread that will execute the invocation (in case of a server interceptor).
However, while an invocation is being executed, others can be issued and the interceptor be invoked again, but it will execute in a different thread.
Therefore, you can use the current thread (oil.tasks.current
) to identify an invocation.
On the server side, the ORB may reuse a thread that executed a previous invocation that finished to execute other invocations.
Multiple Interceptors
Although CORBA brokers only allow one interceptor to be defined on the client side and one on the server side, if you need to define a chain of interceptors, you can register an interceptor that delegates interception to a chain of interceptors.
The following example illustrates the implementation of a interception chain that does not guarantee the order the interceptors are invoked.
The implementation use class loop.object.Publisher
of LOOP library.
require "oil" require "loop.object.Publisher" oil.main(function() local broker = oil.init() InterceptionChain = loop.object.Publisher{ Interceptor_01, Interceptor_02, Interceptor_03, ... } broker:setinterceptor(InterceptionChain, "server") ... end)