Sharding

For a general feature overview please see the Backend Sharding chapter. Following the key components are explained, that are needed to find a shard for a request or to extract a shard from the given request information.

Shard detector

The ShardDetector interface is the key component and needs to be implemented to introduce a new shard detector. There is only one method that contains all the logic to extract the shard information from a request, detectShard. The inner ShardDetectionContext class contains the request.

public interface ShardDetector {

    String DEFAULT_SHARD = "default";

    String NOT_FOUND = null;

    /**
     * Detects the shard for a given HTTP request.
     *
     * @param context Shard detection context
     * @return A valid shard identifier or ShardDetector.NOT_FOUND if a shard could not be determined
     * @throws Exception
     */
    String detectShard(ShardDetectionContext context) throws Exception;

    class ShardDetectionContext {
        private final HttpRequestMessage request;

        public ShardDetectionContext(HttpRequestMessage request) {
            this.request = request;
        }

        public HttpRequestMessage getRequest() {
            return request;
        }
    }

}

The following example shows how the CookieShardDetector works. All needed logic is executed in the detectShard method. The request is obtained from the ShardDetectionContext and the cookies are parsed. If a cookie with the name open-xchange-shard is present, its value is returned, null otherwise.

Example:

public class CookieShardDetector implements ShardDetector {

    @Override
    public String detectShard(ShardDetectionContext context) throws Exception {
        HttpRequestMessage request = context.getRequest();
        Cookies cookies = request.parseCookies();
        String shard = cookies.getFirstValue("open-xchange-shard");
        if (shard == null) {
            return NOT_FOUND;
        }

        return shard;
    }
}

Shard bootstrap handler

The shard bootstrap handler comes into play, if the shard detector is unable to find any kind of shard information in the processed request. A new bootstrap handler can be simply registered via configuration, like in the example found in the operators guide.

The registered shard bootstrap handlers endpoint needs to be an endpoint filter. This filters purpose is to initiate the process that associates a user with a shard. In the example the InitOidcSso Endpoint is shown. This endpoint creates a login request whose destination is the OpenID server that is configured in the bootstrap handlers parameter section.

Example:

public class InitOidcSso extends HttpSyncEndpoint {

    private static final Logger logger = LoggerFactory.getLogger(InitOidcSso.class);

    @Override
    public HttpResponseMessage apply(HttpRequestMessage input) {
        SessionContext context = input.getContext();
        try {
            HttpResponseMessage resp = new HttpResponseMessageImpl(context, input, HttpStatus.SC_MOVED_PERMANENTLY);

            resp.getHeaders().set(HttpHeaders.LOCATION, buildLoginRequest(
                    (Map<String, String>) context.get(ContextKeys.ShardBootstrapParameters)));

            // need to set this manually since we are not going through the ProxyEndpoint
            StatusCategoryUtils.setStatusCategory(context, ZuulStatusCategory.SUCCESS);

            resp.getHeaders().set("Content-Length", "0");
            return resp;
        } catch (URISyntaxException e) {
            logger.error("Failed to build URI to IDP login location", e);
            HttpResponseMessage resp = new HttpResponseMessageImpl(context, input, HttpStatus.SC_INTERNAL_SERVER_ERROR);
            resp.getHeaders().set(HttpHeaders.CONTENT_LENGTH, "0");
            StatusCategoryUtils.setStatusCategory(context, ZuulStatusCategory.FAILURE_LOCAL);
            return resp;

        }
    }

    private String buildLoginRequest(Map<String, String> parameters) throws URISyntaxException {
        return new URIBuilder(parameters.get("endpoint"))
            .setParameter("response_type", parameters.get("responseType"))
            .setParameter("scope", parameters.get("scope"))
            .setParameter("client_id", parameters.get("clientId"))
            .setParameter("redirect_uri", parameters.get("redirectUri"))
            .build().toString();
    }

}