Archived

This forum has been archived. Please start a new discussion on GitHub.

Throwing Custom UserException from DispatchInterceptor

Hi,

We are currently implementing our own dispatch interceptor and we need to throw a custom user exception in the body of the dispatch method. We realized that this exception is transformed into a UnknownUserException, even if it is declared in the slice of the invoked service.

Is there a way to make sure that our custom exception is properly propagated to the client?

Thanks
Cyrille

Comments

  • mes
    mes California
    Hi Cyrille,

    As you've seen, a dispatch interceptor is not allowed to raise a user exception directly. The Ice run time expects the interceptor to dispatch to a servant, which normally either marshals a reply (and returns DispatchOK) or marshals a user exception (and returns DispatchUserException).

    A simple workaround is to dispatch to a special servant implementation that always raises the user exception.

    Regards,
    Mark
  • Hi Mark,

    Thank you for your answer, but I still not quite sure how to implement this workaround. Our current code is the following:
    Ice::DispatchStatus CallContext::ServerManager::dispatch(Ice::Request& request)
    {
       const Ice::Current& current = request.getCurrent();
       std::shared_ptr<CallContext::Server::Validator> validator(nullptr);
    
       try 
       {
          switch (m_mode)
          {
          case Config::SIC: validator = processSecurityContext(current);
          case Config::IC:  processInvocationContext(current, validator);
          default:
             return m_servant->ice_dispatch(request);
          }
       }
       catch (NoPermission&)
       {
          LOG_ERROR("No permission for executing current operation (" + current.operation + ")");
          throw;
       }
    }
    

    Can you confirm that what we need to do is calling the custom servant in the catch(NoPermission&) scope? And if so, what would the invocation look like?

    Thanks
  • mes
    mes California
    Yes, you would dispatch to the servant within the catch block.

    There are a couple of solutions. If all operations received by this interceptor are for a single interface type, then you can write a separate implementation of the interface in which every operation raises the NoPermission exception. Suppose we have this interface:
    interface Example
    {
        void op1() throws NoPermission;
        void op2() throws NoPermission;
        // ...
    };
    
    We can implement a special version of this interface as follows:
    class ExampleThrower : public Example
    {
    public:
        ExampleThrower(const NoPermission& ex) : _ex(ex) {}
        virtual void op1(const Ice::Current&) { throw _ex; }
        virtual void op2(const Ice::Current&) { throw _ex; }
        // ...
    private:
        NoPermission _ex;
    };
    
    Your interceptor can then dispatch the request to this servant in case an exception occurs:
       try 
       {
            // ...
       }
       catch (NoPermission& ex)
       {
          LOG_ERROR("No permission for executing current operation (" + current.operation + ")");
          Ice::ObjectPtr thrower = new ExampleThrower(ex);
          thrower->ice_dispatch(request);
       }
    
    This strategy handles all of the marshaling details for you, but you now have to maintain two implementations of the Example interface. Another strategy is to use a blobject servant. The disadvantage is that you have to marshal the exception manually, but the code is pretty simple.
    class ThrowNoPermission : public Ice::Blobject
    {
    public:
    
        ThrowNoPermission(const Ice::CommunicatorPtr& communicator,
                          const NoPermission& ex) :
            _communicator(communicator),
            _ex(ex)
        {
        }
    
        virtual bool ice_invoke(
            const std::vector<Ice::Byte>& /*params*/,
            std::vector<Ice::Byte>& results,
            const Ice::Current&)
        {
            Ice::OutputStreamPtr out = Ice::createOutputStream(_communicator);
            out->startEncapsulation();
            out->write(_ex);
            out->endEncapsulation();
            out->finished(results);
            return false;
        }
    
    private:
    
        Ice::CommunicatorPtr _communicator;
        NoPermission _ex;
    };
    
    The main advantage here is that you only have to implement one method, ice_invoke, instead of every method in the target interface. In other words, this servant raises NoPermission for every operation that it dispatches.

    One other consideration with the blobject servant is that it does not work for collocated invocations, so a little extra logic is required:
       try 
       {
            // ...
       }
       catch (NoPermission& ex)
       {
          LOG_ERROR("No permission for executing current operation (" + current.operation + ")");
          if(request.isCollocated())
          {
             throw;
          }
          else
          {
             Ice::ObjectPtr thrower = new ThrowNoPermission(ex);
             thrower->ice_dispatch(request);
          }
       }
    
    As you can see here, for the collocated case we can simply throw the exception directly because the Ice run time does not mediate these invocations and therefore will not translate it into UnknownUserException.

    Hope that helps.

    Mark
  • Hi Mark,

    Thanks for your answer. I tried to implement your proposal based on blobject.
    On server side, all seems to work as expected. Unfortunately, there is no way to retrieve the NoPermission user exception on client side: Systematically catching an Ice::UnmarshalOutOfBoundsException instead.
    BasicStream.cpp:320: Ice::UnmarshalOutOfBoundsException:
    protocol error: out of bounds during unmarshaling
    

    Should I implement something specific on client side instead of using a trivial try/catch block ?

    Below the code used on client side ...
    ...
       try {
          // Initializes the Ice run time by calling Ice::initialize.
          communicator = Ice::initialize(argc, argv); 
    
          CallContext::ClientManager clientManager(communicator);
    
          // Obtains a proxy for the remote printer.
          // Creates a proxy by calling stringToProxy on the communicator, 
          // with the string "SimplePrinter:default -p 10000". Note that the string 
          // contains the object identity and the port number that were used by the server.
          // The proxy returned by stringToProxy is of type Ice::ObjectPrx, 
          // which is at the root of the inheritance tree for interfaces and classes.
          Ice::ObjectPrx basePrinter = communicator->stringToProxy("SimplePrinter:default -p 10000");
    
          // Needs to do a down-cast by calling PrinterPrx::checkedCast. 
          // A checked cast sends a message to the server, effectively asking 
          // "is this a proxy for a Printer interface?" If so, the call returns a proxy to a Printer; 
          // otherwise, if the proxy denotes an interface of some other type, 
          // the call returns a null proxy.
          Demo::PrinterPrx printer = Demo::PrinterPrx::checkedCast(basePrinter);
    
          // Tests that the down-cast succeeded and, if not, throw an error message that terminates the client.
          if (!printer ) {
             throw "Invalid proxy";
          }
    
          // Calls the printString method, passing it the time-honored "Hello World!" string. 
          // The server prints that string on its terminal.
          printer->printString("Hello World!");
        } 
        catch (const CallContext::NoPermission& ex) {
          std::cerr << ex << std::endl;
          status = 1;      
        } 
        catch (const Ice::Exception& ex) {
          std::cerr << ex << std::endl;
          status = 1;
        }
    

    Cheers,
    Renaud
  • mes
    mes California
    What Ice version are you using?

    Thanks,
    Mark
  • Hi Mark,

    We are currently using 3.4.2.

    I just recompiled all the stuff with Ice 3.5.0 and ran successfully the test case. Client side is catching the right NoPermission user exception.

    Migration to Ice 3.5.0 is something that we will do soon. Therefore we do not need any proposal for 3.4.2 so far :-)

    Cheers
    Renaud
  • mes
    mes California
    Ok, that explains it. For Ice 3.4 you would need to remove the calls to startEncapsulation and endEncapsulation.

    Regards,
    Mark