// The Main program, which brings up the Ballerina WebSub Hub.
import ballerina/http;
import ballerina/io;
import ballerina/runtime;
import ballerina/websub;

public function main() {

    // Starts the internal Ballerina Hub on port 9191 allowing remote publishers to register topics and publish
    // updates of the topics.
    io:println("Starting up the Ballerina Hub Service");

    websub:Hub webSubHub;
    var result = websub:startHub(new http:Listener(9191), "/websub", "/hub",
        hubConfiguration = {
            remotePublish: {
                enabled: true
            }
        }
    );

    if (result is websub:Hub) {
        webSubHub = result;
    } else if (result is websub:HubStartedUpError) {
        webSubHub = result.startedUpHub;
    } else {
        io:println("Hub start error:" + <string>result.detail()?.message);
        return;
    }

    // Waits for the subscriber to subscribe at this hub and for the publisher to publish the notifications.
    runtime:sleep(10000);

}
// The Ballerina WebSub Publisher, which registers a topic at the hub and publishes updates to the hub for the topic.
import ballerina/io;
import ballerina/runtime;
import ballerina/websub;

// This is the remote WebSub Hub Endpoint to which registration and publish requests are sent.
websub:PublisherClient websubHubClientEP =
                    new ("http://localhost:9191/websub/publish");

public function main() {

    // Registers a topic at the hub.
    var registrationResponse =
                websubHubClientEP->registerTopic("http://websubpubtopic.com");
    if (registrationResponse is error) {
        io:println("Error occurred registering topic: " +
                                <string>registrationResponse.detail()?.message);
    } else {
        io:println("Topic registration successful!");
    }

    // Makes the publisher wait until the subscriber subscribes at the hub.
    runtime:sleep(5000);

    // Publishes updates to the remote hub.
    io:println("Publishing update to remote Hub");
    var publishResponse =
        websubHubClientEP->publishUpdate("http://websubpubtopic.com",
                                {"action": "publish", "mode": "remote-hub"});
    if (publishResponse is error) {
        io:println("Error notifying hub: " +
                                    <string>publishResponse.detail()?.message);
    } else {
        io:println("Update notification successful!");
    }

}
// Ballerina WebSub Subscriber service, which subscribes to notifications at a Hub.
import ballerina/http;
import ballerina/log;
import ballerina/websub;

// The endpoint to which the subscriber service is bound.
listener websub:Listener websubEP = new (8181);

// Annotations specifying the subscription parameters.
@websub:SubscriberServiceConfig {
    path: "/websub",
    subscribeOnStartUp: true,
    target: ["http://localhost:9191/websub/hub", "http://websubpubtopic.com"],
    leaseSeconds: 36000,
    secret: "Kslk30SNF2AChs2"
}
service websubSubscriber on websubEP {

    // Defines the resource that accepts the intent verification requests.
    // If the resource is not specified, intent verification happens automatically. It verifies if the topic
    // specified in the intent verification request matches the topic specified as the annotation.
    resource function onIntentVerification(websub:Caller caller,
                                   websub:IntentVerificationRequest request) {
        // Builds the response for the subscription intent verification request that was received.
        http:Response response = request.
            buildSubscriptionVerificationResponse("http://websubpubtopic.com");

        if (response.statusCode == 202) {
            log:printInfo("Intent verified for subscription request");
        } else {
            log:printWarn("Intent verification denied for subscription request");
        }
        var result = caller->respond(<@untainted>response);

        if (result is error) {
            log:printError("Error responding to intent verification request",
                                                        result);
        }
    }

    // Defines the resource that accepts the content delivery requests.
    resource function onNotification(websub:Notification notification) {
        var payload = notification.getTextPayload();
        if (payload is string) {
            log:printInfo("WebSub Notification Received: " + payload);
        } else {
            log:printError("Error retrieving payload as string", payload);
        }
    }
}

Remote Hub Sample

Ballerina provides the capability to easily introduce publishers and subscribers that are WebSub-compliant. Ballerina WebSub subscribers can specify the topic they wish to subscribe to and the hub they wish to subscribe at, to receive notifications. If specified, the subscription process can be initiated at the startup. Signature validation is performed by default for subscribers that receive authenticated content. Ballerina WebSub Subscriber Services could thus be registered as webhooks to receive event notifications.

Ballerina also comes with an in-built WebSub Hub service, which can be brought up by publishers that need to bring up a hub.

This example demonstrates a simple WebSub publisher and subscriber scenario, in which a Ballerina WebSub Hub that is brought up remotely is used by a Ballerina WebSub publisher to publish updates to a topic. A Ballerina WebSub Subscriber subscribes at this remote hub to get the topic updates.

import ballerina/http;
import ballerina/io;
import ballerina/runtime;
import ballerina/websub;

The Main program, which brings up the Ballerina WebSub Hub.

