Invoking Apex Continuations from Lightning Components

Asynchronous callouts (also referred to as Apex continuations) allow you to invoke long-running services without tying up server resources and running into Apex concurrency limits. Although Apex continuations are not currently supported natively in the Lightning Component Framework, you can use the custom solution described in this article to make asynchronous Apex callouts from within Lightning Components.

Invoking long-running services with synchronous Apex callouts can block server threads and negatively impact the overall system availability. To ensure that system resources are available to all customers, the Salesforce platform limits the number of concurrent long-running requests per organization: each org is allowed a maximum of ten concurrent requests running for longer than five seconds. When that limit is reached, subsequent requests fail until an existing request completes.

Apex continuations provide an alternative architecture for invoking long-running services in a scalable fashion without blocking server threads and without running into Apex concurrency limits. Apex continuations allow you to make asynchronous callouts to long-running services and provide a callback method that is automatically invoked when the request completes.

Here is a simple example showing how to implement an asynchronous callout using an Apex continuation:

global with sharing class ProductController {

    @RemoteAction
    global static Object getProduct(Integer productId, Integer duration){
        // Make an HTTPRequest as we normally would
        // Remember to configure a Remote Site Setting for the service!
        String url = 'https://long-running.herokuapp.com/products';
        HttpRequest req = new HttpRequest();
        req.setMethod('GET');
        req.setEndpoint(url);

        // Create a Continuation for the HTTPRequest        
        Continuation con = new Continuation(60);
        con.state = con.addHttpRequest(req);
        con.continuationMethod = 'callback';        

        // Return it to the system for processing
        return con;
    }

    global static Object callback(Object state) {
        HttpResponse response = Continuation.getResponse((String)state);
        Integer statusCode = response.getStatusCode();
        if (statusCode >= 2000) {
            return 'Continuation error: ' + statusCode;
        }
        return response.getBody();
    }

}

You can invoke Apex continuations from Visualforce pages using Javascript Remoting: all you have to do is annotate the method with @RemoteAction like in the example above. However, there is currently no built-in support for continuations in Lightning Components: you can’t call an @AuraEnabled method that makes an asynchronous callout. In the remainder of this article, I describe an approach to work around this limitation and invoke Apex continuations from Lightning Components using a Visualforce page as a proxy.

At a high level, this approach works like this:

  1. The Lightning Component embeds a Visualforce page (in an iframe).
  2. Using the standard window.postMessage() API, the Lightning Component instructs the iframed Visualforce page to invoke the Apex continuation on its behalf.
  3. The Visualforce page invokes the continuation using Javascript Remoting.
  4. When the request completes, the Visualforce page uses the same window.postMessage() API to pass the result back to its Lightning Component container.

Check out this blog post to learn more about Lightning Component to Visualforce page communication using the window.postMessage() API.

To experiment with invoking Apex continuations from Lightning Components, we will use a sample REST service that allows you to simulate network latency. The source code for this service is available in this repository. The service has two endpoints (you can try these URLs in your browser):

Example 1: Simple Continuation

The sample code for the examples below is available in this repository.

In this first example, the SimpleContinuationDemo Lightning component allows you to retrieve a specific product. For simplicity, the result is displayed as raw json.

Code highlights:

  • The SimpleContinuationDemo Lightning Component embeds the SimpleContinuation Visualforce page in an iframe
  • When you click the Get Product button, SimpleContinuationDemo posts a message to the embedded Visualforce page, passing the id of the product to retrieve, and the simulated service latency in milliseconds.
  • The Visualforce page invokes the getProduct() method in the SimpleContinuationController Apex class using Javascript Remoting. The getProduct() method makes a call to the REST service using an Apex continuation.
  • When the JavaScript Remoting call gets the response, the Visualforce page posts a message back to the Lightning Component with the result.

Limitations:

This simple example is a great way to identify all the pieces required for this integration, but it has a number of limitations:

  • There are a lot of pieces involved and some low level plumbing to set up. If you need to call an Apex continuation from a different component, you’ll have to set up that infrastructure again.
  • The Visualforce base URL (vfBaseURL) and Lightning Component base URL (lcBaseURL) that are required by the window.postMessage() API to ensure the communication is happening between trusted parties are hardcoded.

