AEM Cloud Service - Invalidate Dispatcher and Purge Fastly CDN Cache using API

Goal

A simple servlet /bin/eaem/cache/invalidate for operations team to Invalidate Dispatcher and Purge Fastly CDN cache on demand for a page...


Clear CDN Cache

1) Page served from CDN Cache (Response Header - x-cache: HIT)


2) Purge the Page in cache by executing API request added in servlet /bin/eaem/cache/invalidate. Required in payload, Fastly Purge Key can be requested from the support...

https://author-p10961-e880305.adobeaemcloud.com/bin/eaem/cache/invalidate?type=CDN&publishHost=publish-p10961-e878563.adobeaemcloud.com&purgeKey=32e62d78xxxxxxxxxxxxxxxxx9c9xxxxxxxxxxx922&path=/content/eaem-svg-stream-clear-cache/us/en/dispatcher-and-cdn.html


3) After Purge, request to the page returns x-cache: MISS, meaning the response was served from dispatcher...



3) Page purge using a POSTMAN request..


Clear Dispatcher Cache

1) Page served from Dispatcher Cache


2) Request executed for purging page cache in dispatcher...

https://author-p10961-e880305.adobeaemcloud.com/bin/eaem/cache/invalidate?type=DISPATCHER&path=/content/eaem-svg-stream-clear-cache/us/en/dispatcher


3) Dispatcher INVALIDATE call logged in author distribution logs...

https://author-p10961-e880305.adobeaemcloud.com/ui#/aem/libs/granite/distribution/content/distribution-agent.html?agentName=publish


4) Further requests to the page first log [actionmiss] at dispatcher (requesting the page from publisher) and later log [actionhit] (served from dispatcher)


Solution

1) Add a servlet with path /bin/eaem/cache/invalidate with necessary API to execute purge cache requests to Dispatcher and CDN...

package apps.eaem.assets.core.servlets;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHttpRequest;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.distribution.*;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;

@Component(
name = "Experience AEM Invalidate Cache Servlet",
immediate = true,
service = Servlet.class,
property = {
"sling.servlet.methods=GET",
"sling.servlet.paths=/bin/eaem/cache/invalidate"
}
)
public class InvalidateCacheServlet extends SlingSafeMethodsServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(InvalidateCacheServlet.class);

private static final String PUBLISH_AGENT = "publish";

@Reference
private Distributor distributor;

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
try{
String path = request.getParameter("path");
String type = request.getParameter("type");

if(StringUtils.isEmpty(type)){
type = "BOTH";
}

PrintWriter writer = response.getWriter();

if(StringUtils.isEmpty(path)){
writer.println("---->Empty Path, nothing to invalidate");
return;
}

ResourceResolver resolver = request.getResourceResolver();

if("DISPATCHER".equals(type)){
writer.println("---->DISPATCHER CLEAR CACHE VIA SERVLET : " + path + "\n");
clearDispatcherCache(resolver, path, writer);
}else if("CDN".equals(type)){
writer.println("---->CDN CLEAR CACHE VIA SERVLET : " + path + "\n");
clearCDNCache(path, writer, request);
}else{
LOGGER.info("---->DISPATCHER AND CDN CLEAR CACHE VIA SERVLET : " + path);
clearDispatcherCache(resolver, path, writer);
clearCDNCache(path, writer, request) ;
}
}catch(Exception e){
throw new ServletException("Error", e);
}
}

private void clearDispatcherCache(ResourceResolver resolver, String path, PrintWriter writer ){
DistributionRequest distributionRequest = new SimpleDistributionRequest(DistributionRequestType.INVALIDATE, false, path);

DistributionResponse dResponse = distributor.distribute(PUBLISH_AGENT, resolver, distributionRequest);

if(!dResponse.isSuccessful()){
writer.println("Error Dispatcher Clear : " + dResponse.getDistributionInfo().getId() + " - " + dResponse.getMessage());
}else{
writer.println("Success Dispatcher Cache cleared successfully for - " + path + ", " + dResponse.getMessage());
}
}

private void clearCDNCache(String path, PrintWriter writer, SlingHttpServletRequest request ) throws Exception{
String CDN_PUBLISH_HOST = request.getParameter("publishHost");
String PURGE_KEY = request.getParameter("purgeKey");
String METHOD_PURGE = "PURGE";

HttpHost host = new HttpHost(CDN_PUBLISH_HOST,443,"https");
HttpClient httpclient = HttpClientBuilder.create().build();

BasicHttpRequest purgeRequest = new BasicHttpRequest(METHOD_PURGE, path);
purgeRequest.addHeader("x-aem-purge-key", PURGE_KEY);

HttpResponse cdnResponse = httpclient.execute(host, purgeRequest);
writer.println("Success : " + cdnResponse.getStatusLine());
}
}

2) For testing purposes two pages were created, using the following configuration in dispatcher\src\conf.d\available_vhosts\eaem.vhost, page dispatcher.html is only cached on Dispatcher and page dispatcher-and-cdn.html is cached on both Dispatcher and CDN

   <LocationMatch "/content/eaem-svg-stream-clear-cache/us/en/dispatcher-and-cdn.html">
Header add X-EAEM-TEST "eaem-disp-cdn-01-04"
</LocationMatch>

<LocationMatch "/content/eaem-svg-stream-clear-cache/us/en/dispatcher.html">
Header add X-EAEM-TEST "eaem-disp-01-04"
Header unset Surrogate-Control
Header always set Surrogate-Control "no-store, no-cache"
</LocationMatch>

No comments:

Post a Comment