Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Modern frameworks such as gRPC and Apache Thrift provide a very [1] provide flexible API for implementing request interceptors, with which you can solve almost any middleware task.

...

  • ServiceCallContext -  immutable user parameter map that will can be implicitly passed to the service (and interceptor) on every method call.

    Code Block
    languagejava
    themeRDark
    titleServiceCallContext.java
    linenumberstrue
    collapsetrue
    public interface ServiceCallContext {
        public String attribute(String name);
    
        public byte[] binaryAttribute(String name);
    }


  • ServiceCallInterceptor  - intercepts service method calls.

    Code Block
    languagejava
    themeRDark
    titleServiceCallInterceptor.java
    linenumberstrue
    collapsetrue
    public interface ServiceCallInterceptor extends Serializable {
        //**
     Called BEFORE the service method* isIntercepts executed.
    a call to a public default void onInvoke(ServiceInterceptorContext ctx) throws ServiceInterceptException {service method.
         *
         * @param mtd // No-opMethod name.
        }
    
     * @param args // Called AFTER the service method is executedMethod arguments.
        public default* void onComplete(@Nullable Object res, ServiceInterceptorContext ctx) throws ServiceInterceptException {@param ctx Service context.
         * @param call // No-opDelegated call.
        }
    
     *   // Called when onInvoke, onComplete or service method throws an exception@return Service method call result.
        public default void onError(Throwable err, ServiceInterceptorContext ctx) {
            // No-op.
        }
    }

    ServiceInterceptorContext -  extended mutable version of caller context (interceptor obtains method call parameters from it and can use it to update the caller context).

    Code Block
    languagejava
    themeRDark
    titleServiceInterceptorContext.java
    linenumberstrue
    collapsetrue
    public interface ServiceInterceptorContext extends ServiceCallContext {
        public String method();
    
        public @Nullable Object[] arguments();
    
        public void attribute(String name, String val);
    
        public void binaryAttribute(String name, byte[] val);
    }
  • ServiceInterceptException - unchecked exception that is used to highlight the exception that occurred during method interception (not execution).
  •  */
        public Object invoke(String mtd, Object[] args, ServiceContext ctx, Callable<Object> call) throws Exception;
    }


Usage example

draw.io Diagram
width
bordertrue
diagramNamemiddleware
simpleViewerfalse
linksauto
tbstyletop
lboxtrue
diagramWidth1001
revision13

Code Block
linenumbers
languagejava
themeRDark
titleExample.java
truecollapsetrue
        ServiceCallInterceptor security = new ServiceCallInterceptor() {
            @Override public void onInvoke(ServiceInterceptorContext ctx) throws ServiceInterceptException {
 mtd, args, ctx, svcCall) -> {
               // Check permission before execution of the method.
                if (!CustomSecurityProvider.get().access(mtd, ctx.methodcurrentCallContext(), ctx.attribute("sessionId")))
                    throw new SecurityException("Method invocation is not permitted");

        // Execute remaining  }
interceptors and service method.
         }
return svcCall.call();
    };

    ServiceCallInterceptor audit = new ServiceCallInterceptor() {
    mtd, args, ctx, svcCall) -> {
        @OverrideString publicsessionId void onInvoke(ServiceInterceptorContext ctx) {= ctx.currentCallContext().attribute("sessionId");
        AuditProvider prov = AuditProvider.get();

        // Record an event before execution of the method.
                AuditProvider.get().prov.recordStartEvent(ctx.methodname(), ctx.attribute("sessionId"))mtd, sessionId);

        try    }
{
            @Override// publicExecute void onComplete(@Nullable Object res, ServiceInterceptorContext ctx) {service method.
                AuditProvider.get().recordFinishEvent(ctx.method(), ctx.attribute("sessionId"))svcCall.call();
        }
    }

    catch (Exception e) {
     @Override public void onError(Throwable err, ServiceInterceptorContext ctx) {
  // Record error.
            AuditProvider.get()prov.recordError(ctx.methodname(), mtd, ctx.attribute("sessionId"), erre.getMessage());

            }// Re-throw exception to initiator.
            throw e;
        }
        finally {
            // Set context parameters for Record finish event after execution of the service proxymethod.
        ServiceCallContext  ctx = ServiceCallContextprov.builderrecordFinishEvent()ctx.put("sessionId"name(), mtd, sessionId).build();
        }
    }

    ServiceConfiguration svcCfg = new ServiceConfiguration()
            .setName("service")
            .setService(new MyServiceImpl())
            .setMaxPerNodeCount(1)
            .setInterceptors(security, audit);

    // Deploy service.
    ignite.services().deploy(svcCfg);

    // DeploySet context parameters for the service proxy.
    ServiceCallContext callCtx   ignite.services().deploy(svcCfg= ServiceCallContext.builder().put("sessionId", sessionId).build();

    // Make service proxy.
    MyService proxy = ignite.services().serviceProxy("service", MyService.class, false, ctxcallCtx, 0);

        // A business method call to be intercepted.
        proxy.placeOrder(order1);
        proxy.placeOrder(order2);  

Implementation details

Deployment

...

To add/remove interceptor service should be redeployed.

Interceptor is located and executed where the service is implemented (for Java service - on Java side, for .NET-service on .NET side). Its execution should not cause additional serialization).

