Home

Awesome

Ballerina WebSub Library

Build codecov Trivy GraalVM Check
GitHub Last Commit Github issues

This library provides APIs for a WebSub Subscriber Service.

WebSub is a common mechanism for communication between publishers of any kind of Web content and their subscribers, based on HTTP webhooks. Subscription requests are relayed through hubs, which validate and verify the request. Hubs then distribute new and updated content to subscribers when it becomes available. WebSub was previously known as PubSubHubbub.

WebSub Subscriber is an implementation that discovers the hub and topic URL of a given resource URL, subscribes to updates at the hub, and accepts content distribution requests from the hub.

Basic flow with WebSub

  1. The subscriber discovers (from the publisher) the topic it needs to subscribe to and the hub(s) that deliver notifications on the updates of the topic.

  2. The subscriber sends a subscription request to one or more discovered hub(s) specifying the discovered topic along with the other subscription parameters such as:

    • The callback URL to which the content is expected to be delivered.
    • (Optional) The lease period (in seconds) the subscriber wants the subscription to stay active.
    • (Optional) A secret to use for the authenticated content distribution.
  3. The hub sends an intent verification request to the specified callback URL. If the response indicates the verification (by echoing a challenge specified in the request) by the subscriber, the subscription is added for the topic at the hub.

  4. The publisher notifies the hub of the updates to the topic and the content to deliver is identified.

  5. The hub delivers the identified content to the subscribers of the topic.

Subscribe to a hub

@websub:SubscriberServiceConfig {
    target: ["<HUB_URL>", "<TOPIC_URL>"], 
    leaseSeconds: 36000
} 
service /subscriber on new websub:Listener(9090) {
    remote function onSubscriptionValidationDenied(websub:SubscriptionDeniedError msg) returns websub:Acknowledgement? {
        // implement subscription validation denied logic here
        return websub:ACKNOWLEDGEMENT;
    }

    remote function onSubscriptionVerification(websub:SubscriptionVerification msg)
                        returns websub:SubscriptionVerificationSuccess|websub:SubscriptionVerificationError {
        // implement subscription intent verification logic here
        return websub:SUBSCRIPTION_VERIFICATION_SUCCESS;
    }

    remote function onUnsubscriptionVerification(websub:UnsubscriptionVerification msg)
                        returns websub:UnsubscriptionVerificationSuccess|websub:UnsubscriptionVerificationError {
        // implement unsubscription intent verification logic here
        return websub:UNSUBSCRIPTION_VERIFICATION_SUCCESS;
    }

    remote function onEventNotification(websub:ContentDistributionMessage event) 
                        returns websub:Acknowledgement|websub:SubscriptionDeletedError? {
        // implement on event notification logic here
        return websub:ACKNOWLEDGEMENT;
    }
}

Resource discovery

@websub:SubscriberServiceConfig {
    target: "RESOURCE_URL", 
    leaseSeconds: 36000
} 
service /subscriber on new websub:Listener(9090) {
    remote function onEventNotification(websub:ContentDistributionMessage event) 
                        returns websub:Acknowledgement|websub:SubscriptionDeletedError? {
        // implement on event notification logic here
        return websub:ACKNOWLEDGEMENT;
    }

    // other remote methods are optional to be implemented
}

Dynamic URI generation

@websub:SubscriberServiceConfig {
    target: "RESOURCE_URL", 
    leaseSeconds: 36000
} 
service on new websub:Listener(9090) {
    remote function onEventNotification(websub:ContentDistributionMessage event) 
                        returns websub:Acknowledgement|websub:SubscriptionDeletedError? {
        // implement on event notification logic here
        return websub:ACKNOWLEDGEMENT;
    }

    // other remote methods are optional to be implemented
}

Run a websub:SubscriberService locally

ngrok http -bind-tls=true 9090
@websub:SubscriberServiceConfig {
    target: "RESOURCE_URL", 
    leaseSeconds: 36000,
    callback: "<NGROK_PUBLIC_URL>",
    appendServicePath: true
} 
service on new websub:Listener(9090) {
    remote function onEventNotification(websub:ContentDistributionMessage event) 
                        returns websub:Acknowledgement|websub:SubscriptionDeletedError? {
        // implement on event notification logic here
        return websub:ACKNOWLEDGEMENT;
    }

    // other remote methods are optional to be implemented
}

Unsubscribe from the hub

websub:ListenerConfiguration listenerConfigs = {
    gracefulShutdownPeriod: 15
};

@websub:SubscriberServiceConfig {
    target: ["https://sample.hub.com", "https://sample.topic1.com"], 
    leaseSeconds: 36000,
    // By default this is set to `false`, hence subscriber on default mode would not initiate unsubscription flow
    unsubscribeOnShutdown: true
}
service /subscriber on new websub:Listener(9090, listenerConfigs)  {
    isolated remote function onEventNotification(websub:ContentDistributionMessage event) 
                        returns websub:Acknowledgement {
        // implement logic here
        return websub:ACKNOWLEDGEMENT;
    }

    // other remote methods are optional to be implemented
}

Return errors from remote methods

@websub:SubscriberServiceConfig {
    target: "RESOURCE_URL", 
    leaseSeconds: 36000,
    callback: "<NGROK_PUBLIC_URL>",
    appendServicePath: true
} 
service on new websub:Listener(9090) {
    remote function onEventNotification(websub:ContentDistributionMessage event) 
                        returns websub:Acknowledgement|websub:SubscriptionDeletedError|error? {
        boolean isValidRequest = check validateRequest(event);
        if isValidRequest {
            // implement on event notification logic here
            return websub:ACKNOWLEDGEMENT;
        }
    
    }

    // other remote methods are optional to be implemented
}

function validateRequest(websub:ContentDistributionMessage event) returns boolean|error {
    // validation logic 
}
MethodInterpreted meaning for Error Return
onSubscriptionValidationDeniedSuccessfull acknowledgement
onSubscriptionVerificationSubscription verification failure
onUnsubscriptionVerificationUnsubscription verification failure
onEventNotificationSuccessfull acknowledgement

Issues and projects

Issues and Projects tabs are disabled for this repository as this is part of the Ballerina Standard Library. To report bugs, request new features, start new discussions, view project boards, etc., go to the Ballerina Standard Library parent repository.

This repository only contains the source code for the package.

Build from the source

Set up the prerequisites

  1. Download and install Java SE Development Kit (JDK) version 21 (from one of the following locations).

    • Oracle

    • OpenJDK

      Note: Set the JAVA_HOME environment variable to the path name of the directory into which you installed JDK.

  2. Generate a Github access token with read package permissions, then set the following env variables:

    export packageUser=<Your GitHub Username>
    export packagePAT=<GitHub Personal Access Token>
    

Build the source

Execute the commands below to build from source.

  1. To build the package:
        ./gradlew clean build
  1. To build the package without the tests:
        ./gradlew clean build -x test
  1. To debug the package tests:

     ./gradlew clean build -Pdebug=<port>
    
  2. To run a group of tests

    ./gradlew clean test -Pgroups=<test_group_names>
    
  3. To debug with Ballerina language:

    ./gradlew clean build -PbalJavaDebug=<port>
    
  4. Publish the generated artifacts to the local Ballerina central repository:

    ./gradlew clean build -PpublishToLocalCentral=true
    
  5. Publish the generated artifacts to the Ballerina central repository:

    ./gradlew clean build -PpublishToCentral=true
    

Contribute to Ballerina

As an open source project, Ballerina welcomes contributions from the community.

For more information, go to the contribution guidelines.

Code of conduct

All contributors are encouraged to read the Ballerina Code of Conduct.

Useful links