Headless API

Headless Client Plugin Guide / Headless API

Overview

The Headless API allows you to create and manage headless clients dynamically. These clients can be started from other sessions to offload heavy tasks, allowing for parallel processing. This is especially useful for long-running jobs or tasks that would block normal operations in a single-threaded environment.

You can find more detailed information about Headless API here and about client methods here.

Features

  • Parallel Processing: Servoy clients are single-threaded, meaning they can only perform one task at a time. The Headless API provides a way to offload long-running tasks to another session.

  • Dynamic Client Creation: You can create headless clients programmatically, start, monitor, and shut them down as needed.

  • Job Queuing and Monitoring: Jobs can be queued for execution, and their progress can be monitored through callbacks or status checks.

Creating a client

To create a headless client, you will typically use the createClient method. This method opens a new session of a specified solution on the server, allowing you to interact with it programmatically.

Example: Creating a Headless Client

// Creates a headless client that will open the given solution.
var headlessClient = plugins.headlessclient.createClient("mySolution", "user", "pass", null);

// Check if the headless client was successfully created
if (headlessClient != null && headlessClient.isValid()) {
    application.output("Headless client successfully created.");
    // Perform headlessClient actions 

} else {
    application.output("Failed to create headless client.");
}

Getting a running client

To retrieve a running headless client in Servoy, you can use the getClient or getOrCreateClient methods from the plugins.headlessclient API. These methods allow you to access an existing headless session, enabling interaction with the client that’s already running on the server.

getClient

To retrieve a running headless client using the getClient(clientID) method, you need the clientID that uniquely identifies the headless client session. This method is helpful when you already have an active headless client on the server, and you want to reconnect to it to perform further operations.

This method does not create a new headless client—if the specified client ID doesn’t exist or the session has been closed, the method will return null.

Use Case for getClient This method is particularly useful when:

  • You have already created a headless client, and the process needs to be resumed or continued later.

  • You want to queue additional tasks or check the status of a long-running headless session.

  • The client session has to be accessed across different parts of the application or across different user sessions.

For instance, you might create a headless client to perform batch processing, and after some time, you retrieve it using its clientID to check if the task is complete or to perform more actions.

Example: Retrieving a Running Headless Client

// Retrieve the headless client using the client ID.
var headlessClient = plugins.headlessclient.getClient("ClientID"); // ClientID: This is the unique identifier of the headless client you want to retrieve. It is generated when the headless client is created

// Check if the client is successfully retrieved and still valid
if (headlessClient != null && headlessClient.isValid()) {
    application.output("Headless client successfully retrieved.");
    // Interact with the existing headless client

} else {
    // Handle the case where the client is not found or is invalid
    application.output("No client found with the given ID.");
}

getOrCreateClient

The getOrCreateClient method in Servoy is used to either retrieve an existing headless client using its unique clientID or create a new one if no client with the specified ID is found. This method is ideal when you need to ensure that a headless client session is available, whether it already exists or needs to be created.

If a headless client with the specified clientID exists and is running with the provided solution, it will return that client. If no such client exists, the method will create a new headless client for the specified solution and return it.

Use Case for getOrCreateClient This method is particularly useful when:

  • You are uncertain whether a specific headless client is already running, but you want to ensure that the client is either retrieved or created to perform some tasks.

  • You want to persist a specific session across different operations (e.g., for background processes, integration tasks, or automation) by reusing a clientID.

  • You need a long-running or reusable headless client session that can be accessed and shared by different parts of your solution or after a server restart.

Example: Retrieving or Creating a Headless Client

// Unique identifier for the headless client, which could be stored and reused
var clientID = "storedClientID";

// Attempt to retrieve or create the headless client
var headlessClient = plugins.headlessclient.getOrCreateClient(clientID, "mySolution", "user", "pass", null);

// Check if the client was successfully retrieved or created, and ensure it is valid
if (headlessClient != null && headlessClient.isValid()) {
    application.output("Headless client successfully retrieved.");
    // Interact with the existing or newly created headless client

} else {
    // Handle the case where the client could not be retrieved or created
    application.output("Failed to retrieve or create the headless client.");
}

Difference between getClient and getOrCreateClient

