Skip to the content.

Protocol

The protocol provides support for mapping a request to a method declared on a handler. It also features role-based access control through an AuthorizationHandler.

Javadoc can be found here.

The dynamic invocation when using annotations uses Java reflection and is pretty fast. Previous versions of chili-core used reflectasm but as of J11 Java reflection is 10% faster, or 50% faster if setAccessible(true) is called.

If the programmatic API is used instead, reflection will not be used at all.

Registering a protocol

There are two ways of creating a protocol mapping, lets create the protocol instance first.

private Protocol<Request> protocol = new Protocol<>();

Then we need to set up some mappings and the default role for the API.

Annotations

Annotations can be used to simplify the mapping.

Setting the default Role required for the api to USER and mapping a method to a route called list.

@Roles(USER)
public class MyHandler implements CoreHandler {

    @Api
    public void list(Request request) {
        request.write(someList);
    }
}
Programmatically

Programmatic registration is more dynamic and a tiny bit faster at runtime.

protocol.setRoles(Role.USER)
        .use("ROUTE", this::list)
        .use("ROUTE", this::listAsAdmin, Role.ADMIN);

Authentication

Authentication is required by default for all routes as Role.USER.

Strategy

The @Authenticator annotation can be applied to a function with the signature Function<Request, Future<RoleType>>, which maps a request into a role. If the user is not authorized, then the RoleType of PUBLIC is used. It’s also possible to define custom role types.

@Authenticator
public Future<RoleType> authenticator(Request request) {
    Future<RoleType> future = Future.future();
    
    tokenFactory.verify(request.token()).setHandler(verify -> {
        if (verify.succeeded()) {
            future.complete(Role.USER);
        } else {
            future.complete(Role.PUBLIC);
        }
    });
    return future;
}

For the authenticator to be registered in the protocol the handler needs to be annotated and the handler passed to the Protocol constructor or registered with the Protocol.annotated(handler) method.

If annotations are not being used, the authenticator can be set with the following,

protocol.authenticator(this::authenticator);
Levels

When resolving the access level for a route, the max level of all the current principals roles is used by the default authorizer. This does not allow for granular permission schemes but it’s very simple.

Custom roles can be defined, they need to implement the RoleType interface and be added to the RoleMap.

Example of creating a custom role

public SuperRootRole implements RoleType {
    
    public String getName() {
        return "super root role";
    }
    
    public int getLevel() {
        return Integer.MAX_VALUE;
    }
}

Registering the role in the RoleMap,

RoleMap.put("super_root", new SuperRootRole());

In order to provide more advanced authorization models, the authorization handler can be replaced in the protocol.

This is done by calling

protocol.setAuthorizationHandler(customHandler);

The interface to implement, AuthorizationHandler.

Processing

Processing is the invocation of a mapped method, it’s initiated from the CoreHandler::handle method.

public void handle(Request request) {
    protocol.process(request);
}

This handles authorization and error handling internally within the protocol.

Documenting the API

Programmatically documenting

protocol
    .model(DefaultModel.class)
    .description("this is the API")
    .use("wowza", this::wowza)
        .description("does the wowza.")
        .model(Wowza.class);

Alternatively, with annotations

@Description("this is the API")
@DataModel(Wowza.class)
public class MyHandler implements CoreHandler {

    @Api
    @Description("does the wowza.")
    @DataModel(Wowza.class)
    public void wowza(Request request) {
        request.accept();
    }

}

The annotation based setup requires that either the handler is passed to the protocol constructor or that protocol.annotated(handler) is called.

When a protocol is documented the protocol will automatically add a route for retrieving a the specification in a custom format generated by the Serializer.describe(class) method.

Note that it is possible to mix the annotated and programmatic setup on the same protocol instance. It is also possible to register multiple classes onto the same protocol.

Example

The current format looks like this, from the highscore sample

description: "A simple highscore API."
target: "api"
routes:
  update:
    description: "Updates the highscore entries with the given entry."
    model:
      score: "java.lang.Integer"
      player: "java.lang.String"
    roles:
    - "PUBLIC"
  list:
    description: "Returns a list of the current highscore selection."
    roles:
    - "PUBLIC"

It resembles OpenAPI 3, which means that adding support for it should be easy. Consider implementing codegen from API descriptions.

The protocol description API can also be used programmatically

class Main {
    public static void main(String[] args){
      ProtocolDescription<TestPayload> protocol = new ProtocolDescription<>();
      
      protocol.setDescription("a testing api")
              .setTarget("master")
              .setTemplate(Authentication.class);
      
      Route<TestPayload> route = new Route<TestPayload>("info")
              .setDescription("retrieves the info")
              .setRoles(Role.ADMIN)
              .setTemplate(TestPayload.class);
      
      protocol.addRoute(route);
      
      Serializer.yaml(protocol);
    }
    
    private class Authentication {
        public String token;
    }
    
    private class TestPayload {
        public String details;
    }
}

outputs;

description: "a testing api"
target: "master"
model:
  token: "java.lang.String"
routes:
  info:
    description: "retrieves the info"
    model:
      details: "java.lang.String"
    roles:
    - "ADMIN"

Deserializing a protocol description

ProtocolDescription<Request> description = Serializer.unyaml(description, ProtocolDescription.class);

The protocol description can also be retrieved directly from any Protocol.

Protocol<Request> protocol = new Protocol<>();
ProtocolDescription description = protocol.getDescription();

When using #getDescription the generation of API metadata is not cached as it is when the /api/document route is invoked during request processing. The Protocol instance internally keeps track of when the protocol definition changes and flushes the cache. This means that changes to the protocol during runtime will always be reflected in the documentation.