Example 2: A generic ContinuationProxy component

In this second example, we address the limitations identified in the first example. The low level plumbing and the low level communication infrastructure is encapsulated in a reusable Lightning Component named ContinuationProxy. To call an Apex continuation from a Lightning Component, all you have to do is:

  1. Drop the ContinuationProxy component in your component:
    <c:ContinuationProxy aura:id="proxy" />
    
  2. Use the proxy to invoke the Apex continuation method like this:
    component.find("proxy").invoke(methodName, args, function(result) {
        // your callback function
    });
    

NOTE: You can use the same proxy to invoke multiple methods: Internally, the ContinuationProxy component maps the callback function to an invocationId that it passes to the Visualforce page proxy when requesting the invocation of a continuation. When its JavaScript Remoting call completes, the Visualforce page passes the invocationId back to the ContinuationProxy component (along with the continuation invocation result) so it knows which callback to invoke.

Take a look at the ContinuationProxyDemo component for an example.

Example 3: Continuation broker component

You can use ContinuationProxy in any Lightning Component that needs to invoke Apex continuations. However, if you have multiple Lightning Components that use ContinuationProxy on the same page, they will all have their own iframe, and the proliferation of iframes on a page may lead to some overhead. In that case, you can use a broker component that wraps a single instance of the ContinuationProxy and invokes Apex continuations on behalf of the other components on the page. ContinuationBroker provides an example of such a broker component.

To invoke an Apex continuation using this pattern, a component fires an application event passing the name of the method to invoke, a list of arguments, and a callback method to invoke upon completion of the request. For example, here is how the getProduct() continuation is invoked in ContinuationBrokerDemo:

getProduct : function(component, event, helper) {
    var productId = component.get("v.productId");
    var request = $A.get("e.c:ContinuationRequest");
    request.setParams({ 
        methodName: "getProduct",
        methodParams: [productId],
        callback: function(result) {
            component.set("v.result", result);
        }
    });
    request.fire();
}

Supporting clickjack protection

The clickjack protection settings available in Setup (under session settings) allow you to secure your Visualforce pages against user interface redress attacks. Clickjack protection is implemented by adding the X-Frame-Options: SAMEORIGIN header to Visualforce pages, preventing them from being iframed in pages originating from a different domain.

This impacts the approach described in this article because Visualforce pages and Lightning components are served from different domains:

  • Lightning components are served from https://your-domain.lightning.force.com
  • VF pages are served from https://your-domain–c.naXX.visual.force.com

In other words, if you enable clickjack protections, the X-Frame-Options: SAMEORIGIN header prevents Visualforce pages from being iframed in Lightning Components. The solution is to override the value of the header for the ContinuationProxy page and set it to ALLOW FROM https://xyz-domain.lightning.force.com. This allows the ContinuationProxy page (and only that page) to be embedded in a Lightning Component hosted on your own Lightning domain (and only that domain). You can easily set that header in the constructor of the Visualforce page controller. Here is the ContinuationController constructor as an example:

public ContinuationController() {
    String hostname = URL.getSalesforceBaseUrl().getHost();
    String mydomain = hostname.substring(0, hostname.indexOf('--c'));
    String lcBaseURL = 'https://' + mydomain + '.lightning.force.com';
    Map headers = Apexpages.currentPage().getHeaders();	
    headers.put('X-Frame-Options', 'ALLOW-FROM ' + lcBaseURL);
    headers.put('Content-Security-Policy', 'frame-ancestors ' + lcBaseURL);
}

NOTE: We also set the ‘Content-Security-Policy’, ‘frame-ancestors’ header for wider browser support.

Summary

Apex continuations (aka asynchronous callouts) provide a scalable architecture for calling long-running services without blocking server threads and without running into Apex concurrency limits. There is currently no built-in support for continuations in Lightning Components. However, using the approach described in this article, you can use a Visualforce page as a proxy to invoke Apex continuations from Lightning Components.

Resources

Leave your comments...

Invoking Apex Continuations from Lightning Components