class ServerRpcHandler implements Serializable {

    public void handleRpc(UI ui, Reader reader, VaadinRequest request)
            throws IOException, InvalidUIDLSecurityKeyException {
        ui.getSession().setLastRequestTimestamp(System.currentTimeMillis());

        String changeMessage = getMessage(reader);

        if (changeMessage == null || changeMessage.equals("")) {
            // The client sometimes sends empty messages, this is probably a bug
            return;
        }

        RpcRequest rpcRequest = new RpcRequest(changeMessage, request);

        // Security: double cookie submission pattern unless disabled by
        // property
        if (!VaadinService.isCsrfTokenValid(ui, rpcRequest.getCsrfToken())) {
            throw new InvalidUIDLSecurityKeyException();
        }

        String hashMessage = changeMessage;
        if (hashMessage.length() > 64 * 1024) {
            hashMessage = changeMessage.substring(0, 64 * 1024);
        }
        byte[] messageHash = MessageDigestUtil.sha256(hashMessage);

        int expectedId = ui.getInternals().getLastProcessedClientToServerId()
                + 1;
        int requestId = rpcRequest.getClientToServerId();

        if (requestId != -1 && requestId != expectedId) {
            // Invalid message id, skip RPC processing but force a full
            // re-synchronization of the client as it might have not received
            // the previous response (e.g. due to a bad connection)

            // Must resync also for duplicate messages because the server might
            // have generated a response for the first message but the response
            // did not reach the client. When the client re-sends the message,
            // it would only get an empty response (because the dirty flags have
            // been cleared on the server) and would be out of sync

            if (requestId == expectedId - 1 && Arrays.equals(messageHash,
                    ui.getInternals().getLastProcessedMessageHash())) {
                /*
                 * Last message was received again. This indicates that this
                 * situation is most likely triggered by a timeout or such
                 * causing a message to be resent.
                 */
                getLogger().info(
                        "Ignoring old duplicate message from the client. Expected: "
                                + expectedId + ", got: " + requestId);
            } else {
                /*
                 * If the reason for ending up here is intermittent, then we
                 * should just issue a full resync since we cannot know the
                 * state of the client engine.
                 *
                 * There are reasons to believe that there are deterministic
                 * issues that trigger this condition, and we'd like to collect
                 * more data to uncover anything such before actually
                 * implementing the resync that would thus hide most symptoms of
                 * the actual root cause bugs.
                 */
                String messageStart = changeMessage;
                if (messageStart.length() > 1000) {
                    messageStart = messageStart.substring(0, 1000);
                }
                throw new UnsupportedOperationException(
                        "Unexpected message id from the client."
                                + " Expected sync id: " + expectedId + ", got "
                                + requestId + ". Message start: "
                                + messageStart);
            }
        } else {
            // Message id ok, process RPCs
            ui.getInternals().setLastProcessedClientToServerId(expectedId,
                    messageHash);
            handleInvocations(ui, rpcRequest.getRpcInvocationsData());
        }

        if (rpcRequest.isResynchronize()) {
            getLogger().warn("Resynchronizing UI by client's request. Under "
                    + "normal operations this should not happen and may "
                    + "indicate a bug in Vaadin platform. If you see this "
                    + "message regularly please open a bug report at "
                    + "https://github.com/vaadin/flow/issues");

            // Run detach listeners and re-attach all nodes again to the
            // state tree, in order to send changes for a full re-build of
            // the client-side state tree in the response
            ui.getInternals().getStateTree().prepareForResync();

            // At this point, make no assumptions about which dependencies have
            // been accepted by the client
            ui.getInternals().getDependencyList().clearPendingSendToClient();

            // Signal by exception instead of return value to keep the method
            // signature for source and binary compatibility
            throw new ResynchronizationRequiredException();
        }
    }

}