16.1. Handling RPC requests

To handle RPC requests, your application must first register procedures. The service endpoint has three overloaded methods for this, which differ in how they define access rules:

1) The first method applies no access restrictions and authorizes any request:

void registerProcedure(
    String procedureName,
    RPCRequestHandler requestHandler,
    int concurrencyLimit)

2) The second method creates an access rule for guest clients using the value specified in the fourth parameter. Clients from all named users are allowed:

public void registerProcedure(
    String procedureName,
    RPCRequestHandler requestHandler,
    int concurrencyLimit,
    GuestAccess guestAccess)

3) The third method creates an access rule based on the list of user roles specified in the fourth parameter:

public void registerProcedure(
    String procedureName,
    RPCRequestHandler requestHandler,
    int concurrencyLimit,
    String roles)

See the chapter “Access rule definition technique” for the technique of defining access rules. Now let’s consider the first three parameters of the method:

  • procedureName is the name of the procedure that clients specify when making requests;
  • requestHandler is an instance of the RPCRequestHandler interface implementation. The interface is described below;
  • concurrencyLimit determines how many request handler instances can run concurrently. By choosing the appropriate value for this parameter, you can control the consumption of the host resources.

Here is the RPCRequestHandler interface. You have to implement the only method execute:

public interface RPCRequestHandler {
    int execute(
        RequestContext context,
        SequenceDecoder parameters,
        SequenceEncoder result,
        SequenceEncoder error);
}

The following list is the execute method’s parameters:

  • context is, as usual, the first parameter of any native request handler of Softnet;
  • parameters is of type asn.SequenseDecoder and contains the data passed by the caller in ASN.1 DER format. It can be a maximum of 64 kilobytes in size. If it contains serialized data in binary or UTF8 string format, your handler receives it as an ASN.1 OctetString or UTF8String element and deserializes it;
  • result is an output parameter of type asn.SequenseEncoder. Your handler can return the result in this. The maximum result data size is limited to 64 kilobytes;
  • error is an output parameter of type asn.SequenseEncoder. Your handler can return additional data in case of error. For example, it could be an error message. Note that Softnet only accepts this data for delivery to the client if the execute method returns a non-zero value. The maximum data size in ‘error’ is limited to 4 kilobytes.

The execute method returns an integer value. If the method succeeds, it must return zero, otherwise non-zero. In case of an error, your application can use the return value as an error code. Along with this, the handler can return additional data in the error parameter.

Below is an example of handling an RPC request. The RPCRequestHandler interface is implemented using an anonymous class. The procedure transponses a matrix provided in parameters. In the first two elements, the parameters sequence provides the size of the original matrix. The next element is of type SEQUENCE OF Raw, where Raw is of type SEQUENCE OF INTEGER. The number of raws and colomns are restricted by the range [2, 32]. The following is an ASN.1 definition for both input and output data structures:

parameters ::= SEQUENCE {
	rawsCount ::= INTEGER (2..32)
	colomnsCount ::= INTEGER (2..32)
	rows ::= SEQUENCE (SIZE (2..32)) OF Raw
}
Raw ::= SEQUENCE (SIZE (2..32)) OF INTEGER

And then the code itself:

serviceEndpoint.registerProcedure("TransposeMatrix", new RPCRequestHandler() {				
    public int execute(
        RequestContext context,
        SequenceDecoder parameters,
        SequenceEncoder result,
        SequenceEncoder error) {
        try 
        {
            int rowsCount = parameters.Int32(2,32);
            int colomnsCount = parameters.Int32(2,32);

            SequenceOfDecoder asnRows = parameters.SequenceOf(UType.Sequence);
            if(asnRows.count() != rowsCount) {
                error.UTF8String(String.format(
                    "The actual number of rows %d does not
                    correspond to the declared %d", 			
                    asnRows.count(),
                    rowsCount));
                return -1;
            }
						
            result.Int32(colomnsCount);
            result.Int32(rowsCount);						
            SequenceOfEncoder asnResultRows = result.SequenceOf(UType.Sequence);

            SequenceOfEncoder[] resultRows = new SequenceOfEncoder[colomnsCount];	
            for(int i=0; i < colomnsCount; i++)
                resultRows[i] = asnResultRows.SequenceOf(UType.Integer);
	
            for(int j = 0; j < asnRows.count(); j++) 
            {							
                SequenceOfDecoder asnRow = asnRows.SequenceOf(UType.Integer);
                if(asnRow.count() != colomnsCount) 
                {
                    error.UTF8String(String.format(
                        "The actual number of colomns %d in the row %d 
                        does not correspond to the declared %d",
                        asnRow.count(),
                        j,
                        colomnsCount));
                    return -1;
                }                
                for(int t=0; t < colomnsCount; t++)
                    resultRows[t].Int32(asnRow.Int32());
            }					
            return 0;
        }
        catch(RestrictionAsnException e) {
            error.UTF8String(e.getMessage());
            return -1;						
        }
        catch(AsnException e) {
            error.UTF8String(e.getMessage());
            return -2;						
        }
    }
},
5 /* Concurrency Limit */,
"Operator; Editor" /* Authorized Roles */);

TABLE OF CONTENTS