The key difference between getClient and getOrCreateClient lies in their behavior when a headless client with the specified clientID does not exist. Here’s a detailed breakdown of how each method operates:

  • getClient

    • Purpose: The getClient method is used solely to retrieve an existing headless client.

    • Behavior:

      • If a headless client with the specified clientID is running, it returns that client.

      • If no client with the given clientID is found, it returns null.

    • Use Case: Use getClient when you know that a headless client already exists, and you want to reconnect or continue interacting with it. This method does not create a new client if the specified one doesn't exist.

  • getOrCreateClient

    • Purpose: The getOrCreateClient method can either retrieve an existing client or create a new one.

    • Behavior:

      • If a headless client with the specified clientID already exists for the specified solution, it returns that client.

      • If no client with the given clientID exists, it creates a new headless client using the provided solutionName, username, password, and solutionOpenMethodArgs.

    • Use Case: Use getOrCreateClient when you want to ensure that a client exists for the given clientID. If it’s not running already, this method will create a new one, making it useful in situations where you want to avoid checking whether a client exists before creating it.

Key Differences:

Reusing a fixed clientID vs creating a unique (UUID)

When working with Servoy headless clients, you have the option to either reuse a fixed clientID or generate a unique clientID (UUID) for each new client session. Both approaches have their advantages and use cases depending on the nature of your application, the longevity of the headless client sessions, and how you intend to interact with those sessions.

  1. Reusing a Fixed clientID

Overview:

  • In this approach, you use a predefined, fixed clientID for a specific headless client session.

  • The same clientID is reused across multiple sessions or interactions, ensuring that the same headless client session can be retrieved and continued over time.

Benefits:

  • Session Continuity: By reusing a fixed clientID, you ensure that the headless client session can be persisted and accessed repeatedly, even after restarts. This is ideal for long-running processes where the client must be revisited later to resume operations or access its state.

  • Resource Efficiency: Reusing a fixed clientID prevents creating multiple headless clients for the same task. Instead of starting a new session each time, you can retrieve and reuse the existing client, which saves server resources.

  • State Preservation: If your headless client maintains important state or context (such as working with foundsets, data processing, etc.), using a fixed clientID allows you to retrieve that session and continue where you left off.

Use Case:

  • Ideal for long-running processes that may need to be resumed over time (e.g., batch data processing, scheduled tasks, or background jobs).

  • Useful when multiple systems or components need to interact with the same session or share client data.

Considerations:

  • Single-Session Limitation: If the same clientID is reused in multiple places or by different users, it can lead to conflicts or unexpected behavior, as there will only be one session associated with that ID.

  • Concurrency: Only one instance of the headless client will be running for the given clientID, which could be a limitation if multiple processes need to run independently.

Example:

var clientID = "fixedClientID";  // Predefined ID stored in the system
var headlessClient = plugins.headlessclient.getOrCreateClient(clientID, "mySolution", "user", "pass", null);

if (headlessClient != null && headlessClient.isValid()) {
    // Continue working with the fixed client session
    headlessClient.queueMethod(null, "longRunningTask", [param1], callback);
}
  1. Creating a Unique clientID (UUID)

Overview:

  • In this approach, you generate a unique identifier (UUID) for each new headless client session. UUIDs are universally unique and ensure that every client session is completely independent from others.

  • This is usually done by creating a new clientID each time a headless client is instantiated, ensuring that no session collides with another.

Benefits:

  • Isolation of Sessions: Each headless client gets its own unique session, which prevents any interference between different processes. This is important when tasks need to run independently without affecting each other.

  • Concurrency: You can run multiple independent headless clients in parallel, each with its own UUID, enabling concurrent processing. Each session will have its own state, and multiple clients can perform tasks simultaneously without conflict.

  • Automatic Cleanup: If your system doesn’t require long-term persistence of the client session, using UUIDs ensures that each session is isolated and can be easily discarded when no longer needed.

Use Case:

  • Ideal for short-lived or one-off tasks that don’t need to persist across sessions (e.g., processing user-specific requests, running temporary background tasks, handling concurrent jobs).

  • Useful when you need to run multiple, independent headless client sessions simultaneously.