Invocation order

The user can specify multiple interceptors.Each interceptor invokes the next interceptor in the chain using a delegated call, the last interceptor will call the service method.

So the interceptor specified first in the configuration will process the result of the service method execution last.

draw.io Diagram
bordertrue
diagramNameinvocation
simpleViewerfalse
width
linksauto
tbstyletop
lboxtrue
diagramWidth471
revision3

Resource injection

...

Interceptor must support ignite instance resource injectionthe injection of generic resources.

(question) Interceptor should support LifeCycleAware

Interception scope

Interceptor only applies to user-defined service methods and does not apply to service lifecycle methods - init, execute andcancel,

Service call context

The user can create this context (map with custom parameters) and bind it to the service proxy. After that, each call to the proxy method will also implicitly pass context parameters to the service.

Service method can read current context parameters using ServiceContext#currentCallContext method. It is only accessible from the current thread during the execution of a service method.

Interceptor can read and update context parameters using ServiceInterceptorContext.

If an interceptor has been specified, but the user has not passed the caller context through the proxy, then for each call to the service method, an empty context will be dynamically created.

If one service calls another, then by default the current call context will not be bound to the created proxy - the user must explicitly bind it. But Java service has a special ServiceResource annotation to inject another service proxy into the current service. If the user wants to redirect the current call context to this (injected) proxy, he can set the forwardCallerContext option of this annotation.

Exception handling

Interceptor can only throw unchecked exceptions.

Any runtime exception Exception thrown by the onInvoke/onComplete methods interceptor will be wrapped in a ServiceInterceptException.This exception will be into unchecked IgniteException and passed to the initiator (user) and to onError method of the interceptor.

If onInvoke throws an exception, then the service method will not be called.

The exception thrown by the onError method will be added to the main exception as suppressed.

Risks and Assumptions

.

Risks and Assumptions

The interceptor gives the user full control over the invocation of the service methods, so in case of implementation errors, the user may get unexpected behavior of the service.

Discussion Links

https://lists.apache.org/thread.html/r4236c1f23e524dc969bc55057467a2bbe7f9a59a6db7c7fcdc1b7d37%40%3Cdev.ignite.apache.org%3E

Reference Links

[1] https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware

Tickets

Jira
serverASF JIRA
columnIdsissuekey,summary,issuetype,created,updated,duedate,assignee,reporter,priority,status,resolution
columnskey,summary,type,created,updated,due,assignee,reporter,priority,status,resolution
maximumIssues20
jqlQuerylabels = IEP-79 order by fixVersion
serverId5aa69414-a9e9-3523-82ec-879b028fb15b