public function main() {
    io:println("Starting up the Ballerina Hub Service");

Starts the internal Ballerina Hub on port 9191 allowing remote publishers to register topics and publish updates of the topics.

    websub:Hub webSubHub;
    var result = websub:startHub(new http:Listener(9191), "/websub", "/hub",
        hubConfiguration = {
            remotePublish: {
                enabled: true
            }
        }
    );
    if (result is websub:Hub) {
        webSubHub = result;
    } else if (result is websub:HubStartedUpError) {
        webSubHub = result.startedUpHub;
    } else {
        io:println("Hub start error:" + <string>result.detail()?.message);
        return;
    }
    runtime:sleep(10000);

Waits for the subscriber to subscribe at this hub and for the publisher to publish the notifications.

}
# This sample requires the Hub Service to start via the hub.bal file, prior to running the Publisher main program. The
# Subscriber Service needs to start after the publisher registers the topic at the hub.
# If the port is not specified, the hub service starts on the default port.
# To run this sample, navigate to the directory that contains the
# `.bal` file, and execute the `ballerina run` command.
ballerina run hub.bal
Starting up the Ballerina Hub Service
[ballerina/websub] Ballerina WebSub Hub started up.
[ballerina/websub] Publish URL: http://localhost:9191/websub/publish
[ballerina/websub] Subscription URL: http://localhost:9191/websub/hub
[ballerina/http] started HTTP/WS listener 0.0.0.0:9191
2019-11-01 14:36:36,782 INFO  [ballerina/websub] - Topic registration successful at Hub, for topic[http://websubpubtopic.com]
2019-11-01 14:36:42,764 INFO  [ballerina/websub] - Subscription request received for topic[http://websubpubtopic.com] with callback[http://localhost:8181/websub]
2019-11-01 14:36:42,807 INFO  [ballerina/websub] - Sending intent verification request to callback[http://localhost:8181/websub] for topic[http://websubpubtopic.com]
2019-11-01 14:36:43,116 INFO  [ballerina/websub] - Intent verification successful for mode: [subscribe], for callback URL: [http://localhost:8181/websub]
2019-11-01 14:36:46,952 INFO  [ballerina/websub] - Update notification done for Topic [http://websubpubtopic.com]
import ballerina/io;
import ballerina/runtime;
import ballerina/websub;

The Ballerina WebSub Publisher, which registers a topic at the hub and publishes updates to the hub for the topic.

websub:PublisherClient websubHubClientEP =
                    new ("http://localhost:9191/websub/publish");

This is the remote WebSub Hub Endpoint to which registration and publish requests are sent.

public function main() {
    var registrationResponse =
                websubHubClientEP->registerTopic("http://websubpubtopic.com");
    if (registrationResponse is error) {
        io:println("Error occurred registering topic: " +
                                <string>registrationResponse.detail()?.message);
    } else {
        io:println("Topic registration successful!");
    }

Registers a topic at the hub.

    runtime:sleep(5000);

Makes the publisher wait until the subscriber subscribes at the hub.

    io:println("Publishing update to remote Hub");
    var publishResponse =
        websubHubClientEP->publishUpdate("http://websubpubtopic.com",
                                {"action": "publish", "mode": "remote-hub"});
    if (publishResponse is error) {
        io:println("Error notifying hub: " +
                                    <string>publishResponse.detail()?.message);
    } else {
        io:println("Update notification successful!");
    }

Publishes updates to the remote hub.

}
# To run this sample, navigate to the directory that contains the
# `.bal` file, and execute the `ballerina run` command.
ballerina run publisher.bal
Topic registration successful!
Publishing update to remote Hub
Update notification successful!
import ballerina/http;
import ballerina/log;
import ballerina/websub;

Ballerina WebSub Subscriber service, which subscribes to notifications at a Hub.

listener websub:Listener websubEP = new (8181);

The endpoint to which the subscriber service is bound.

@websub:SubscriberServiceConfig {
    path: "/websub",
    subscribeOnStartUp: true,
    target: ["http://localhost:9191/websub/hub", "http://websubpubtopic.com"],
    leaseSeconds: 36000,
    secret: "Kslk30SNF2AChs2"
}
service websubSubscriber on websubEP {

Annotations specifying the subscription parameters.

    resource function onIntentVerification(websub:Caller caller,
                                   websub:IntentVerificationRequest request) {

Defines the resource that accepts the intent verification requests. If the resource is not specified, intent verification happens automatically. It verifies if the topic specified in the intent verification request matches the topic specified as the annotation.

        http:Response response = request.
            buildSubscriptionVerificationResponse("http://websubpubtopic.com");

Builds the response for the subscription intent verification request that was received.

        if (response.statusCode == 202) {
            log:printInfo("Intent verified for subscription request");
        } else {
            log:printWarn("Intent verification denied for subscription request");
        }
        var result = caller->respond(<@untainted>response);
        if (result is error) {
            log:printError("Error responding to intent verification request",
                                                        result);
        }
    }
    resource function onNotification(websub:Notification notification) {
        var payload = notification.getTextPayload();
        if (payload is string) {
            log:printInfo("WebSub Notification Received: " + payload);
        } else {
            log:printError("Error retrieving payload as string", payload);
        }
    }
}

Defines the resource that accepts the content delivery requests.

# To start the service, navigate to the directory that contains the
# `.bal` file, and use the `ballerina run` command.
ballerina run subscriber.bal
[ballerina/http] started HTTP/WS listener 0.0.0.0:8181
2019-11-01 14:36:42,797 INFO  [ballerina/websub] - Subscription Request successfully sent to Hub[http://localhost:9191/websub/hub], for Topic[http://websubpubtopic.com], with Callback [http://localhost:8181/websub]
2019-11-01 14:36:43,050 INFO  [] - Intent verified for subscription request
2019-11-01 14:36:47,098 INFO  [] - WebSub Notification Received: {"action":"publish", "mode":"remote-hub"}