Considerations:

  • Resource Usage: Since each UUID generates a new client session, you might use more server resources as multiple headless clients are created and managed simultaneously.

  • No Continuity: Unlike fixed clientIDs, sessions created with a UUID typically don’t have long-term persistence. Once the session ends, the client is usually discarded, meaning you cannot revisit that session later.

  • Session Management: You need to ensure that unused sessions are properly cleaned up (e.g., using shutdown()), especially in cases where many UUIDs are generated.

Example:

var headlessClient = plugins.headlessclient.createClient("mySolution", "user", "pass", null);
var ClientID;
if (headlessClient != null && headlessClient.isValid()) {
    // Store the generated UUID (clientID) somewhere to reference this session later
    ClientID = headlessClient.getClientID();

    // Perform operations with the unique client session
    headlessClient.queueMethod(null, "processData", [param1], callback);
}

Summary of Key Differences:

When to Use:

  • Reusing a fixed clientID is best when you need session persistence and plan to revisit the same client session over time. It is ideal for background processes, long-running tasks, or shared headless sessions.

  • Creating a unique clientID (UUID) is useful when tasks need to run independently, or you are handling short-lived, one-time processes. It is perfect for cases where multiple clients need to run concurrently, with each having its own isolated session.

Running/scheduling on startup

Running or scheduling headless clients on startup in Servoy can be an effective way to initiate background processes, such as data synchronization, batch processing, or scheduled tasks, as soon as the application server or solution starts. Here’s how you can automate headless client operations to run on startup.

Running a headless client on startup

The simplest way to run a headless client on startup is by placing the headless client creation logic inside the solution's onSolutionOpen method. This method runs automatically when the solution starts and can be used to kick off background processes.

Example: Starting a Headless Client in the onOpen Method

function onSolutionOpen() {
    // Automatically create and start a headless client when the solution starts
    var headlessClient = plugins.headlessclient.createClient("mySolution", "admin", "password", null);

    if (headlessClient != null && headlessClient.isValid()) {
        // Queue a method to be executed by the headless client on startup
        headlessClient.queueMethod(null, "initializeBackgroundTasks", null);
        application.output("Headless client started and background task initialized.");
    } else {
        application.output("Failed to create the headless client.");
    }
}

In this approach:

  • A headless client is created automatically when the solution starts.

  • The initializeBackgroundTasks method is queued for execution by the headless client.

Scheduling a headless client on startup

If you want to schedule headless clients to run at regular intervals (including on startup), you can use the Scheduler Plugin. You can schedule a headless client to start immediately after the solution loads, or at a specific time.

Example: Scheduling a Headless Client with the Scheduler

function onSolutionOpen() {
    // Schedule a headless client to run on startup
    plugins.scheduler.addCronJob('startupHeadlessClient', '0 0 * * * ?', startHeadlessClientOnStartup); // Runs hourly

    application.output("Scheduled headless client task on startup.");
}

function startHeadlessClientOnStartup() {
    var headlessClient = plugins.headlessclient.createClient("mySolution", "admin", "password", null);

    if (headlessClient != null && headlessClient.isValid()) {
        headlessClient.queueMethod(null, "executePeriodicTask", null);
        application.output("Headless client started with periodic task.");
    }
}

In this approach:

  • A cron job is scheduled using addCronJob, which will trigger the startHeadlessClientOnStartup function at the specified time (in this case, hourly).

  • The headless client is created within the scheduled job, and a method is queued for execution.

Queuing a job

Queuing a job in Servoy, especially within a headless client, is a common task used to perform background operations without interrupting the main application flow. The method queueMethod() is used to queue jobs for headless clients, allowing for asynchronous execution of server-side logic, such as data processing, external API calls, or other long-running tasks.

When multiple jobs are queued, they will be executed sequentially in the order they are added to the queue.

Example: Queuing Multiple Jobs

// Create a headless client
var headlessClient = plugins.headlessclient.createClient("mySolution", "admin", "password", null);

if (headlessClient != null && headlessClient.isValid()) {
    // Queue multiple methods to execute on the server without callback
    headlessClient.queueMethod(null, "processJob1", [param1]);
    headlessClient.queueMethod(null, "processJob2", [param2]);
    headlessClient.queueMethod(null, "processJob3", [param3]);

    // Optionally log that the jobs have been queued
    application.output("Multiple jobs queued for execution.");
} else {
    application.output("Failed to create the headless client.");
}

Steps in the Example:

  1. Create a Headless Client: The headless client is created using createClient. If you have an existing client, you can use getClient or getOrCreateClient to retrieve it.

  2. Queue Multiple Jobs: The queueMethod() is used to queue multiple methods (processJob1, processJob2, processJob3) for execution on the server. Each method is passed a set of parameters (in this case, param1, param2, param3). No callback is provided, meaning the job runs in the background, and the result or any exceptions are not tracked.

  3. Execution Order: Jobs are executed in the order they are queued. In this case:

    • processJob1 will execute first.

    • After that, processJob2 will run.

    • Finally, processJob3 will execute.

  4. Optional Logging: Logging the job queue status with application.output() helps track that the jobs were queued successfully.


Example: Queuing Jobs with Complex Parameters If the methods require complex parameters (e.g., objects or arrays), you can pass them in the queueMethod() call:

var job1Params = { id: 1, action: "update", data: { name: "John", age: 30 } };
var job2Params = { id: 2, action: "delete", data: null };
var job3Params = { id: 3, action: "create", data: { name: "Doe", age: 25 } };

// Queue the jobs with complex parameters
headlessClient.queueMethod(null, "processJob1", [job1Params]);
headlessClient.queueMethod(null, "processJob2", [job2Params]);
headlessClient.queueMethod(null, "processJob3", [job3Params]);

application.output("Jobs with complex parameters queued without callback.");

Handling the callback

Handling the callback in Servoy when queuing jobs in a headless client is important when you need to track the result of the background task, manage errors, or take further actions based on the outcome of the job. The queueMethod() function allows you to define a callback method that is triggered once the job is completed, giving you the flexibility to manage success or handle any exceptions that occur during execution.

When queuing a method using queueMethod(), you can provide a callback function that will be invoked after the method completes its execution on the server. The callback function receives a JSEvent object that provides information about the execution, including whether it succeeded or failed.

Example: Handling the Callback

// Create a headless client
var headlessClient = plugins.headlessclient.createClient("mySolution", "admin", "password", null);

if (headlessClient != null && headlessClient.isValid()) {
    // Queue a job with a callback function to handle the result
    headlessClient.queueMethod(null, "processJob", [param1], jobCallback);

    application.output("Job queued for execution.");
} else {
    application.output("Failed to create or retrieve the headless client.");
}

// Callback function to handle the result of the job
function jobCallback(event) {
    if (event.getType() == JSClient.CALLBACK_EVENT) {
        // Handle success
        application.output("Job completed successfully. Data: " + event.data);
    } else if (event.getType() == JSClient.CALLBACK_EXCEPTION_EVENT) {
        // Handle error
        application.output("An error occurred during job execution: " + event.data);
    }
}

Key Components:

  1. Queuing the Job:

    • The queueMethod() method is used to queue the processJob method for execution.

    • The last argument in the method is the jobCallback function, which will be invoked once the job is completed.

  2. The Callback Function:

    • The callback function receives a JSEvent object.

    • You can use event.getType() to check if the job completed successfully or if an error occurred.

    • There are two possible outcomes:

  3. Handling Success:

  4. Handling Errors:

    • If an exception occurs during the job execution, event.getType() will return JSClient.CALLBACK_EXCEPTION_EVENT.

    • The error message or exception details will be available in event.data.

    • You can log the error or trigger corrective actions based on this information.


Example: Handling Multiple Jobs with Different Callbacks You can queue multiple jobs with different callback functions to handle each job’s result individually.

// Queue multiple jobs with different callbacks
headlessClient.queueMethod(null, "processJob1", [param1], callbackForJob1);
headlessClient.queueMethod(null, "processJob2", [param2], callbackForJob2);

// Callback for the first job
function callbackForJob1(event) {
    if (event.getType() == JSClient.CALLBACK_EVENT) {
        application.output("Job 1 completed successfully. Data: " + event.data);
    } else if (event.getType() == JSClient.CALLBACK_EXCEPTION_EVENT) {
        application.output("Job 1 failed with error: " + event.data);
    }
}

// Callback for the second job